I have moved my active blog over to tumblr. I've maintained this blog for reference but will be posting to http://www.robustsoftware.co.uk instead. I've pointed my Feedburner feed to tumblr so if you're subscribed already you should already have switched with me.

Strongly typed links for ASP.NET MVC through the power of LINQ expressions

Wouldn't it be nice if you could have strongly typed links in your ASP.NET MVC views rather than a having to use floaty strings? Something like this for example:


    1 <%= Html.LinkTo<ProjectsController>("Settings", c => c.Index()) %>



Or something similar with an id argument?


    1 <%= Html.LinkTo<ProjectsController>(ViewData.Model.Name, c => c.Details(ViewData.Model.Id)) %>



Well utilising the awesomeness of LINQ expressions, generics and extension methods (that's right, 3 kinds of awesome, at the same time) you can:


    1 public static string LinkTo<TController>(this HtmlHelper helper, string linkText,

    2     Expression<Func<TController, ActionResult>> action) where TController : IController

    3 {

    4     object routeValues;

    5 

    6     var methodExpression = action.Body as MethodCallExpression;

    7     var actionName = methodExpression.Method.Name;

    8     var controllerName = methodExpression.Object.Type.Name.Replace("Controller", string.Empty);

    9 

   10     // is there an id parameter on the action method being called?

   11     if (methodExpression.Method.GetParameters().Where(p => p.Name == "id").SingleOrDefault() != null)

   12     {

   13         // yes - retrieve the value of the id through magicks

   14         var idPropertyExpression = methodExpression.Arguments[0];

   15         var id = Expression.Lambda(idPropertyExpression).Compile().DynamicInvoke();

   16 

   17         routeValues = new { controller = controllerName, id };

   18     }

   19     else

   20     {

   21         routeValues = new { controller = controllerName };

   22     }

   23 

   24     return helper.ActionLink(linkText, actionName, routeValues);

   25 }



So what's going on here?

On lines 1 and 2 we are defining a generic signature for an extension of the HtmlHelper class which can only be used by implementations of IController (the interface which all ASP.NET MVC controllers must implement). This extension method takes two arguments, the text to display for the link and an Expression for a call to one of the actions on the controller.

So what does this buy us?

Well by casting the body of the Expression as a MethodCallExpression (line 6) we can gain access to the name of the method being called (line 7) and the name of the controller (line 8).

We can then look further at the method being called to see if it has a parameter with the name "id" (line 11). If there is such a parameter we begin to descend into the witchcraft of expression trees.

Firstly we grab the node of the expression tree which is used for the "id" parameter which we are assuming will be the first parameter (line 14). We can then use this node's Expression to create a LambdaExpression (line 15) which then means we can compile and invoke the node to retrieve the value the Expression represented. The way that this works allows you to use any statement that will produce an integer value within the expression. Either a property on an object as in the example or a regular integer will work.

We use the values we have retrieved from the Expression to create an anonymous type to be passed to the regular ActionLink extension. If there is no "id" parameter we will just create an anonymous type with only a value for the controller.

These are then passed to the regular ActionLink method but we have been hidden from all the floaty string nastiness.

Please note, this isn't a complete solution, more a proof of concept.