Optimize your MVC Umbraco site

There are a number of ways to optimize your MVC Umbraco application which can be achieved with little code effort. Actual code optimization may still be required if the following fails to address any performance issues your application may have, but as a first port of call these will result in some noticeable improvements.

Use bundling to combine and minimize your resources

With bundling you can combine and minimize your style-sheet and script assets. You can read in more detail about setting up bundling with Umbraco.

Use output caching

If you are implementing your own controllers then you can decorate them with the OutputCache attribute as you would in a normal MVC application.

[OutputCache(Duration=3600)]

This will instruct MVC to cache the output of that particular action for an hour (or whatever duration you want). The problem is that Umbraco will use the same action for multiple pages: All pages that use the same doc type and template combination will then be treated as the same cached page.

The fix is to implement a custom URL parameter so that MVC will cache by URL and not by route alone. If you don’t add this in you will cache the first page of that doc type and then MVC will serve up that page for all subsequent requests.

You need to override the GetVaryByCustomString method in your own custom Global.cs file and reference that in the Global.ascx like so.

public override string GetVaryByCustomString(System.Web.HttpContext context, string custom)
{
    if (custom.ToLower() == "url") {  
        return "url=" + context.Request.Url.AbsoluteUri; 
    } 
    
    return base.GetVaryByCustomString(context, custom); 
}

Now you can add the varyByCustom attribute to your actions. To save a bit of typing you can specify one or more cache profiles in your web.config. Note we also add in the varyByParam attribute so that pages are also cached by the route parameters.

<caching>
    <outputCacheSettings>
        <outputCacheProfiles>
            <add name="ControllerCache" duration="3600" varyByCustom="url" varyByParam="*"/>
        </outputCacheProfiles>
     </outputCacheSettings>
</caching>

Then reference the profile where needed to control the behavior from a single location

[OutputCache(CacheProfile = "ControllerCache")]

The next issue with OutputCache is that is does not cater for Child Actions on a page that should not be cached. For example you may have a child action that submits a form and is dependent on user actions. OutputCache will simply cache the full page and interfere with form submission.

Instead we need a way to omit certain parts of the page from the cache. Donut cache is a way to do this and can be installed easily via nuget.

PM> Install-Package MvcDonutCaching

Once installed all you need to do is add a using to your custom controllers and replace the attribute:

[DonutOutputCache(CacheProfile = "ControllerCache")]

To remove a specific action from the cache add a flag to the Html helper method using the overload provided by Donut Cache.

@Html.Action("RenderForm", "Form", true)

Ensure Umbraco is up to date

Anyone who has had to upgrade Umbraco on an existing website will know that this is not always a straightforward task. Third party plugins can break and API methods can change. So the decision should be around what the benefits of an upgrade will give compared to the development time required.

Some updates demand the effort however; Security is an obvious one any major security flaws should be patched. Major bug fixes are another, most notably the recent fix for PetaPoco’s.

If you are using a version of Umbraco prior to 7.1.7 or 6.2.3 then there is a known issue with PetaPoco which can cause Umbraco to consume large amounts of memory. You can read about the issue on the Umbraco Issue Tracker. http://issues.umbraco.org/issue/U4-5556

So if you are finding your Umbraco installation is consuming larger than normal amounts of memory upgrading Umbraco to get the patch may prove the answer.

Remove unused view engines, use full paths for referencing razor files

This will only give you a small performance boost, but it is almost a freebie in terms of effort. By default MVC will search through all available view engines when trying to resolve a view.

Although I am sure this has been thoroughly optimized, if you know for certain you are only using razor views then it makes sense to simply switch off the unused ones. Simply override the ApplicationInitialized method as follows.

protected override ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
    ViewEngines.Engines.Clear();

    RazorViewEngine engine = new RazorViewEngine();
    ViewEngines.Engines.Add(engine); 
}

MVC uses conventions to resolve a specific view. This means it will search through more than one location for a view based on these conventions. This may be useful in some cases but if you are sure that your view will only exist in a single location you can specify the full path so that MVC does not have to search for it.

return View("~/Views/Content.cshtml", model); 

@Html.Partial("~/Views/Partials/_Partial.cshtml", Model)

Use child actions sparingly

Child actions are usually pushed as a way for code reuse, and they are useful in some situations but it pays to understand what is actually happening when a child action is used.

The thing to remember is that a child action is really a separate route and as such comes with all the overhead of routing that is normally associated with a page in MVC. This means constructing an instance of the relevant controller, looking for the appropriate view etc.

This may not create a performance issue in itself but it is worth knowing; for example you may be tempted to put common code in a controller’s constructor to aid reuse. If you then use the same controller for multiple child actions on the page you will run the constructor code several times also. This may be the intended result but if that code is not required for every child action you may be adding unnecessary overhead to your application.