One of the model binders in MVC 3 is JSON model binder. It is clear that it tries to bind json objects passed into the controller action via ajax call. To clarify the issue let’s start with a simple scenario.
Assume that we need to pass Student json object to a controller action. Here is the Student class:
public class Student { public int Id { get; set; } public string Name { get; set; } public long? Grade { get; set; } }
The action is called Save and accepts a parameter of type Student:
[HttpPost] public ActionResult Save(Student student) { // save the student return View(); }
We are using jquery for ajax call. Just put a button on the view and bind the click event using following code:
So, on button click event we create a student object and initialize its properties. Then post that object into the Save action of the Student controller. Now put a breakpoint in action code and run the project. If you look at the student parameter passed into the Save action you will find out that the model binder has created the Student object and assigned all the properties except Grade!
Is that a bug? Yes it is! If you just change the Grade property type from long? To long you will see that the issue solved! I don’t know why the model binder cannot bind the nullable properties and we have to wait to see if it’s solved in the next version. However we cannot stop our projects and have to find a solution.
AtionFilterAttribute is our hero here. As you may know, we can apply a filter before executing the Action in the request pipeline of MVC. Please look at the documentations in MSDN for further info.
ActionFilter gives us the ability to inspect the executing context of the action (ActionExecutingContext). In this context, we have access to HttpContext and the HttpRequest. Also, we have read and write access to the action parameters. What we can do is to bind the incoming JSON data to the action parameter. Here is the code for our custom action filter:
public class JsonModelBinder : ActionFilterAttribute { public JsonModelBinder() { } public Type ActionParameterType { get; set; } public string ActionParameterName { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { HttpRequestBase request = filterContext.HttpContext.Request; Stream stream = request.InputStream; stream.Position = 0; filterContext.ActionParameters[ActionParameterName] = (new DataContractJsonSerializer(ActionParameterType)).ReadObject(stream); } }
As you see, I’ve used the DataContractJsonSerializer to deserialize the object. This type is defined in System.Runtime.Serialization.Json namespace and you have to add a reference to System.Runtime.Serialization to use it.
The important assumption here is that the InputStream contains a JSON data. ActionParameterName and ActionParameterType are two properties that we have to initialize them when decorating the action with this attribute:
[HttpPost] [JsonModelBinder(ActionParameterName="student", ActionParameterType=typeof(Student)] public ActionResult Save(Student student) { // save the student return View(); }
Now let’s run the project again and look at the action parameter:
Bingo!