asp.net-mvcperformanceurlhelperroute-constraint

First call to Url.Action on a page is slow


I have a performance issue with a fairly simple ASP.MVC view.

It's a log-on page that should be almost instant, but is taking about half a second.

After a lot of digging it looks like the problem is the first call the Url.Action - it's taking around 450ms (according to MiniProfiler) but that seems insanely slow.

Subsequent calls to Url.Action are taking <1ms, which is more in line with what I would expect.

This is consistent whether I use Url.Action("action", "controller") or Url.Action("action"), but doesn't seem to happen if I use Url.Content("~/controller/action"). This also happens when I call Html.BeginForm("action").

Does anyone have any idea what's causing this?

A dig into the source suggests that RouteCollection.GetVirtualPath might be the culprit, as that's common to both Url.Action and Html.BeginForm. However, surely that's used all over the place? I mean, ½ a second is far too slow.

I have 20 or so custom routes (it's a fairly large app with some legacy WebForms pages) but even then the times seem far too slow.

Any ideas how to fix it?


Solution

  • Problem found, and it is with the routing tables (cheers Kirill).

    Basically we have lots of routes that look something like this:

    string[] controllers = GetListOfValidControllers();
    
    routes.MapRoute(
        name: GetRouteName(),
        url: subfolder + "/{controller}/{action}/{id}",
        defaults: new { action = "Index", id = UrlParameter.Optional },
        constraints: new { controller = "(" + string.Join("|", controllers) + ")" });
    

    It turns out that the Regex check is very slow, painfully slow. So I replaced it with an implementation of IRouteConstraint that just checks against a HashSet instead.

    Then I changed the map route call:

    routes.MapRoute(
        name: GetRouteName(),
        url: subfolder + "/{controller}/{action}/{id}",
        defaults: new { action = "Index", id = UrlParameter.Optional },
        constraints: new { controller = new HashSetConstraint(controllers) });
    

    I also used the RegexConstraint mentioned in that linked article for anything more complicated - including lots of calls like this (because we have legacy WebForm pages):

    routes.IgnoreRoute(
        url: "{*allaspx}", 
        constraints: new { allaspx = new RegexConstraint( @".*\.as[pmh]x(/.*)?") });
    

    Those two simple changes completely fix the problem; Url.Action and Html.BeginForm now take a negligible amount of time (even with lots of routes).