The ugly truth behind pretty URLs

Tags: .net, asp.net mvc, performance

Attribute routing (RouteAttribute) is a quite handy feature of ASP.NET MVC. It allows you to have nice looking URLs that everybody has already accustomed to and your client surely love it. Plus it hides your true controller/action structure which sometimes might be desirable.

But there's one catch. As your web app gets bigger and bigger, attribute routing might have a negative performance impact on your WHOLE website.

RouteAttribute can slow down you web app

Let's say you have ASP.NET MVC website that has about 60-80 controllers with 8-10 actions each. Every controller and action is of course decorated with RouteAttribute.

Suppose you want to display a table with few columns and 10 or 20 rows and assume that at least half the cells contains ActionLinks like so:

@Html.ActionLink( "Item details", MVC.Item.Details( i ) )

(I'm using T4MVC here, but it has nothing do to with it)

You fire up your website, navigate to a view with a table and then you wait. You wait a lot, like 150-300 ms to finally have your table displayed. Quick glimpse at Glimpse (ha!) or MiniProfiler reveals, that there's nothing wrong with SQL or your code in controller. The real mess happens in the view when rendering the table.

According to my tests, execution time of single Html.ActionLink call can take up to 2-3 ms (on a decent i7 machine)!

What are the reasons, you may ask? Well, there's pretty heavy stuff going on under the hood when calling Html.ActionLink. Basically, the framework just goes over a list of all registered routes and it tries to match each item with controller name, action name and route values you've passed to Html.ActionLink. This is also the reason why calling Html.ActionLink for controllers/actions registered "later" takes more time than for controllers/actions registered "earlier" (of course you have no control over registration order, but it's just lexicographic order I guess). So the more controllers/actions with RouteAttribute you have, the longer it takes to go over all registered routes to find the one that you want.

This all kinda sucks, if you ask me

But fortunately there's a solution to this problem: use named routes. So first of all, set Name property on RouteAttribute for a given action:

[Route( "details/{id:long}", Name = "item-details" )]
public virtual ActionResult Details( long id )
{
    return View();
}

And then instead of Html.ActionLink, use Html.RouteLink specifying the name you set:

@Html.RouteLink( "Details", "item-details", MVC.Item.Details( id ), null )

By setting Name property on RouteAttribute, the framework add this route to its internal dictionary, which is queried by name when calling Html.RouteLink. As a result, you get super-fast link generation mechanism. On my machine it is at least 100x faster than regular Html.ActionLink.

One downside of this approach is that you now have to deal with some magic strings (routes names), but (1) you can use it only for the most frequently called actions and (2) you can have static string fields that hold those magic strings, so you can use them in your views.

I know, the solution is not perfect. But unless they change something in ASP.NET Core, we're stuck with it, weather we like it or not.

Read also

T4MVC model unbinders

Sometimes you need to write some infrastructure code just to get basic stuff working. I don't like it either, so here're some tips about T4MVC that might ease your pain.

Data export tools for .NET

How to export data from .NET to PDFs, Word/Exel documents or CSVs.

SQL backup to Azure using Powershell

Everybody knows you should be making backups of everything. Without db backup you'll probably be bald soon.

Comments