Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / SharePoint

SharePoint Custom Provisioning Provider

4.67/5 (5 votes)
21 Apr 2011CPOL8 min read 46.6K   554  
A discussion of site creation methods and using a custom Provisioning Provider

Introduction

In SharePoint, you have multiple ways to create sites with each method having its own pros and cons. A custom site provision provider is considered by many to be the best approach to creating custom SharePoint sites. In this brief article, I'll demonstrate how to create and use a custom site provisioning provider.

Background

The samples presented in this article use SharePoint Server 2010 although the techniques will also work for Microsoft Office SharePoint Server 2007.

Multiple Ways to Create a Site

There are multiple ways to create a custom site definition in SharePoint; Site Template, Site Definition and Site Provisioning Provider.

Site Template

Microsoft SharePoint is very configurable by end users with the proper rights and permissions; this is what it was designed for. Users may spend a considerable amount of time configuring a site with lists, pages and views and may want to replicate this across other environments. This is where Site Templates can be useful.

To create a Site Template, go to the Site Actions -> Site Settings and under the Site Actions heading, click on Save site as template link.

image1.PNG

The displayed page will ask for a filename, template name and description, and also whether to include content. After filling in this information and clicking the OK button, SharePoint will package this site as a WSP file with appropriate configuration files and place it in the Solutions Gallery. From here, it can be used to create new sites within the Site Collection. To use the template in other Site Collections, you must go to the Solutions Gallery and save the template to an external location, your hard drive, then, in another Site Collections upload and active this template in the Solution Gallery there.

All of the above processes can be accomplished without the intervention of developers and Farm Administrators. However, there are drawbacks to this approach.

Using a Site Template is not really a reusable solution. Yes, it can be saved and uploaded to different Site Collections but it may be dependent on features or security that are not present in another Site Collection. When using the Include Content option, you are also limited to 10MB, which may be fine if the site was created for structure only, but if it used a lot of lists with items required for functionality, you are out of luck. Item level security is also not supported with a Site Template so after creating a new site from the template, all security may need to be reset.

The Site Template is packaged as a WSP, which, using Visual Studio 2010, can be imported into a new project. However, it brings a considerable amount detritus with it.

image2.PNG

Although you can select or unselect elements that you want, some do have dependencies and you will need to import them for the project to be complete.

image3.PNG

Site Definition

Site Definitions are what SharePoint is based on and are used when creating a site from the built-in templates. A custom Site Definition is also created when saving a Site Template as described above. Since there are many resources that cover SharePoint Site Definitions, I won't go into detail, only hitting some high points for this discussion.

A Site Definition is comprised of an Onet.xml file; I have included a basic version below. More information about this file can be found here.

XML
<?xml version="1.0" encoding="utf-8"?>
<Project Title="SiteDefinitionProject1" 
         Revision="2" 
         ListDir="" 
         xmlns:ows="Microsoft SharePoint" 
         xmlns="http://schemas.microsoft.com/sharepoint/">
  <NavBars>
  </NavBars>
  <Configurations>
    <Configuration ID="0" Name="SiteDefinitionProject1">
      <Lists/>
      <SiteFeatures>
      </SiteFeatures>
      <WebFeatures>
      </WebFeatures>
      <Modules>
        <Module Name="DefaultBlank" />
      </Modules>
    </Configuration>
  </Configurations>
  <Modules>
    <Module Name="DefaultBlank" Url="" Path="">
      <File Url="default.aspx">
      </File>
    </Module>
  </Modules>
</Project>

As you can see, quite a lot of information can be included in the Onet.xml to define how the site is created and what features it will use. You can define list templates used by the site, navigation elements, and files, such as site pages. The file also describes any features that need to be activated at the site and web levels.

Although this method has been used for years with SharePoint and the built-in site templates use this method, it does have one significant drawback. Any changes to the site definition will affect all sites created with that definition. To illustrate this, build and deploy the CPSiteDef solution that has been provided in the code download with this article and add a new site using this definition. After the site case has been created, the home page should look like this:

image4.PNG

Now, in the Visual Studio project, expand the Site Definition module and open the default.aspx page and add, or modify, any text you would like. When you redeploy the solution and refresh the page, you should see the changes that were made.

image5.PNG

You did not need to create another site using the template in order to see this change, it would be updated on all automatically. Obviously, this can have some very undesirable and unexpected effects.

Site Provisioning Provider

The new recommended method for providing a mechanism to create a new site is to use a custom Site Provisioning Provider.

The first thing we need of course is to create a new Empty SharePoint 2010 project in Visual Studio. Then, add a code file to this project and derive it from SPWebProvisioningProvider. This base class has one method that needs to be implemented, Provision:

C#
public class CPSiteProvider : SPWebProvisioningProvider
{
    public override void Provision(SPWebProvisioningProperties props)
    {
        throw new NotImplementedException();
    }
}

Of course, whether using a Site Definition or a custom provisioning provider such as this, there must be a webtemp* file so SharePoint can recognize and make the template available. More information about the details of this file can be found here. For this discussion however, the key points are the attributes ProvisioningAssembly, ProvisioningClass and ProvisionData:

XML
<?xml version="1.0" encoding="utf-8" ?>
<Templates>
  <Template Name="CPSiteProvider" ID="25001">
    <Configuration
		ID="0"
		Title="Code Project Site Provisioning Provider"
		Hidden="false"
		ImageUrl="/_layouts/images/CPSiteProvider/CP_Logo.gif"
		Description="A demonstration or using a custom Site 
				Provisioning Provider"
		DisplayCategory="Code Project"
		ProvisionAssembly="$SharePoint.Project.AssemblyFullName$"
		ProvisionClass="CPSiteProvider.ProvisioningProvider"
		ProvisionData="TEMPLATE\FEATURES\
		CPSiteProvider_SiteData\ProvisioningData\CPSiteProvider.xml"
		SubWebOnly="TRUE">
    </Configuration>
  </Template>
</Templates>

For ProvisioningAssembly, we'll just make use of the built-in Visual Studio macro which will expand to the proper four-part entry when the project is built. The ProvisioningClass is the fully qualified name of the class that implements SPWebProvisioningProvider, which in our case is ProvisioningProvider. The ProvisionData attribute is used to specify any extra data that is need by the Provisioning Provider. In this case I am using the path, relative to the SharePoint root folder, of an XML file that will contain additional information.

The Provisioning Provider is a blank slate and will not create anything unless it is told to. With that in mind, the first we'll do is to use the ApplyWebTemplate method of the SPWeb object to create a blank site to start with. As you can see, web is one of the properties available from the SPWebProvisioningProperties property passed into the Provision method. The other property is Data which will contain the value of the ProvisionData attribute specified in the webtemp file which we'll make use of next.

C#
private const string SITE_TEMPLATE = "STS#1";

public override void Provision(SPWebProvisioningProperties props)
{
    // Create a blank site to begin from
    props.Web.ApplyWebTemplate(SITE_TEMPLATE);
}

Now that the basic site has been created, we'll add the features required to configure our site appropriately.

C#
public override void Provision(SPWebProvisioningProperties props)
{
    // Create a blank site to begin from
    props.Web.ApplyWebTemplate(SITE_TEMPLATE);

    // Save this so it is available in other methods
    Properties = props;

    SPSecurity.CodeToRunElevated code = 
		new SPSecurity.CodeToRunElevated(CreateSite);
    SPSecurity.RunWithElevatedPrivileges(code);
}

private void CreateSite()
{
    using(SPSite site = new SPSite(Properties.Web.Site.ID))
    {
        using(SPWeb web = site.OpenWeb(Properties.Web.ID))
        {
            // Add specified features to this site
            AddSiteFeatures(site);

            // Add specified features to this web
            AddWebFeatures(web);

            // Add new default page
            AddDefaultPage(web);
        }
    }
}

Since site could be created by anyone, we need to ensure the proper security privileges are available, this is where SPSecurity.RunWithElevatedPrivileges is useful. This will have any code that is run in its context to be executed with the credentials of the Web Application AppPool. Although you could use an anonymous delegate with this method, for readability and maintainability, I'll use create a SPSecurity.CodeToRunElevated object and assign a method to run. One minor issue with this is the delegate for this method takes no parameters. To be able to have access to the SPWebProvisioningProperties, I'll assign it to private property.

In the AddSiteFeatures method, the first thing to do it is access the XML file that contains the features we want to enable in this site. Since the path was relative, we need to get to the SharePoint root folder. Although it is typically %Program Files%\Common Files\Microsoft Shared\Web Server Extensions\14 as good developers, we can't rely on this and should specifically query the object model for the correct path. This is done in the DataFile property using the SPUtility.GetGenericSetupPath method.

C#
private void AddSiteFeatures(SPSite site)
{
    List<xelement> features = (from f in DataFile.Elements("SiteFeatures")
                        .Elements("Feature")
                            select f).ToList();

    foreach(XElement feature in features)
    {
        // Make sure the feature hasn't already been activated
        SPFeature f = site.Features[new Guid(feature.Attribute("ID").Value)];
        if(f == null)
        {
            site.Features.Add(new Guid(feature.Attribute("ID").Value));
        }
    }
}

private XElement DataFile
{
    get
    {
        XElement featuresXml = null;
        if(Properties != null)
        {
            // Construct the path from the SharePoint root folder to
            // the file specified in the webtemp
            string path = SPUtility.GetGenericSetupPath
			(Path.GetDirectoryName(Properties.Data));
            path = Path.Combine(path, Path.GetFileName(Properties.Data));

            // Load the xml file
            featuresXml = XElement.Load(path);
        }

        return featuresXml;
    }
}
</xelement>

After getting the XML file, it is simply a matter of adding the specified features to either the Site or Web. I have used the same features as the Site Definition project to make it comparable, but you can add any other features that are required. Any lists or other items can also be added to the site at this point.

The benefit to this method is the site is not created until runtime and is independent of files in the file system unlike the Site Definition method. To get an apples to apples comparison, we need to include the default.aspx page.

C#
private void AddDefaultPage(SPWeb web)
{
    string file = (from f in DataFile.Elements("DefaultPage")
                    select f).Single().Attribute("file").Value;

    string filePath = FeaturePath + "\\" + file;
    TextReader reader = new StreamReader(filePath);

    MemoryStream outStream = new MemoryStream();
    StreamWriter writer = new StreamWriter(outStream);

    writer.Write(reader.ReadToEnd());
    writer.Flush();

    web.Files.Add("Default.aspx", outStream, true);
}

As you see here, we extract the path to the Default.aspx page that will be used from the provisioning file, then read the contents of the file into a Stream and write it back into a StreamWriter. Using true in the SPWeb.Files.Add method will allow the existing Default.aspx page to be overwritten by our new file. Since this causes the page to be added to the content database, it is now unghosted and thus independent of the file in the file system.

You can test this process in the same way as before. Create a new site based on this template. Update the default.aspx page in the Visual Studio project and redeploy the solution. If you refresh the page, you will not see any changes. If you create a new site, the modified default.aspx page will then be used.

Conclusion

This article has attempted to explain the different methods to create and apply site template within SharePoint and some of the pros and cons for each. Hopefully this will give the reader a good understanding to choose the most appropriate method to use for a project.

History

  • Initial posting: 4/21/11

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)