views:

160

answers:

3

I am building a .NET MVC app that has a page with a list of delete buttons, one for each item in a list. The problem I'm having is that the foreach variable "item" is not visible inside the LoginView, which results in the following error:

Compiler Error Message: CS0103: The name 'item' does not exist in the current context

Below is a simplified version of the view. The error occurs at the "new {id=item.Id}" in the LoggedInTemplate - the reference to "item" in the ActionLink works fine:

<% foreach (var item in Model) { %>

  <%= Html.ActionLink("Item", "Details", new { id = item.Id })%>

  <asp:LoginView runat="server">
      <LoggedInTemplate>
          <% using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post)) 
          { %>
               <input type="submit" value="Delete" runat="server" />
          <% } %>
      </LoggedInTemplate>
  </asp:LoginView>
<% } %>

To clarify the problem is not that the Model has not been successfully passed to the View. The Model is visible from both inside and outside the LoginView. The foreach loop as no problem in iterating through the items in the Model (which is a List). The problem is that the iteration variable "item" is not accessible from within the LoginView - though the original Model is.

Is there any way to pass "item" through to the LoginView's templates? Or is building LoginViews within a foreach loops the wrong way of doing things?

Is there a scoping rule that prevents using local variables within controls - perhaps because the control is rendered at a different time to the main page?

A: 

Are you passing the Model to the view and are you also inheriting from the model within that view?

So if this is a View then in your C# code you need to return the list of items like return View(listofitems);

If this is a partial view then <% Html.RenderPartial("MyPartial", listofitems) %>

And in the view you need to

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IQueryable<ListOfItems>>" %>

If all that is in place then it should work no probs.

griegs
Thanks, but that's not the problem. The Model is passed to the View without issue. I can access the Model fine both inside and outside the LoginView. It's the iteration variable that causes the compilation error.
ctford
+1  A: 

With ASP.NET MVC you really shouldn't use user/custom controls, so if you omit the <asp:LoginView/> and write a line of code to check if the user is authenticated, you are good to go.

Instead of your current code:

<asp:LoginView runat="server">
 <LoggedInTemplate>
  <div>Show this to authenticated users only</div>
 </LoggedInTemplate>
</asp:LoginView>

Just use an if-statement and the value of Request.IsAuthenticated:

<% if (Request.IsAuthenticated) { %>
 <div>Show this to authenticated users only</div>
<% } %>
troethom
I understand that partial views are normally the way to go in MVC, but why the rule against user/custom controls? Is it because user controls make assumptions about Page_Load() and posting-back that don't hold in an MVC app?
ctford
The core reason of this is the scope of the enclosing page; you can't transfer variables declared in your page markup to any controls. This behaviour is not specific to ASP.NET MVC, but a consequence of the way a page is rendered (the markup is parsed and the controls are rendered without relation to any script executing in the markup).
troethom
The reason why I think you should avoid user/custom controls completely, is that they typically don't go well with the MVC-pattern. They tend to mix layers and require state and events (which has nothing to do with web, really). It's not impossible in many cases though, but I would favor doing it the proper way - also to avoid issues like this one.
troethom
Thanks for taking the time to explain it to me. It might be nice if you included your comment about scoping in the main body of the answer in case anyone refers to this question in the future.
ctford
A: 
<% foreach (var item in Model) { %>
  <%= Html.ActionLink("Item", "Details", new { id = item.Id })%>
  <%= if( Request.IsAuthenticated ) {
      using( Html.BeginForm( "Delete", "Items", new {id=item.Id}, FormMethod.Post)) 
      { %>
          <input type="submit" value="Delete" runat="server" />
      }
    } %>
<% } %>

There is no need to use the LoginView, its not really giving you anything. Use something like the above instead.

Alternatively, you can move the decision of whether to show the delete option for the specific item into the controller, so instead of doing if( Request.IsAuthenticated ) you would do if( item.ShowDelete ) ... assuming item's type is a view model. Another option is to use an extension method for the same, item.ShowDelete(). I prefer the earlier, because there might be logic associated to deciding whether to show delete for a given item, so its better to not have it in the controller or a related logic.

eglasius