Posted on 18. May 2011

CRM Developer ‘Must Know’ #2: Web Resource Caching

With the introduction of Web Resources in CRM 2011 the task of adding custom user interface functionality (beyond simple JavaScript) has become a whole lot easier to build and deploy. The fact that web resource are part of the solution means that there is no need to have custom deployment routines to create sites in the ISV folder.

You always want your users to have the faster experience possible, so it is important in each of these situations to ensure that your web resources are being cached by the Client browser to avoid downloading a copy with each request.

How Web Resources are cached

CRM 2011 uses an very simple but effective means of ensuring that not only Web Resources are cached by the browser, but when you update any of them, the cache is invalidated and the new version is downloaded.

When a Web Resource is referenced, although the Url shown on the Web Resource form is something like:

http://crm/Contoso/WebResources/new_/custom_page.htm

If you request this url in a browser, and use Internet Explorer's F12 Developer Tab Network monitor, you'll see that the response has the following Headers:

Cache-Contro:    private
Expires:     <Today's Date Time>

This instructs the browser/proxy server that is should never cache this file, and every time the browser asks for it, get the latest version from the server. This may not seem so bad for the odd file, but if you add all the files the browser needs for every page request and then multiply by the number of users you have, it introduces a considerable network load and download time.

So how are they cached?

When the Web Resource is referenced by a CRM 2011 page, the following format is used:

http://crm/Contoso/%7B634411504110000000%7D /WebResources/new_/custom_page.htm

If you look at the response headers you'll now see:

Cache-Control:    public
Expires:     <Today's Date Time Plus One Year>

So the browser/proxy server will keep a copy of this web resource for a year and use that before it goes to get the latest version.

So how is the client cache cleared when I publish a new version of the web resource?

The additional %7B634411504110000000%7D is the 'cache directory' and every time the customisations are re-published is updated to a new number. Since the browser/proxy cache is linked to the url of the file, if the url changes, the cache is no longer valid, and it is considered a new file altogether to be downloaded.

So how do we ensure that this cache strategy is always used?

Luckily, in most cases CRM 2011 handles this for us providing we play by its rules:

Among the options for showing a Web Resource

1) Embedding in an Entity form
2) Showing from a site map link
3) Ribbon button image
4) A Popup dialogue from a Ribbon button via a javascript function.

Here are the ways to ensure caching in each of these scenarios:

1) Embedding in an Entity form

If you embed a web resource in an Entity form, the cache directory is automatically used for you. However, if that web resource is an html page, ensure that you use relative paths in any javascript/css/image links so that you always stay in the cache directory.

E.g. If you have the following webresources:

  • new_/Account/AssignAccount.htm
  • new_/scripts/Common.js

In your AssignAccount.htm page, reference relatively using:

<script src="../scripts/Common.js" type="text/javascript"></script>

Do not use absolute:

<script src="/Webresources/scripts/Common.js" type="text/javascript"></script>

2) Showing from a site map link

When showing an html page from a site map link, you can ensure that CRM uses the cache directory by avoiding absolute paths and use the $webresource token in the SubArea definition:

<SubArea 
     Id="nav_assignaccount"
     PassParams="0" 
     Client="All" 
     Url="$webresource:new_/Account/AssignAccount.htm" 
     Icon="$webresource:new_/icons/assign16.gif">

3) Ribbon button image

When referencing images from site maps/ribbons, use the $webresource token as above rather than using the absolute path of the web resource.

4) - A Popup dialogue from a Ribbon button via a JavaScript function

UR8 or later 

UR8 introduced a new utility function called 'openWebResource' - this will ensure that if you need to show a popup window with a webresource, caching is used rather than having to provide an absolute path:

Xrm.Utility.openWebResource("new_page.htm");

This results in a url being used along the lines of:

http://server/Org/%7B634940089750000000%7D/WebResources/new_page.htm

If you do need to manually construct a Web Resource Url, you can use the contstant 'WEB_RESOURCE_ORG_VERSION_NUMBER' but baring in mind that this is not a supported/documented SDK constant.

Before UR8

Prior to UR8, this final one poses a bit more of a challenge. If you want to show a popup window that references a web resource page, before UR8 there was no way in the customisations to achieve this – it has to be JavaScript which means you need to construct the cache directory web resource location yourself. This is biggest area where I see developers writing code resulting in the browser always downloading the file with every request.

To avoid this, you can find the current cache directory from another web resource that is currently loaded using something similar to:

function GetCacheDirectory(webresourceUrl) {
    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
        var url = scripts[i].src;
        var p1 = url.search("/%7B")
        if (p1 > 0) {
            var p2 = url.search("%7D/", p1) + 4;
            var resourceCache = url.substr(p1, (p2 - p1));
            break;
        }
    }
     var url = "/" + Xrm.Page.context.getOrgUniqueName() + resourceCache + webresourceUrl;
    return url;
}

And calling it using:

// Get cache directory resource url
var url = GetCacheDirectory('/WebResources_/new/Account/AssignAccount.htm');

// Open Window
window.open(url);

Of course - I would recommend you upgade to the latest rollup if you don't have UR8 installed yet!

If you follow these steps, you will ensure that your user's browser only download files when they need to resulting in less network load and faster load time.

Happy caching!