8

In my current application I'm generating a fairly lengthy table to display to the user. I've been seeing some serious performance issues with it, which I've tracked down to usage of @Html.DisplayFor, and I'm not entirely sure why.

Edit: I've replaced the code sample with a more concise and reproducible setup.

In order to isolate the issue, I have created a new asp.net core MVC project using all default settings in visual studio, with no authentication. I created a view model as such:

public class TestingViewModel
{
    public int Id { get; set; }
    public string TextValue1 { get; set; }
    public string TextValue2 { get; set; }
}

Then added a controller which fills the view model with data to pass to the view:

    public IActionResult TestThings()
    {
        var list = new List<TestingViewModel>();
        for(var i = 0; i < 1000; i++)
            list.Add(new TestingViewModel {Id = i, TextValue1 = "Test", TextValue2 = "Test2"});

        return View(list);
    }

The view is the bare minimum possible to display the data:

@model List<DisplayForTest.ViewModels.TestingViewModel>

@foreach (var item in Model)
{
    @Html.DisplayFor(m => item.Id)
    @Html.DisplayFor(m => item.TextValue1)
    @Html.DisplayFor(m => item.TextValue2)
}

When running this code, it takes over one second to run! The culprit is the DisplayFor. If I change the view as follows:

@model List<DisplayForTest.ViewModels.TestingViewModel>

@foreach (var item in Model)
{
    @item.Id
    @item.TextValue1
    @item.TextValue2
}

This renders in 13ms. It's clear DisplayFor is adding a huge amount of time to rendering... on my PC that's nearly 0.4ms per call. While that's not bad in isolation, it makes it a pretty bad choice for lists or other things.

Is DisplayFor really just that slow? Or am I using it incorrectly?

Doddler
  • 93
  • 1
  • 7
  • 3
    `DisplayFor` uses reflection to access every property in your object, this might trigger an EF lazy-load if it's a navigation property. This might be what's happening. – Dai Oct 13 '16 at 18:43
  • One more reason why you shouldn't use persistence model in your views, use view models instead. Though we don't know what the OP uses, EFCore doesn't have lazy Loading implemented yet, only EF6 – Tseng Oct 13 '16 at 18:58
  • I am using EFCore for this. I quickly converted it over to a view model, and selected the data with `members.Select(s => new MembersListViewModel {Id = s.Id}).ToList();`, but it seems to have the same performance issues. – Doddler Oct 13 '16 at 19:24
  • Actually, to follow up, using the view model improves performance of the test not using display for considerably, taking only 5ms, but the DisplayFor test still takes 400ms. So thanks for the tip, but that's not what's causing headaches here unfortunately. – Doddler Oct 13 '16 at 19:44
  • Can you create a new project and a [mcve], and include the relevant parts of the latter in the question? In other words: go exclude things. Are there other libraries at play, what references does the project have, what else is included in the view, and so on. – CodeCaster Oct 13 '16 at 20:29
  • 1
    Thank you for your comment CodeCaster, I have updated the post with a minimum needed to reproduce it. The code I am using now is based on the default template (no authentication) and adds only a single controller action, view, and model, with all the code presented. – Doddler Oct 13 '16 at 21:06
  • If you're concerned about rendering time, I'm not sure that's a fair comparison. What is the file size difference and load time difference? – Khyron Oct 14 '16 at 02:39
  • Have a look at this for an explanation - https://stackoverflow.com/questions/11758338/find-displaytemplates-speed/12583763#12583763 – Stuart.Sklinar Feb 12 '19 at 13:44
  • This really helped me with a slow view taking 4 secs to load. Just removed all the DisplayFor's and the view is really snappy now. Thanks. – Norbert Norbertson May 15 '22 at 16:58

2 Answers2

3

Is DisplayFor really just that slow? Or am I using it incorrectly?

It takes a bit of overhead to evaluate and execute lambda expressions. First the framework has to validate it, then evaluate it. I am speculating a bit here, but it seems likely this is where the performance concerns are coming from; both of these methods require reflection.

All of the other display methods I've played with (ValueFor, DisplayTextFor, etc.) have the same performance effect in your example.

I can't speak for the MVC team as to why it is used for the default scaffolding, but it does make sense to me. DisplayFor can handle both of the most common use cases (displaying the value of a property, and displaying the value of a property using a custom template), and performs reasonably well in most cases.

In this case, I don't see a problem with just using the raw value (basically .ToStringing it), or using it within an Html.Encode/Html.Raw method depending on what you're looking for.

Will Ray
  • 10,621
  • 3
  • 46
  • 61
  • Is it fair to conclude that for types that don't need custom display templates such as this, that `DisplayFor` is overkill? It's clear to me that I can't rely on it if I wish to have this many records visible on the page. With 10 columns, each row I render will take the server 4-5ms using `DisplayFor`, which isn't really acceptable. I did give `ValueFor` a try, and while it was a little faster, it was still really slow. The primary reason I'm using it is because the scaffolding sets you up with it automatically. If this is expected behavior though, I can accept it as the answer. – Doddler Oct 14 '16 at 20:34
  • @Doddler You're correct about the `ValueFor` performance - my answer was a bit off the mark. I've updated my answer to address the question better. – Will Ray Oct 16 '16 at 22:31
  • 2
    Thanks for your help! After messing around with it for a bit, I'm leaning towards just straight outputting regular types, and using TagHelpers or HtmlHelpers for displaying more complex types. I expected to incur some cost for tag helpers but they seem really efficient. – Doddler Oct 17 '16 at 22:55
3

Using .Net Core on a Raspberry PI, I experienced the same problem. The performance went really bad. I used dotTrace and found that the reflection uses much of the time.

Based on the following article, I was able to speedup the application.