Recently I've been experimenting with the way Sitecore detects what sites are included in an instance.
When Sitecore starts up, by default it looks in the “Sites” node of the web.config
and builds up a list of in-memory Site objects which are kept for the lifetime
of the application. I was intrigued to see that, like many Sitecore subsystems, a provider approach is used. This means we’re able to replace the default behaviour
with one of our own.
EDIT: Weirdly, Sitecore's own Integration Solutions Team published a blog article on this exact subject a few hours before me. I can only conclude that this is no mere "coincidence". They clearly hacked my laptop and stole my idea. They will be hearing from my lawyers...
It occurred to me that many of the attributes that you add to Site definition elements in the web.config could be inferred so long you can identify the “Site Root” item. You don’t need to specify most of the information explicitly because it’s relative to that one item. For example, when I create a new site in the content tree, the home page is typically a child item of the root item.
I decompiled Sitecore’s default ConfigSiteProvider, and after some
investigation I came up the idea of finding all the items with a "Site root" template, using them to generate Sitecore site objects. I decided that it would be useful to also include the base behaviour of the ConfigSiteProvider, so after getting our root items, we should also get data from the config file
EDIT: Weirdly, Sitecore's own Integration Solutions Team published a blog article on this exact subject a few hours before me. I can only conclude that this is no mere "coincidence". They clearly hacked my laptop and stole my idea. They will be hearing from my lawyers...
It occurred to me that many of the attributes that you add to Site definition elements in the web.config could be inferred so long you can identify the “Site Root” item. You don’t need to specify most of the information explicitly because it’s relative to that one item. For example, when I create a new site in the content tree, the home page is typically a child item of the root item.
I wondered if it was possible to dispense with “sites”
config node altogether, and just use data obtained from Sitecore items. If this
is possible, that means we could give more freedom to Sitecore users to
create new sites themselves without involvement from a developer.
class CustomSiteProvider : ConfigSiteProvider { private object _lock = new object(); private SiteCollection _sites; private SafeDictionary_siteDictionary; public override Site GetSite(string siteName) { InitializeSites(); // first we look in our custom collection of sites if (_siteDictionary.ContainsKey(siteName)) return _siteDictionary[siteName]; // and fallback to the sites from config if we didn't find one return base.GetSite(siteName); } public override SiteCollection GetSites() { InitializeSites(); // we get our custom collection of sites var allSites = new SiteCollection(); allSites.AddRange(_sites); // then append the sites obtained from config var configSiteDefintitions = base.GetSites(); allSites.AddRange(configSiteDefintitions); return allSites; } private void InitializeSites() { // check if we've already built our site dictionary if (_siteDictionary != null) return; // if not, then get on with it lock (_lock) { if (_siteDictionary != null) return; _sites = GetSitesFromSiteRootItems(); _siteDictionary = GetSiteDictionary(_sites); } } private SiteCollection GetSitesFromSiteRootItems() { // This is where you get a list of items based on the "Root Item" // template. In my test environment I used a Lucene search, but you // can do this in whatever way you prefer. throw new NotImplementedException(); // Then for each item, we generate a site. var collection = new SiteCollection(); foreach(item in rootItems) { var site = GenerateSite(item); collection.Add(site); } return collection; } private SafeDictionary GetSiteDictionary(SiteCollection sites) { // This provides a way of looking up a site by name. var dict = new SafeDictionary (StringComparer.OrdinalIgnoreCase); foreach (Site site in sites) { if (_siteDictionary.ContainsKey(site.Name)) continue; _siteDictionary.Add(site.Name, site); } return dict; } private Site GenerateSite(Item item) { // We populate the 'properties' dictionary with the relavant // information. This would normally be the attributes in your // config site definition, but we're using the site root Item // as the source. Sitecore.Diagnostics.Assert.ArgumentNotNull(item, "item"); var site = new Site(item.Name); site.Properties.Add("virtualFolder", "/" + item.Name); site.Properties.Add("physicalFolder", "/" + item.Name); site.Properties.Add("rootPath", item.Paths.FullPath); var startItem = item.Children. FirstOrDefault(child => child.TemplateID.ToString() == "{my-homepage-template-id}"); site.Properties.Add("startItem", "/" + startItem.Name); site.Properties.Add("name", item.Name); // This information can't be inferred, so we get // it from fields on the root item. site.Properties.Add("hostName", item["Host name"]); site.Properties.Add("targetHostName", item["Target host name"]); site.Properties.Add("database", item["database"]); site.Properties.Add("domain", item["domain"]); site.Properties.Add("cacheHtml", item["Cache html"]); site.Properties.Add("htmlCacheSize", item["Html cache size"]); site.Properties.Add("registryCacheSie", item["Registry cache size"]); site.Properties.Add("viewStateCacheSie", item["View state cache size"]); site.Properties.Add("xslCacheSize", item["Xsl cache size"]); site.Properties.Add("enableAnalytics", item["Enable analytics"]); site.Properties.Add("allowDebug", item["Allow debug"]); site.Properties.Add("allowPreview", item["Allow preview"]); site.Properties.Add("enableWebEdit", item["Enable web edit"]); site.Properties.Add("enableDebugger", item["Enable debugger"]); site.Properties.Add("disableClientData", item["Disable client data"]); return site; } }
Did it work?
Well, kind of. When using this approach, the sites do get built up as expected
and the resulting URL’s do resolve correctly. However, as far as I can tell
Sitecore only goes through this process at the very beginning of the
application. If you add a new site root item, the only way to register it is to
restart the application (which sort of defeats the object). I’d be interested
to see if any Sitecore devs out there can come up with a solution that allows
me to register a new site while the application is still running.
Another thing to note is that the provider needs to be set
up in a way that conforms to a specific usage. You would have to adapt the code
to meet the specific requirements of each individual implementation.
What do you think? Are there any other good use cases for
creating a custom site provider?
Comments
Post a Comment