Framework Madness!

And other adventures C# and asp.net …

Asp.net MVC 2 Peview 1 – Work Around for ‘Templates can be used only with field and property accessor expressions.’ Exception When Calling ‘DisplayFor’ and Using a List

with one comment

 

Update 1 – Looks like Matt Hidinger of  matthidinger.com has a much better solution. How about ‘HtmlDisplayForMany’. Yeah. That’s right. It works like a charm. Have a look:

http://blog2.matthidinger.com/archive/2009/08/15/creating-a-html.displayformany-helper-for-mvc-2.aspx 

Nice work! 🙂 Looks like it pays to understand lambdas.

Update 2 – Looks like it works, but his implementation is a bit off. Looks like the ‘DisplayFor’ method always returns ‘String.Empty’. This is because the ‘DisplayFor’ method is a facade that calls a single ‘TemplateHelper’ method and this method writes directly to the HttpResponse output. So if you want to write tags around it you have to call response.write. I’ll try to have an updated version of Matt’s code that supports a WrapTag later this evening. For now, it’s time to take a nap and then to work.

 

I’ve been playing around with Asp.net Mvc 2 the past few days and tonight I thought I would get into some of the new goodness – specifically DisplayTemplates. And so what I have is a scenario where I am using strong typed views to make master details pages for simple, blog-like content. Just trying to kick the tires.

Note: the code presented below is simplified from my actual implementation, so bear with me. 🙂

We start with a DisplayTemplate using a PartialView for the Element object. The plan is to use this on both the Master and Detail pages. (This is inside the ‘DisplayTemplates’ folder for the controller.) Here the is the display template code:

   1: <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NextBase.Contents.Element>" %>

   2:  

   3: <h2><%
   1: =Html.ActionLink(Model.Title,"Article",new {id=Model.ElementID}) 

%> </h2>

   4: <p><%
   1: =Model.Contents 

%></p>

   5: <ul>

   6:    <%
   1: =Html.WriteTagArray(TagUtility.Li,Model.Keywords.Select( k=>k.Keyword).ToArray()) 

%>

   7: </ul>

… and I had used it on the Detail page which uses a complex view model type but the Element is a single property. Here is the usage:

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NextBase.Contents.ElementViewModel>" %>

   2:  

   3: <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">

   4:     Article

   5: </asp:Content>

   6:  

   7: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

   8:  

   9:     <p><%
   1: =Html.ActionLink(String.Format("Return to '{0}'",Model.Catalog.Name),"Default") 

%></p>

  10:     

  11:     <!-- ***************************** -->

  12:     <!-- here is the Display for usage -->

  13:     <!-- ***************************** -->

  14:  

  15:     <%
   1: =Html.DisplayFor(m=>m.Element) 

%>

  16:  

  17:  

  18: </asp:Content>

It works great! The content is rendered in the page as expected.

But now for the master view the complex view model that has a list of elements instead of a single element. Here is what my master view model looks like:

   1: public class CatalogViewModel

   2: {

   3:     //constructor ommited for clarity

   4:  

   5:     public virtual Int32 PageIndex { get; private set; }

   6:  

   7:     public virtual Int32 PageCount { get; private set; }

   8:  

   9:     public virtual Catalog Catalog { get; private set; }

  10:  

  11:     public virtual List<Element> Elements { get; private set; }

  12:  

  13: }

I am passing a bit of pager information, a Catalog object, and an Elements list for display. And here is what I would like the view to look like:

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NextBase.Contents.CatalogVisualModel>" %>

   2: <%@ Import Namespace="NextBase.Contents"%>

   3:  

   4: <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">

   5:     Default

   6: </asp:Content>

   7:  

   8: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

   9:     <!-- Header -->

  10:     <h1><%
   1:  =Model.Catalog.Name 

%></h1>

  11:     

  12:     <ul>

  13:     <%
   1: for (int index = 0; index < Model.Elements.Count; index++){ 

%>

  14:        <li> 

  15:             <!-- ***************************** -->

  16:             <!-- here is the display for usage -->

  17:             <!-- ***************************** -->          

  18:             <%
   1: =Html.DisplayFor(m=> m.Elements[index]) 

%>

  19:        </li>

  20:     <%
   1: }

%>

  21:     </ul>

  22:     <p><%
   1: =Html.ActionPager(new ActionPagerSettings() { Label="Pages", PageCount = Model.PageCount,PageIndex = Model.PageIndex,RouteAction="Default",RouteParameter="id" })

%></p>

  23:  

  24: </asp:Content>

But that crashes and burns. It turns out while you can use only simple expressions for fields and properties only. Calls against indexers, methods and LINQ don’t work, so far. (And your stuck with expressions only against the model). Here is the error:

System.InvalidOperationException: Templates can be used only with field and property accessor expressions.

Yikes – what a bummer. I tried taking the current element and setting it in the ViewData and calling  ‘Html.Display’, but no dice. So my solution, which is very inelegant, but does work involves creating a CurrentElement property on the ViewModel and passing in a current index before calling ‘Html.DisplayFor’. Here is the updated CatalogViewModel with the CurrentElement property and support method:

   1: public class CatalogViewModel

   2: {

   3:  

   4:     public virtual Int32 PageIndex { get; private set; }

   5:  

   6:     public virtual Int32 PageCount { get; private set; }

   7:  

   8:     public virtual Catalog Catalog { get; private set; }

   9:  

  10:     public virtual List<Element> Elements { get; private set; }

  11:  

  12:     //method to set the current element index from try loop

  13:     public void SetCurrentElementIndex(Int32 Index)

  14:     {

  15:         this._CurrentElementIndex = Index;

  16:     }

  17:  

  18:     private Int32 _CurrentElementIndex;

  19:  

  20:     //CurrentElement property

  21:     public virtual Element CurrentElement

  22:     {

  23:         get

  24:         {

  25:             if (_CurrentElementIndex > -1 && _CurrentElementIndex < this.Elements.Count)

  26:             {

  27:                 return Elements[_CurrentElementIndex];

  28:             }

  29:  

  30:             return null;

  31:         }

  32:     }

  33:  

  34: }

and a snippet of the code inside the master view:

   1: <%
   1: for (int index = 0; index < Model.Elements.Count; index++){ 

%>

   2:    <li>

   3:        <%
   1:  Model.SetCurrentElementIndex(index); 

%>

   4:        <%
   1: =Html.DisplayFor( m=> m.CurrentElement) 

%>

   5:    </li>

   6: <%
   1: }

%>

And it does work since CurrentElement is a property. So the content renders on the page.

But what would be better in this case would be a LINQ query that returned a single object or null such as:

   1: <!-- this would be ideal -->

   2: <%
   1: =Html.DisplayFor(m=> m.Elements.Skip(index).Take(1).FirstOrDefault()) 

%>

But I’m not a LINQ guru by any means and I have no idea of how hard this would be to implement.

It will be nice to see what the team has for is in Preview 2. I can live with the work around, but to be able to use LINQ here would be great.  That said, as a well-versed web forms developer I am enjoying MVC preview 2 thus far.

Advertisements

Written by Lynn Eriksen

September 14, 2009 at 2:56 am

Posted in Uncategorized

Tagged with

One Response

Subscribe to comments with RSS.

  1. […] to VoteAsp.net MVC 2 Peview 1 – Work Around for ‘Templates can be used only with field and property acc…Monday, September 14, 2009 from leriksen71.wordpress.comI’ve been playing around with Asp.net Mvc […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: