Stefan Koenen
Stefan Koenen

Reputation: 2337

Posting/Binding multiple forms on backend ASP.NET MVC (MEF)

We are trying to send multiple forms with one Ajax (jQuery) Call to an ASP application.

We use the following jQuery code:

var formContainer = {
      Form1 : form1.serialize(),
      Form2 : form2.serialize()
     }
     $.ajax({
         type: "POST",
         url: '@Url.Action("CreateModel", "Controller")',

      data: formContainer,
         success: function (result) { }
     });

On the server we receive the following in the Request.Form property:

Key   : Value
Form1 : All serialized form elements for Form1
Form2 : All serialized form elements for Form2

Normally we use the following method so ASP is automaticly creating the object with the right property value:

public ActionResult CreateModel(ClassForForm1 obj)

But because the two forms are send together the modelbinder cannot bind and build the class. So for this action we want the modelbuilder to use the values in Request.Form["Form1"].

We can't use a custom modelbinder, because we use an extern library (DevExpress ,they wrote an own implementation above this).

We are using the MEF framework to add functionalities (these functionalities are added as forms on the view). For this reason we do not know what too expect on the backend. So writing a wrapper ViewModel is not acceptable.

The functionality for proccessing the other forms data will be handeled inside other modules.

Any solutions are welcome!

Thanks in advance.

Upvotes: 8

Views: 1906

Answers (3)

Baximilian
Baximilian

Reputation: 885

My previous sample worked just because my class has name and value props. I'm realy sorry for that. But now you can see working DEMO

JS

function mapForm(form)
{
    var result = {};

    $.map($(form).serializeArray(), 
                    function(el){
                        mapFormProperty(result, el);
                             });    
    return result;
}

function mapFormProperty(form, property)
{
    form[property.name] = property.value;
}

$('.submit').click(function(){

   var form1 = mapForm($('#form1'));
   var form2 = mapForm($('#form2'));        

   var formContainer  = {
      'Form1': form1,
      'Form2': form2};

    $.ajax({
       type: "POST",
       url: '@Url.Action("CreateModel", "Controller")',
       data: JSON.stringify(formContainer), 
       success: function (result) { }
});

Operations with forms and form container should give you next json string

"{"Form1":{"Prop1":"Value1","Prop2":"Value2"},"Form2":{"Prop1":"Value1","Prop2":"Value2"}}"

And your model binder will be able solve this, if you change your action signature

Action

public ActionResult CreateModel(ClassForForm1 Form1) //argument name must be equal to data property
                                                     //but not equal to class name

And it should work. it works in my test sample

Upvotes: 1

Jim Crandall
Jim Crandall

Reputation: 84

If you create you javascript object like this:

var formContainer = { obj : {
          Form1 : form1.serialize(),
          Form2 : form2.serialize()
      }
 }

The controller should match it up with the name 'obj' you created in the javascript with the 'obj' in your method....

 public ActionResult CreateModel(ClassForForm1 obj)

Upvotes: 1

B2K
B2K

Reputation: 2611

This is typically done using a combined view model. Otherwise, you would need to manually parse the request parameters. Here is a fiddle showing how to combine the data from multiple forms.

$(function() {
    $('button').click(function(e) {
        var form1 = $('#form1');
        var form2 = $('#form2');
         $.ajax({
             type: "POST",
             url: '/echo/html/',
             data: form1.serialize()+"&"+form2.serialize(),
             success: function (result) {
                 alert(result);
             }
         });
    });
});

On the server, your view model would require:

public class IndexViewModel {
   // properties from form1
   public string first { get; set; }

   // properties from form2
   public string last { get; set; }
}

public class First {
    public string first { get; set; }
}

public class Last {
   public string last { get; set; }
}

And your action signature:

[HttpPost]
public ActionResult Index(IndexViewModel model) {
    var firstModel = (new First()).CloneMatching(model);
    var lastModel = (new Last()).CloneMatching(model);

    return RedirectToAction("Thanks");
}

See Best way to clone properties of disparate objects for the CloneMatching extension method.

Upvotes: 2

Related Questions