Freigeben über


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.

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

  • Anonymous
    June 01, 2009
    PingBack from http://asp-net-hosting.simplynetdev.com/a-buildprovider-to-simplify-your-aspnet-mvc-action-links/

  • Anonymous
    June 02, 2009
    Thank you for submitting this cool story - Trackback from DotNetShoutout

  • Anonymous
    June 02, 2009
    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?

  • Anonymous
    June 02, 2009
    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.

  • Anonymous
    June 02, 2009
    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?

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

  • Anonymous
    June 02, 2009
    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!

  • Anonymous
    June 02, 2009
    Very nice! Thanks a lot for posting this.

  • Anonymous
    June 03, 2009
    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.

  • Anonymous
    June 03, 2009
    Daily Tech Links - June 4, 2009 Web Development ASP.NET MVC Training Kit Visual Studio 2010: Multiple

  • Anonymous
    June 03, 2009
    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Anonymous
    June 04, 2009
    Have you tested the performance gains of using this solution rather than the regular Url Helpers?

  • Anonymous
    June 04, 2009
    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?

  • Anonymous
    June 04, 2009
    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.

  • Anonymous
    June 04, 2009
    Earlier this week, I wrote a post on using a BuildProvider to create ActionLink helpers .&#160; That

  • Anonymous
    June 14, 2009
    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.

  • Anonymous
    June 14, 2009
    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.

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

  • Anonymous
    June 21, 2009
    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.

  • Anonymous
    June 21, 2009
    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.

  • Anonymous
    June 24, 2009
    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?

  • Anonymous
    June 24, 2009
    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.

  • Anonymous
    June 28, 2009
    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?

  • Anonymous
    June 28, 2009
    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.

  • Anonymous
    July 19, 2009
    Спасибо за частые обновления на блоге!!

  • Anonymous
    July 28, 2009
    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() %>

  • Anonymous
    November 21, 2012
    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.

  • Anonymous
    November 22, 2012
    @Ricardo: see T4MVC (https://t4mvc.codeplex.com/) for a T4 based solution.

  • Anonymous
    November 22, 2012
    Great stuff! Will definitely take a look. Cheers