Sitecore dynamic site provider

I did a bit of playing around recently with Sitecore’s SiteProvider infrastructure.  Our client wanted to be able to create new websites without needing a developer to set it up for them.  The nuts and bolts of sharing templates and renderings is already well documented, so I’m not going to go into that.  I am going to write a bit about how a custom SiteProvider can be created to avoid the need for developers to add a new <site> entry in the configuration. I did come across an older blog article from the integration solution team about writing a SiteProvider, but it’s worth noting that this doesn’t take into account more recent developments and so might not function as expected in some newer installations.

SiteProviders are (fairly obviously) responsible for providing Sitecore with the list of Site objects, representing the websites that it should be serving.  This list of sites is accessed through the SiteManager class, and this feeds into the SiteContextManager, which keeps a lookup table for the purposes of site resolution and appears to be used much more generally throughout Sitecore.

Glossing over FXM for the moment, Sitecore ships with two SiteProvider implementations: ConfigSiteProvider and SitecoreSiteProvider.  The former is the one we know and love, and is what reads the sites from the <sites> section of the Sitecore configuration (you may have guessed this from the name).  The latter is much newer (maybe Sitecore 8 or late Sitecore 7?) and acts as an umbrella for any providers that it is told to reference (allowing the application to have multiple SiteProviders active at any one time).  The SitecoreSiteProvider is not especially complex: for the most part it simply delegates calls to all the registered providers and aggregates the result.  This works fine, but can cause some issues that I mention in the list below if you aren’t careful.

Having previously mentioned FXM, it is important to know that it registers its own SiteProvider with the SitecoreSiteProvider, and this is necessary to support its use.  If those sites are not present, then it will not function correctly.  For that reason, I would consider it inadvisable to select any provider other than the SitecoreSiteProvider as the default one.  Conceivably, it would be possible to support it, but I don’t see any benefit to doing so when the default provider handles it already.

I decided to do a proof-of-concept based on what I’d found out so far.  I knew that a custom list of sites could be provided by implementing a custom SiteProvider, and that this could be to the SitecoreSiteProvider to ensure it was called alongside the existing providers.  My proof-of-concept implementation simply looked for items using a custom SiteRoot template under a particular parent item, and created a blank Site object with that item’s name and populated the properties from the default “website” site, and then to refresh that collection after a publish:end event.  I then created a quick test page to list all the registered sites and their names so that I could check on my progress.

As you can likely see from the <site> nodes, there are a fair few properties that Sitecore supports in terms of site configuration (plus any custom ones you wish to add).  Even with the best content team in the world, giving editors access to all of these is probably not a great idea.  I went down the route of allowing editors to supply the host name, target host name, and a few other bits and bobs.  Everything else I decided to inherit from a related site in the configuration.

Implementing this lead to a few interesting discoveries:

  1. The name of the site is pulled from a property called name, not the name parameter that you give the Site constructor, so make sure you set it explicitly.
  2. If you get the Properties collection from a Site object, you are given a direct reference to that object’s property collection.  If you pass that collection in to another Site constructor, then the direct reference is saved to that object too. So, changing one changes the other – don’t do it.
  3. Don’t try to get a Site while you’re in the constructor of your SiteProvider, you’ll cause a StackOverflowException.
  4. Sitecore Rocks does not seem to pick up sites that come from a custom SiteProvider.

After a bit of trial and error, I got the list of sites showing what I was expecting.  I then tried creating a new site item and publishing it.  My breakpoints on the refresh logic in my SiteProvider were hit, and I could see my new site in the internal site collection.  I refreshed my test page, but was surprised to see that it wasn’t there.  This is what led me back to the SiteContextManager: as it holds its own list of sites, I needed to somehow tell it that this list had changed and make it re-fetch the new site list.  So I called SiteContextManager.Reset(), which clears the table.  On the next access, this table gets repopulated.  After that, I could add (and remove) sites by publishing them.

A second part of my proof-of-concept was to determine whether I could also leverage the CanEnter method on the SiteProvider to provide custom logic controlling whether a user would be able to enter the various sites that I was providing.  The short answer is: no.  The SiteProvider class provides a default (and virtual) implementation of CanEnter, which checks the site:enter permission on the site, but the SitecoreSiteProvider does not override this method to allow delegation to the registered child providers.  It would be possible if I elected not to use the SitecoreSiteProvider at all, and registered my one as the only one Sitecore should use, but in that case I would need to surface all the default sites as well as any added by the FXM, which as I previously mentioned is not an attractive option. I could also override the SitecoreSiteProvider, but that option was not terribly attractive either.

As this was all based on my findings from a proof-of-concept, where you are aiming to (as the name implies) prove certain ideas rather than write production code, parts of the code that do not need proving are often simplified – sometimes drastically so.  Certainly, if you follow the above then your solution will work, but it might not perform as well as it could and in some cases you may experience a few problems.  Here are a few extra things that you should probably consider:

  1. I subscribed to the publish:end events, because it’s quick and easy.  However, only a small fraction of publish events will be changes to the site manifest and so will result in a disproportionate number of re-builds of the site list and the site resolution table.  In cases where the website experiences high traffic, this may also cause performance problems.  You might want to look into a solution involving either filtering the events based on the items published, or subscribing to custom events.
  2. I didn’t make any allowance for the HtmlCacheClearer.  As the default implementation requires a list of site names in the configuration, this undermines the “no development” requirement.  To get around this, you’ll need to provide your own implementation of the clearer, but this is really quite trivial.
  3. Sitecore site resolution will resolve sites based on primacy: the first matching site is the one selected, regardless of whether there is a more specific match later.  If your sites overlap with the ones in your configuration at all, then you may experience some unusual behaviour that is difficult to track down.
  4. I also completely ignored the fact that, for sites of any reasonable size, authors will want to be able to preview the sites before actually publishing them; so they’ll likely need to be available on some other URL before they actually become available to the public.  You may want to add something to allow editors to switch a site from ‘preview’ to ‘active’ or some such.

I know that I haven’t provided much (any) code in this post, but I was reluctant to do this in case someone used it and then had issues because of it.  In my opinion, the information here should be enough to get you over the major hurdles that left me scratching my head for a while.  The meat of your provider will be down to your specific requirements around how the sites should work.  Hopefully people who stumble across this will find it helpful.