A BuildProvider to simplify your ASP.NET MVC Action Links


Update: Please see this newer post for the latest and greatest MVC T4 template

 

One downside of using Html.ActionLink in your views is that it is late bound.  e.g. say you write something like this:

<%= Html.ActionLink("Home", "Index", "Home")%>

The second parameter is the Action name, and the third is the Controller name.  Note how they are both specified as plain strings.  This means that if you rename either your Controller or Action, you will not catch the issue until you actually run your code and try to click on the link.

Now let’s take the case where you Action takes parameters, e.g.:

public ActionResult Test(int id, string name) {
    return View();
}

Now your ActionLink calls looks something like this:

<%= Html.ActionLink("Test Link", "Test", "Home", new { id = 17, name = "David" }, null) %>

So in addition to the Controller and Action names changing, you are vulnerable to the parameter names changing, which again you won’t easily catch until runtime.

One approach to solving this is to rely on Lambda expressions to achieve strong typing (and hence compile time check).  The MVC Futures project demonstrates this approach.  It certainly has merits, but the syntax  of Lambda expressions in not super natural to most.

Here, I’m exploring an alternative approach that uses an ASP.NET BuildProvider to generate friendlier strongly typed helpers.  With those helpers,  the two calls below become simply:

<%= Html.ActionLinkToHomeIndex("Home")%>

<%= Html.ActionLinkToHomeTest("Test Link", 17, "David")%>

Not only is this more concise, but it doesn’t hard code any of the problematic strings discussed above: the Controller and Action names, and the parameter names.

Steps to enable the ActionLink helpers in your MVC app

You can easily integrate these helpers in any ASP.NET MVC app by following three steps:

1. First, add a reference to MvcActionLinkHelper.dll in your app (build the project  in the zip file attached to this post to get it)

2. Then, register the build provider in web.config.  Add the following lines in the <compilation> section:

      <buildProviders>
        <add extension=".actions" type="MvcActionLinkHelper.MvcActionLinkBuildProvider" />
      </buildProviders>

3. The third step is a little funky, but still easy.  You need to create an App_Code folder in your app, and add a file with the .actions extension in it.  It doesn’t matter what’s in the file, or what its full name is.  e.g. add an empty file named App_Code/generate.actions.  This file is used to trigger the BuildProvider.

How does it all work?

I included all the sources in the zip, so feel free to look and debug through it to see how it works.  In a nutshell:

  • When the first request is made at runtime, ASP.NET needs to build the App_Code assembly
  • It finds our .actions file, which triggers the registered BuildProvider
  • The BuildProvider goes through all the reference assemblies and looks for Controller classes
  • It then generates a static class with extension methods for each action
  • Since every aspx page is built with a reference to the App_Code assembly, all  the views are able to use the generated helpers.

Where is this going?

At this point, this is just a quick proof of concept.  There are certainly other areas of MVC where the same idea can be applied.  e.g. currently it only covers Html.ActionLink, but could equally cover Url.Action(), or HTML form helpers (standard and AJAX).

Please send feedback whether you find this direction interesting as an alternative to the Lambda expression approach.

MvcActionLinkHelper.zip

Comments (29)

  1. Thank you for submitting this cool story – Trackback from DotNetShoutout

  2. Eric Hexter says:

    Is there a way to tie this into the build with a post build event… sort of like the aspcompile task that was added with the 1.0 release?

  3. davidebb says:

    Eric, the use of BuildProvider makes it intrinsically something that gets generated at runtime rather than design time. If you precompile the app (e.g. using aspnet_compiler.exe), it will get compiled in there. But I’m not sure that it could be hooked into using a post build event.

  4. Eric Hexter says:

    I see how the runtime compile is nice, I see that I can get intellisense support with VS but I loose the intelisense with my resharper plugin… 🙁  It would be nice if I could specify my intellisense provider by file type (extension) and that would do what I need.  It seems like the aspx web form editor must reference the dlls from the compiled (temp) directory, is that right?

  5. What's New says:

    One downside of using Html.ActionLink in your views is that it is late bound.&#160; e.g. say you write

  6. Tony Chevis says:

    Very nice, thank you. I modified it to include the Title attribute for the anchor tag and plan to use it on our current project. YES! to Url and Form helpers!

  7. Andrew Gunn says:

    Very nice! Thanks a lot for posting this.

  8. Eric Hexter says:

    I prototyped a Url helper from your example… I do like this approach.  I am thinking a syntax like.  Url.Controller.Action  would be interesting.  Where I would use this in a form and I would expect all the parameters from my form post to come as form inputs . In this case I would make my "Action" a property rather than a method.

  9. Daily Tech Links – June 4, 2009 Web Development ASP.NET MVC Training Kit Visual Studio 2010: Multiple

  10. You’ve been kicked (a good thing) – Trackback from DotNetKicks.com

  11. Sruly Taber says:

    Have you tested the performance gains of using this solution rather than the regular Url Helpers?

  12. davidebb says:

    Eric, I’m not sure I’m quite following your Url.Controller.Action idea.  Can you include a small example of what the code you’ll end up writing in the view would look like?

  13. davidebb says:

    Sruly, I have not done in formal perf testing, but those helpers should be basically equivalent to calling Html.ActionLink directly, which is probably the fastest method available.  So it should be much faster than the helpers that are based on Lambda Expressions.

  14. Earlier this week, I wrote a post on using a BuildProvider to create ActionLink helpers .&#160; That

  15. Our Html.ActionLink Helpers in ASP.NET MVC could use a little dash of named routes in Rails along with compile-time safety and refactoring support.

  16. Our Html.ActionLink Helpers in ASP.NET MVC could use a little dash of named routes in Rails along with compile-time safety and refactoring support.

  17. A couple weeks ago, I blogged about using a Build provider and CodeDom to generate strongly typed MVC

  18. Bernard Simmons says:

    Thanks this template is a great aid. I was wondering if this template could be modified to allow subfolders in the controllers Folder (all controllers in the same namespace) so that I can better organize them to avoid clutter.

  19. davidebb says:

    Bernard, did you mean to comment on my later post (A new and improved ASP.NET MVC T4 template)? This post doesn’t involve a template.

  20. Andrew Gunn says:

    I’ve taken your idea a bit further by creating extension method for UrlHelper (e.g. UrlHelper.ActionToHomeIndex()). This works fine when I’m working in HTML, but I can’t use the newly added helpers in class files. I’ve tried adding the same namespace to the top of the file with a using statement but still no joy. Am I missing something?

  21. davidebb says:

    Andrew, please see my later post using T4 template (http://blogs.msdn.com/davidebb/archive/2009/06/17/a-new-and-improved-asp-net-mvc-t4-template.aspx). It supports Url helpers, and works in the class files.

  22. Andrew Gunn says:

    Cheers for the new post link but I prefer the BuildProvider approach if I’m honest :-P. Is there no possible way to get this solution working in class files?

  23. davidebb says:

    Andrew: unfortunately, that is not really possible in a Web Application, because the class files are built by VS at design time, while the BuidlProvider is a runtime thing. So the class files simply can’t reference that code. With a Web *Site*, this would work, though most MVC apps use Web *Apps*.

  24. Pelez says:

    Спасибо за частые обновления на блоге!!

  25. In the next version of the MVC framework will we get strongly typed action links that we can point to our view.  The MVC Futures has it already.

    To me it’s cleaner having an actionlink as something like:

    <%= Html.ActionLink<CalveNet.Controllers.PatwareController>(c => c.Index(), "Here")%>

    As opposed to:

    <% Html.ActionLinkToPatwareIndex() %>

  26. Ricardo Rodrigues says:

    Why not use a T4 template instead of a build provider to generate the typed ActionLinks ? That whay when you build it's there, no need to fire up the app and wait for it to start to have the static generation.

  27. davidebb says:

    @Ricardo: see T4MVC (https://t4mvc.codeplex.com/) for a T4 based solution.

  28. Ricardo Rodrigues says:

    Great stuff! Will definitely take a look.

    Cheers