LibrarySites.Banner

Sitecore Dynamic Site Provider

In this post I’ll show you how to create Site Provider that resolves sites dynamically. Just imagine that you want to use one Sitecore instance to support multisite and each item is added under \sitecore\contents corresponds for a new site dynamically.

Multisite

But first of all I would like to describe how resolving sites works in Sitecore by default. By default all sites should be configured in the web config in node sitecore/sites.

1.  <sites>
2.      <site name="shell" virtualFolder="/sitecore/shell" physicalFolder="/sitecore/shell" rootPath="/sitecore/content" startItem="/home" language="en" database="core" domain="sitecore" loginPage="/sitecore/login" content="master" contentStartItem="/Home" enableWorkflow="true" enableAnalytics="false" analyticsDefinitions="content" xmlControlPage="/sitecore/shell/default.aspx" browserTitle="Sitecore" htmlCacheSize="2MB" registryCacheSize="3MB" viewStateCacheSize="200KB" xslCacheSize="5MB" disableBrowserCaching="true" />
3.
4.</sites>

As you can see, site has many attribute properties. Here you can find more details about each of the property:
http://sdn.sitecore.net/Articles/Administration/Configuring%20Multiple%20Sites/Adding%20New%20Site/site%20Attribute%20Properties.aspx

The sites are resolved by site provider configured in the web config in node sitecore/siteManager:

1.<siteManager defaultProvider="config">
2.  <providers>
3.    <clear />
4.    <add name="config" type="Sitecore.Sites.ConfigSiteProvider, Sitecore.Kernel" siteList="sites" checkSecurity="false" />
5.  </providers>
6.</siteManager>

All Site Providers must be inherited from Sitecore.Sites.SiteProvider. Its code is presented here:

01.public abstract class SiteProvider : ProviderBase
02.  {
03.    private bool _checkSecurity = true;
04. 
05.    protected SiteProvider()
06.    {
07.    }
08. 
09.    public virtual bool CanEnter(Site site, Account account)
10.    {
11.      Assert.ArgumentNotNull(site, "site");
12.      Assert.ArgumentNotNull(account, "account");
13.      if (this.CheckSecurity && (AuthorizationManager.GetAccess(site, account, AccessRight.SiteEnter).Permission == AccessPermission.Deny))
14.      {
15.        return false;
16.      }
17.      return true;
18.    }
19. 
20.    public abstract Site GetSite(string siteName);
21.    public abstract SiteCollection GetSites();
22.    public override void Initialize(string name, NameValueCollection config)
23.    {
24.      base.Initialize(name, config);
25.      this._checkSecurity = config["checkSecurity"] != "false";
26.    }
27. 
28.    public virtual bool CheckSecurity
29.    {
30.      get
31.      {
32.        return this._checkSecurity;
33.      }
34.    }
35.  }

As you can see, the provider has two abstract methods GetSite and GetSites.

By default Sitecore.Sites.ConfigSiteProvider is used to resolve Sitecore sites. The provider overrides methods “GetSite” and “GetSites”. Methods initialize sites if sites aren’t initialized and return the specific site by name or collection of all sites accordingly. Site initialization is implemented in the method InitializeSites that is called from “GetSite” and “GetSites”. It reads the node “sitecore/sites” in the web.config file and generates a collection of site instances. That’s why your new site should be configured in the config.file to be resolved by the default site provider.

This solution has limitations. If you edit the config file, the application pool will restart and session state will be lost.

So, if you need to add sites dynamically you should create your custom Site Provider.

In the next code example you can see how to create a Site Provider that resolves sites from items. Item are based on the specific template “ExternalSite”. In this screenshot you can see the required site parameters are entered in the item's fields.

Because some standard sites are configured in the web config and new sites in items, they should be retrieved in different ways. To retrieve standard sites, our provider should call Sitecore.Sites.ConfigSiteProvider methods.

Site parameters

001.public class DynamicSiteProvider : SiteProvider
002.  {
003.    private object _lock = new object();
004.    private SafeDictionary<string, Site> _siteDictionary;
005.    private SiteCollection _sites;
006.    public string Database { get; set; }
007.    protected readonly SiteProvider Implementation;
008. 
009.    public DynamicSiteProvider(SiteProvider implementation)
010.    {
011.      Assert.ArgumentNotNull(implementation, "implementation");
012.      this.Implementation = implementation;
013.    }
014. 
015.    public override Site GetSite(string siteName)
016.    {
017.      Assert.ArgumentNotNullOrEmpty(siteName, "siteName");
018.      this.InitializeSites();
019.      Site value;
020.      if (!this._siteDictionary.TryGetValue(siteName, out value))
021.      {
022.        value = this.Implementation.GetSite(siteName);
023.      }
024. 
025.      return value;
026.    }
027. 
028.    public override SiteCollection GetSites()
029.    {
030.      this.InitializeSites();
031.      var sites = new SiteCollection();
032.      sites.AddRange(this._sites);
033.      sites.AddRange(this.Implementation.GetSites());
034.      return sites;
035.    }
036.      
037.    public override void Initialize(string name, NameValueCollection config)
038.    {
039.      base.Initialize(name, config);
040.      this.Implementation.Initialize(name, config);
041.    }
042. 
043.    private void InitializeSites()
044.    {
045.      if (this._siteDictionary == null)
046.      {
047.        lock (this._lock)
048.        {
049.          if (this._siteDictionary == null)
050.          {
051.            var sites = new SiteCollection();
052.            var siteDictionary = new SafeDictionary<string, Site>(StringComparer.OrdinalIgnoreCase);
053. 
054. 
055.            Database database = Factory.GetDatabase(this.Database, false);
056.            if (database == null)
057.            {
058.              return;
059.            }
060. 
061.            foreach (Item siteItem in this.GetSiteDefinitionItems(database))
062.            {
063.              Site site = this.ResolveSite(siteItem);
064. 
065.              if (site != null)
066.              {
067.                siteDictionary[site.Name] = site;
068.                sites.Add(site);
069.              }
070.            }
071. 
072.            this._sites = sites;
073.            this._siteDictionary = siteDictionary;
074.          }
075.        }
076.      }
077.    }
078. 
079.    // get all site items under the root folder
080.    public virtual List<Item> GetSiteDefinitionItems(Database db)
081.    {
082.      if (db == null)
083.      {
084.        Log.Warn("GetSiteDefinitionItem failed. Database argument is null", this);
085.        return null;
086.      }
087. 
088.      var sites = new List<Item>();
089. 
090.      Item root = db.GetItem(Sitecore.ItemIDs.ContentRoot);
091.      if (root != null)
092.      {
093.        sites.AddRange(root.GetChildren(ChildListOptions.SkipSorting).Where(siteItem => siteItem.TemplateID == TemplateIDs.ExternalSite));
094.      }
095. 
096.      return sites;
097.    }
098. 
099.    public virtual Site ResolveSite(Item item)
100.    {
101.      Assert.ArgumentNotNull(item, "item");
102.      if (this.IsValidSiteNameBySitecore(item[FieldIDs.Site.SiteName]))
103.      {
104.        var properties = this.GetProperties(item);
105. 
106.        return new Site(item[FieldIDs.Site.SiteName], properties).SetSiteItemId(item.ID);
107.      }
108. 
109.      return null;
110.    }
111. 
112.    public virtual StringDictionary GetProperties(Item item)
113.    {
114.      var parameters = new StringDictionary();
115.      NameValueListField field2 = item.Fields[FieldIDs.SiteResolver.SiteParameters];
116. 
117.      var collection = field2.NameValues;
118. 
119.      foreach (string key in collection)
120.      {
121.        parameters.Add(key, HttpUtility.UrlDecode(collection[key]));
122.      }
123. 
124.      return parameters;
125.    }
126. 
127.    [MethodImpl(MethodImplOptions.NoOptimization)]
128.    private bool IsValidSiteNameBySitecore(string siteName)
129.    {
130.      try
131.      {
132.        //workaround for invalid site names
133.        string tmp = Factory.CreateObject("cacheSizes/sites/" + siteName, false) as string;
134.        return true;
135.      }
136.      catch (Exception)
137.      {
138.        return false;
139.      }
140.    }
141.  }

As it was mentioned before, your provider should be inherited from the Sitecore.Sites.SiteProvider. The methods “GetSite” and “GetSites” should be overridden. In this example the method “InitializeSites” selects all site definition items by template.

For each site definition item the site parameter fields are read. A Site instance is created using the parameters, The site instance is added to the SiteCollection _sites and SafeDictionary _siteDictionary.

The field “_sites” is used by method “GetSites”. “_siteDictionary” – by “GetSite”. The method GetSites retrieves all sites from items and then call Sitecore.Sites.ConfigSiteProvider to add sites configured in the configuration file. The method GetSite tries to get site configured in items. If the site isn't found it uses Sitecore.Sites.ConfigSiteProvider to get site configured in config file. The method “GetProperties” reads all site properties from the site item field “Site Parameters”.

Dynamic Site Provider configuration is presented here:

01.<siteManager>
02.  <patch:attribute name="defaultProvider">dynamic</patch:attribute>
03.  <providers>
04.    <add name="dynamic" type="Sitecore.Sites.DynamicSiteProvider, Sitecore.Example" siteList="sites" checkSecurity="false">
05.      <param desc="impl" type="Sitecore.Sites.ConfigSiteProvider, Sitecore.Kernel"/>
06.      <database>web</database>
07.    </add>
08.  </providers>
09.</siteManager>

That is all! In this post you got information how to resolve site with standard Sitecore behavior (resolving via sitecore site node in Web.config) and Dynamic site provider that resolves sites from items(doesn't require restarting of application pool).

  • @Mrunal: Well yes, bindings are needed of course, so a restart is inevitable. I always try to set up the bindings as wide as possible (with wildcards) to prevent having to modify them.