8

I'm trying to tack on some additional data a POST request sent to my server. Originally, I was sending just several forms worth of information:

$.ajax({
    url: 'SaveAllDetails',
    type: 'POST',
    data: $('form').serialize(),
    dataType: 'json'
});

and the MVC Controller method:

[HttpPost]
public ActionResult SaveAllDetails([Bind(Prefix = "order")]ExistingOrderDetailsModel existingOrderDetailsModel, 
    [Bind(Prefix = "task")]ExistingTaskDetailsModel existingTaskDetailsModel, [Bind(Prefix = "device")]DeviceDetailsModel deviceDetailsModel)
{
    ....
}

This works great. MVC's model binder is able to correctly de-serialize the URL-encoded string.

Now, requirements have changed. I need to send an additional array of data along with my three forms. This array of data is not held within a form and does not have a binding prefix. I need to do this all in the same Controller method because all validation needs to be performed inside of a single transaction.

So, I've now got:

var subcomponentsGridRows = JSON.stringify(subcomponentsDetailsView.getAllGridData());
var existingOrderDetailsFormData = $('form#existingOrderDetailsForm').serialize();
var existingTaskDetailsFormData = $('form#existingTaskDetailsForm').serialize();
var deviceDetailsFormData = $('form#existingDeviceDetailsForm').serialize()

$.ajax({
    url: 'SaveAllDetails',
    type: 'POST',
    data: {
        existingOrderDetailsModel: existingOrderDetailsFormData,
        existingTaskDetailsModel: existingTaskDetailsFormData,
        deviceDetailsModel: deviceDetailsFormData,
        subcomponentsGridRows: subcomponentsGridRows
    },
    dataType: 'json'
});

This doesn't work for at least one reason. Each form is represented as a URL-encoded string. subcomponentsGridRows is a JSON structure. The MVC model binder isn't capable of deciphering both types of information in one go as far as I can tell.

What's a good way to go about tackling this problem?

Sean Anderson
  • 27,963
  • 30
  • 126
  • 237
  • `subcomponentsGridRows is a JSON structure and the MVC model binder isn't capable of deciphering that mess`. How so? If you create a model that represents this structure (by say, using this website: http://json2csharp.com/), then the Model Binder will happily bind to it. – Simon Whitehead Feb 13 '14 at 21:57
  • you can merge the data before submitting or pass other info using GET in the POST url... – dandavis Feb 13 '14 at 21:58
  • @SimonWhitehead When I post the data to the server I have to indicate what type of information I am sending. The forms are URL-encoded entities. The grid data is a JSON structure. MVC will happily take one or the other, but, as far as I can see, there isn't an easy way to combine both of them in one request. – Sean Anderson Feb 13 '14 at 21:59
  • @dandavis but how would I merge the two? One is a URL-encoded string and the other is a JSON structure. I looked at this answer: http://stackoverflow.com/questions/17038396/posting-additional-data-alongside-serialized-form-data but data still isn't getting through. – Sean Anderson Feb 13 '14 at 22:00
  • turn the url string into an object, then merge. js libs and .net provide queryString parsers. – dandavis Feb 13 '14 at 22:01
  • @SeanAnderson - not sure how to update the github repo with my newer better commented version, but this will fix the re-basing of name indecies within an serializeArray() https://github.com/yupdon/RebaseSerializeArray/blob/master/trust.RebaseSerializeForm.js then simply post it as you would any normal Jquery.SerializeArray() best part is this can be used on a single form with as many HtmlFieldPrefix using partial views as you wish. – Pakk Oct 29 '15 at 20:53

1 Answers1

13

You might find the following plugin useful.

Here's how it might be useful to you. Let's start by cleaning your controller action by defining a view model:

public class MyViewModel
{
    public ExistingOrderDetailsModel Order { get; set; }
    public ExistingTaskDetailsModel Task  { get; set; }
    public DeviceDetailsModel Device  { get; set; }

    public AdditionalRowsViewModel[] AdditionalRows { get; set; }
}

In this example the AdditionalRowsViewModel will obviously hold the additional information you are trying to pass to the controller action.

And then your controller action will become:

[HttpPost]
public ActionResult SaveAllDetails(MyViewModel model)
{
    ....
}

OK, this step was absolutely necessary, it's just that when I see a controller action taking more than 1 parameter I simply define a view model.

And finally let's adapt our AJAX call:

$.ajax({
    url: 'SaveAllDetails',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({
        order: $('form#existingOrderDetailsForm').serializeObject(),
        task: $('form#existingTaskDetailsForm').serializeObject(),
        device: $('form#existingDeviceDetailsForm').serializeObject(),
        additionalRows: subcomponentsDetailsView.getAllGridData()
    }),
    success: function(result) {
        // do something with the result of the AJAX call here
    }
});

Things to notice:

  1. Get rid of this dataType: 'json' parameter in your AJAX request. You are using ASP.NET MVC and hopefully you are returning a Jsonresult from your controller action which is successfully setting the Content-Type response header to the correct value. jQuery is intelligent enough to use the value of this response header and pre-process the result variable that will be passed to the success callback of your AJAX request. So in this case you will already get a javascript object
  2. Since you are sending JSON to the server in your AJAX request you need to properly specify the contentType: application/json parameter. Otherwise how do you expect that ASP.NET MVC will know that the client is sending JSON and apply the correct model binder? By the way the default Content-Type request header that jQuery will send is application/x-www-form-urlencoded, so if you are sending JSON payload in your POST request that would be a conflict and violation of the protocol.
Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • How does your solution take into account the form prefixes? I can't bind individual prefixes to each property of MyViewModel. As far as I am aware, prefixes must be associated with individual parameters or an entire class. Should I modify serializeObject to strip the "order.", "task." and "device." prefixes from the keys? – Sean Anderson Feb 13 '14 at 22:35
  • If the property names are unique then you could indeed modify the plugin to take them into account and remove them. Alternatively you could update it in order to generated complex JSON sub-objects. – Darin Dimitrov Feb 13 '14 at 22:38
  • The property names are unique for each object, but non-unique between the forms. I think this is OK for the solution though because once each form has been isolated into a given object it's non-ambiguous. I'll update when I have my final solution, but thank you this seems like the correct way to go. Cheers. – Sean Anderson Feb 13 '14 at 22:40
  • What is the .serializeObject() ? – Stas Boyarincev Sep 02 '16 at 15:35
  • @SeanAnderson i got around the prefixed problem by using partial views for my forms. Not sure if it is best practice but it works – JustLearning Apr 27 '17 at 17:30