Optimizing URL generation in ASP.NET MVC – Part 1
Tuesday, April 21, 2009 at 12:44PM Part 1
Part 2
Inspired by a great presentation by Rudi Benkovic I thought I would make a series of blog posts about improving ASP.NET MVC performance through URL generation.
While developing an application for a project of mine I went gung-ho with expressions wherever I could. Whether it was methods in my controllers or URL generation they were everywhere. While stress-testing my app I ramped up the items on a page and noticed performance dipped significantly. At the time I thought it may have been the data access since I’ve had troubles before with LINQ to SQL performance. Though I found out it was actually the URL generation. I was using the expression syntax from MvcFutures that looked like this.
Html.ActionLink<HomeController>(c => c.Index())
This is great if you want compile-time checking by compiling your views. Problem is, this is horribly inefficient. Let’s compare it against some other methods of linking. I’m going to be creating a brand new ASP.NET MVC Project for those that want to follow along. I’ll be modifying the Index action of the Home controller for the sake of testing.
public ActionResult Index(string name, int age)
{
ViewData["Message"] = string.Format("Hello {0}. I see you're {1} year(s) old.");
return View();
}
I added to parameters to the Index controller so we can test passing arguments into the method as part of the performance testing. I added the following to web.config
<add namespace="Microsoft.Web.Mvc"/>
<add namespace="URLGeneration"/>
<add namespace="System.Diagnostics"/>
I also added the following routes to the list. The reason I added them individually is to simulate a site having a few routes. Putting the one we want to use at the bottom.
routes.MapRoute(
"Default",
"",
new { controller = "Home", action = "Index" }
);
routes.MapRoute(
"AccountChangePassword",
"Account/ChangePassword",
new { controller = "Account", action = "ChangePassword" }
);
routes.MapRoute(
"AccountChangePasswordSuccess",
"Account/ChangePasswordSuccess",
new { controller = "Account", action = "ChangePasswordSuccess" }
);
routes.MapRoute(
"AccountLogOn",
"Account/LogOn",
new { controller = "Account", action = "LogOn" }
);
routes.MapRoute(
"AccountRegister",
"Account/Register",
new { controller = "Account", action = "Register" }
);
routes.MapRoute(
"HomeAbout",
"Home/About",
new { controller = "Home", action = "About" }
);
routes.MapRoute(
"HomeIndex",
"Home/Index/{name}/{age}",
new { controller = "Home", action = "Index" }
);
The name of the project I created was URLGeneration thus the namespace. Now the code for testing URL generation performance.
<%
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
{
%><%= // URL generation %><%
}
sw.Stop();
%><%= sw.ElapsedMilliseconds %><%
%>
This code is pretty straight forward and should allow for accurate testing. I’ll be running this code on a dedicated server with nothing else running and to help accuracy I’ll run the test 5 times and average out the time. This will ensure we’re testing JUST the URL generation time and not the rest of the page or transfer time. Some of you might be thinking 10,000 seems like a high number and you’ll never have that many links on your site. Well, you’re right but if you have a lot of requests coming in at a given time then you may be generating that many overall. This will help spread out the difference between the tests.
The methods of URL generation will be the following, probably most used methods first.
Html.ActionLink<> expression (Method 1)
Html.ActionLink<HomeController>(c => c.Index("Chad", 23), "Link")
Html.ActionLink using action name and anonymous object (Method 2)
Html.ActionLink("Link", "Index", new { name = "Chad", age = 23 })
Html.ActionLink using action name, controller name and anonymous object (Method 3)
Html.ActionLink("Link", "Index", "Home", new { name = "Chad", age = 23 }, null)
Html.RouteLink using anonymous object for guessing route (Method 4)
Html.RouteLink("Link", new { name = "Chad", age = 23 })
Html.RouteLink using named route (Method 5)
Html.RouteLink("Link", "HomeIndex", new { name = "Chad", age = 23 })
I ran all tests after a couple of warm-up loads. None of the runs were on a cold start.
UPDATE These results were actually skewed due to a mistake I made in the the default route. To see updated, correct results please check out the second part of this series.
As you can see the method in MvcFutures is 10x as slow as the others. I was actually surprised about Method 5 I thought that it would have been the fastest.
Now you may be thinking, well you tested 10,000 links and you’re right. Let’s say you have 10 links and 1,000 requests coming in (which isn’t a lot for high-volume sites) then you’re adding 2 seconds in processing time just to generate links. This was not the time it took to generate the whole page just the links. That’s a lot of time to be adding meaning your requests/sec is going to dip.
In the next installment I’ll go over how we can go about optimizing this performance and how much of an impact it can have with some load testing.
ASP.NET MVC,
Performance