RSolberg
RSolberg

Reputation: 26972

MVC DropDown Lists Complex Parent Model

I'm guessing I've missed a simple principle here which someone will point out rather quickly. I essentially have a Person object that contains some basic properties and then a couple of Lists<> for Phones and Addresses. A Phone and Address each have a Type (home/work/etc.). Addresses also has a State and a County.

When I'm executing the Manage action, I'm setting up the ViewBag for CountyId, StateProvinceID, etc. but when I have a second address or phone, these dropdowns don't seem to have the correct selected value.

I'm sure this is something pathetically basic that I'm doing wrong here...

Resolved...

I knew there was something basic I was missing and the answer was to simply create custom view models for the Address and Phone objects. These new view models could contain a list for each of of these select lists...

MODEL SAMPLE

public CustomerModel()
    {
        IsActive = true;
        if (Phones == null)
        {
            Phones = new List<Phone>();
        }
        if (Addresses == null)
        {
            Addresses = new List<Address>();
        }
    }

    public Guid Id { get; set; }
    public Guid OrganizationId { get; set; }

    [Required]
    public String FirstName { get; set; }
    public String MiddleName { get; set; }
    [Required]
    public String LastName { get; set; }

    [RegularExpression(".+\\@.+\\..+", ErrorMessage = "Please enter a valid email address.")]
    public String EmailAddress { get; set; }

    [DataType(DataType.Date)]
    public DateTime? DateOfBirth { get; set; }

    public List<Phone> Phones { get; set; }
    public List<Address> Addresses { get; set; }
}

PARENT VIEW SAMPLE

<tbody id="tblAddressRows">
    @foreach (var address in Model.Addresses)
    {
        Html.RenderPartial("_AddressRow", address);
    }
</tbody>

PARTIAL VIEW SAMPLE

<td>
    @Html.HiddenFor(a => a.Id)
    @Html.DropDownList("AddressTypeId", ViewData["AddressTypeId"] as SelectList)
</td>
<td class="AddressLines">
    @Html.EditorFor(a => a.AddressLine1)
    @Html.EditorFor(a => a.AddressLine2)
</td>
<td>
    @Html.EditorFor(a => a.City)
</td>
<td>
    @Html.DropDownListFor(m => m.StateProvinceId,  ViewData["StateProvinceId"] as SelectList, new {@class = "State"})
</td>
<td>
    @Html.EditorFor(a => a.PostalCode, new {@class = "ZipCode"})
</td>
<td>
    @Html.DropDownList("CountyId", ViewData["CountyId"] as SelectList, new {@class = "County"})
</td>

CONTROLLER SAMPLE

//
// GET: /Customer/Manage/5
public ActionResult Manage(Guid id)
{
    CustomerModel customer = DataService.GetCustomer(id, HttpContext.User.Identity.Name);
    if (customer == null)
    {
        return HttpNotFound();
    }
    ViewBag.AddressTypeId = new SelectList(DataService.GetOrganizationAddressTypes(HttpContext.User.Identity.Name), "Id", "Name");
    ViewBag.PhoneTypeId = new SelectList(DataService.GetOrganizationPhoneTypes(HttpContext.User.Identity.Name), "Id", "Name");
    ViewBag.StateProvinceId = new SelectList(DataService.GetStates(), "Id", "Name");
    ViewBag.CountyId = new SelectList(DataService.GetCounties(0), "Id", "Name");
    return View(customer);
}

Upvotes: 2

Views: 1654

Answers (2)

Ian
Ian

Reputation: 67

try putting the partial view in the shared/editortemplates folder and using html.editorfor(model => model.address)

Upvotes: 2

Erik Funkenbusch
Erik Funkenbusch

Reputation: 93444

You have a number of things wrong here, some of them you haven't noticed yet. But you will.

The first is that you should never name your dropdownlist's collection the same as your selected value. MVC uses the ViewBag and ViewState in some non-obvious ways and you will run into lots of trouble if you do this. Change your list of items to StateProvinceList and CountyList and PhoneTypeList and AddressTypeList.

The second thing is that you will be unable to post the values back to the controller correctly, because you are using a partial view to render a sub-item of a collection, and passing that item as the model to the view. MVC renders objects based on the model as the root, and it creates names assuming that this will be the case. If you are posting the items to an action method that accepts the parent class, then the model binder will not be able to bind the partial rendered values correctly (because MVC rendered them as if they were that submodel, not a collection of that submodel).

You should instead be using an EditorTemplate, which will do several things. First, the EditorTemplate will automatically iterate over any collections, and second it render the names of the form controls with the proper names that take into account the parent class.

Read more about EditorTemplates here:

http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html

ASIDE:

Why are you using a DropDownListFor with StateProvince but a DropDownList for County and Address? Always use strongly typed versions if you can, it helps prevent form item misnaming.

Another thing, why the if block in the constructor? By definition, a constructor only gets called when constructing an object, thus the properties will always be null (well, someone could derive from your class and set those properties in the subclass constructor, but if it's your code you would know that)

Upvotes: 4

Related Questions