Navigation
About Me

MS certified C#/ASP.NET software developer from Baltimore, MD.

Search
Subscribe
« Extension method digest #4 RouteBase.AsRoute | Main | Synthetic followers, sign of something deeper? »
Thursday
23Apr2009

Optimizing URL generation in ASP.NET MVC – Part 2

Part 1
Part 2

So in continuing with the series of URL generation optimization in ASP.NET MVC we’re going to take a look at the actual optimization part of things.

Before I get started I’d like to start off by saying the results of the first part’s test were a little off due to my mistake.  When I was setting up the routes I didn’t setup defaults for the default route (“/”) so the URLs actually being generated were /name=chad&age=23 instead of /Home/Index/Chad/23 and thus skewing the results so to fix that I’ll be running the first 5 all over again including 3 new ones.  Note that the expression syntax method was not changed from this and the performance still stands.  The only methods that were really affected by this were those that found the route by action and controller names.  I apologize for the mistake and I will update the post accordingly.

In the first part we looked at some methods that are probably most commonly used by ASP.NET MVC devs and this part I’d like to look at some lesser-used methods of getting these URLs generated.  In this part I’m going to go over 2 other methods of getting URLs generated.  Though these methods might not generate all of the HTML they’re just as useful and even faster than previous methods.

Url.Action using action name and anonymous object (Method 6)

Url.Action("Index", new { name = "Chad", age = 23 })

Url.Action using action name, controller name and anonymous object (Method 7)

Url.Action("Index", "Home", new { name = "Chad", age = 23 })

Url.RouteUrl using route name and anonymous object (Method 8)

Url.RouteUrl("HomeIndex", new { name = "Chad", age = 23 })

Same conditions as the the first part.  After thinking about it I should have put these tests into the first part but what can you do so here they are.  Before the results I’d like to point out these methods don’t generate the whole URL rather the part after the TLD.  So in a URL like http://mydomain.com/Home/Index these methods would only generate the /Home/Index parts.  I understand this isn’t the same as the previous methods but they can still be used the same.  Only differences is you would have to wrap them in your own HTML (blasphemy, I know).  It would look something like this.

<a href="<% Url.RouteUrl("HomeIndex", new { name = "Chad", age = 23 }); %>">Link</a>

Now for the results.

URL Generation test

As you can see there was actually a difference between using action name and controller name versus using route names in performance like I originally suspected there should have been.

This is where the second part of this series should have started considering those 3 last tests should have been in the last one.

In the first part I linked to a great presentation that showed off how you can improve URL generation performance by not using the expression syntax and not using anonymous objects.  I’d also like to cover that.

When you use method 1 for URL generation it has to get the parameter values from your expression and put them into a RouteValueDictionary and pass that into another method to get the URL.  The method it uses is a nasty one (not coded badly but, expensive).  This method is in Microsoft.Web.Mvc.Internal.ExpressionHelper class.

private static void AddParameterValuesFromExpressionToDictionary(RouteValueDictionary rvd, MethodCallExpression call)
{
ParameterInfo[] parameters = call.Method.GetParameters();
if (parameters.Length > 0)
{
for (int i = 0; i < parameters.Length; i++)
{
Expression expression = call.Arguments[i];
object obj2 = null;
ConstantExpression expression2 = expression as ConstantExpression;
if (expression2 != null)
{
obj2 = expression2.Value;
}
else
{
Expression<Func<object>> expression3 = Expression.Lambda<Func<object>>(Expression.Convert(expression, typeof(object)), new ParameterExpression[0]);
obj2 = expression3.Compile()();
}
rvd.Add(parameters[i].Name, obj2);
}
}
}

As you can see on lines 17 and 18 this is getting called for every parameter coming into your method.  If you have a method with more parameters performance would be affected.  I decided to run a test on this too and see how performance would be changed as you add parameters onto your actions.  Again, same conditions used as the all other tests.

Expression syntax test

As you can see performance gets thrown out the door as you add more and more parameters to the action method.

So that’s what is killing performance in the expression syntax of URL generation.  What about the other methods, what could be hurting their performance?  The main performance loss in all other methods is using anonymous objects.  When you use an anonymous object for your route parameter values it has to use reflection to put them into a RouteValueDictionary and this is how it’s done.

private void AddValues(object values)
{
if (values != null)
{
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
{
object obj2 = descriptor.GetValue(values);
this.Add(descriptor.Name, obj2);
}
}
}

So again, reflection is happening for each parameter passed in so the same performance decrease would happen with all other methods when adding more parameters.

To get around this we can just pass in our own RouteValueDictionary to avoid having to any reflection and it’s pretty easy to do.  So we’ll run the same 9 tests as before but without using anonymous objects and see what kind of difference we see.

Html.ActionLink<> expression (Method 1)

Html.ActionLink<HomeController>(c => c.Index("Chad", 23), "Link")

 

Html.ActionLink using action name and RouteValueDictionary (Method 2)

Html.ActionLink("Link", "Index", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

Html.ActionLink using action name, controller name and RouteValueDictionary (Method 3)

Html.ActionLink("Link", "Index", "Home", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } }, null)

Html.RouteLink using RouteValueDictionary (Method 4)

Html.RouteLink("Link", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

Html.RouteLink using named route and RouteValueDictionary (Method 5)

Html.RouteLink("Link", "HomeIndex", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

Url.Action using action name and RouteValueDictionary (Method 6)

Url.Action("Index", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

Url.Action using action name, controller name and RouteValueDIctionary (Method 7)

Url.Action("Index", "Home", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

Url.RouteUrl using route name and RouteValueDictionary (Method 8)

Url.RouteUrl("HomeIndex", new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } })

TIme to run the tests…

URL Generation test

I put method 1 there for consistency.  The rest saw a pretty substantial boost in performance.  The most was Method 5 going from 282 to 120 ms, that’s more than double boost in performance.

As you can see the difference was going from this…

new { name = "Chad", age = 23 }

To this…

new RouteValueDictionary { { "name", "Chad" }, { "age", 23 } }

Again, I’m sure there are those out there that will point out this looks like a pain to write these types of links with this code and I agree.  That’s why in the next part I’m going to go over how we can get intellisense and maintainability over our code when generating URLs.  I am fully aware that this is only a difference of 100ms~ over 10,000 links but this can be very helpful with high-volume sites and sites that generate a large amount of URLs per page.

So now we’ve seen which methods are fastest and how we can make them twice as fast.  In the next part of the series I’ll go over how we can utilize this faster code and still keep it maintainable and easy to use.

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (1)

References allow you to track sources for this article, as well as articles that were written in response to this article.
  • Response
    One of my new productivity tools for 2007 is a VOIP business phone. This post will interest readers in small offices or in their personal capacity more than those working in large organizations.

Reader Comments (10)

Did you ran the tests against a Debug or Retail build? There was a post on this. I'm interested the results would be (if you arent already running in retail)

http://codeclimber.net.nz/archive/2009/04/22/how-to-improve-htmlhelper.renderpartial-performances-donrsquot-run-in-debug-mode.aspx

April 23, 2009 | Unregistered CommenterJesus DeLaTorre

@Jesus

These tests were run under release configuration. I made sure that the compile debug flag was not set. They were preformed on a quad-core dedicated server box running Windows Server 2008/IIS7.

April 24, 2009 | Registered CommenterChad Moran

Nice post...

April 27, 2009 | Unregistered CommenterJones

In the next part of the series I’ll go over how we can utilize this faster code and still keep it maintainable and easy to use.

Chad, when's that next part coming? Interesting read so far, and it matches our own test results but I still haven't figured out a maintainable and fast way so I look forward to see your way to achieve it!

May 13, 2009 | Unregistered CommenterCasper

Interesting
How all this compares to a direct link without any helper

a href="/Home/Index/chad/23">Link /a>

July 2, 2009 | Unregistered Commenternachid

I was just thinking about Optimizing URL generation in ASP.NET MVC and you've really helped out. Thanks!

August 1, 2009 | Unregistered CommenterSEO

Wow, I never knew that Optimizing URL generation in ASP.NET MVC. That's pretty interesting…

September 5, 2009 | Unregistered CommenterSite Tool

I was just thinking aboutOptimizing URL generation in ASP.NET MVC – Part 2 and you've really helped out. Thanks!

September 9, 2009 | Unregistered CommenterSite Tool

I think you are wrong about the reason of problem with expression parsing overhead. Both parameters in samples have ConstantExpression type. So highlighted lines will never be called during filling RouteValueDictionary while parsing expression. Just to be sure I've debug it manually. It is possible only in case Html.ActionLink<HomeController>(c => c.Index( ()=>"Chad" , 23), "Link") or someting.

September 15, 2009 | Unregistered CommenterAndrey

On this web site only people can find interesting issue corresponding with this post. With your help the good features of our terms used to be manifest. We really know that now is really easy to have well accomplished critical essay writing from custom essay writing company.

January 19, 2010 | Unregistered CommenterGEENAHT32

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>