Binding Anonymous Types in MVC Views by ThinqLinq

Binding Anonymous Types in MVC Views

While translating this site over to MVC, I ran into a challenge when converting the RSS feed implementation. Currently I'm using XML Literals to generate the RSS and I could certainly continue to use that track from the Controller similar to the Sitemap implementation on Mikesdotnetting. However, putting the XML generation in the controller directly conflicts with the separation of concerns that MVC embraces. If I were only displaying one RSS feed, I might be willing to break this here. However, I'm rendering a number of different RSS feeds here: Posts, Posts by Category, and Files.

Since it would be good to have a reusable view, I decided to create a single view which various controllers can use. I was dynamically generating the XML in the past so my queries would now need to project into a type that the view can consume. Here we have several alternatives:

  1. Create a strongly typed object structure which is strictly used to shape our results for the shared Rss view.
  2. Project into a list of System.ServiceModel.SyndicationItem and then bind to that.
  3. Project into an anonymous type and figure out a way to bind to that projection in our view.

I initially thought I would go down the second route similar to the implementation discussed on the DeveloperZen post. However, I wanted to support some of the RSS extensions including comments and enclosures that aren't directly supported in that implementation.

At first I was unsure how to bind an anonymous projection in a View, so I eliminated option 3 and implemented option 1 similar to the strongly typed implementation discussed on Mikesdotnetting blog. To do this, I needed to build the following set of strongly typed structures:


Public Structure RssElement
    Public Title As String
    Public Link As String
    Public PubDate As DateTime
    Public PermaLink As String
    Public TrackBackUrl As String
    Public CommentRss As String
    Public CommentUrl As String
    Public CommentCount As Integer
    Public Description As String
    Public Categories() As Category
    Public Enclosures() As Enclosure
End Structure

Public Structure Category
    Public Url As String
    Public Title As String
End Structure

Public Structure Enclosure
    Public Url As String
    Public Length As Integer
    Public Type As String
End Structure

If I were using VB 10, this would have been done with auto-implemented properties. However I went with structures at this point because I didn't want to type that much for something that was going to be view only anyway.

With this structure in place, I could go ahead and implement the controller and view. The controller simply projected into this new object structure in the Select clause of a LINQ query. The view then was able to consume this as we could strongly type the view as a ModelView(Of IEnumerable(Of RssElement)). Here's the view that I created:

<%@ Page Language="VB" ContentType="application/rss+xml"
Inherits="System.Web.Mvc.ViewPage(Of IEnumerable(Of RssElement))" %> <rss version='2.0' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:slash='http://purl.org/rss/1.0/modules/slash/' xmlns:wfw='http://wellformedweb.org/CommentAPI/' xmlns:trackback='http://madskills.com/public/xml/rss/module/trackback'> <channel> <title>Thinq Linq</title> <link><%=Url.Action("Post") %></link> <description>LINQ and related topics.</description> <dc:language>en-US</dc:language> <generator>LINQ</generator> <% For Each item In Model%> <item> <title><%=item.Title%></title> <link><%=item.Link%></link> <pubDate><%=item.PubDate%></pubDate> <guid isPermaLink="false"><%= item.PermaLink %></guid> <dc:creator>jwooley</dc:creator> <slash:comments><%=item.CommentCount%></slash:comments> <trackback:ping><%=item.TrackBackUrl%></trackback:ping> <comments><%=item.CommentUrl%></comments> <wfw:commentRss><%=item.CommentRss%></wfw:commentRss> <wfw:comment><%=item.CommentUrl%></wfw:comment> <description><%=Html.Encode(item.Description)%></description> <% if Not item.Categories is Nothing then %> <%For Each c In item.Categories%> <category domain="<%= c.Url %>"><%=c.Title%></category> <% Next %> <% End If%> <%If Not item.Enclosures is Nothing then %> <% For Each e In item.Enclosures%> <enclosure url='<%=e.Url %>' length='<%=e.length %>' type='<%=e.type %>' /> <% Next%> <% end if %> </item> <% Next%> </channel> </rss>

In comparing this code with the XML Literal implementation, they are amazingly similar. With MVC, I may be able to live without XML Literals in the views as we simply replace a LINQ projection with a For Each loop. Notice here I check to see if the Categories and Enclosures objects exist before I enumerate over each of those arrays. This is because the Post feed doesn't include enclosures and the File feed doesn't need Categories. This flexibility allows us to create a reusable view for all of our needs.

But, I'm not quite happy with this implementation. I would prefer not to have to declare the additional structure layer just to pass the view something to consume. In this case, it feels like we are having the Controller consume the data Model and create a ModelView (RssElement) to be consumed by the View. We don't really need a new pattern (M-C-MV-V), do we? Instead, I would like to be a bit more "Dynamic" in my implementation so that I didn't need this class and could simply project into an anonymous type and eliminate the RssElement structures entirely.

After a bit of reflection, I realized that this is a case where VB is uniquely positioned crossing the bridge between strong typing and dynamic languages. Normally, I do not recommend using the Option Strict Off option, but this is one case where it does come in useful. To begin, we'll remove those pesky structures. Next, we'll change the controllers to project into anonymous types. Here's the revised code for the Post Rss Controller:


    Function ShowPosts() As ActionResult
        Dim posts = From p In (From post In Context.PostItems _
                    Order By post.PublicationDate Descending _
                    Take 20).AsEnumerable _
                    Select New With { _
                        .Description = p.Description, _
                        .Link = Url.Action("Title/" & p.TitleUrlRewrite & ".aspx", "Post"), _
                        .PubDate = p.PublicationDate.ToString("r"), _
                        .Title = p.Title, _
                        .TrackBackUrl = Url.Action("Trackback/" & p.Id, "Seo"), _
                        .PermaLink = "42f563c8-34ea-4d01-bfe1-2047c2222a74:" & p.Id, _
                        .Categories = (From c In p.CategoryPosts _
                                    Select New With { _
                                         .Title = c.Category.Title, _
                                         .Url = Url.Action("Category/" & c.CategoryID, _
                                                           "Post")}).ToArray, _
                        .Commentrss = Url.Action("Comment/" & p.Id, "Rss"), _
                        .CommentUrl = Url.Action("Title/" & p.TitleUrlRewrite, "Post"), _
                        .CommentCount = p.Comments.Count, _
                        .Enclosures = Nothing}

        Return View("ShowFeed", posts.ToList)
    End Function

Notice here, that we have to be very careful with our property naming and can't leave off anything. This is why we have to initialize our .Enclosures property to Nothing because we can't initialize it to an empty collection. Since our view checks to see if the object is null before binding it, we are fine here.

Now back to the view. How do we tell the view what type of data the Model contains if we can't name it? Here's where option strict off comes in handy. However, in a View page, we can't simply state Option Strict Off at the top of our code. Instead, we need to set the CompilerOptions to set optionstrict- as follows:

<%@ Page Language="VB" ContentType="application/rss+xml" 
CompilerOptions="/optionstrict-" Inherits="System.Web.Mvc.ViewPage" %>

In this case, we are not only setting the CompilerOptions, but removing the generic type definition in the Inherits clause. The rest of the view remains intact. Now, we can consume our anonymous type (because we aren't typing the view) and let the Option Strict setting dynamically resolve our method and type names. Notice here, if we were using C# 4.0, we wouldn't be able to use the Dynamic option and state that the page inherits ViewPage<Dynamic> because we can't project into a Dynamic type in our LINQ query.

Now that we have modified our view, we can reuse it. First move it to the Shared folder so that the view will be accessible regardless of which controller tries to consume it. Next, we create other controllers making sure that all of the properties are projected correctly in our LINQ query.


    Function Rss() As ActionResult
        Return View("ShowFeed", _
            From f In GetFiles() _
            Select New With { _
                .Description = f.Description, _
                .Link = "http://www.ThinqLinq.com/" & f.URL, _
                .PubDate = f.LastWriteTime.ToString("r"), _
                .PermaLink = "42f563c8-34ea-4d01-bfe1-2047c2222a74:" & f.Name, _
                .TrackBackUrl = "", _
                .CommentRss = "", _
                .CommentUrl = "", _
                .CommentCount = 0, _
                .Categories = Nothing, _
                .Title = f.Name, _
                .Enclosures = New Object() {New With { _
                                            .Length = f.Length, _
                                            .Type = "application/x-zip-compressed", _
                                            .Url = "http://www.ThinqLinq.com/" & f.URL}}})
    End Function

 

Be aware. Here we are playing with the dangerous part of dynamic languages. We no longer get the compiler to ensure that our type includes all of the necessary properties. If we forget a property or mis-type the property name, we will only know about it when a run-time exception is thrown. Of course, since this is MVC, we can use unit tests to check our type. With dynamic programming, think of the compiler as just another unit test. You need to write the rest of them by hand.

While I like the flexibility that the new dynamic option provides, I miss the comfort that comes from strong typing. Also, I haven't checked the performance differences between these implementations and suspect that the previous strongly typed option may out perform this one. With optimizations in VB 10 around Option Strict Off, I suspect that the performance differences may shrink, but would need to test this as well.

I'll also admit to being relatively new to MVC and welcome better alternatives from those who have been using it longer. What do you Thinq?

Posted on - Comment
Categories: LINQ - VB - VB Dev Center -
comments powered by Disqus