ASP.net MVC3: Doesn't Deserialize Nullable Properties from Json

A couple of days ago I noticed that the Nullable properties of a C# object were not properly deserialized when posting back a Json object to an MVC3 action. It seems like this is a bug in the JavaScriptSerializer used by MVC3 to serialize/deserialize Json data.

This behavior is quite nerving as you probably have a lot of nullable properties, often resulting directly from the underlying data structure in the DB (especially in a data-base first case with EF). So one solution might be to create your separate ViewModels and somehow handle the mapping of the nullable properties through those. Extremely smelly!

While browsing for a potential solution I came across this blogpost. According to the author of the post, apparently the DataContractSerializer doesn't have this problem. So what he does is to create an MVC3 ActionFilter for intercepting the call to the Action method and then to deserialize the arriving Json data using the DataContractSerializer.
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);
}     
}
The real action method then looks as follows:
[HttpPost]
[JsonModelBinder(ActionParameterName="student", ActionParameterType=typeof(Student)]
public ActionResult Save(Student student)
{
// save the student
return View();
}
Now, I didn't try "Syper blogger"'s approach, also because I don't really want all of the devs to require to put those attributes on every action method they write. This is a huge overhead and introduces redundancy in the type definition etc.

So I aimed for a more clean approach by implementing a custom IModelBinder and by using the popular (and even more performant) Json.net library for deserializing my entities. So the result looked like
public class MyCustomModelBinder: IModelBinder
{
private IModelBinder fallbackModelBinder;

public MyCustomModelBinder(IModelBinder fallbackModelBinder)
{
this.fallbackModelBinder = fallbackModelBinder;
}

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.HttpContext.Request.HttpMethod.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
{
return this.fallbackModelBinder.BindModel(controllerContext, bindingContext);
}
else
{
//ONLY do this for POST,PUT,DELETE requests that transmit application/json
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);

return JsonConvert.DeserializeObject(bodyText, bindingContext.ModelType);
}
}
}
Inside global.asax you have then to register the IModelBinder as follows:
protected void Application_Start()
{
...
ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder(ModelBinders.Binders.DefaultBinder);
...
}
Note that I'm only touching POST,PUT or DELETE requests that post Json data to my MVC3 webapp. All other requests are directly forwarded to the default model binder.

I prefer this solution of the registration of ActionFilter attributes on each action method because in this way the deserialization process is completely transparent. Let me know if any better solution comes to your mind for solving this issue.
Kindle

blog comments powered by Disqus

Your ad here?