Resource Combiner with Kentico Portal Engine

A major goal with any website we develop at pmc.digital is to optimize it accordingly to the best practise recommendations for our industry.

Search engines are known to rank sites that load faster and follow these recommendations (like responsive design for mobile devices for example) better than others. Organic ranking is just as important as the PPC campaigns that drive traffic to your website.

Kentico already includes solutions to deal with the majority of these recommendations, like Resource Compression and Minify among other features to improve a website's performance, that you just simply enable and/or configure very easily.

However if you're not using the MVC implementation, like us you don't want to miss on the great features that come with the Portal Development mode in Kentico, you'll find that if you run your website in a page speed analyser, like GTMetrix, there are some recommendations that are harder to fix due to how the Portal Engine uses the WebForms implementation of ASP.net.

Our challenge

With our goal to implement these recommendations as much as we can in mind, be it with Kentico's in-built features or by rolling our own, the most obvious one to concentrate first was the references to the ScriptResource.axd and WebResource.axd resources. Surely these can be combined somehow!

Kentico does not include this improvement so we thought it would be a great one to start with and learn something new about Kentico in the process.

Thanks to a great article by Mads Kristensen on how to optimize .net's AXD resources, and other articles from the Kentico Devnet, we've managed to port the code to Kentico by reutilizing its Global Events to parse the Output of a page and replace the references to all the resources with a single reference to a handler that has, in cache, the combined resources found in the page.

public override void Init()
{
    RequestEvents.PostMapRequestHandler.Execute += PostMapRequestHandler_Execute;
}
private void PostMapRequestHandler_Execute(object sender, EventArgs e)
{
    if (!PMCResourceCombinerHelper.Enabled) return;

    // Creates an output filter instance
    if (!URLHelper.IsExcludedSystem(RequestContext.CurrentRelativePath)
            && PortalContext.ViewMode == ViewModeEnum.LiveSite
            && !RequestHelper.IsAsyncPostback())
    {
        ResponseOutputFilter.EnsureOutputFilter();

        OutputFilterContext.CurrentFilter.OnAfterFiltering += CombineAxdResources;
    }
}

(this is just a snippet, the whole of the code can be found in the download link below, but keep reading because it gets more interesting)

That's done, what's next?

With the AXD's taken care of, we started looking into how we include our CSS and JS with Kentico. We use the inbuilt compress and minify helper, the GetResource.ashx, to include all references to CSS or JS. It works great but it uses query strings (something that is also not recommended because some URL's with "?" are not cached by some proxy caching servers) and it's somewhat limited if you want to combine resources.

We first tried to see if solutions that were already available could be used with Kentico's Portal Engine, unfortunately none proved successful to us as it seemed we always had to make a compromise somewhere, and we didn't like some of them!

We looked at the AXD combiner we had just built and whether this could be extended to also combine JavaScript and CSS. However, we wanted to able to:

  • Combine JavaScript includes in groups, e.g. before </head>, after <body> or before </body>.
  • Combine CSS included in groups, less needed but also handy if you have clashing rules.
  • Continue to combine de AXD's separately.
  • Reutilize as much of Kentico's cache, compression and minify features, so a website runs with or without the Resource Combiner Enabled.

With a lot of trial and error, some heavy googling and extra-hours learning about compression, differences between GZIP and Deflate, minify, etc, we've managed to build a working module that can be easily included with any Kentico website (untested on V8, yet!) just like the one we've recently launched, the Covema website that already uses a version of this Resource Combiner for AXD's, CSS's and JavaScript resources.

How does it work?

If the module is enabled (please readme.txt if you download the zip below) it will automatically combine all the WebResource.axd or ScriptResource.axd it can find on the output of a page.

All references will be replaced by a single reference identified by a guid that the handler uses to get the combined AXD scripts:

<script src="/axd/guid/combiner.axd" type="text/javascript"></script>

Please note, that the combiner module will reuse the same guid if the same exact AXD includes are found across different pages, if they are not the same it will generate a new guid and cache content key.

However when combining JS or CSS includes, you'll need to register these in a specific way that the module can parse when replacing the output of a page.

For CSS, the link include needs to be registered with the attributes in the href, type and rel order, and the GetResource.ashx url must contain the "&amp;combiner=css" querystring key:

<link href="/CMSPages/GetResource.ashx?stylesheetfile=/App_Themes/Covema/CSS/normalize.css&amp;combiner=css" type="text/css" rel="stylesheet">

Each group of CSS includes will be replaced by a single reference identified by a guid that the handler uses to get the combined CSS for the whole group:

<link href="/css/guid/combiner.axd" type="text/css" rel="stylesheet">

With Javascript, the script include attributes need to be in the src and type order, and the GetResource.ashx url must also contain the "&amp;combiner=js" querystring key:

<script src="/CMSPages/GetResource.ashx?scriptfile=/App_Themes/Covema/Js/main.js&amp;combiner=js" type="text/javascript"></script>

Like how it works with the other combine modes, each Javascript include group will be replaced by a single reference identified by a guid that the custom handler uses to get the combined Javascript for the whole group:

<script src="/js/guid/combiner.axd" type="text/javascript"></script>

If you have not configured the references like above, the Resource Combiner will not be able to match them to the regular expression it uses specifically for each type of the combination.

Additional notes

  • You can create additional js and css groups by defining a "&amp;combiner=group1" instead of the default css or js.
  • Guid is created at response output time with the combination of the files grouped and cached so the handler can retrieve them in a separate request.
  • The handler include for the group will be placed where the first item of its group is on the original response output.
  • The attribute order of the targets matters (e.g. type="text/javascript" after src) so please follow the examples above.
  • The Resource Combiner does not work with CSS Stylesheets that are included with the "stylesheetname" option.
  • Resolving of Kentico macros may not be working within the JS and/or CSS code.
  • JavaScript or CSS included in a postback is not combined.
  • You can follow me via GitHub where this code may end up eventually it there are any issues.
  • It's been reported that the module does not work with virtual directory Kentico setup.
  • Now also available for download in the Kentico Marketplace, however updates will always be posted here first.

Update (July 13th 2016)

The download link below has been updated with a new version of the resource combiner that now includes support for CSS Stylesheets with "stylesheetname" option, Macros and an important improvement related to caching.

Update (July 14th 2016)

For those having build issues in App_Code, updated download link below.

Update (July 15th 2016)

Module is now also available for download in the Kentico Marketplace, where it has been tested and QA'ed, however updates will always be posted here first.

Update (July 18th 2016)

Support for installations in virtual directory.


You can download the whole module code in the download link below,
but please test and include it at your own risk.
DOWNLOAD

pmc.digital

WebDev & DevOps Services

Find out more