The Sitecore Pie: strategic slicing for better implementations

The Sitecore Pie

pie
At some point I want to catalog the full set of features a Sitecore Content Delivery and Content Management server can manage in a project.  My goal would be to identify all the elements that can be split apart into independent services.  This post is not a comprehensive list of those features, but serves to introduce the concept.

Think of Sitecore as a big blueberry pie that can be sliced into constituent parts.  Some Sitecore sites can really benefit from slicing the pie into small pieces and letting dedicated servers or services manage that specific aspect of the pie.  Too often, companies don’t strategize around how much different work their Sitecore solution is doing.

An example will help communicate my point: consider IIS and how it serves as the execution context for Sitecore.   Many implementations will run logic for the following through the same IIS server that is handling the Sitecore request for rendering a web page.  These are all slices of the Sitecore pie for a Sitecore Content Delivery server:

  1. URL redirection through Sitecore
  2. Securing HTTP traffic with SSL
  3. Image resizing for requests using low-bandwidth or alternative devices
  4. Serving static assets like CSS, JS, graphics, etc
  5. Search indexing and query processing (if one is using Lucene)

If you wanted to cast a broader net, you could include HTTP Session state for when InProc mode is chosen, Geo-IP look-ups for certain CD servers, and others to this list of pie slices.  Remember, I didn’t claim this was an exhaustive list.  The point is: IIS is enlisted in all this other work besides processing content into HTML output for Sitecore website visitors.

Given our specific pie slices above, one could employ the following alternatives to relieve IIS of the processing:

  1. URL Redirection at the load balancer level can be more performant than having Sitecore process redirects
  2. Apply SSL between the load balancer and the public internet, but not between the IIS nodes behind your load balancer — caled “SSL Offloading” or “SSL Termination”
  3. There are services like Akamai that fold in dynamic image processing as part of their suite of products
  4. Serving static assets from a CDN is common practice for Sitecore
  5. Coveo for Sitecore is an alternative search provider that can take a lot of customer-facing search aspects and shift it to dedicated search servers or even Coveo’s Cloud.  One can go even further with Solr for Sitecore or still other search tiers if you’re really adventurous

My point is, just like how we hear a lot this election season about “let Candidate X be Candidate X” — we can let Sitecore be Sitecore and allow it to focus on rendering content created and edited by content authors and presenting it as HTML responses.  That’s what Sitecore is extremely valuable for.

Enter the Cache

I’ve engaged with a lot of Sitecore implementations who were examining their Sitecore pie and determining what slices belong where . . . and frequently we’d make the observation that the caching layer of Sitecore was tightly coupled with the rest of the Sitecore system and caching wasn’t a good candidate for slicing off.  There wasn’t a slick provider model for Sitecore caches, and while certain caches could be partially moved to other servers, it wasn’t clean, complete, or convenient.

That all changed officially with the initial release of Sitecore 8.2 last month.  Now there is a Sitecore.Caching.DefaultCacheManager class, a Sitecore.Caching.ICache interface, and other key extension points as part of the standard Sitecore API.  One can genuinely add the Sitecore cache to the list of pie slices one can consider for off-loading.

In my next post, I will explore using this new API to use Redis as the cache provider for Sitecore instead of the standard in-memory IIS cache.

Customizing the Sitecore MediaCreator Logic

Many customers use a CDN to improve the performance of their site (static assets will be cached at the CDN and load more quickly).  There are various ways of implementing a CDN with Sitecore, and one customer I work with takes advantage of Sitecore’s flexibility around media item storage to facilitate the synchronization of the file system with their CDN.  Without going into all the details, they had enabled file system storage for media (by altering the Sitecore setting to true: <setting name=Media.UploadAsFiles value=true/>) and were using the physical file system on the Sitecore server to populate their CDN.

I should interject here that Sitecore generally recommends keeping the default UploadAsFiles setting (to false) and implement a CDN by extending Sitecore’s save or publish process to synchronize media — there are other advantages to keeping media stored in the Sitecore databases.  This post isn’t exploring CDN setups, though, so I’ll leave that as a topic for a later day.

For this particular customer, storing Sitecore media as files on disk made the most sense given all their various constraints.  The problem I helped them to solve was that Sitecore’s default naming system for media was creating items on disk that looked like this:

  • /App_Data/MediaFiles/3/F/7/{3F784C5A-68CA-4B93-8CE7-9CC32F8DC5FD}Red.jpg
  • /App_Data/MediaFiles/2/C/A/{2CA14D5F-FC67-9A71-C911-5D26F817D241}Green.jpg

Those ugly GUIDs were displaying in the customer requests, which can apparently hurt SEO.  Usually the media item names wouldn’t be a concern since using the Sitecore FieldRenderer object one can use Sitecore’s <setting name=Media.UseItemPaths value=true/> setting to control how the names are exposed.  For this customer, however, the FieldRenderer wasn’t an option due to their CDN approach.

The challenge was: how can one have full control over the media item names generated by Sitecore?  The final approach we settled on was to extend the Sitecore.Resources.Media.MediaCreator class with our own logic for the GetMediaStorageFolder method. This is a virtual method in Sitecore’s MediaCreator class, which means it is readily extensible.

I created a new VS.Net class library project and added a reference to the Sitecore.Kernel for this customer’s implementation. Then, using Reflector to inspect the existing GetMediaStorageFolder implementation, I added the custom logic to remove the GUID (in truth, any logic could be applied here).

Here’s the default GetMediaStorageFolder method Siteocre uses OOTB:

public virtual string GetMediaStorageFolder(ID itemID, string fullPath)
{
    Assert.IsNotNull(itemID, "itemID is null");
    Assert.IsNotNullOrEmpty(fullPath, "fullPath is empty");
    string fileName = FileUtil.GetFileName(fullPath);
    string str2 = itemID.ToString();
    return string.Format("/{0}/{1}/{2}/{3}{4}", new object[] { str2[1], str2[2], str2[3], str2, fileName });
}

And here’s our customized version that eliminates the GUID from the name of the item, I’m including the full class so you can see in inherits the Sitecore.Resources.Media.MediaCreator class:

 public class CustomMediaCreator : Sitecore.Resources.Media.MediaCreator 
    {
        public override string GetMediaStorageFolder(Sitecore.Data.ID itemID, string fullPath)
        {
            Sitecore.Diagnostics.Assert.IsNotNull(itemID, "itemID is null");
            Sitecore.Diagnostics.Assert.IsNotNullOrEmpty(fullPath, "fullPath is empty");
            string fileName = Sitecore.IO.FileUtil.GetFileName(fullPath);
            string str2 = itemID.ToString();
            return string.Format("/{0}/{1}/{2}/{3}", new object[] { str2[1], str2[2], str2[3],fileName });        
        }    
    }

The real trick comes in how one introduces this custom logic into Sitecore . . . we need to take advantage of the Hooks extensibility point in Sitecore and use our CustomMediaCreator in place of the default.

This requires writing a Hook class that implements the Sitecore.Events.Hooks.IHook interface. IHook requires an Initiliaze method to connect up the custom functionality. In our case, we’re setting the following:

Sitecore.Resources.Media.MediaManager.Provider.Creator = new CustomMediaCreator();

For convenience for this blog, I folded it all into a single file and namespace, but a better design would be to separate this out of course. The finished product, both the Hook and the CustomMediaCreator, are now here:

namespace CustomMediaStorage
{
    public class CustomHook: Sitecore.Events.Hooks.IHook
    {
        public void Initialize()
        {
            Sitecore.Resources.Media.MediaManager.Provider.Creator = new CustomMediaCreator();
        }
    }

    public class CustomMediaCreator : Sitecore.Resources.Media.MediaCreator 
    {
        public override string GetMediaStorageFolder(Sitecore.Data.ID itemID, string fullPath)
        {
            Sitecore.Diagnostics.Assert.IsNotNull(itemID, "itemID is null");
            Sitecore.Diagnostics.Assert.IsNotNullOrEmpty(fullPath, "fullPath is empty");
            string fileName = Sitecore.IO.FileUtil.GetFileName(fullPath);
            string str2 = itemID.ToString();
            return string.Format("/{0}/{1}/{2}/{3}", new object[] { str2[1], str2[2], str2[3],fileName });        
        }    
    }
}

To wire up our custom Hook, we go to web.config and go to definition of our Hooks. HealthMonitorHook and MemoryMonitorHook are the two default hooks defined in Sitecore; I put our configuration just above those two definitions:

<!– HOOKS –>

<hooks>

<hook type=CustomMediaStorage.CustomHook, CustomMediaStorage />

Now, when we add new media items to Sitecore, they’re stored in a path like the following:
/App_Data/MediaFiles/9/C/7/CustomIcon.jpg.  Hooray, no GUID!

This isn’t quite case-closed, however, as the customer was using MVC and initializing the application using a dependency injection registration engine which overwrote our Hook. So it turns out the Hook wasn’t necessary for them, but they needed to include the Sitecore.Resources.Media.MediaManager.Provider.Creator = new CustomMediaCreator(); into that app startup logic.