0
votes

What should the controller should receive when I am sending a view that contains multiple partial view with different models.

I have a view that renders multiple/dynamic partial views (these views are selecting depending on the package that the user previously selected ), all of these partial views have different models and I need to submit them in one big "parent" form. My main concern is what should the controller receive? and how to handle all the information inside he form, since I have multiple and variable models (depending of what the user choose) that are being send from the form.

This is my my parent view, that renders all the partial views, depending of which package the user chose (i.e. The parent view can render from 1 to 7 forms. all of them have different models). IEnumerable string contains a list of strings what I will use to spit out a Dictionary with Views and Models to render them later in this view.

@using UI.Shared.Utils
@model IEnumerable<string>
@{
  var forms = CustomFormUtil.GetCustomMetaPartial(Model);       
}                    
@using (Html.BeginForm(MVC.Service.NewOrderRequest.Index(**I am confused what to pass here**), FormMethod.Post))
{
  foreach (var form in forms)
  {
    { Html.RenderPartial(form.View, form.Model); }
  }
  <p style="clear: both">
    <input id="submitNOR" type="submit" value="Submit" style="float: right" />
  </p>
}

So let's say the user fills 4 partial views, that means I have to send 4 different models to the controller so I can use the data filled by the user.

This is my controller:

[HttpPost]
[SaveModelState]
public virtual RedirectToRouteResult Index(**What should I receive**)
{
  if (!ModelState.IsValid)
  {
    ModelState.AddModelError("error", "Please fill out the required fields");
    return RedirectToAction(MVC.Service.NewOrderRequestForms.Index());
  }
  ...
}

My main concern is what should the controller receive? I have tried the following:

[HttpPost]
[SaveModelState]
public virtual RedirectToRouteResult Index(IEnumerable<ICustomModel> models)

but after some research this is not possible also I have tried to pass all the possible models to the controller

[HttpPost]
[SaveModelState]
public virtual RedirectToRouteResult Index(Model1 model1, ..., ModelN modeln)

This is also not working, and my latest attempt was to create a Master Model that contains all the possible models and pass it to the Controller, this is my Master Model

namespace UI.Next.Areas.Service.Models
{
  public class TdlMasterModel : ICustomMetaModel
  {
    public TdlMasterModel()
    {
    }
    public TdlMasterModel(Guid? accountId)
    {
      AccountId = accountId;
    }
    public Guid? AccountId { get; set; }
    public Model1 model1{ get; set; }
    ...
    public ModelN modeln{ get; set; }
  }
}

Controller:

[HttpPost]
[SaveModelState]
public virtual RedirectToRouteResult Index(MasterModel masterModel)

So far, nothing of the proposed solutions had worked. Is there any straight solution for this or I will have to create a ModelBinder to handle this.

1
Q1 - Nothing needs to be in BeginForm if the GET and POST methods are the same (i.e. Index() - it can be just @using(Html.BeginForm()) {user3559349
Q2.- The 'master' view model is the correct approach. You need to initialise a new instance of the view model in the GET method and pass it to the view which will have @model TdlMasterModeluser3559349
Thank you for answering, I am confused about Q2, could you give an example please?. Also, should the individual models have a "name" property, or something similar, to differentiate them in the controller? I am asking this because some of the property fields in the models share the same name field and I am concern about how to differentiate them when stripping the data from the MasterModelGuillermo Sánchez
In order to 'differentiate' them (and bind on post back) you need to specify the HtmlFieldPrefix. Give me 30 minutes and I'll post an exampleuser3559349
Very appreciated, thank you =)Guillermo Sánchez

1 Answers

1
votes

Use a view model containing the models for each form you want to render

View Model

public class MasterVM
{
  public Model1 Model1 { get; set; }
  public Model2 Model2 { get; set; }
  ....
}

Controller

public ActionResult Index()
{
  MasterVM model = new MasterVM();
  // Set the values of only those models you want to display forms for
  model.Model2 = new Model2();
  return View(model);
}

[HttpPost]
public ActionResult Index(MasterVM model)
{
  if(!ModelState.IsValid)
  {
    return View();
  }
  if (model.Model1 != null)
  {
    // save Model1
  }
  else if (model.Model2 != null)
  {
    // save Model2
  }
  ....
  // redirect somewhere
}

Partial _Model1.cshtml

@model Model1

@Html.LabelFor(m => m.SomeProperty)
@Html.TextBoxFor(m => m.SomeProperty)
@Html.ValidationMessageFor(m => m.SomeProperty)

@Html.LabelFor(m => m.AnotherProperty)
@Html.TextBoxFor(m => m.AnotherProperty)
@Html.ValidationMessageFor(m => m.AnotherProperty)

....

View

@model MasterVM
@using Html.BeginForm())
{
  if(Model.Model1 != null)
  {
    @Html.Partial("_Model1", Model.Model1, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "Model1" }})
  }
  if(Model.Model2 != null)
  {
    @Html.Partial("_Model2", Model.Model2, new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "Model2" }})
  }
  ....
  <input type="submit" />
}

Note the 3rd parameter of @Html.Partial. This will add a prefix to the control names, so if Model1 contains property string Name, the control will be generated as <input name="Model1.Name" ...> allowing you to post back and bind to MasterVM