I wrote an HtmlHelper expression I use a lot of the time to put title tags into my dropdown lists like so:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
string idPropertyName,
string displayPropertyName,
string titlePropertyName,
object htmlAttributes) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id="{0}" name="{0}"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}="{1}"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">
";
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == idPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var titleValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == titlePropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var displayValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == displayPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
returnText += string.Format("<option value="{0}" title="{1}"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected="selected"";
}
returnText += string.Format(">{0}</option>
", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
...this works swimmingly, but there are times when I want to go further. I'd like to customize the id, display, and title pieces of this beast without having to write out the html. For example, if I have some classes in a model like so:
public class item
{
public int itemId { get; set; }
public string itemName { get; set; }
public string itemDescription { get; set; }
}
public class model
{
public IEnumerable<item> items { get; set; }
public int itemId { get; set; }
}
In my view I can write:
@Html.SelectFor(m => m.itemId, Model.items, "itemId", "itemName", "itemDescription", null)
...and I'll get a nice dropdown with title attributes etc. This is great as long as the enumerated items have properties exactly as I'd like to display them. But what I'd really like to do is something like:
@Html.SelectFor(m => m.itemId, Model.items, id=>id.itemId, disp=>disp.itemName, title=>title.itemName + " " + title.itemDescription, null)
...and have, in this case, the title attribute on the options be a concatenation of the itemName
property and the itemDescription
property. I confess the meta-level of lambda expressions and Linq functions has got me a little dizzy. Can someone point me in the right direction?
FINAL RESULT For those who are curious, the following code gives me complete control over the select list's ID, Title, and DisplayText properties using lambda expressions:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> forExpression,
IEnumerable<TListItem> enumeratedItems,
Attribute<TListItem> idExpression,
Attribute<TListItem> displayExpression,
Attribute<TListItem> titleExpression,
object htmlAttributes,
bool blankFirstLine) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(forExpression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id="{0}" name="{0}"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}="{1}"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">
";
if (blankFirstLine)
{
returnText += "<option value=""></option>";
}
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = idExpression(listItem).ToStringOrEmpty();
var displayValue = displayExpression(listItem).ToStringOrEmpty();
var titleValue = titleExpression(listItem).ToStringOrEmpty();
returnText += string.Format("<option value="{0}" title="{1}"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected="selected"";
}
returnText += string.Format(">{0}</option>
", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
public delegate object Attribute<T>(T listItem);
See Question&Answers more detail:os