One of the problems with ASP.NET MVC is it's use of magic strings in almost every facet of the architecture. I've mentioned this before but I have a few other things I'd like to mention. In my last post I used T4MVC to generate static classes that layer on top of the magic strings but if these don't generate when you want them to they become out of sync and break compile time checking. One solution is to generate the "proxy" files one every save however if you have a large project this could be a bit of a pain.
Other solutions exist already. MVC2 comes with strongly typed HtmlHelpers so which turns
<%=Html.TextBox("FullName")%>
Into,
<%=Html.TextBoxFor(m => m.FullName)%>
Great - compile time checking and the ability to reflect things such as attribute for generating validation etc. Problem is they didn't really do much else. Controller methods still suffered from Magicstringitis. RedirectToAction still needed magic strings for controller and action names and ModelState.AddModelError still needed string for property names.
MvcContrib
MvcContrib offers a load of non-official but excellent extensions to MVC and one of the things that comes bundled with the build is a strongly typed RedirectToAction extension method. This lets change this,
this.RedirectToAction("Index", "Home");
Into
this.RedirectToAction<HomeController>(c => c.Index());
Yet again - compile time checking, refactoring support and no magic strings - Win. But that leaves one last thing that still bugs me - ModelState.AddModelError. To add an error for a particualr property of your model you still need to do something like this...
ModelState.AddModelError("Username", "Username is already in use")
YAMS! Yet another magic string!
DIY
Seeing as I couldn't find a solution from my good friend Google I decided to see if I could do it myself. Turns out it's bloody easy. This extension method will allow us to get rid of magic strings when expecting to map to a property of a simple class (such as a model or DTO)
/// <summary>
/// Adds a model error using strongly typed lambda expressions
/// </summary>
public static void AddModelError<TModel>(
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> method,
string message)
{
if (method == null)
{
throw new ArgumentNullException("method");
}
MemberExpression mce = method.Body as MemberExpression;
string property = mce.Member.Name;
modelState.AddModelError(property, message);
}
And you can now call it like this...
ModelState.AddModelError<Model.User>(u => u.Username, "Username already in use");
Neat!
One more bit of proof that lambda expressions are awesome - true there is reflection involved so it's going to be slower than magic strings but in my daily use I haven't really noticed any performance hits so (thanks to Jeff Atwood @ codinghorror.com) I can give it a "Works on my Machine" badge.