SharePoint Dragons

Nikander & Margriet on SharePoint

Tag Archives: PnP

How to create an OfficeDev PnP Provisioning engine extensibility provider

The OfficeDev PNP provisioning engine (https://github.com/officedev/pnp-sites-core) is able to create an XML template based on a given SharePoint site and then use that XML template to create new sites. Ootb, the provisioning engine contains a considerable amount of stuff it can do as detailed in the PNP provisioning schema (https://github.com/OfficeDev/PnP-Provisioning-Schema/blob/master/ProvisioningSchema-2015-12.md). The provisioning engine allows you to define extension points that allow you to add custom steps to the provisioning process, and in this article we’ll explain how to do it. 

First of all, it’s quite possible to get the OfficeDevPNPCore15 (for SharePoint 2013 on prem) or OfficeDevPNPCore16 (for SharePoint Online) NuGet packages and use the provisioning engine like that. We’ve found that there’s tremendous value in being able to step through and debug source code, so unless you’ve got a tool that allows you to debug 3rd party assemblies on the fly within Visual Studio we far more prefer to add the OfficeDevPnP.Core project itself to our own provisioning tool and add a project reference to it so we have access to all source code. You can either obtain the source code by creating a project based on OfficeDevPnP.Core.dll (for example, via Telerik JustDecompile at http://www.telerik.com/products/decompiler.aspx) or get it directly by cloning it from the GitHub repository. This allows you to get much needed insights into the inner workings of the provisioning engine.

When building an extensibility provider, we’ve used 2 sources:

– The succinct article at http://www.erwinmcm.com/using-an-extensibility-provider-with-the-pnp-provisioning-engine/

– The PNP provisioning schema at https://github.com/OfficeDev/PnP-Provisioning-Schema/blob/master/ProvisioningSchema-2015-12.md

The process of building an extensibility process goes like this:

1. Create a custom provider class that implements the IProvisioningExtensibilityProvider.

2. Add a custom provider section to the XML template.

3. Implement the logic of the custom provider.

The C# code of a custom provider class looks like this:

using OfficeDevPnP.Core.Framework.Provisioning.Extensibility;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.SharePoint.Client;

using OfficeDevPnP.Core.Framework.Provisioning.Model;

using System.Xml.Linq;

namespace MyTest.Providers

{        

    public class CustomProvider : IProvisioningExtensibilityProvider

    {

        public void ProcessRequest(ClientContext ctx, ProvisioningTemplate template, string configurationData)

        {

        }

    }

}

Then, you need to adjust the XML generated by the provisioning engine and, if it’s not already there, add a custom <pnp:Providers> section. The <pnp:Providers> section needs to be placed within the <pnp:ProvisioningTemplate> section, and although the exact position doesn’t really seem to matter we place it pretty close to the end of the <pnp:ProvisioningTemplate> section. The <pnp:Providers> needs two attributes:

– Enabled, this is true or false and allows you to temporarily disable a custom provider.

– HandlerType, which expects the FQDN of the code that will be executed once the provisioning engine comes across this XML. It expects the following info: {Namespace + class name of extensibility provider}, {Assembly name}, {Version}, {Public key token, if the assembly is strong named}.

Within the <pnp:Providers> section, you can place anything you like as long as it’s valid XML. The following XML fragment is a minimal provisioning template that just creates a web property bag entry and executes a custom extensibility provider: 

<?xml version=”1.0″?>

<pnp:Provisioning xmlns:pnp=”http://schemas.dev.office.com/PnP/2015/12/ProvisioningSchema“>

  <pnp:Preferences Generator=”OfficeDevPnP.Core, Version=2.2.1603.0, Culture=neutral, PublicKeyToken=3751622786b357c2″ />

  <pnp:Templates ID=”CONTAINER-TEMPLATE-[GUID]”>

    <pnp:ProvisioningTemplate ID=”TEMPLATE-[GUID]” Version=”1″>            

      <pnp:Providers>

        <pnp:Provider Enabled=”true” HandlerType=”MyTest.Providers.CustomProvider, MyTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”>

          <pnp:Configuration>

            <MyProviderConfiguration id=”SampleConfig” xmlns=”http://schemas.loisandclark.eu/MyProviderConfiguration“>

              <ChildNode Attribute=”value”>TextContent</ChildNode>

            </MyProviderConfiguration>

          </pnp:Configuration>

        </pnp:Provider>

      </pnp:Providers>

      <pnp:PropertyBagEntries>

<pnp:PropertyBagEntry Key=”lois” Value=”clark” Overwrite=”true” />

      </pnp:PropertyBagEntries>  

    </pnp:ProvisioningTemplate>

  </pnp:Templates>    

</pnp:Provisioning>

You can’t exert fine grained control over the exact execution point in the provisioning pipeline, but all extensibility providers are executed sequentially and almost at the end of the provisioning pipeline. Currently, only WebSettings (containing settings for the current web site such as a SiteLogo and Master page URL, see https://github.com/OfficeDev/PnP-Provisioning-Schema/blob/master/ProvisioningSchema-2015-12.md#websettings) and PersistTemplateInfo (info about the provisioning template that gets persisted in a web property bag entry) are executed after your extensibility providers.

So what’s left to do is provide an implementation that of the ProcessRequest() method of the extensibility provider. It gets passed the SharePoint context and gets access to the XML in the custom <pnp:Provider> section. Your code will have to process that config info and use the current web to do something useful. The following code is a valid implementation:

using OfficeDevPnP.Core.Framework.Provisioning.Extensibility;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.SharePoint.Client;

using OfficeDevPnP.Core.Framework.Provisioning.Model;

using System.Xml.Linq;

namespace MyTest.Providers

{        

    public class CustomProvider : IProvisioningExtensibilityProvider

    {

        public void ProcessRequest(ClientContext ctx, ProvisioningTemplate template, string configurationData)

        {

            ClientContext clientContext = ctx;

            Web web = ctx.Web;

            string configurationXml = configurationData;

            XNamespace ns = “http://schemas.somecompany.com/MyProviderConfiguration“;

            XDocument doc = XDocument.Parse(configurationXml);

            string id = doc.Root.Attribute(“id”).Value;

            var childNode = doc.Root.Descendants(ns + “ChildNode”).FirstOrDefault();

            if (childNode != null)

            {

                string innerValue = childNode.Value;

                string attr = childNode.Attribute(“Attribute”).Value;

            }

        }

    }

}

Concluding, this means the extension points in the provisioning process aren’t exactly great, but it’s easy to do and at least you get the correct SharePoint context for free and have the opportunity to store all config info in a single place.

Advertisements