Saturday, March 5, 2011

Passing anonymous type to ASP.NET MVC's View

We could take two approaches for passing anonymous type to View. One is to use a ViewModel, another one is to use dynamic(albeit you will lose intellisense and type-safety)

Approach #1 Using a ViewModel:

If your view is expecting a model of Person type (e.g. @model IEnumerable<TestEf.Models.Person>) and you pass this to your View:

var r = (from p in db.Persons
   join q in db.Qualifieds on p.PersonId equals q.PersonId into pQ
   from l in pQ.DefaultIfEmpty()
   orderby p.Lastname
   select new { Person = p, IsQualified = l != null })
  .Skip(0).Take(10) // do the paging logic here

return View(r);

That will result to an error. The best way to handle anonymous type while maintaining strongly-typed Model is to introduce a ViewModel, so in our example here, we need to create this class on Models:

public class QualifiedStatus
{
    public Person Person { get; set; }
    public bool IsQualified { get; set; }
}


This will be the code for our controller:

var r = (from p in db.Persons
   join q in db.Qualifieds on p.PersonId equals q.PersonId into pQ
   from l in pQ.DefaultIfEmpty()
   orderby p.Lastname
   select new QualifiedStatus { Person = p, IsQualified = l != null })
  .Skip(0).Take(10) // do the paging logic here

return View(r);


And this will be the code in our View's header:

@model IEnumerable<TestEf.Models.QualifiedStatus>

Approach #2:

If we don't want to introduce ViewModels for Linq operation that results to anonymous type, we must devise a way to persist the anonymous type in a dynamic(C# 4 feature)-friendly structure. In this regard, we could use Json, as it is easy to parse-back so-to-speak(in fact, there's no need for us to parse back the string, there are many Javascript serializer/deserializer libraries that can do the heavylifting for us). And fortunately someone already written a Json deserializer that leverages C# 4's dynamic feature.


So given this:

var r = (from p in db.Persons
   join q in db.Qualifieds on p.PersonId equals q.PersonId into pQ
   from l in pQ.DefaultIfEmpty()
   orderby p.Lastname
   select new { Person = p, IsQualified = l != null })
  .Skip(0).Take(10) // do the paging logic here


We must be able to pass it directly to MVC's view, to make our API simple, we could make an extension method that allows serializing an object to Json string:

return View((object)r.JsSerialize());

By the way, we cannot pass the string result of JsSerialize directly to View, otherwise the View string overload function will be the one being invoked in our code, and that string is a url, it will be an error to pass Json as url :-) So in our code, we must cast string to object

On the View side of things, we put this on View's header(actually this is optional, dynamic is the default):
@model dynamic

And put this code:

@{ dynamic r = ((string)Model).JsDeserialize(); }


We could now code our View in the usual way, albeit without the type-safety and without the comfort of intellisense(if lacking this is not enough to scare you, I don't know what could :p bottomline, use strongly-typed model, use ViewModels):

@foreach (var item in r) {
 <tr>
  <td>
   @item.Person.Lastname
  </td>
  <td>
   @item.Person.Firstname
  </td>
  <td>
   @item.Person.FavoriteNumber
  </td>
  <td>
   <input type="checkbox" disabled="disabled" @(item.IsQualified ? "checked" : "") />
  </td>
 </tr>
}


See things in action: http://code.google.com/p/sample-poc-for-passing-anonymous-type-to-aspnet-mvc-view/downloads/list

Just change Web.config's connection string:
connectionString="Data Source=C:\Users\Michael\Documents\Visual Studio 2010\Projects\TestEf\TestEf\Database\Ent.sqlite; Version=3"


If you want to use Sql Server instead, change the connection's providerName to System.Data.SqlClient and the connectionString accordingly, here's the DDL:
create table Person
(
PersonId bigint identity(1,1) not null primary key,
Lastname varchar(50) not null,
Firstname varchar(50) not null,
FavoriteNumber int not null
);

create table Qualified
(
QualifiedId bigint identity(1,1) not null primary key,
PersonId bigint references Person(PersonId) not null
);

insert into Person(Lastname, Firstname, FavoriteNumber) values
('Harrison','Jorg', 7),
('Lennon','Jan', 9),
('McCartney','Pol',8);


insert into Qualified(PersonId) values
(1),(3);

UPDATE April 17, 2011

Updated to Entity Framework 4.1. And also, the code is not dependent on installing Sqlite.NET provider anymore, uses installer-less Sqlite .NET provider, just add the System.Data.SQLite.DLL and System.Data.SQLite.Linq.dll directly; necessary Web.config entry for uninstaller-less Sqlite .NET provider is also added to Web.config
http://code.google.com/p/sample-poc-for-passing-anonymous-type-to-aspnet-mvc-view/downloads/detail?name=TestEf-4.1.zip&can=2&q=

4 comments:

  1. Sure, this works (json serialization), but at what cost to performance? All that serializing and deserializing, isn't that expensive?

    ReplyDelete
  2. True :-) That's another reason for scaring those who don't want to use ViewModel, and minus the type-safety too

    ReplyDelete
  3. Hey - I went down the same path as you, but with a slightly different solution: http://rhizohm.net/irhetoric/post/2011/05/25/Taking-The-M-out-of-MVC-JSON-The-dynamic-Keyword-LINQ-.aspx

    ReplyDelete
  4. Hi! Thanks for this entry, I was not managing to understand the viewmodel solution until I found your example, good job and thanks a ton!

    ReplyDelete