tags:

views:

1133

answers:

5

I have an ASP.Net MVC application with a model which is several layers deep containing a collection. I believe that the view to create the objects is all set up correctly, but it just does not populate the collection within the model when I post the form to the server.

I have a piece of data which is found in the class hierarchy thus:

person.PersonDetails.ContactInformation[0].Data;

This class structure is created by LinqToSQL, and ContactInformation is of type EntitySet<ContactData>. To create the view I pass the following:

return View(person);

and within the view I have a form which contains a single text box with a name associated to the above mentioned field:

<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>

The post method within my controller is then as follows:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create (Person person)
{
// Do stuff to validate and add to the database 
}

It is at this point where I get lost as person.PersonDetails.ContactInformation.Count() ==0. So the ModelBinder has created a ContactInformation object but not populated it with the object which it should hold (i.e ContactData) at index 0.

My question is two fold: 1. Have I taken the correct approach.. i.e. should this work? 2. Any ideas as to why it might be failing to populate the ContactInformation object?

Many thanks, Richard

A: 

Maybe lack of Bind attribute is the case:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create ([Bind] Person person)
{
// Do stuff to validate and add to the database 
}
Arnis L.
Thanks, I have given this a try with no luck. The fact that it is binding to the person and creating objects down to the ContactInformation object indicates to me that it is binding, just not to the ContactData level.
Richbits
No, Bind is only necessary on an argument if you need to change the default binding rules.
Craig Stuntz
A: 

The first argument of Html.TextBox is the name of the textbox, the second would be the value.

"Wrong":

<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>

"Right":

<%= Html.TextBox("nameoftextbox", person.PersonDetails.ContactInformation[0].Data)%>
Ropstah
Thanks, but my understanding is that the second parameter is the value used to populate the textbox in a e.g. editing scenario. The first value is used by the defaultmodelbinder to bind the form to an object created on posting back.
Richbits
My bad...........
Ropstah
+5  A: 

I think that your model is too complex for the default model binder to work with. You could try using multiple parameters and binding them with prefixes:

public ActionResult Create( 
    Person person,
    [Bind(Prefix="Person.PersonDetails")]
    PersonDetails details,
    [Bind(Prefix="Person.PersonDetails.ContactInformation")] 
    ContactInformation[] info )
{
      person.PersonDetails = details;
      person.PersonDetails.ContactInformation = info;

      ...
}

Or you could develop your own custom model binder that would understand how to derive your complex model from the form inputs.

tvanfosson
Thanks, that works perfectly. I have tried an example with much deeper heirarchy which worked perfectly well, but you are probably right that it is getting lost in the complexity. Two comments though, for clarity:1. I only needed the [Bind(Prefix="Person.PersonDetails.ContactInformation")]ContactInformation[] info) and corresponding setting of the model object. 2. ContactInformation[] needed to be EntitySet<ContactData> to correspond to the correct type. Now to look up some more detail on Prefix.... Thanks for your help.Richard
Richbits
+1  A: 

If a property is null, then the model binder other could not find it or could not find values in the submitted form necessary to make an instance of the type of the property. For example, if the property has a non-nullable ID and your form does not contain any data for that ID , the model binder will leave the property as null since it cannot make a new instance of the type without knowing the ID.

In other words, to diagnose this problem you must carefully compare the data in the submitted form (this is easy to see with Firebug or Fiddler) with the structure of the object you are expecting the model binder to populate. If any required fields are missing, or if the values are submitted in such a way that they cannot be converted to the type of a required field, then the entire object will be left null.

Craig Stuntz
This sounds right.
Arnis L.
Thanks, Surely if what you say here is correct, then tvanfossen's solution would fail as well?
Richbits
Not necessarily. I describe one possible issue. He describes another.
Craig Stuntz
A: 

I've been struggling with this same type of scenario and eventually came to realize that the underlying problem is that the MVC default model binder does not seem to work on EntitySet<T> fields, only List<T> fields. I did however find a simple workaround that seems acceptable. In my case, I have a Company entity that has one to many relationship to Contacts (my Linq-to-Sql EntitySet).

Since it seems that when I change my code from EntitySet<Contact> to List<Contact>, the MVC default model binder starts working as expected (even though the LTS isn't now), I figured I would provide an alternate, "aliased" property to MVC that is of type List<Contact>, and sure enough, this seems to work.

In my Company entity class:


// This is what LINQ-to-SQL will use:
private EntitySet<Contact> _Contacts = new EntitySet<Contact>();
[Association(Storage="_Contacts", OtherKey="CompanyID", ThisKey="ID")]
public EntitySet<Contact> Contacts
{
    get { return _Contacts; }
    set { _Contacts.Assign(value); }
}

// This is what MVC default model binder (and my View) will use:
public List<Contact> MvcContacts
{
    get { return _Contacts.ToList<Contact>(); }
    set { _Contacts.AddRange(value); }
}

So now, in my View, I have the following:

<label>First Name*
    <%= Html.TextBox("Company.MvcContacts[" + i + "].FirstName") %>
</label>
<label>Last Name*
    <%= Html.TextBox("Company.MvcContacts[" + i + "].LastName") %>
</label>

Seems to work like a charm!

Best of luck! -Mike

Funka
Thanks, sorry been away for a while. I hope that you saw the answer to this on MSDN (I assume that it was you who commented there). I also logged a bug on the connect site, and this has been confirmed to be fixed in .Net 4.0. In my case I think that your solution becomes too complicated as the entity set is deep within my class structure. I have also found that editing of EntitySets is a real problem as a new db row is created instead of update. Hopefully this will be sorted out for 4.0 too.
Richbits
It is nice to hear that this will be addressed. And yes, I posted this same solution to the microsoft newsgroup practically the same time I did here: I didn't realize until now that you were the one who started it! You did get one reply from Allen that shows how to fix the property-setter which seems more ideal, but in my case I'm using LTS-generated entities and not sure how to easily fix this (without of course more giant workarounds). P.S., the other thread is here: http://groups.google.com/group/microsoft.public.dotnet.framework.aspnet/browse_thread/thread/a2869970f33c712a/188c0a9e5c00dc2a
Funka