11
\$\begingroup\$

My goal was to be able to render my view models using a standard Object.cshtml editor and display template. In order to do so I needed to be able to always call Html.Editor(propertyName) or Html.Display(propertyName) in order to render the HTML element.

There were plenty of resources for creating custom attributes and custom editor templates. However I ran into a wall when creating the DropDownListAttribute and template since I needed to obtain the values for the select list as well as set the selected item based on the current instance of the view model.

Here is the reference I used to create the custom ModelMetadataProvider and custom attributes.

Here is the problem I ran into with getting the current instance of the view model.

It turns out using reflection here to access the view model is not safe. Depending on how you iterate over the property metadata the view model can sometimes be null.

Here is my implementation using the resource referenced above. This is a simple version that only uses a List<string> for the DropDownList. Using this example it would be trivial to use dictionaries and set the text/value properties:

View Model:

public class PhoneNumberViewModel
{
 // ... normal properties and attributes
 [DropDownList("ContactTypes")] // <-- This is the important one!
 public string ContactType { get; set; }
 public static IEnumerable<string> ContactTypes
 {
 get
 {
 return new List<string>()
 {
 "Home", "Work", "Mobile"
 }
 }
 }
}

DropDownListAttribute Implementation

public class DropDownListAttribute : MetadataAttribute
{
 private string _items;
 public DropDownListAttribute(string items)
 {
 _items = items;
 }
 public override void Process(System.Web.Mvc.ModelMetadata modelMetaData)
 {
 modelMetaData.AdditionalValues.Add("Items", _items);
 modelMetaData.TemplateHint = TemplateHints.DropDownList; // Just a const string
 }
}

Custom HTML Helper used to render elements that need access to the view model instance

@Html.ContextualEditor(propertyName)

ContextualEditor HTML Helper Implementation

public static MvcHtmlString ContextualEditor(this HtmlHelper html, string expression)
{
 var model = html.ViewData.Model;
 var propertyMetadata = html.ViewData.ModelMetadata.Properties.Where(x => x.PropertyName == expression).Single();
 switch (propertyMetadata.TemplateHint)
 {
 case TemplateHints.DropDownList:
 var items = (IEnumerable<string>)model.GetType().GetProperty((string)propertyMetadata.AdditionalValues["Items"]).GetValue(model, null);
 var selectedValue = (string)model.GetType().GetProperty(expression).GetValue(model, null);
 var selectList = new TagBuilder("select");
 selectList.MergeAttribute("id", expression);
 selectList.MergeAttribute("name", expression);
 var options = new StringBuilder();
 foreach (var item in items)
 {
 var option = new TagBuilder("option");
 option.InnerHtml = item;
 if (item == selectedValue)
 option.MergeAttribute("selected", "selected");
 options.Append(option.ToString());
 }
 selectList.InnerHtml = options.ToString();
 return MvcHtmlString.Create(selectList.ToString());
 default:
 return System.Web.Mvc.Html.EditorExtensions.Editor(html, expression);
 }
}

Please let me know what you think of the implementation. I'd be interested to know if anyone has found a more elegant way of achieving this result.

asked Sep 18, 2011 at 18:06
\$\endgroup\$
1
  • \$\begingroup\$ What exactly are you trying to do? Are you trying to display a drop-down list? \$\endgroup\$ Commented Apr 27, 2012 at 0:54

1 Answer 1

3
\$\begingroup\$

Why not just do this?

public class PhoneNumberViewModel
{
 // ... normal properties and attributes
 [DropDownList("ContactTypes")] // <-- This is the important one!
 public string ContactType { get; set; }
 public static IEnumerable<SelectListItem> ContactTypes
 {
 get { return new[] { "Home", "Work", "Mobile" }.ToSelectList(); }
 }
}
public static class ExtensionsForSelectListItem
{
 public static IEnumerable<SelectListItem> ToSelectList(this IEnumerable<string> items)
 {
 return items.Select(x => new SelectListItem { Value = x });
 }
 public static MvcHtmlString ContextualEditor(this HtmlHelper html, string expression)
 {
 var model = html.ViewData.Model;
 var propertyMetadata = ModelMetadata.FromStringExpression(expression, html.ViewData);
 switch (propertyMetadata.TemplateHint)
 {
 case TemplateHints.DropDownList:
 var items = (IEnumerable<SelectListItem>)model.GetType().GetProperty((string)propertyMetadata.AdditionalValues["Items"]).GetValue(model, null);
 return html.DropDownList(expression, items);
 default:
 return System.Web.Mvc.Html.EditorExtensions.Editor(html, expression);
 }
 }
}

I didn't test the code to get the items. But I'm assuming that you had that part working.

I also authored my own HTML Helper Library if you want check out some other examples.

https://bitbucket.org/grcodemonkey/buildmvc

answered Sep 8, 2012 at 13:24
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.