Default view model binding of ASP.NET MVC has a disadvantage when using view models with nested classes and JSON objects. The binding is unable to recognize such nested classes and it initializes them to NULL value. This text describes a cause and solutions.
The Issue
Let’s we have the following declaration of objects in Javascript:
var person = { "Name": "Person name", "Addr": { "Location": "Address location" } };
We’d like to post this data to MVC action with jQuery function $.post() this way:
$.post("@Url.Action("Index")", person);
In the MVC action we want to use the property Location. If you set a breakpoint into the action, you’ll see that the property is NULL in spite of the fact the data is sent. You may find that using FireBug. The requests’ form data represents all sent properties:
Name=Person+name&Addr%5BLocation%5D=Address+location
A sample code which demonstrates some aspects described in the text you can find here: http://github.com/xtrmstep/NestedViewModelsInMVC/
The Cause
JSON formatting does not corresponds to what MVC model binding expects. You can notice that when you’re using MVC extensions like @Html.EditorFor(…) and others with submitting a form, the MVC model binding works correctly. Tracing requests to the server you can notice that data is passed in different formats:
- MVC model binding cannot recognize JSON nested objects which are sent as array elements, like this way Addr[Location]
- MVC can recognized nested objects if they are sent as properties, like this way Addr.Location
MVC DefaultModelBinder has its own convention for naming complex objects and this is the reason we cannot use it as it is. Probably you would suggest using the following JSON object initialization:
var obj = {}; obj.Name = "Person Name; obj.Addr = {}; obj.Addr.Location = "Person Location;
It does not help here since representation of the object is the same when you post it to the server.
The Solution
The simplest solution to send JSON objects without any custom development is sending objects as text. That can be achieved with JSON function stringify. In order to do that JSON object should be converted to string before posting to the server. When server receives the string, it should convert it back with JavaScriptSerializer . The MVC action should receive strings instead of objects. The solution above can be automated with ActionFilterAttribute which encapsulate all deserialization logic and allows to use objects in action parameters. The automation can be useful in some big systems, but what if we want to use the default MVC model binding and have objects in action parameters?
In the later case we need to patch JSON object representation before sending it to the server. The following jQuery plugin helps with that.
$.postify = function(value) { var result = {}; var buildResult = function(object, prefix) { for (var key in object) { var postKey = isFinite(key) ? (prefix != "" ? prefix : "") + "[" + key + "]" : (prefix != "" ? prefix + "." : "") + key; switch (typeof (object[key])) { case "number": case "string": case "boolean": result[postKey] = object[key]; break; case "object": if (object[key].toUTCString) result[postKey] = object[key].toUTCString().replace("UTC", "GMT"); else { buildResult(object[key], postKey != "" ? postKey : key); } } } }; buildResult(value, ""); return result; };