ASP.net MVC3: Doesn't Deserialize Nullable Properties from Json
3 min read
3 min read
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.
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 : ActionFilterAttributeThe real action method then looks as follows:
{
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);
}
}
[HttpPost]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.
[JsonModelBinder(ActionParameterName="student", ActionParameterType=typeof(Student)]
public ActionResult Save(Student student)
{
// save the student
return View();
}
IModelBinder
and by using the popular (and even more performant) Json.net
library for deserializing my entities. So the result looked likepublic class MyCustomModelBinder: IModelBinderInside global.asax you have then to register the
{
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);
}
}
}
IModelBinder
as follows: protected void Application_Start()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.
{
...
ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder(ModelBinders.Binders.DefaultBinder);
...
}