Search code examples
c#htmlasp.net-mvcmvc-editor-templates

MVC DropDownList with EditorTemplate showing Selected Value and invalid name field as propertyName.propertyName


I have a dropdown list that is rendered through an EditorTemplate. The property has UIHints in a Validation class and displays correctly but when I look at the HTML the name of the control is PropertyType.PropertyName rather than just PropertyName.

This prevents the model from binding.

I also can't return a selected value to the View.

How do I get around this?

UPDATE See answer below for details.

ViewModel

public partial class HouseholdEditViewModel
{
    public int householdID { get; set; }
    public int familyID { get; set; }
    public string address { get; set; }
    public HousingTypeDropDownViewModel housingType { get; set; }
    public KeyworkerDropDownViewModel keyworker { get; set; }
    public string attachmentDate { get; set; }
    public bool loneParent { get; set; }
    public string familyPhoneCode { get; set; }
    public string familyPhone { get; set; }
}

DropDown ViewModel

public class HousingTypeDropDownViewModel
{
    public int housingTypeID { get; set; }
    public IEnumerable<SelectListItem> Items { get; set; }
}

EditorTemplate

@model WhatWorks.ViewModels.HousingTypeDropDownViewModel

@Html.DropDownListFor(h => h.housingTypeID, new SelectList(Model.Items, "Value", "Text"))

View

using (Html.ControlGroupFor(property.Name))
{                    
@Html.Label(property.GetLabel(), new { @class = "control-label" })
 <div class="controls">
         @Html.Editor(property.Name, new { @class = "input-xlarge" })
         @Html.ValidationMessage(property.Name, null, new { @class = "help-inline" })
 </div>
}

HTML

<div class="control-group">
    <label class="control-label" for="Housing_Type">Housing Type</label>                 
    <div class="controls">
        <select data-val="true" data-val-number="The field housingTypeID must be a number." data-val-required="The housingTypeID field is required." id="housingType_housingTypeID" name="housingType.housingTypeID">
            <option value="1">Owner Occupied</option>
            <option value="2">Rented - Social Landlord</option>
        </select>
        <span class="field-validation-valid help-inline" data-valmsg-for="housingType" data-valmsg-replace="true"></span>
    </div>
</div>

Solution

  • Having researched this a bit more, it would seem that this is working as intended.

    The simplest workaround is to use a DropDownList in the EditorTemplate rather than the DropDownListFor. Set the name to a blank string and the Html.Editor only picks up the property name once. Also note the change to Model.Items for the SelectList

    @model WhatWorks.ViewModels.HousingTypeDropDownViewModel
    
    @Html.DropDownList("", Model.Items)
    

    To elaborate a bit further on this, I was trying to use a DropDownList with an IEnumerable<SelectListItem> from my ViewModel and was struggling to get my HTML tags rendering correctly. Part of this involved needing a Selected Value on my Edit View.

    As this seems to be a regular problem, I've amended the title of this post slightly and will document Controller code here - this is what worked for me and may not be best practice... Caveat Emptor!

    IRepository

    public interface IHouseholdRepository : IDisposable
    {
        IEnumerable<tHousehold> Get();
        tHousehold GetById(int Id);
        IEnumerable<SelectListItem> AddHousingType();
        IEnumerable<SelectListItem> EditHousingType(int id);
        //etc...
    
    }
    

    Repository

    public IEnumerable<SelectListItem> AddHousingType()
    {
        var query = from ht in context.tHousingType
                    orderby ht.housingType
                    select new
                    {
                        ht.housingTypeID,
                        ht.housingType
                    };
    
        return query.AsEnumerable()
            .Select(s => new SelectListItem
            {
                Value = s.housingTypeID.ToString(),
                Text = s.housingType
            });
    }
    
    public IEnumerable<SelectListItem> EditHousingType(int id)
    {
        var housingType = (from h in context.tHousehold
                          where h.householdID == id
                          select new
                              {
                                  h.tHousingStatus.FirstOrDefault().housingTypeID
                              }
                          );  
    
        var query = from ht in context.tHousingType
                    orderby ht.housingType
                    select new
                    {
                        ht.housingTypeID,
                        ht.housingType
                    };
    
        return query.AsEnumerable()
            .Select(s => new SelectListItem
               {
                   Value = s.housingTypeID.ToString(),
                   Text = s.housingType,
                   Selected = (housingType.FirstOrDefault().housingTypeID == s.housingTypeID ? true : false)
               });
    }
    

    Controller

        public ActionResult Edit(int id = 0)
        {
            HousingTypeDropDownViewModel housingType = new HousingTypeDropDownViewModel
            {
                Items = _repo.EditHousingType(id)
            };
    
            HouseholdEditViewModel h = GetUpdate(id);
            //GetUpdate is Automapper code unrelated to the DropDown 
    
            h.housingTypeID = housingType;
    
            if (h == null)
            {
                return HttpNotFound();
            }
            return View(h);
        }
    

    Inspiration for the above taken from too many SO posts to mention. Thanks all and hope this helps others.