Wednesday, October 12, 2011

MVC3 Helper methods supporting Linq Expressions

I was a bit frustrated with the .DropDownFor() extension on the HtmlHelper class.  There was no access to the OPTION tags, and I've never been a fan of the SelectList object.  I wanted to put some tool tip text (using the title attribute) on the options to provide more context in the selection.  Of course there was no way to do that with the .DropDownFor() helper method.  So, time to roll my own.

Only thing was, I'd never written an extension that used a Linq expression before, so it took a little research.  Finally I found this post (I heart StackOverflow) and piecing it together from there, came up with this joker:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web;


namespace System.Web.Mvc
{
public static class MoreHelpers
{
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 = metaData.Model.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 += ">\n";


//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>\n", displayValue);
}


//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}


public static string ToStringOrEmpty(this object target)
{
if (target == null) return string.Empty;
return target.ToString();
}
}
}


Works like a champ!

1 comment:

  1. Great post! I was also trying to write my own custom DropdownlistFor extension method but got stuck with the Linq Func<> stuff. Thanks a lot for your post :)

    ReplyDelete