Introduction
This article shows how we can apply a DropDownList
metadata attribute to a View Model property, and have a DropdDownList
rendered by the EditorFor
HTML helper.
Background
I'm a big fan of runtime scaffolding in ASP.NET MVC. This is where you use e.g. the EditorFor HTML Helper to decide how to render an HTML element for a property of your view model. For example, if a property is marked with the DataType
attribute and that indicates the property's DataType
is DateTime
, then it will automatically render a date picker. For integer properties, EditorFor
renders an UpDown
spinner control. If EditorFor
works smoothly for all your properties, you can use a single EditorForModel
element to render your whole view model into the view correctly.
Using the Code
Using this system involves new code and changed code in several areas of your MVC application. It is not just a library or plug in, although I have plans for version 2 to use Unity at these points where I modify the code.
public class DemoModel
{
[DropDownList("LanguageSelect")]
public int? LanguageId { get; set; }
public SelectList LanguageSelect { get; set; }
}
Now when I use the Razor markup @EditorFor(m => m.LanguageId)
, I get a drop-down populated from the LanguageSelect
list. I get this because the DropDownListAttrbute
class attaches the select list name to the LanguageId
model:
public class DropDownListAttribute : UIHintAttribute, IMetadataAware
{
public DropDownListAttribute(string selectListName) :
base(KnownUiHints.DropDown, KnownPresentationLayers.Mvc, selectListName)
{
SelectListName = selectListName;
}
public string SelectListName { get; set; }
public void OnMetadataCreated(ModelMetadata metadata)
{
var listProp = metadata.ContainerType.GetProperty(SelectListName);
metadata.AdditionalValues[KnowMetadataKeys.SelectListName] = SelectListName;
}
}
All my view models derive from ViewModel
, which offers a SelectListDictionary
property:
private IDictionary<string,> _selectListdictionary;
public virtual IDictionary<string,> SelectListDictionary
{
get
{
if (_selectListdictionary == null)
{
var props = GetType().GetProperties().Where(p => p.PropertyType == typeof(SelectList));
_selectListdictionary = props.ToDictionary
(prop => prop.Name, prop => (SelectList)prop.GetValue(this, null));
}
return _selectListdictionary;
}
}
In my base controller, I override the View
method to pull the entire select list dictionary from the view model, and insert it into the view's viewdata, making it available for the editor template:
protected override ViewResult View(string viewName, string masterName, object model)
{
var result = base.View(viewName, masterName, model);
if ((model is ViewModel) && (!ViewData.ContainsKey(KnowMetadataKeys.ViewDataSelectLists)))
{
var vm = (ViewModel)model;
result.ViewData.Add(KnowMetadataKeys.ViewDataSelectLists, vm.SelectListDictionary);
}
return result;
}
Said editor template:
@using Erisia.Constants
@{
var list = (SelectList)ViewData.ModelMetadata.AdditionalValues
[ViewData.ModelMetadata.AdditionalValues[KnowMetadataKeys.SelectListName].ToString()];
var listWithSelected = new SelectList(list.Items, list.DataValueField, list.DataTextField, Model);
}
@Html.DropDownListFor(m => Model, listWithSelected, " - select - ")
Remarks
There are probably some better ways of doing this, and populating the select list inside the view model is probably not too kosher, but this little 'framework' has saved me so much time on so many projects, and I've just received a glowing compliment for it on Code Review beta. It's been way too long since I wrote an article and I thought sharing this would make a good one.