3

I have the following form:

<form id="MakeDocumentForm" name="MakeDocumentForm" 
      action="Document/GetWordDocument" method="post" 
      enctype="application/json">

    <button type="submit" style="float:right;">Make Word Document</button> 
    <textarea id="hiddenJson" 
              name="hiddenJson" 
              data-bind="text: ko.toJSON(viewModel.selectedDocument)" 
              rows="5" cols="100" 
              style="visibility:hidden;" >
    </textarea>

</form>

The data-bind attribute is knockoutjs - but this isn't important, the textarea correctly contains the JSON that is the serialized object.

[HttpPost]
public void GetWordDocument(DocumentModel hiddenJson)
{
   //hiddenJson is not a correctly populated instance of my DocumentModel class
   //any MVC experts know what I am doing wrong?
}

Now, how do I do a form POST to an MVC 3 application and obtain the deserialized class?

Jeroen
  • 60,696
  • 40
  • 206
  • 339
pilavdzice
  • 958
  • 8
  • 27

1 Answers1

6

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.

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • Awesome answer thank you! I don't know how I can use ajax because the result of the form-post is a word document that I want users to be able to get the popup asking them if they want to open it directly and I only figured out how to get this working properly in all browsers as a normal html form post. However your other ideas sound good. – pilavdzice Jul 24 '11 at 16:13
  • Thanks Ryan. Great solution. Once again you've gotten me out of a bind :-) – Damien Sawyer Aug 31 '11 at 01:04