This is no doubt a working approach that can be utilized in real life scenarios. Still, it has the flaw that it defeats one of the main purposes of the SharePoint features in relation to SharePoint provisioning which is namely that features are supposed for the greater part to be reusable blocks of functionality. Of course a good share of the features that you create and use will be tailor-made for a specific site definition and won't be intended for being reused in other site definitions but the idea that you always should use tailor-made features doesn't sound good at all. For instance if you create a small custom feature that adds some web analytics support to your sites and intend for it to be reused in many site definitions, the natural approach would be to just extend these site definitions and add the feature to their CAML schema, rather than copy-pasting the feature's code to one of the existing features in these site definitions.
And just to add a small but very important detail to the idea of the SharePoint features as being small reusable blocks of functionality - this is the concept of feature properties. The feature properties allow you to customize the functionality that the feature adds to the target sites, so the outcome of the activation of a specific feature may in many cases depend on the specific context provided by the feature properties.
I have already mentioned several times in a few previous postings of mine that I look at the site definitions and web templates in SharePoint as mere groupings or sets of features. The availability of feature properties extends this idea somewhat, because the site definitions and web templates not only activate the set of features from their schema in the newly created sites but can use one and the same common features in different ways customizing the functionality that the latter add. Actually the ability to activate features with properties is legally available only declaratively in the onet.xml schema files of the site definitions and web templates, you can't do that using code and the object model (unless you use some trickery with reflection). I will mention one other way to achieve that in a little while.
Here we come close to the subject of the pros and cons of using site definitions and web templates as a provisioning approach in SharePoint. Many SharePoint professionals would even recommend against using these in favor of activating the desired features to the target sites manually. In my opinion the site definitions and web templates add a well justified level of abstraction on top of the SharePoint features, the two levels can be seen as the features being the basic blocks of functionality and the site definitions/web templates as sets of features (basic blocks) adding a level of parametrization (by means of feature properties) that creates a complete set of functionality (the sites that you create based on the site definition/web template).
Let's now go back to the main subject of this posting that I mentioned briefly in the beginning. The problem is that we have our site definitions and web templates, which are nice groupings of SharePoint features but SharePoint itself gives us no support for upgrading these "groupings", when we want that for existing sites. Of course, we can automate a lot of stuff in SharePoint 2010 (and with some limitations in SharePoint 2007) using PowerShell but still the best thing you will get out of it is to start activating the new features of your site definition to the old sites that you created based on it before you modified the site definition itself. And I wanted to find a solution to that which was neater and more self-contained. First I defined the task that I wanted to solve - and in the way that I defined it, it didn't look very hard to accomplish - I wanted to only activate the new features of the site definition/web template automatically to the sites that were created with this site definition/web template, before these new features were added to it. And what about the case when we want to make changes to an existing feature in the site definition. Well, it is simple - this is so to say not a problem of the site definition/web template, because there is a dedicated built-in mechanism for that in SharePoint 2010 - the feature upgrade capability. So if you want to modify an existing feature you should do that using the built-in approach for that, and this is basically outside the scope of the site definition/web template upgrade issue (with the concept of the site definition as a grouping, we only care of what features we have in the group, that is which new features we add, I didn't even consider the case of removing features from the site definition).
So, with the clear idea of what I wanted to do, I split the problem in several smaller tasks - the first three were these (the others will be covered in the second part of this posting):
- first we have to have a way to identify the web template which was used to create the target site - unfortunately SharePoint doesn't provide an OOB way to check this. For site templates this is an easy and trivial job with the help of the SPWeb.WebTemplate and SPWeb.Configuration properties.
- secondly, a way to get the current/latest version of the onet.xml file of the site definition or web template. We will need this to get the set of features and compare it to the set of already activated features in the target site or sites
- thirdly, a way to activate feature with feature properties. Remember that using code with the SharePoint object model we can only activate features without properties, the methods that allow activating features with properties are internal and can be accessed only with reflection (and I don't normally like to resort to reflection).
Let me now give you some quick and brief details about the way I solved these tasks.
Starting with the first one - just to mention, that it is a bit of a pity that SharePoint doesn't help us much here and we need a custom solution. The good news is that it is fairly easy to achieve that - the main idea comes from this excellent posting about web templates in SharePoint 2010 by Vesa Juvonen. The idea is to save the name of the web template in a the SPWeb.Properties property bag - whose purpose is exactly that - to store any custom data associated with the given SPWeb. Vesa achieves this by using the new PropertyBag feature element in one of the features of his sample web template (check the posting to see exactly how). I wanted to have this approach more readily reusable, so I wrapped it up in a reusable feature, which uses feature properties (no wonder). The feature has a custom feature receiver which is the aforementioned WTMarkFeatureReceiver class - you can check the code in the solution project. What it does is to save several important pieces of information related to the web template in the SPWeb.Properties properties bag property - these are the Guid of the parent feature of the web template, the name of the web template and the version of the parent feature - these are saved with the following keys in the property bag: WebTemplateFeatureId, WebTemplateName and WebTemplateFeatureVersion - basically all you need to identify the web template used to provision the target site.
And here is how you use the "marking" feature in the onet.xml file of your web template:
<Feature ID="48681fc1-97e0-4c9c-8061-c90890aeb64b"> <Properties xmlns="http://schemas.microsoft.com/sharepoint/"> <Property Key="WebTemplateFeatureId" Value="$SharePoint.Feature.Id$" /> <Property Key="WebTemplateName" Value="$SharePoint.ProjectItem.Name$" /> Properties> Feature>
Note that the feature ID is of a sample feature that I used on my development server. You need to create your own web scoped feature specifying the WTMarkFeatureReceiver class as the feature receiver class for it (the feature can be in both a Farm or a Sandbox solution, the feature receiver works in both cases). Except for the feature ID, which you will have replaced, you don't need to change any other bit in this snippet. It cleverly makes use of two Visual Studio 2010 replace tokens which allow you to easily pass the feature ID of the parent feature of the web template (in whose onet.xml you have placed the snippet) and also the name of the web template itself - which is actually the name of the feature element that contains the web template's onet.xml file. So, you can easily copy-paste this snippet to all your web templates without having to manually enter the parent features' Guid-s and the web templates' names. Pretty neat, isn't it.
As regards the third task (let me jump ahead a bit, I will come back later to the second task) - if you have followed my recent postings you may have noticed that I had a dedicated posting on this subject, which was titled "SharePoint 2010–activate features with feature properties". It achieves this by ... using the SharePoint deployment API (the classes in the Microsoft.SharePoint.Deployment namespace) - maybe something that you wouldn't normally associate with feature activation. Check the posting itself for more details. The bottom line is that this gives you a valid approach for activating features with properties without having to resort to reflection.
And finally ... to the second task, which is solved with the WebTemplateHelper class in the solution project. This class provides a public method to determine which is the parent web template of a give SharePoint site (or the parent site definition if a site definition and not a web template was used to create the target site). Note that this method works only for sites which were "marked" with the custom "marking" feature mentioned above. The class also contains the implementation for retrieving and parsing the onet.xml file of the parent site definition or web template (whichever was used for the specified SharePoint site). And it also contains a public method which actually "upgrades" the target site to the latest version of its parent site definition or web template (only activating the features that are present in the latest onet.xml and not still activated in the specified site):
public static void UpdateSiteToLatestOnet (SPWeb web, bool updateIfWebTemplateOnly, bool updateWebFeaturesOnly)
You can see above the declaration of the site definition/web template "upgrading" method for an existing site. You can see that it uses only three parameters - the first one is the target SPWeb instance, the second one - specifies whether to upgrade the site only in case it is a "marked" web template based site (the "non-marked" web template based sites will have their SPWeb.WebTemplate and SPWeb.Configuration properties set, so they will be indistinguishable from sites created with the web template's base site definition). The third parameter specifies whether you want to activate only the missing web scoped features or also the site scoped features when your site is the root site of its site collection.
The code of the class is very brief and easy to follow (at least I hope so), so you can check it for further details.
In the second part of this posting I will continue with explaining how this custom web template upgrading approach can be coupled with the built-in feature upgrade capabilities in SharePoint 2010. Stay tuned.