Making MVC 3 a little more… dynamic
The other day I ran into a little problem. I wanted to be able to post some JSON to a web service and have it accept a whole bunch of data (not exactly the technical term) that was not predefined.
Given this very vague problem, I decided I wanted to use ASP .NET MVC 3. So starting with File\New Project, I added a method to my HomeController as follows:
[HttpPost] public ActionResult DoSomething(string a, string b, dynamic c) { return new EmptyResult(); }
The thought behind this was that I wanted to supply two required properties (a and b) and basically have a dynamic bag for everything else. Also, I didn’t want to be restricted to a single layer of properties. Instead, I wanted to be able to pass a deep tree and have it totally accessible.
Then I pulled up fiddler and set up a request with the following details:
Method | POST |
URL | http://localhost:2643/Home/DoSomething |
Request Headers | User-Agent: Fiddler
Content-Type: application/json |
Request Body | { a: “Hello”, b: “World”, c: { d: “this”, e: “is”, f: “dynamic” } } |
When I hit execute and set a breakpoint on the action, I received the following values in my locals:
As expected, the JsonValueProviderFactory has kicked in and populated the values of a and b using the JSON provided. However, it cannot match c properly so simply throws an object at it that is not dynamic and does not have values on it, hence useless.
After some searching, I found a few valid solutions. The most useful one was this blog post by Anoop Madhusundanan. In this post, he describe the process of using a model binder and a custom model binder attribute to identify parameters of an action method that are to use the model binder. This seemed like a great solution for me, but had a couple of little problems… Most importantly, Anoop’s solution does not allow for multiple parameters on an action method or mapping the parameter to the property in the JSON object.
I came up with a very similar way of solving this problem. I created a class named DynamicJsonBinder which implements IModelBinder, just like Anoop. The main difference with my solution is that I provided a way to ensure that the parameter being populated with the dynamic object is mapped based on the name of the parameter. This is done by using a switch on the attribute called MatchName, which will cause the binder to look at the parameter name and find the matching property in the JSON object to return.
Here’s the attribute:
using System.Web.Mvc; public class DynamicJsonAttribute : CustomModelBinderAttribute { public override IModelBinder GetBinder() { return new DynamicJsonBinder(MatchName); } public bool MatchName { get; set; } }
And here’s the model binder:
using System; using System.IO; using System.Linq; using System.Collections.Generic; using System.Web.Helpers; using System.Web.Mvc; public class DynamicJsonBinder : IModelBinder { private readonly bool matchName; public DynamicJsonBinder(bool matchName) { this.matchName = matchName; } public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var contentType = controllerContext.HttpContext.Request.ContentType; if (!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) return null; string bodyText; using (var stream = controllerContext.HttpContext.Request.InputStream) { stream.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(stream)) bodyText = reader.ReadToEnd(); } if (string.IsNullOrEmpty(bodyText)) return null; var desiralized = Json.Decode(bodyText); if (!matchName) return desiralized; var members = desiralized.GetDynamicMemberNames() as IEnumerable<string>; return members == null || members.Contains(bindingContext.ModelName) ? desiralized[bindingContext.ModelName] : null; } }
So if by adding a DynamicJson attribute to the c parameter on the action method, we’ll get the following:
public ActionResult DoSomething(string a, string b, [DynamicJson] dynamic c) { return new EmptyResult(); }
Debugging into this with the same post request as earlier:
We see that c has been populated with a DynamicJsonObject (the result of the call to Json.Decode) and that it has recursively mapped the properties under the JSON object to dynamic object. However, the value is the entire JSON object, that is it’s not mapped to the c property.
To enable name matching we get the following code:
[HttpPost] public ActionResult DoSomething(string a, string b, [DynamicJson(MatchName = true)] dynamic c) { return new EmptyResult(); }
Debugging we get:
And we see that the property has been matched correctly and the correct value is passed through to the parameter.
Using these two simple classes and a bit of dynamic know-how, I’ve managed to post anything to my web service and have it dynamically accessible. This basically means we have the equivalent of a property bag on steroids that can be queried and manipulated in the same way as any dynamic object. We can even re-serialize the dynamic object to JSON and store it as is if required.
Posted on 7 June, 2011, in ASP.NET, MVC. Bookmark the permalink. 2 Comments.
Great blog post I’ve also written a blog post about how to bind to dynamic types using a custom ValueProviderFactory http://www.dalsoft.co.uk/blog/index.php/2012/01/10/asp-net-mvc-3-improved-jsonvalueproviderfactory-using-json-net/
Pingback: iterate through a DynamicJsonObject | FaceColony.org - Developers Network