Hooking into Sitecore's save pipeline

Hooking into Sitecore's save pipeline

Some time ago I did some work on a web application built on Sitecore which used the new application cache introduced in HTML5. Application cache essentially makes your web site/application or parts of it available offline and thus can also speed up your application in certain scenarios as content is cached locally.

Using application cache with Sitecore and content that is often changed, however, introduced a few hurdles to overcome. One of them was to make sure that the client always got the newest content from Sitecore and that is what this (relatively) short blog post is going to be about.

We need to do something whenever a file is saved in Sitecore.

HTML5 Application Cache

Before we get to that part, let me just roughly explain how application cache works and you will see what the problem is.

You start out by referencing a manifest file in your html like this:

<html manifest="/manifest.appcache">
...
</html>

The file can be named anything you like but .appcache is usually used a the file extension. Just make sure that your web server knows how to handle the extension. You might need to add it’s mimetype to your Web.config like below for it to work correctly.

<system.webServer>
    <staticContent>
        <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest"/>
    </staticContent>
</system.webServer>

By specifying the manifest attribute the current page is automatically cached by your browser and available without an internet connection. The browser will now always load the cached version even if it is actually online – it won’t even ask the server if it has been modified or anything. The browser will periodically check the manifest file for changes (the files content has to have changed for it to update the cached pages) and if it has been changed then refetch the page from the server.

The manifest file can contain references to other files that will then follow the same behaviour. By specifying all the files your web application is dependent on in the manifest file, your application will basically be downloaded when the user hits the front page that references the manifest.

But now it will never ask the server for updated versions of the cached pages, unless the content of the manifest file changes. Additionally, if the cache headers for the cached pages allows the pages to be cached, changing the manifest won’t actually update the files unless they have expired.

So be sure to disable caching for any files that is part of your manifest, if you want to make sure they are updated whenever the manifest is changed.

Read my earlier post about how to easily disable caching for specific files or folders here.

If you want to know more about application cache and format of the manifest file then take a look at this tutorial.

Updating the manifest whenever an item is saved in Sitecore

Because pages are not refetched from the server – even if the browser is actually online – when content is updated in Sitecore, the clients will still be seeing the old content and herein lies the problem we needed to solve.

We need to make a change to the manifest file whenever an item is saved to force the clients to refetch the cached pages from our server with the updated content.

Luckily this is pretty easy in Sitecore. All we need to do is add a processor to the saveUI pipeline.

The Code

Let’s write the code to actually update the manifest file.

public class UpdateManifest
{
    public void Process(SaveArgs args)
    {
        Assert.ArgumentNotNull(args, "args");

        SaveArgs.SaveItem[] savedItems = args.Items;

        // Abort if there is no items being saved
        if (!savedItems.Any())
            return;

        // Abort if the item doesn't exist anymore
        var item = Client.ContentDatabase
                         .GetItem(savedItems[0].ID, savedItems[0].Language, savedItems[0].Version);
        if (item == null)
            return;

        // Check if the saved item is relevant to us.
        // In my case I only needed to update the manifest 
        // if the item was in a specific folder.
        if (item.Paths.FullPath.Contains("/sitecore/content/service-app/"))
        {
            UpdateManifestFile();
        }
    }

    public void UpdateManifestFile() {
        try
        {
            var path = Sitecore.IO.FileUtil.MapPath("~/manifest.appcache");
            var lines = File.ReadAllLines(path);

            // Find current version in comment on line 2
            // and increment it if it exists
            var match = Regex.Match(lines[1], @"# Version (\d+)");
            if (match.Success)
            {
                int version;
                int.TryParse(match.Groups[1].Value, out version);

                lines[1] = "# Version " + (++version);
                File.WriteAllLines(path, lines);
            }
        }
        catch (Exception ex)
        {
            Log.Warn("Could not update version of 'manifest.appcache'", ex, this);
        }
    }
}

That is all there is to it. We just make sure that an item is actually being saved, that it exists and that the saved item is in a specific part of the Sitecore content tree, as we only need to this for that specific tree. Updating the file is just incrementing a version number in a comment line at the second line in the file.

Now we just need to have this code run as part of the saveUI pipeline. To do this we just create a new config file UpdateManifestOnSave.config with the following content.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <processors>
      <saveUI>
        <processor mode="on" 
          type="[NAMESPACE].UpdateAppCache, [ASSEMBLY]" 
          patch:before="processor[@type='Sitecore.Pipelines.Save.WorkflowSaveCommand, Sitecore.Kernel']"
        />
      </saveUI>
    </processors>
  </sitecore>
</configuration>

The file should be placed under App_Config/Include and it is then automatically patched into the configuration by Sitecore.

Now our manifest file will be modified whenever an item is saved in Sitecore (and is located under /sitecore/content/service-app/) and thereby forces the clients to refetch the cached pages from the server – now with updated content. As I mentioned earlier make sure that your web server has disabled caching for the files included in your manifest to make sure that the browser actually gets the update files.

In this case my site is using the master database and not the usual web database, so my items are never published and I’m therefore hooking into the saveUI pipeline. If your site is using the web database you should instead hook into the publishItem pipeline. Your class containing your code then needs to derive from Sitecore.Publishing.Pipelines.PublishItem.PublishItem.Processor.

Conclusion

With Sitecore’s pipelines it’s quite easy to hook into different parts of Sitecore and customize your solution.

In this blog post you’ve seen how this can be used to hook into the saveUI pipeline and in this case update the manifest file. In another project we are renaming the saved item from a selected start and end date on the item, so the editors get a better overview in the Sitecore tree without manually having to rename items depending on their current content.

The stuff in this blog post can easily be applied to other pipelines as well in more or less the same way. If you are interested to know which other pipelines Sitecore has, you can have a look in your Web.config under configuration/sitecore/pipelines and configuration/sitecore/processors.