Hooking into Sitecore's save pipeline

Search for a command to run...

No comments yet. Be the first to comment.
Polly is great library for easily configuring different kinds of policies around code execution, be it retry, circuit breaker, caching or lots of other things. It's probably most often used around HTTP requests. If you haven't heard about it you defi...

This is part 2 of my series on unit testing in .NET (C#) and will be about my conventions regarding naming unit tests, how to structure them and which parts to test. And lastly I will help you increase readability of assertions and potential error me...

I feel like unit testing is very important and very beneficial in a lot of ways. It helps with not breaking existing functionality and thereby also making you more confident in changing code without fear of breaking important stuff. Often it also mak...

I've been working with Swagger through NSwag a lot recently and also needed to get it to work when hidden behind a reverse proxy - i.e. another service forwarding a request to the service exposing the Swagger UI. Some of the issues I've had along the...

The other day I was writing a unit test to verify what was supposed to happen when a Task would throw an exception when awaited. It wasn't as straight forward as I first assumed, but I quickly found this Stack Overflow post that helped me: https://st...

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.
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.
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.
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
masterdatabase and not the usualwebdatabase, so my items are never published and I’m therefore hooking into thesaveUIpipeline. If your site is using thewebdatabase you should instead hook into thepublishItempipeline. Your class containing your code then needs to derive fromSitecore.Publishing.Pipelines.PublishItem.PublishItem.Processor.
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.