How to do a form POST to an MVC 3 application and

2020-07-18 10:38发布

问题:

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?

回答1:

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.