Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model Binding on Multiple Model Form Submission from Strongly-Typed View

I'm having problems binding on a form with multiple models being submitted. I have a complaint form which includes complaint info as well as one-to-many complainants. I'm trying to submit the form but I'm getting errors on the bind. ModelState.IsValid always returns false.

If I debug and view the ModelState Errors, I get one saying: "The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph".

Also, when debugging, I can see that the the Complaint Model does get populated with Complainants from the form submission, so it seems that part is working.

I'm not sure if what I'm doing is not possible with the default ModelBinder, or if I'm simply not going about it the right way. I can't seem to find any concrete examples or documentation on this. A very similar problem can be found on stackoverflow here but it doesn't seem to deal with strongly typed views.

Controller Code:

    public ActionResult Edit(int id)
    {
        var complaint = (from c in _entities.ComplaintSet.Include("Complainants")
                     where c.Id == id
                     select c).FirstOrDefault();

        return View(complaint);
    }

    //
    // POST: /Home/Edit/5
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(Complaint complaint)
    {
        if (!ModelState.IsValid)
        {
            return View();
        }
        try
        {
            var originalComplaint = (from c in _entities.ComplaintSet.Include("Complainants")
                                     where c.Id == complaint.Id
                                     select c).FirstOrDefault();
            _entities.ApplyPropertyChanges(originalComplaint.EntityKey.EntitySetName, complaint);
            _entities.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

View Code (This is a partial view that gets called by Create/Edit Views, which are also strongly typed with Complaint):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ProStand.Models.Complaint>" %>

<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>

<table cellpadding="0" cellspacing="0" class="table">
    <tr>
        <td>
        <label for="DateReceived">Date Received:</label>
            <%= Html.TextBox("DateReceived") %>
            <%= Html.ValidationMessage("DateReceived", "*") %>    
        </td>
        <td>
            <label for="DateEntered">Date Entered:</label>
            <%= Html.TextBox("DateEntered")%>
            <%= Html.ValidationMessage("DateEntered", "*") %>
        </td>
    </tr>
    <tr>
        <td>
            <label for="Concluded">Concluded:</label>
            <%= Html.CheckBox("Concluded")%>
            <%= Html.ValidationMessage("Concluded", "*") %>
            </td>
        <td>
            <label for="IncidentDate">Incident Date:</label>
            <%= Html.TextBox("IncidentDate")%>
            <%= Html.ValidationMessage("IncidentDate", "*") %></td>
    </tr>
</table>
    <hr />
    <table>
    <% if (Model != null) {
           int i = 0;
       foreach (var complainant in Model.Complainants){ %>
       <%= Html.Hidden("Complainants[" + i + "].Id", complainant.Id)%>
    <tr>
        <td>
            <label for="Surname">Surname:</label>

            <%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
            <%= Html.ValidationMessage("Surname", "*")%>
        </td>
        <td>
            <label for="GivenName1">GivenName1:</label>
            <%= Html.TextBox("Complainants[" + i + "].GivenName1", complainant.GivenName1)%>
            <%= Html.ValidationMessage("GivenName1", "*")%>
        </td>
    </tr>
    <% i++; %>
    <% }} %>
    <tr>
        <td colspan=2>
            <input type="submit" value="Submit" />
        </td>
    </tr>
</table>
<% } %>
<div>
    <%=Html.ActionLink("Back to List", "Index") %>
</div>
like image 732
jebcrum Avatar asked Jun 30 '09 17:06

jebcrum


People also ask

Can we bind multiple models to view?

Introduction. In MVC we cannot pass multiple models from a controller to the single view.

What is model binding in MVC?

Model binding is a process in which we bind a model to controller and view. It is a simple way to map posted form values to a . NET Framework type and pass the type to an action method as a parameter. It acts as a converter because it can convert HTTP requests into objects that are passed to an action method.


1 Answers

Blind guess:

change:

<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>

with:

<%= Html.TextBox("Complaint.Complainants[" + i + "].Surname",  
complainant.Surname)%>

Respectively - add "Complaint." before "Complainants[..."

EDIT:

This is NOT a right answer. Left it undeleted just because that might add some value until proper answer pops up.

EDIT2:

I might be wrong, but for me it seems there's problem with entity framework (or - with the way you use it). I mean - asp.net mvc manages to read values from request but can't initialize complainants collection.

Here it's written:

The InitializeRelatedCollection(TTargetEntity) method initializes an existing EntityCollection(TEntity) that was created by using the default constructor. The EntityCollection(TEntity) is initialized by using the provided relationship and target role names.

The InitializeRelatedCollection(TTargetEntity) method is used during deserialization only.

Some more info:

Exception:

  • InvalidOperationException

Conditions:

  • When the provided EntityCollection(TEntity) is already initialized.
  • When the relationship manager is already attached to an ObjectContext.
  • When the relationship manager already contains a relationship with this name and target role.

Somewhy InitializeRelatedCollection gets fired twice. Unluckily - i got no bright ideas why exactly. Maybe this little investigation will help for someone else - more experienced with EF. :)

EDIT3:
This isn't a solution for this particular problem, more like a workaround, a proper way to handle model part of mvc.

Create a viewmodel for presentation purposes only. Create a new domain model from pure POCOs too (because EF will support them in next version only). Use AutoMapper to map EFDataContext<=>Model<=>ViewModel.

That would take some effort, but that's how it should be handled. This approach removes presentation responsibility from your model, cleans your domain model (removes EF stuff from your model) and would solve your problem with binding.

like image 176
Arnis Lapsa Avatar answered Sep 19 '22 10:09

Arnis Lapsa