Strong typing routes in ASP.NET MVC

Let’s say you have Test action in Home controller like this:

public ActionResult Test(int id, int tubo)
{
return View("Index");
}

And you want to create a hyperlink to that action on your view. So, did you ever wonder why do you have to write code like:

< %= Html.ActionLink("Classic Action", "Test", "Home", new { tubo=8, id=77 }, null) %>

[Note: I had to insert a space after each less than character (<) becaue of formatting issues in IE7. For some reason the code disappeared when there was no space after <. Firefox didn't show any such issues. If you try running the code contained in the code snippets you'll have to remove those spaces.]

(I won’t even consider lambda version because it is awfully slow). This version has two problems:

  1. It isn’t strong typed by any mean.
  2. The anonymous class holding id and tubo values isn’t the best performer either.

To solve the second issue you might opt for RouteValueDictionary instead of anonymous class, like this:

< %= Html.ActionLink("Classic Action", "Test", "Home", 
new RouteValueDictionary {  { "tubo", 8 } ,{ "id", 77 } }, null) %>

This version is less readable but slightly better performer.

The bigger problem is the first one, which wasn’t possible to overcome, unless you wanted to get rid of performance (by using lambdas). Until now, that is. I created a solution that would provide best of both worlds: strong typing and performances.

< %= Html.LinkTestOnHome("TestLink", 8, 77)%>

Note the advantages:

  1. Parameters are strongly typed
  2. The most performing call (currently to ActionLink) is handled internally (with possibility to improve).

Sounds good? If yes, read futher.

Instructions

The generator comes in a form of DXCore/CodeRush plugin and there is a single requirement: you’ll need the best Visual Studio productivity tool CodeRush. Or even better, this plugin requires only DXCore, which is a free subset of CodeRush (untested scenario at this point though). In other words, this is a free lunch albeit you should really take a look at CodeRush and all of its super features. You won’t regret it.

Here are the steps to test it out:

1. Unzip the attached MvcStrongTyping0.1.zip file into [Program Files]\Developer Express Inc\DXCore for Visual Studio .NET\2.0\Bin\Plugins folder (on a 64 bit Windows it should look like C:\Program Files (x86)\Developer Express Inc\DXCore for Visual Studio .NET\2.0\Bin\Plugins).

2. Start an instance of Visual Studio 2008 and open (or create a new) ASP.NET MVC project.

3. Add a root folder named Helpers in the ASP.NET MVC project if it doesn’t exist yet.

4. Create a file named UrlExtensions.cs in Helpers folder.

5. Open the file and/or make it the active document.

6. WARNING: Any content in active document gets deleted and replaced by new one during this operation. Click DevExpress\Create MVC Routes menu entry.

route1
7. Include [Rootnamespace].Helpers (check out UrlExtensions class’ namespace) namespace to the web.config file in the <namespaces> node:

<system.web>
...
<pages>
...
<namespaces>
...
<add namespace="[YourRootNamespace].Helpers"/>
< /namespaces>
< /pages>
...
< /system.web >

That’s it. There should be a lot of code generated to UrlExtenstions.cs file and you are able now to use both new methods (or their overloads):

< %= Html.LinkTestOnHome("TestLink", 8, 77)%>
< %= Url.ActionTestOnHome(8, 77) %>

Generated code

For the Test action there should be this code (shortened) present:

...
namespace [YourRootNamespace].Helpers
{
public static class UrlExtensions
{
...
#region Fixed params action for Home.Test
public static string ActionTestOnHome(this UrlHelper helper, object routeValues) ...
public static string ActionTestOnHome(this UrlHelper helper, object routeValues, 
string protocol)...
#endregion
#region Fixed params links for Home.Test
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
object routeValues) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
object routeValues, object htmlAttributes) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
string protocol, string hostName, 
string fragment, object routeValues, object htmlAttributes) ...
#endregion
#region Variable params for action Home.Test
public static string ActionTestOnHome(this UrlHelper helper, int tubo)
public static string ActionTestOnHome(this UrlHelper helper, int tubo, 
RouteValueDictionary routeValues) ...
public static string ActionTestOnHome(this UrlHelper helper, int tubo, 
RouteValueDictionary routeValues, 
string protocol, string hostName) ...
#endregion
#region Variable params links for Home.Test
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, int tubo) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, int tubo, 
RouteValueDictionary routeValues) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, int tubo, 
RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, int tubo, 
string protocol, string hostName, string fragment, 
RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes) ...
#endregion
#region Variable params for action Home.Test
public static string ActionTestOnHome(this UrlHelper helper, int tubo, int id) ...
public static string ActionTestOnHome(this UrlHelper helper, int tubo, int id, 
RouteValueDictionary routeValues) ...
public static string ActionTestOnHome(this UrlHelper helper, int tubo, int id, 
RouteValueDictionary routeValues, string protocol, string hostName) ...
#endregion
#region Variable params links for Home.Test
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
int tubo, int id) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
int tubo, int id, RouteValueDictionary routeValues) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
int tubo, int id, 
RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes) ...
public static string LinkTestOnHome(this HtmlHelper helper, string linkText, 
int tubo, int id, 
string protocol, string hostName, string fragment, 
RouteValueDictionary routeValues, IDictionary<string, object> htmlAttributes) ...
#endregion
}
}

That’s quite a lot of overloaded methods (and this is just an example for a single action!).
First note the name of the method that corresponds to the ActionOnController pair.
Next, there are basically two groups:
  • Fixed parameters – just strong typed links to the action (without any explicit action parameter)
  • Variable parameters – strong typed links with various combinations of action’s input parameters

Both groups are subdivided into two groups:

  • Actions – generates string representation of matching URL through UrlHelper.Action
  • Links – generates links through HtmlHelper.ActionLink method

Both internal calls might be optimized further and perhaps they will be in the future. At this point it is important that the methods work.

 

Implementation details

The magic of the autogenerated code comes from DXCore’s StructuralParser, which allowed me easy parsing of the project’s code (something that should be provided by Visual Studio from the beginning). And by easy I mean really easy and straightforward. I can’t praise it enough. On the other hand if it wasn’t for DXCore my plugin wouldn’t be even possible. At least not in time that I can allocate to this project.

So, all it took me was a couple of advices from Mark Miller (who is an incredibly helpful person btw) on where to start with parser and a bunch of basic operations of building a DXCore plugin. All in all it took me around 4 hours of my time. Thanks to my friend Miha for method naming suggestion. If it wasn’t for him the method names would be a lot worse. And thanks also to Whiletrue guys for their asp.net mvc performance presentation.

So, here it is. Let me know what do you think about it and don’t forget, this is an early, an experimental version of the plugin. All feedback is appreciated.

MvcStrongTyping0.1.zip (9.62 kb)

See also my previous article on strong typing the views.

9 thoughts on “Strong typing routes in ASP.NET MVC

  1. Cannot see any code at the top of this article.

    "And you want to create a hyperlink to that action on your view. So, did you ever wonder why do you have to write code like:

    (I won’t even consider lambda version because it is awfully slow). This version has two problems:

    It isn’t strong typed by any mean.
    The anonymous class holding id and tubo values isn’t the best performer either.
    To solve the second issue you might opt for RouteValueDictionary instead of anonymous class, like this:

    This version is less readable but slightly better performer."

    No code shown

  2. Update: hopefully the formatting issues on IE7 have been fixed. For some reason the issues were due to <%= and <tag> character combinations. I added a space char just after < and it works now. Go figure, this time code snippet formatter isn’t at fault after all.

  3. Regarding the less-than-percent problem. This is not really a browser issue.

    It’s a basic programming rule: Treat your data as the context requires in which you want to insert them. Ignoring this rule leads to all sorts of injection problems.

    A less-than-sign has special meaning in HTML context. If you want to insert one that is treated as data not as a tag opener, you have to write it as ampersand-l-t-semicolon.

  4. The comment engine seems to work fine. You can enter < that will correctly converted to &lt; but in the main article there are < instead of &lt; for all kinds of <. Maybe the intention of the blog engine’s author was to let the article’s author enter HTML code that should really be rendered in the browser.

Leave a Reply