Performance Issues With Razor Views and Helpers

The problem

I was looking at a page that renders around 6,000 items in a list. Each of these items was shown as a title linking to the edit section and had another supporting link with it. The view code looked something like this:

<div class="container">
@foreach(var item in Model)
{
    <div class="...">
        @Html.ActionLink(
            item.Title, 
            "SomeController", "SomeAction", 
            new { id = item.Id }, 
            new { @class = "..." })
        <span>@item.Comment</span>
        @Html.ActionLink(
            "complete", 
            "OtherController", 
            "AnotherAction",
            new { id = item.Id }, 
            new { @class = "..." })
    </div>
}
</div>

The page was extermely slow loading. Yes, I know it has 6,000 items on it; should I expect something different? In my initial benchmark timing it took about 8 mississipis to completely load. Needless to say, this was not acceptable.

I added a Stopwatch aroun the foreach loop. The loop was taking 6,600ms to complete. At this point, we wanted to know what was going slow. The data query returned to the server code in about 90ms. That wasn’t it. The controller logic was almost insigficant. It had to be the view.

The solution

Use @Url.Action instead of @Html.ActionLink whenever possible.

The discussion

To page or not to page.

The full list was 6,000 items. Given the current system and where we’re going, there’s never going to be more than 6,000 items. In fact, soon there will be 1,000-3,000. So performance is going to get better just because the data is going to change.

We considered paging. Obviously, taking out list of 6,000 down to 100 would make things faster. So we quickly tried it by changing the loop to foreach(var item in Model.Take(100)).

As expected, that was much more performant. But it still took around 120ms to render that section fo the view. Paging would solve - or at least hide - the underlying issue.

I wasn’t satisfied with switching to paging.

There be dragons

I removed the @Html.ActionLink calls and ran the timing test on the full list. The render time was now 52ms. Wait, what? It took 6,548ms to create too ActionLink elements? I din’t really feel like digging in to the ActionLink source code to figure it out. Maybe later.

Obviously, the links are important. The system won’t work without the links. We hardcoded the a href="..." for the moment. Time didn’t go up at all. But this wasn’t a permanent solution.

The hard coded link wouldn’t work for may reasons. It was switched to use the @Url.Action method and the total render time for that section of the view went up to just under 200ms. Not blazing fast, but, perhaps, acceptable.

The final solution

<div class="container">
@foreach(var item in Model)
{
    <div class="...">
        <a href='@Url.Action(
            "SomeController", "SomeAction", 
            new { id = item.Id })' 
            class="...">@item.Title</a>
        <span>@item.Comment</span>
        <a href='@Url.ActionLink(
            "OtherController",  "AnotherAction", 
            new { id = item.Id })' 
            class="...">complete</a>
    </div>
}
</div>