If you are posting it via AJAX with the content type set to JSON, then MVC 3 will be able to bind it properly in your controller action.
$.ajax({
url: location.href,
type: "POST",
data: ko.toJSON(viewModel),
datatype: "json",
contentType: "application/json charset=utf-8",
success: function (data) { alert("success"); },
error: function (data) { alert("error"); }
});
However, if like in your example, you want to do a normal form post that includes JSON, then you need to do some more work as MVC3 won't automatically bind it to your model, as the content type will be application/x-www-form-urlencoded.
Steve Sanderson has an older sample that demonstrates getting submitted JSON data to be bound properly in your controller action here: http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
The gist of it is that he creates an attribute called "FromJson" that looks like:
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
Then, the action looks like:
[HttpPost]
public ActionResult Index([FromJson] IEnumerable<GiftModel> gifts)
Also, if you don't like having to use the attribute, then you can actually register a type to always use a certain model binder.
You could create a model binder that looks like:
public class JsonModelBinder: IModelBinder
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
Then, register it in global.asax.cs like:
ModelBinders.Binders.Add(typeof(DocumentModel), new JsonModelBinder());
Now, you would not need to use an attribute and your DocumentModel would be bound properly. This would mean that you would always be sending the DocumentModel via JSON though.