Taking The M out of MVC - JSON, The dynamic Keyword, LINQ

May 25 2011

Okay, so I’ve been working on a new MVC project in all the data is stored as JSON. The plan is to have JSON deserialized in the controller (using the WCF JSON classes) and then pass the deserialized JSON to the view. The general thinking is that we won't have any models, just controllers and views that work with dynamically created object graphs from JSON.

So, I started implementing.  Works great. For example, here’s controller code that grabs some JSON out of blob storage, deserializes it, and returns it to the view:

public ActionResult Index(string name) { ICloudBlobContainer container = cloudBlobClient.GetContainerReference("applications"); ICloudBlob blob = container.GetBlobReference(string.Format("{0}.txt",name)); string json = blob.DownloadText(); JsonObject achievementModel = JsonValue.Parse(json) as JsonObject; return View(achievementModel); }

And here's what the view looks like:

@model dynamic <h2>@Model.Name</h2> <h3>@Model.Description</h3>

Okay, so far so good. Notice how the JSON gets to the view as dynamic and the view can party on it.

But, here's where things got tricky. What happens when I want to LINQ queries over the JSON and then pass it to my view? This is going to be a common scenario for the application, where the controller filters the model, if you will, before handing it off to the view.

For example, consider the following code:

var achievementsByCategory = from JsonValue v in achievementModel["Achievements"] group v by v["Category"].ReadAs<string>("") into grp select new { Category = grp.Key, Items = grp }; return View(achievementsByCategory);


Basically, I'm doing a group by clause on the object tree created in the deserialized JSON and creating an anonymous type, then returning that to the view. Nice idea, but MVC and anonymous types are not friends. Run this code and when you attempt to read the model and look at the properties , you'll get an exception of the like:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException was unhandled by user code -- 'object' does not contain a definition for 'Category'

So, what to do? One solution would be to create a typed object as part of the LINQ result and return that to the view. But that defeats the whole point of the architecture, which is to stay flexible, have no strict typing, but still getting the goodness of LINQ. What I realized is that I need to trick LINQ into returning a dynamic type, not an anonymous type. How to do this? Well, I realized that all the Json classes implement IDynamicMetaObjectProvider.

 Awesome! I could have LINQ return an IEnumerable of JsonObjects, which I could then cast as dynamic in my view and party on. So, the code looks like this:

var achievementsByCategory = from JsonValue v in achievementModel["Achievements"] group v by v["Category"].ReadAs<string>("") into grp select new JsonObject { {"Category", grp.Key}, {"Items", new JsonArray(grp)} }; return View(achievementsByCategory);

What's clever about this code is that my LINQ query is returning a set of JsonValue objects, which I can pass directly to the ctor of JsonArray.  And my view is happy, because it is getting something it knows how to handle: an IEnumerable. I can then walk the JsonObject results in the view, casting them back to dynamic and getting all the lovely property syntax:

@foreach (dynamic group in Model) { <h3>@group.Category</h3> <ul> @foreach (dynamic item in group.Items) { <li>@item.Name - @item.Description </li> } </ul> }
Pretty slick, methinks. My JSON can now change, morph, etc. and my code is rock solid. But at the same time, I get all the goodness of LINQ. And my front end Razor code ends up feeling more like Javascript than C#. It is the weird, trippy marriage of JSON and dynamic. I'm stoked about it. In this bizarre way, it takes the model out of MVC.