Skip to main content

Mark Stokes

Go Search
Home
People & Blogs
Twitter
Wiki
Events
Forums
Resources
Jobs
Support
Account
SP2010
  

Sharepoint Studio > People & Blogs > Mark Stokes > Posts > Threaded Code Clash when mixing SPWebProvisioningProvider and Stapled Feature Receiver
Threaded Code Clash when mixing SPWebProvisioningProvider and Stapled Feature Receiver
I came across an interesting issue recently that only has limited documentation on the web.
 
I am creating my site templates using the methods descirbed here:
 
So, using this method, I create a blank Site Definition and 2 Web Templates, one visible and one hidden.  I define a WPP (SPWebProvisioningProvider) in the hidden template and that piece of code applies the hidden template and runs some code against that new web to configure it.  This keeps us supported so that we don't have to change the Site Definition post deployment to change the site template, we can just update the WPP assembly.
 
WebtempCustom.xml

<Templates xmlns:ows="Microsoft SharePoint">
  <
Template Name="FakeCustomTeamSite" ID="10001">
    <
Configuration ID="0" Title="Team Site" Hidden="False"
ImageUrl="../images/stsprev.png"
Description="A Custom Team Site"
DisplayCategory="Custom"
ProvisionAssembly="Custom.SharePoint.SiteTemplates.TeamSite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cb281607c9ad5cff"
ProvisionClass="Custom.SharePoint.SiteTemplates.TeamSite.WebProvisioningProvider">
    </
Configuration>
  </
Template>
  <
Template Name="CustomTeamSite" ID="10002">
    <
Configuration ID="0" Title="Hidden Custom Team Site" Hidden="True" ImageUrl="../images/stsprev.png" Description="A Custom Team Site" DisplayCategory="Custom">
    </
Configuration>
  </
Template>
</
Templates>

WebProvisioningProvider.cs
 

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace Custom.SharePoint.SiteTemplates.TeamSite
{
 
class WebProvisioningProvider : SPWebProvisioningProvider
 
{
   
public override void Provision(SPWebProvisioningProperties props)
   
{
     
SPWeb web = props.Web;
     
web.ApplyWebTemplate("CUSTOMTEAMSITE#0"); //apply a template to the web
     
web.Description = "Created from a privisioning class.";
     
web.Update();
   
}
 
}
}

Receiver code for Stapled Feature

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
  using (SPWeb Web = (SPWeb)properties.Feature.Parent)
  {
   
#region Create Calendar List
   
ExceptionBlock.LogSection("Create Calendar List:");

   
try
   
{
     
Guid listGuid = Web.Lists.Add("Calendar", "Calendar for team events", SPListTemplateType.Events);
      SPList list = Web.Lists[listGuid];
      list.OnQuickLaunch =
true;
      list.Update();
   
}
   
catch (Exception ex)
    {
}
    #endregion
 
}
}

Then wanted to not define the features associated with that Site Definition in the onet.xml file, but use Feature Stapling. Again, making my solution more suportable in the future.
 
A problem I have come across though, is that when I have feature receivers in those stapled features that udate the SPWeb object, it clashes with the web updates in the SPWebProvisioningProvider.
 
The error message I receive is:
 
The web being updated was changed by an external process.   at Microsoft.SharePoint.Library.SPRequestInternalClass.SetWebProps(String bstrUrl, String bstrTitle, String bstrDescription, UInt32 nLocale, UInt16 nTimeZone, Boolean bTime24, Int16 nCalendarType, Int16 nAdjustHijriDays, Int16 nAltCalendarType, Boolean bShowWeeks, Int16 nFirstWeekOfYear, Int16 nFirstDayOfWeek, Int16 nWorkDays, Int16 nWorkDayStartHour, Int16 nWorkDayEndHour, Int32 lFlags, Int16 nCollation, UInt32 nAuthor, String bstrCustomizedCssFileList, String bstrAlternateCssUrl, String bstrAlternateHeaderUrl, String bstrMasterUrl, String bstrCustomMasterUrl, String bstrSiteLogoUrl, String bstrSiteLogoDescription, Boolean bUpdateTimeCreated, DateTime dtTimeCreated, Boolean bUpdateTimeLastModified, DateTime dtTimeLastModified, UInt32& nCollationLCID)
   at Microsoft.SharePoint.Library.SPRequest.SetWebProps(String bstrUrl, String bstrTitle, String bstrDescription, UInt32 nLocale, UInt16 nTimeZone, Boolean bTime24, Int16 nCalendarType, Int16 nAdjustHijriDays, Int16 nAltCalendarType, Boolean bShowWeeks, Int16 nFirstWeekOfYear, Int16 nFirstDayOfWeek, Int16 nWorkDays, Int16 nWorkDayStartHour, Int16 nWorkDayEndHour, Int32 lFlags, Int16 nCollation, UInt32 nAuthor, String bstrCustomizedCssFileList, String bstrAlternateCssUrl, String bstrAlternateHeaderUrl, String bstrMasterUrl, String bstrCustomMasterUrl, String bstrSiteLogoUrl, String bstrSiteLogoDescription, Boolean bUpdateTimeCreated, DateTime dtTimeCreated, Boolean bUpdateTimeLastModified, DateTime dtTimeLastModified, UInt32& nCollationLCID)
 
This appears to be because both pieces of code reference the same SPWeb Object (the contet one?) and cannot update it at the same time.  This occurrs because the Stapled Feature Receivers don't know when the web has been provisioned and just run when they are ready.
 
So, I guess the only way around this is to thread the code, put in some waits to ensure the SPWebProvisioningProvider has completed or create our own SPWeb Objects in all WPPs and Feature Receivers.
 
I am investigating the solutions now and will update when I have more information.
 
Mark

Comments

Code Update: Do NOT dispoe for Feature.Parent SPWeb

I have just noticed that I am using:
using (SPWeb Web = (SPWeb)properties.Feature.Parent)

This is incorrect, and I should not be disposing of the SPWeb object provided to me from Feature.Parent.

This, however does NOT fix the issue.

Mark
Mark Stokes at 6/8/2009 11:04 AM

Update: Do not mix methods

So, in the end I decided to ditch my SPWebProvisioningProvider method altogether and use stapled features.

I have created a TeamSiteConfigurator Feature that is stapled to team sites, a ProjectSiteConfigurator Feature that is staped to Project sites, etc.

These SPFeatureReceivers spin up a new thread, wait for a couple of seconds to ensure the site has been privisioned and then set about creating the lists and webparts that are needed.

This enables me to easily update the initial template configurations by simply changing the code in the SPFeatureReceiver and I don't have to change the configuration of my Site Definitions (which is unsupported.)

I also set a property in the SPWeb Properties bag to say that the site has been configured.  This way, if the feature is deactivated and re-activated (which it shouldn't as it is hidden) then this value i checked and if set, it doesn't re-run. 

This could be enhanced in the future to store a configuration version number and if the template changes we could use this version number to go through all our sites based on the template and upgrade them.

Mark
Mark Stokes at 6/19/2009 10:30 AM