Avoid the pain of magic strings

Published on den 17 September 2010

Magic strings are evil and should be avoided as much as possible. In this post I will show how you can create strongly typed SelectList in ASP.NET MVC.

If you like runtime errors, manually looking through your code to refactor any changed property names and writing code without intellisense this is NOT a post for you.  Infact you should probably get that investigated instead.

There are a lot of nice things is ASP.NET MVC but there are a bit too much magic string for me to be truly happy. With MVC 2 we got the strongly typed helpers and with T4MVC we can avoid those pesky strings in links and addresses.  Still there are a few places where those magic string show up and cause missery whenever we refactor. Take this line for example:

vm.CommentViewModel.Classes = new SelectList(activity.Classes, "ID", "Name");

If I decide to change the name of ID or Name this code will still compile and if I'm not a paying attention I will soon get one of those wonderful "It doesn't work!" calls or an emails from a customer or user assuming that we are some kind of psychics. Now wouldn't it be nicer if we could have something like this:

vm.CommentViewModel.Classes = new SelectList(activity.Classes, c => c.ID, c => c.Name);

I haven't got exactly that to work but I can do this in my controllers:

vm.CommentViewModel.Classes = CreateSelectList(activity.Classes, c => c.ID, c => c.Name);

Because I usually derive my controllers from a BaseController it is a simple thing to add this kind of utility methods to my controllers. Another approach I was considering was to derive a SelectList from SelectList and then I could create a SelectList like this:

vm.CommentViewModel.Classes = new SelectList<Class>(activity.Classes, c => c.ID, c => c.Name);

If you find that approach more suitable for your needs it is a simple excercise to create that class.

The code

In my BaseController I add these two methods:

/// <summary>
/// Creates a SelectList with no selected value
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="items"></param>
/// <param name="valueExpression"></param>
/// <param name="textExpression"></param>
/// <returns></returns>
protected SelectList CreateSelectList<TModel>(IEnumerable<TModel> items, Expression<Func<TModel, object>> valueExpression, Expression<Func<TModel, object>> textExpression){
    return new SelectList(items, LambdaHelpers.GetPropertyName(valueExpression), LambdaHelpers.GetPropertyName(textExpression));
}

/// <summary>
/// Creates a SelectList with the item that matches selectedValue as selected
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="items"></param>
/// <param name="valueExpression"></param>
/// <param name="textExpression"></param>
/// <param name="selectedValue">The value that should be selected. </param>
/// <returns></returns>
protected SelectList CreateSelectList<TModel>(IEnumerable<TModel> items, Expression<Func<TModel, object>> valueExpression, Expression<Func<TModel, object>> textExpression, object selectedValue)
{
    return new SelectList(items, LambdaHelpers.GetPropertyName(valueExpression), LambdaHelpers.GetPropertyName(textExpression), selectedValue);
}

The class LambdaHelpers is defined like this:

public static class LambdaHelpers
{
    /// <summary>
    /// Returns the proprtyname from a lambda expression. Shamelessly copied from from Cinch http://cinch.codeplex.com/
    /// </summary>
    /// <param name="propertyExpression">The Expression</param>
    /// <returns>The name of the property</returns>
    public static string GetPropertyName<T>(
        Expression<Func<T, Object>> propertyExpression)
    {
        var lambda = propertyExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        return propertyInfo.Name;
    }

}

The same approach could be used for MultiSelectList and any other places where you input property names.

Then feel free to it or if you have any comments or questions mention @MikaelEliasson on Twitter.

CTO and co-founder at Bokio with a background as an elite athlete. Still doing a lot of sports but more for fun.

#development, #web, #orienteering, #running, #cycling, #boardgames, #personaldevelopment