Sunday, September 11, 2011

SharePoint 2010–activate features with feature properties

There are two ways to achieve this in SharePoint – specify the feature properties in the ONET.XML file of your site definition or web template (the new SharePoint 2010 feature) or put the properties directly in the FEATURE.XML definition file of the feature. The latter is almost useless since it almost defeats the purpose of having the type of parameterization that you can achieve with feature properties – which is a very simple and powerful concept – use one and the same feature which can act differently in different contexts. As for the ONET.XML usage of feature properties – I think that this is probably the only thing that justifies the usage of site templates at least for me. In this sense site definitions are not merely a set or a grouping of features that should be activated to a site, provisioning some SharePoint artifacts, but rather a grouping of specifically configured features with this configurability being possible with the usage of feature properties. So despite the numerous issues that one may encounter with site definitions in SharePoint especially when it comes to updating the definition in existing sites, site definitions still have this important and pretty useful bit to offer.

The next question is can this same thing be achieved programmatically. In the SharePoint object model we have the SPFeatureCollection.Add method which allows you to activate features to a site collection or a site. It actually comes with three overloads but none of them allows you to provide specific feature properties that should be used for the feature activation. Of course if you are the “hacker” type of developer and use frequently the .NET Reflector utility you will probably already know that there is an internal method in the aforementioned SPFeatureCollection class that actually accepts a parameter in which you can specify the feature properties for activation. If you Google this you will find that there are actually several blog articles describing this technique. There is one big problem with reflection though – apart from the fact that it is not one of the best practices to use, there is always the risk to get things broken with the next service pack or version of SharePoint. In fact the notation of this exact method changed in SharePoint 2010 as compared to SharePoint 2007.

But is reflection the only option that we have here? It turns out that there is one other way to get this working and you can get an idea of how this is possible again by using the .NET Reflector tool (quite popular for SharePoint development actually). If you use the “analyze” command for the SPFeatureCollection.AddInternal method (the internal method, that I mentioned above) in the .NET Reflector you will see that it is used directly by an internal class called FeatureSerializer from the Microsoft.SharePoint.Deployment namespace. The latter is a namespace in the core Microsoft.SharePoint.dll which contains the SharePoint Deployment API and this is an important clue. If you have done export and import operations for SharePoint site collections or sites you’ve probably noticed that the imported site collections and sites are pretty much exact replicas of the source site collections and sites and that they contain all features with exactly the same feature properties activated as their source counterparts. It is obvious that the deployment API handles not only SharePoint artifacts like SharePoint lists and list items but for sites and site collections the associated features as well. This is where the SPFeatureCollection.AddInternal internal method comes into play invoked by the deployment API.

So, we know that if you have an export package of a site or a site collection (created with the STSADM utility or PowerShell) it will activate the very same set of features in a target site or site collection (or will create them if they don’t exist) but it will also import a ton of other things like all SharePoint lists and list items in them. We face two questions here – one is – is it possible to create an export package that can activate only features to a target site or site collection and the second is – is it legal and allowed by SharePoint standards to create Deployment API packages manually, i.e. without using the Deployment API itself. Starting with the second – this is perfectly OK, the schema of the XML files in a SharePoint deployment package is described officially in MSDN - http://msdn.microsoft.com/en-us/library/bb249989.aspx – the idea is that it can be used by third party providers who want to import content in SharePoint. If you check the article in MSDN you will see that there are eight deployment schema files that a deployment package should contain (although not all of them are actually required). For all deployment schema files there are XSD files which you can find in the 14/TEMPLATE/XML folder (all starting with “Deployment”) and which you can use to validate the XML of the deployment schema files that you create.

And to answer the first question – this can be achieved by creating three small deployment schema files that look like this:

ExportSettings.xml:

<ExportSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" SiteUrl="http://mysite/sites/2" FileLocation="" BaseFileName="" IncludeSecurity="None" IncludeVersions="LastMajor" ExportPublicSchema="true" ExportFrontEndFileStreams="true" ExportMethod="ExportAll" ExcludeDependencies="false" xmlns="urn:deployment-exportsettings-schema">
  <ExportObjects>
    <DeploymentObject Id="ea0d5873-cf3a-4015-b015-db74f39db27a" Type="Web" ParentId="00000000-0000-0000-0000-000000000000" Url="" ExcludeChildren="false" IncludeDescendants="None" />
  </ExportObjects>
</ExportSettings>

SystemData.xml:

<SystemData xmlns="urn:deployment-systemdata-schema">
  <SchemaVersion Version="14.0.0.0" Build="14.0.4762.1000" DatabaseVersion="3683" SiteVersion="0" ObjectsProcessed="1" />
  <ManifestFiles>
    <ManifestFile Name="Manifest.xml" />
  </ManifestFiles>
  <SystemObjects></SystemObjects>
  <RootWebOnlyLists />
</SystemData>

Manifest.xml:

<SPObjects xmlns="urn:deployment-manifest-schema">
  <SPObject Id="04a18cb4-39d8-4371-8a21-d11f2b54a333" ObjectType="SPFeature" ParentId="" ParentWebId="" xmlns="urn:deployment-manifest-schema">
    <Feature Id="04a18cb4-39d8-4371-8a21-d11f2b54a333" FeatureDefinitionName="WTSample_SiteScopedFeature" Version="0.0.0.0" IsUserSolutionFeature="false" Properties="&lt;Properties&gt;&#xD;&#xA;  &lt;Property Key=&quot;a&quot; Value=&quot;b&quot; /&gt;&#xD;&#xA;&lt;/Properties&gt;" />
  </SPObject>
  <SPObject Id="4edea5b8-7da5-46cc-b1c6-7a8ce4bcb899" ObjectType="SPFeature" ParentId="" ParentWebId="ea0d5873-cf3a-4015-b015-db74f39db27a" xmlns="urn:deployment-manifest-schema">
    <Feature Id="4edea5b8-7da5-46cc-b1c6-7a8ce4bcb899" FeatureDefinitionName="WTSample_WTMarkFeature" Version="1.0.0.0" IsUserSolutionFeature="false" WebId="ea0d5873-cf3a-4015-b015-db74f39db27a" Properties="&lt;Properties&gt;&#xD;&#xA;  &lt;Property Key=&quot;c&quot; Value=&quot;d&quot; /&gt;&#xD;&#xA;&lt;/Properties&gt;" />
  </SPObject>
</SPObjects>

You see that the important bits are in the “manifest.xml” schema file – you can see two “SPObject” elements in it with one “Feature” element directly beneath each – these correspond to two features – one “Site” and one “Web” scoped. Have a look at the “Properties” attribute of the “Feature” element – you can see there a familiarly looking “Properties” XML fragment, exactly the same as you have in a standard feature.xml file.

After you have these three schema deployment files you can simply place them in a disc folder and use the deployment API with the SPImport.Run method to import the package as any normal deployment package (note that you will have an uncompressed package as opposed to the compressed packages that STSADM and PowerShell normally output).

An important note here – you can activate only features that are not already activated on the target site or site collection in this manner. If the features are already activated they won’t get reactivated as you can do with the STSADM utility or PowerShell using the “-force” parameter.

If you want to use this approach, instead of formatting the deployment schema files yourselves you can use a handy helper method that I created which nicely wraps the whole logic of the deployment package creation and the running of the SPImort.Run method. You can download the sample solution containing this method from here.

You can also see the source code of the helper method here (it uses some additional private helpers and helper classes whose definitions you can see in the full source code from the above link):

        // provide the target SPWeb and one or several FeatureInfo instances each containing a SPFeatureDefinition and Dictionary<string, string> (for the feature properties) pair

        // you can provide features with Site and Web scopes, Site scoped features are valid only for a root SPWeb

        public void EnableFeature(SPWeb web, params FeatureInfo[] infos)

        {

            // check for valid feature scopes

            bool valid = infos.All(info => info.FeatureDefinition.Scope == SPFeatureScope.Web || (info.FeatureDefinition.Scope == SPFeatureScope.Site && web.IsRootWeb));

            if (!valid) throw new ArgumentException("Invalid feature scope.");

 

            // load the template SystemData.xml, ExportSettings.xml, Manifest.xml files which are stored as embedded resources

            XDocument systemDataDoc = XDocument.Parse(this.GetManifestResource("WTSample.Core.Xml.SystemData.xml"));

            XDocument exportSettingsDoc = XDocument.Parse(this.GetManifestResource("WTSample.Core.Xml.ExportSettings.xml"));

            XDocument manifestDoc = XDocument.Parse(this.GetManifestResource("WTSample.Core.Xml.Manifest.xml"));

 

            XNamespace ns = systemDataDoc.Root.Name.Namespace;

            XElement schemaVersionElement = systemDataDoc.Root.Element(ns + "SchemaVersion");

            // set the Build and DatabaseVersion attributes in the SystemData.xml

            schemaVersionElement.SetAttributeValue("Build", SPFarm.Local.BuildVersion.ToString());

            schemaVersionElement.SetAttributeValue("DatabaseVersion", web.Site.ContentDatabase.Version.ToString(CultureInfo.InvariantCulture));

 

            ns = exportSettingsDoc.Root.Name.Namespace;

            IXmlNamespaceResolver resolver = exportSettingsDoc.Root.CreateNavigator();

            // set the SiteUrl and Id attributes in the ExportSettings.xml

            exportSettingsDoc.Root.SetAttributeValue("SiteUrl", web.Url);

            XElement deploymentObject = exportSettingsDoc.Root.Descendants(ns + "DeploymentObject").FirstOrDefault();

            if (deploymentObject != null) deploymentObject.SetAttributeValue("Id", web.ID);

 

            ns = manifestDoc.Root.Name.Namespace;

            // extract the existing SPObject element from the Manifest.xml - we will use it as a template for adding the feature SPObject nodes

            XElement featureElement = manifestDoc.Root.Elements(ns + "SPObject").First();

            string featureElementStr = featureElement.ToString();

            featureElement.Remove();

 

            foreach (FeatureInfo info in infos)

            {

                // create a new feature SPObject element for every feature definition

                featureElement = XElement.Parse(featureElementStr);

                manifestDoc.Root.Add(featureElement);

 

                // set its Id and ParentWebId (the latter only for Web scoped features)

                featureElement.SetAttributeValue("Id", info.FeatureDefinition.Id.ToString());

                if (info.FeatureDefinition.Scope == SPFeatureScope.Web) featureElement.SetAttributeValue("ParentWebId", web.ID.ToString());

 

                // get the feature's name and the feature's parent solution type

                bool isUserSolution = false;

                string rootDirectory = info.FeatureDefinition.RootDirectory;

                int startIndex = rootDirectory.LastIndexOf(@"\") + 1;

                if (startIndex >= 0) { rootDirectory = rootDirectory.Substring(startIndex); }

                if (rootDirectory.Length == 0)

                {

                    isUserSolution = true;

                    rootDirectory = info.FeatureDefinition.GetTitle(CultureInfo.InvariantCulture) ?? "";

                }

 

                // create the child Feature element

                XElement contentElement = featureElement.Element(ns + "Feature");

                // set its Id, FeatureDefinitionName, Version, IsUserSolutionFeature and WebId attributes

                contentElement.SetAttributeValue("Id", info.FeatureDefinition.Id.ToString());

                contentElement.SetAttributeValue("FeatureDefinitionName", rootDirectory);

                contentElement.SetAttributeValue("Version", info.FeatureDefinition.Version.ToString(4));

                contentElement.SetAttributeValue("IsUserSolutionFeature", isUserSolution.ToString().ToLower());

                if (info.FeatureDefinition.Scope == SPFeatureScope.Web) contentElement.SetAttributeValue("WebId", web.ID.ToString());

                if (info.Properties != null && info.Properties.Count > 0)

                {

                    // create Properties XML fragment

                    XElement propertiesElement = new XElement("Properties", info.Properties.Select(kv => new XElement("Property", new XAttribute("Key", kv.Key), new XAttribute("Value", kv.Value))));

                    // and assign its value to the Properties attribute

                    contentElement.SetAttributeValue("Properties", propertiesElement.ToString());

                }

 

                if (string.IsNullOrEmpty(contentElement.Attribute("WebId").Value)) contentElement.Attribute("WebId").Remove();

                if (string.IsNullOrEmpty(contentElement.Attribute("Properties").Value)) contentElement.Attribute("Properties").Remove();

            }

            // create a temporary folder to store the export schema files

            string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

            Directory.CreateDirectory(tempFolder);

            try

            {

                // create the export schema files

                systemDataDoc.Save(Path.Combine(tempFolder, "SystemData.xml"));

                exportSettingsDoc.Save(Path.Combine(tempFolder, "ExportSettings.xml"));

                manifestDoc.Save(Path.Combine(tempFolder, "Manifest.xml"));

                // set up the SPImportSettings for the created export batch

                SPImportSettings importSettings = new SPImportSettings()

                {

                    FileCompression = false,

                    FileLocation = tempFolder,

                    SiteUrl = web.Site.Url,

                    WebUrl = web.Url

                };

                // run the SPImport.Run methods to import the batch

                SPImport import = new SPImport(importSettings);

                import.Run();

            }

            finally

            {

                Thread.Sleep(0);

                // clean up - delete the temporary folder

                try { Directory.Delete(tempFolder, true); }

                catch { }

            }

        }

The code contains pretty verbose comments, so that you can easily see how the three deployment schema XML files are formatted and how the input bits from the target SPWeb instance and the feature or features to be activated are fitted in XML elements or attributes inside them. The method accepts a SPWeb input parameter but you can use it for activating “Site” scoped features as well, as long as the SPWeb instance represents the root site of the target site collection.

One last thing to mention – this approach (at least its implementation) may seem a bit complicated and overly verbose as opposed to the one-liner solution when you use reflection. Another thing is that the deployment schema files can hardly be seen as a sort of a contract and we cannot expect that their format won’t get changed in the future in some next release of version of SharePoint. Still, one thing that I find important is that the very same approach of constructing manually deployment packages can as well be used to provision other types of SharePoint artifacts that we thought so far are possible only with XML in feature element files which I think can reveal the great potential of the SharePoint deployment API in this field.

12 comments:

  1. Wow what a great post.I am impressed from it.

    Thanks for more sharing..........



    zvnproperties

    ReplyDelete
  2. Hi Stefan its really a great post.I go thorough your post it's very systematic and very precise to understand.Thanks for sharing such a informative post with us
    Find Property

    ReplyDelete
  3. Informative article. I enjoyed reading the above information. I recently started learning about SharePoint and your blog is a great resource for me. Thanks, I will visit again.
    electronic signature for sharepoint

    ReplyDelete
  4. Share Market Investment made profitable by SHRISTOCKTIPS- Get NSE/BSE Tips via SMS and Yahoo Messenger. We give daily stock market tips for future and cash segments. As said last time we made a sell position in NIFTY around 6350-6400 we booked the profit at 6100 level. Our all paid & trial clients made a very handsome profit. Now for the coming week weSHARE, STOCK TIPSsuggest all the traders to make a buy position in NIFTY around 5950-6050 for the target of 6300-6400 with stoploss of 5800. Traders can also make a buy position in all NIFTY 50 stocks according to the level of NIFTY. Please trade with strict stoploss because there may be a high volatility in the market. For Further update keep following our website & you can also avail our two days trial to check our accuracy.
    Regards
    SHRISTOCKTIPS TEAM

    ReplyDelete
  5. Thanks for sharing such informative article on Load runner Automation testing tool. This load testing tool will provide most precise information about the quality of software.SharePoint Course in Chennai

    ReplyDelete
  6. We provide best intraday tips,100 accurate intraday trading tips and jackpot share market tips. Call now +91-8486224486
    share market tips
    intraday trading tips
    intraday tips

    ReplyDelete
  7. Definitely its a very precious information!! i can say that the blog author have excellent knowledge about that!!!

    ReplyDelete
  8. I have been following you for a couple of months now but this is my first time commenting on a blog post. Thank you for sharing your knowledge and experience with us. Keep up the good work. Already bookmarked for future reference.
    Hadoop Training in Chennai
    Hadoop Training
    Best Hadoop Training in Chennai
    Best Hadoop Training Institute in Chennai

    ReplyDelete
  9. If deliver of blood is gradual or much less, Fierce Male Enhancement or penile tissues are in terrible health and do no longer take in blood in sufficient quantity and also if male does now not get sufficiently aroused, erections are susceptible, sluggish and smaller. Why overnight oil is the first-class male enhancement oil i might advocate is due to the fact it.For more ==== >>>>>> http://www.healthsupreviews.com/fierce-male-enhancement/

    ReplyDelete
  10. There are people who erroneously anticipate that Penetrex Male Enhancement folks who undergo breast augmentation procedures are folks that are very vain and with money to burn. possibly this perception turned into planted of their minds because of the a lot-publicized sports of celebrities, including the plastic surgical procedures they undergo. For more ==== >>>>>> http://boostupmuscles.com/penetrex-male-enhancement/

    ReplyDelete
  11. The enhancement arrives in huge jumps. From Brick Muscle one specific particular day for the subsequent your events within the pool may additionally possibly drop 5 seconds according to 100, this is an tremendous improve in velocity. although, it might have taken going towards the pool for weeks on concluding with out possessing any seen enhancement across the clock.For more ==== >>>>>> http://www.strongtesterone.com/brick-muscle/

    ReplyDelete