Inconvenient caching dynamically generated files in BLOB cache


Caching files using BLOB cache is a great performance improvement in SharePoint 2010 Web Content Management solutions. Unfortunately it turns out, that if your files are dynamically created, they are not being cached by BLOB cache. So is creating a custom caching solution the only option to have a good performing solution?

Optimizing SharePoint for performance – caching

The SharePoint 2010 platform provides you with a number of caching mechanisms. Depending on if you want to cache objects in memory or would like to optimize loading files, SharePoint gives you the ability to configure caching for all the different purposes and scenarios.

As you know all SharePoint content is stored in the database. And even though Microsoft did great job optimizing loading content from the database, it’s just not as fast as loading files from a hard disk. And this is exactly where BLOB cache comes is.

Improving performance of SharePoint solutions with BLOB cache

BLOB cache is a caching mechanism available as a part of SharePoint Server 2010 which allows you to cache some of your SharePoint content on disk for quicker loading. You can enable and configure BLOB cache in web.config of your Web Application by changing the values in the configuration/SharePoint/BlobCache configuration node.

The BlobCache configuration node in web.config

How they do it?

After you set the value of the enabled attribute to true BLOB cache will start working. Every time a file is requested BLOB cache will use the regular expression from the path attribute in web.config to check if the file should or should not be processed. If the match is successful BLOB cache will try to deliver the file from the location specified in the location configuration attribute. If the file is found, BLOB cache will deliver it to the client. If the file hasn’t been cached yet BLOB cache will try to retrieve and cache the file. And this is exactly when things get inconvenient.

BLOB cache and dynamically generated files

There are many scenarios to think of when you dynamically generate files. You might be providing a dynamic an XML Sitemap, a robots.txt file or maybe something more business related like images with watermarks or charts. No matter the reason, if you are working with a high-traffic website, it’s only the matter of time before you get the requirement not to generate those files every time they are requested.

Although you might think about creating a caching mechanism yourself, keep in mind that you’re working with SharePoint which is a platform that already provides you with a number of capabilities. Reusing something that is a part of the platform is in most cases better than creating something yourself. It’s not only cheaper but it also lowers maintenance, and improves standardization. Using BLOB cache for caching dynamically generated files seems like a proper idea.

As I mentioned before, the first time a file is requested, it is not yet in BLOB cache and BLOB cache engine will try to retrieve the file from SharePoint. For this however it will not make another web request which would trigger your code to dynamically generate the file, but it will use the SPWeb.GetObject method instead! So if your file doesn’t exist as a static file in SharePoint it will never be cached in BLOB cache.

Before you start developing your own caching solution, you might think of one more approach. You could consider using the BLOB cache API to write your dynamic files to BLOB cache yourself. Unfortunately, at this moment the BLOB cache API is not public and cannot be used in custom solutions.

So is there anything else left other than creating a custom caching solution?

Caching dynamically generated files in BLOB cache

Yes, it turns out that you can cache dynamically generated files in BLOB cache. To let BLOB cache store your file in its cache, you have to save the file in SharePoint after its been generated so that it can be picked up on subsequent requests. Let’s take a dynamically generated chart as a sample scenario.

Let’s start by creating a Document Library where we will be storing generated charts. Let’s call it Charts.

Next we need to create an HTTP Handler responsible for creating chart images and storing them in the Charts library so that they can be cached in BLOB cache. The following code snippet contains a blueprint for such HTTP Handler. Methods responsible for generating charts and error handling have been omitted for brevity.

public class ChartHttpHandler : IHttpHandler {
    #region IHttpHandler Members

    public bool IsReusable {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context) {
        SPFile file = GenerateChart(context.Request.Url);

        if (file != null) {
            SendChart(context, file);
        }
        else {
            Send404(context);
        }
    }

    #endregion

    private static SPFile GenerateChart(Uri imageUrl) {
        // omitted for brevity
    }

    private static Hashtable GetBlobCacheSettings() {
        return WebConfigurationManager.GetSection("SharePoint/BlobCache") as Hashtable;
    }

    private static void SendChart(HttpContext context, SPFile file) {
        SetResponseHeaders(context);
        byte[] fileBytes = file.OpenBinary();
        context.Response.ContentType = "image/png";
        context.Response.AppendHeader("Content-Length", fileBytes.Length.ToString());
        context.Response.OutputStream.Write(fileBytes, 0, fileBytes.Length);
        context.Response.End();
    }

    private static void SetResponseHeaders(HttpContext context) {
        context.Response.Cache.SetCacheability(HttpCacheability.Public);
        int maxAge = -1;
        if (!Int32.TryParse(GetBlobCacheSettings()["maxAge"] as string, out maxAge)) {
            maxAge = 86400; // 24h
        }

        context.Response.Cache.SetMaxAge(new TimeSpan(0, 0, maxAge));
    }

    private void Send404(HttpContext context) {
        context.Response.StatusCode = (int)HttpStatusCode.NotFound;
    }
}

On the first request the requested chart image doesn’t exist in BLOB cache so BLOB cache will try to retrieve it from the content database using the SPWeb.GetObject method. Once it fails, because the image doesn’t exist in the Charts library yet, the request will hit our HTTP Handler. We start our work with creating the chart image. The GenerateChart method is responsible for creating the chart image, saving it as a file in the Charts library and returning a reference to the SPFile that represents the image. Once the file has been successfully created we send it to the client (12).

On the next request to the chart image BLOB cache will once again try to deliver the image file from its cache. Once it finds out that it hasn’t been cached yet, it will once again attempt to retrieve the file. Because we stored the previously generated chart in the Charts library, BLOB cache can retrieve it using the SPWeb.GetObject method and it will then store it in cache. This time, the request will not reach our HTTP Handler since BLOB cache managed to retrieve the chart image file.

Until the cache expires, every next time someone requests the chart image, BLOB cache will retrieve the image from its cache and will send it directly to the client skipping the whole SharePoint request execution pipeline. This is exactly why using BLOB cache for optimizing performance is such a great idea!

Mind the configuration

There is one more thing left before we see this working and it has to do with configuring the HTTP Handler.

To use an HTTP Handler with your Web Application you have to register it in web.config. You do this by adding an add element in the configuration/system.webServer/handlers section of your web.config.

HTTP Handler configuration in web.config

In order to make the HTTP Handler and BLOB cache work together correctly, you have to make sure that the HTTP Handler is mapped to the exact same URL as the chart image stored in the Charts library. Let me explain why.

You want to display the dynamically generated chart somewhere on your site. For this you edit one of the pages and add to it the HTML <img /> tag which makes the browser to load the image of specified URL:

<img src="/Charts/_chrt_p1val_p2val.png" alt="My Chart" />

As you can see in the previous screenshot, we have mapped our HTTP Handler to the _chrt_*.png path, so every time a file that matches that path is requested our HTTP Handler will be called by ASP.NET to handle the request. When someone opens a page with the img tag showed above the browser will request the chart image. That request will hit our HTTP Handler, which will generate the image, save it in the Charts library and send it to the browser. Next time someone will open that page, the request will be handled by BLOB cache, which will retrieve the chart image from the Charts library and copy it to the BLOB cache folder, to a path similar to:

C:\BlobCache\14\1216437384\1CyNKa3+YEmnkYKXxCMXQw\Charts\_CHRT_P1VAL_P2VAL-4cc7nxmI6UKu0W3WgxOhYg.PNG

Because all query string parameters are removed from the cached file name, you have to pass all parameters required to create and uniquely identify the file in the filename. This allows you not only to correctly store the file in the Charts library but also to quickly determine its contents without opening it.

Chart showing % of visits

If you would use a different URL to call the HTTP Handler and different to store the image those URLs would never met and the dynamically created chart would never get cached in BLOB cache.

Summary

When working with SharePoint 2010 solutions that need high performance you can use BLOB cache to improve loading files. Although BLOB cache works perfectly with static files, it doesn’t automatically pick up files that are generated automatically. You can however leverage the BLOB cache capability in custom code responsible for creating dynamic images what eliminates the need of a custom caching solution.

Others found also helpful: