Thursday, April 23, 2009 11:22 PM Kazi Manzur Rashid

ASP.NET MVC View Location and Performance Issue

Recently there has been quite some talk on HtmlHelper.RenderPartial() in ASP.NET MVC space which you will find over here and here. After reading those posts, I did an in depth analysis of the view location part, I am sharing the details with you so that it can help  both of us better understanding the internal details of the ASP.NET MVC framework. But before that I would like to suggest that the on going discussions should not be on partial view specific as there is no difference between the logic finding partial or regular view of default the WebFormViewEngine.

The default WebFormViewEngine is inherited from the base VirtualPathProviderViewEngine which in turns uses DefaultViewLocationCache for caching the location when running in web server in release mode. The DefaultViewLocationCache uses the ASP.NET built-in cache to cache the location. The following shows the code of DefaultViewLocationCache(I have excluded the other parts of the codes that are irrelevant in this discussion):

public class DefaultViewLocationCache : IViewLocationCache
{
    private static readonly TimeSpan _defaultTimeSpan = new TimeSpan(0, 15, 0);

    public static readonly IViewLocationCache Null = new NullViewLocationCache();

    public DefaultViewLocationCache() : this(_defaultTimeSpan)
    {
    }

    public void InsertViewLocation(HttpContextBase httpContext, string key, string virtualPath)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }

        httpContext.Cache.Insert(key, virtualPath, null /* dependencies */, Cache.NoAbsoluteExpiration, TimeSpan);
    }
}

The first thing which I like to point is the default cache duration is 15 minutes(line 3 & 7) and this duration is never overridden in the ASP.NET MVC framework. Next, when caching it uses the relative expiration which means if the view is not accessed in this duration, the cache will become invalid (line 18).  So there should be a file exists check for each inactive view after the 15 minutes, so the maximum (15 x 4 x 24) = 1440 number of file checks in a day assuming that the view is accessed just after the cache becomes invalid. I did a small benchmark with the following code(FileExists method is exactly taken from the WebFormViewEngine)  and it took around 8.5 seconds for 1440 checks to complete.

public ActionResult Index()
{
    Stopwatch watch = new Stopwatch();

    watch.Start();

    for (int i = 1; i <= 1440; i++)
    {
        FileExists(ControllerContext, "~/Views/Home/TopMenu.ascx"); //Does not exist
        FileExists(ControllerContext, "~/Views/Shared/TopMenu.ascx");
    }

    watch.Stop();
    TimeSpan elapsed = watch.Elapsed;

    ViewData["elapsed"] = elapsed;

    return View();
}

private static bool FileExists(ControllerContext controllerContext, string virtualPath)
{
    try
    {
        object viewInstance = BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(object));

        return viewInstance != null;
    }
    catch (HttpException he)
    {
        if (he.GetHttpCode() == (int)HttpStatusCode.NotFound)
        {
            // If BuildManager returns a 404 (Not Found) that means the file did not exist
            return false;
        }
        else
        {
            // All other error codes imply other errors such as compilation or parsing errors
            throw;
        }
    }
    catch
    {
        return false;
    }
}

I don’t think 8.5 seconds for 1440 checks is a performance issue. But anyway, if you are wondering how to reduce the number of checks, here is the code that you can put in your global.asax or bootstrapper.

foreach (var viewEngine in ViewEngines.Engines.OfType<VirtualPathProviderViewEngine>())
{
    viewEngine.ViewLocationCache = new DefaultViewLocationCache(TimeSpan.FromHours(24));
}

The above code will cache the location for 24 hours that means there will be only one check for an inactive view in a day.

So if you are wondering why this post shows so many exceptions, the reason is as Simone pointed out that it was running in debug mode which means the path is never cached. There might be also some exceptions generated (which is gracefully handled by the ASP.NET MVC framework) when a view is accessed for the first time even running in release mode. The reason behind it, the view itself or the part of the view(partial view) is located under the shared folder instead of the controller specific view folder, the framework first tries to find the view in the controller view folder, when it does not find the view, it throws an exception, which the framework handles itself(line 31- 35 in the above FileExists method), then it again tries the shared folder and finds the view and caches the file path, so the next request for the same view in that cached period returns the path from the cache, instead of generating the exception in finding the view.

I hope the above will clarify the confusion related with view location performance concerns.

Before ending this post I would like to comment that the reason behind making the above thing bit complex(caching, handling exception internally etc etc) is due to a missing public method of asp.net BuildManager. It is obvious that the BuildManager must have the virtual path existence checking as well as file location caching and once again the Reflector proves that it does have these methods.

Reflector1

The GetVPathBuildResultInternal is called by the CreateInstanceFromVirtualPath method which is used by the WebFormViewEngine. Next, the caching, by default the ASP.NET comes with two built-in internal VirtualPathProvider: 1. MapPathBasedVirtualPathProvider and 2. ClientVirtualPathProvider. The following shows that the ClientVirtualPathProvider itself maintaining a cache:

Reflector2

So the question remain does the ClientVirtualPathProvider comes into action when resolving the virtual path which I would like to left for the ASP.NET Team to answer.

Shout it
Filed under: , , ,

Comments

# Решение проблемы с MVC View

Thursday, April 23, 2009 12:32 PM by progg.ru

Thank you for submitting this cool story - Trackback from progg.ru

# ASP.NET MVC View Location and Performance Issue - Kazi Manzur Rashid's Blog

Thursday, April 23, 2009 12:33 PM by DotNetShoutout

Thank you for submitting this cool story - Trackback from DotNetShoutout

# ASP.NET MVC Archived Buzz, Page 1

Thursday, April 23, 2009 1:19 PM by ASP.NET MVC Archived Buzz, Page 1

Pingback from  ASP.NET MVC Archived Buzz, Page 1

# re: ASP.NET MVC View Location and Performance Issue

Thursday, April 23, 2009 1:51 PM by Simone

I don't think that 8.5 sec per day is such a big performance issue

# re: ASP.NET MVC View Location and Performance Issue

Thursday, April 23, 2009 3:42 PM by RichardD

I can't see why they don't add a call to HostingEnvironment.VirtualPathProvider.FileExists at the start of the FileExists method - it would avoid this exception entirely!

# Reflective Perspective - Chris Alcock &raquo; The Morning Brew #334

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #334

# Summary 23.04.2009 &laquo; Bogdan Brinzarea&#8217;s blog

Friday, April 24, 2009 8:06 AM by Summary 23.04.2009 « Bogdan Brinzarea’s blog

Pingback from  Summary 23.04.2009 &laquo;  Bogdan Brinzarea&#8217;s blog

# ASP.NET MVC Performance Issues with Render Partial &laquo; Teme on .NET

Pingback from  ASP.NET MVC Performance Issues with Render Partial &laquo; Teme on .NET