Over the last few months I’ve been asked numerous times and I’ve seen quite a few e-mail conversations on how to work with new Host Named Site Collections (HNSC) and Content Databases. In this post I will show you how I have solved the problem using the native API hooks in SharePoint.
Background
Host Named Site Collections are not a new thing in SharePoint, it has been with us for quite some time, but not been extensively used due to previous limitations (and there still are some). With SharePoint 2013 one strong recommendation is to consider using HNSC, in contrast to the traditional path based site collections. It gives you a couple of benefits in management, performance and is required for Apps to work properly. On the other hand it also has a couple of downsides such as not being able to create new Site Collections in the UI.
If you are using HNSC, a single content Web Application and you also use the same Web Application for Personal Sites (My Sites) then you might have stumbled upon the problem that your personal sites will be mixed in the content databases with the “normal” site collections. This is in many cases not a desirable solution, since you might have different SLA’s on personal sites and “normal” sites. Personal sites are created automatically when someone hits the My Site Host and you have no option to select the content database, compared to if you pre-create sites and use the –ContentDatabase parameter of the New-SPSite cmdlet. Fortunately there is a solution to this problem!
A custom Site Creation Provider
As you already might know SharePoint uses a very simple (stupid) algorithm by default to select the content database in which new Site Collections should be created – it takes the database with the least number of sites (somewhat simplified), not the one with least amount of data. This can actually, and possibly should, be overwritten with a custom algorithm. Such a custom algorithm can be implemented in something called a Site Creation Provider.
A custom Site Creation Provider implements the abstract class SPSiteCreationProvider and then implements its logic in the SelectContentDatabases() method. The SelectContentDatabases method returns a list of possible content database candidates – if it only returns one that one will be used, if it returns many the default algorithm will be used on that list of databases and if it returns zero or null then it will of course not be created anywhere.
The problem with the Site Creation Provider and its implementation is the limited number of options we have to do the selection of the content database. All we have is the set of content databases attached to the Web Application and a number of properties, defined in the SPSiteCreationParameters object passed into the method. We can get the the URL for the site to be created and but not the template used, so we have to be a bit clever when implementing it.
What we could do is to make sure that all our content databases follows a strict naming schema – for instance databases that should contain Personal Sites always has a name like this for instance: FarmX_WSS_Content_MySite_nnn. Secondly we can get the URL to the My Site host from the User Profile Service. Using this we can create an algorithm that says: that any Sites (remember we don’t know what template is used) that is created on or under the My Site Host URL will end up in databases containing the text “MySite”.
This is how a custom Site Creation Provider using this algorithm can be implemented:
public sealed class WictorsSiteCreationProvider : SPSiteCreationProvider
{
public override IEnumerable<SPContentDatabase> SelectContentDatabases(
SPSiteCreationParameters creationParameters,
IEnumerable<SPContentDatabase> contentDatabases)
{
SPServiceContext context = SPServiceContext.GetContext(
creationParameters.WebApplication.ServiceApplicationProxyGroup,
creationParameters.SiteSubscription == null ?
new SPSiteSubscriptionIdentifier(Guid.Empty) :
creationParameters.SiteSubscription.Id);
UserProfileManager upManager = new UserProfileManager(context);
List<SPContentDatabase> databases = new List<SPContentDatabase>();
if (new Uri(upManager.MySiteHostUrl).DnsSafeHost == creationParameters.Uri.DnsSafeHost)
{
// find the My Sites databases
SPContentDatabase smallestContentDb =
contentDatabases.
Where(contentDb => contentDb.Status == SPObjectStatus.Online).
Where(contentDb => contentDb.Name.Contains("MySite")).
OrderBy(contentDb => contentDb.DiskSizeRequired).First();
databases.Add(smallestContentDb);
}
if (databases.Count == 0)
{
// choose from all databases
SPContentDatabase smallestContentDb =
contentDatabases.
Where(contentDb => contentDb.Status == SPObjectStatus.Online).
Where(contentDb => !contentDb.Name.Contains("MySite")).
OrderBy(contentDb => contentDb.DiskSizeRequired).First();
databases.Add(smallestContentDb);
}
if (databases.Count == 0)
{
// Log some error!!!
}
return databases.AsEnumerable();
}
}
As you can see we use the site creation parameters to locate the UserProfileManager which can give us the My Site Host Url. In this case we expect the My Site host be a HNSC and all Personal Sites to be path based underneath that HNSC, so all we need to do is to compare the DnsSafeHost. If they match we retrieve all the databases containing the word “MySite” and add them to a list which will be returned by the method. If it does not find any databases or if the URL’s don’t match we retrieve the databases without the “MySite” in its name and return them.
For the sharp eyed one you will notice that in this implementation we do not return a list of content databases, but instead always one – and we select only the one with the least amount of used space. Very useful if you want to keep a similar size of all your content databases – if not just remove the OrderBy() and First() statements.
Registering the Site Provider
Of course you need to build this as a full trust solution – any other way just don’t work. To register it with your farm you can either do it in PowerShell or using a Farm scoped feature with a Feature Receiver. The registration could look something like this:
SPWebService contentService = SPWebService.ContentService;
contentService.SiteCreationProvider = new WictorsSiteCreationProvider();
contentService.Update();
To unregister it you just set the value to null instead.
Summary
There you have it! It wasn’t that hard! Just a few lines of code and you have much more control over your site collections and content databases in the Host Named Site Collection scenario. Just remember to have proper error handling and logging in your Site Creation Provider, any errors or exceptions will guarantee that you don’t have any site collections at all.