Sunday, 12 August 2007

Site definitions - custom code in the site creation process

This is the second article in a series of three, where I aim to show how to customize the site creation process (known as site provisioning) with your own API code. The full introduction and series contents can be found at http://sharepointnutsandbolts.blogspot.com/2007/07/article-series-custom-permissions-with.html. The example customization I'm using is as follows: any sites created with the definition should use a specific set of permissions, and not simply follow the default behavior of inheriting the parent site's permissions. Since this can't be done with a standard site definition (like many other things you might want to do), use of the API is required.

However, today the focus is less on the permission specifics of my example, and more on how generally to add your own code which runs in the site provisioning process. And the best thing is, it's actually very simple if you understand SharePoint Features.

There are many reasons why you might have cause to use the API in the site provisioning process. Essentially, if you can't find a way to do what you want using CAML schema in the onet.xml file, chances are you'll have to write code. Hence, it's almost easier to think of what you can do in the onet.xml file and reverse the list in order to work out scenarios which require code, but some examples which spring to mind anyhow are:

  • changing the custom master page of a site
  • creating a site column which gets it's data from a list (see my post on my Feature receiver which does this at Feature to create lookup fields on Codeplex)
  • adding custom unique permissions to a site (the example in this article series)
  • set a site property from any kind of dynamic lookup

In short, there are many scenarios.


Creating site definitions with VSeWSS

If you've ever created a site definition with Visual Studio Extensions for Windows SharePoint Services, you'll notice that the VS project it gives you contains a file called SiteProvisioning.cs. Inside is an event-handler method, where you can add your custom code which will execute when a site is created from the definition. The class looks like this:

namespace COB.Demos.SiteDefinition

{

    public partial class ProjectXSiteDefinition

    {

        /// <summary>

        ///  Define your own feature activation action code here

        /// </summary>

        public void OnActivated(SPFeatureReceiverProperties properties)

        {

            // my code here..

        }

    }

}

 

The plumbing behind all this is interesting. At first glance, the method signature looks like a Feature receiver, but it's actually not. However, examining the VS project (you'll need to build the project with F5 at least once to generate the files) reveals that VSeWSS has in fact created some Features in the background. These files can be found under the bin\Debug\solution folder in your VS project (hidden by default - you'll need to do a 'Show All Files' in Visual Studio Solution Explorer). If you do some more delving around to see exactly what VSeWSS is doing, you'll find the following:

  • 2 hidden Features have been created - 1 deploys the 'default.aspx' file, the other has no 'elements' file but is hooked up to a Feature receiver - this is a class in an assembly named the same as your VS project. If you check the GAC, you will indeed find this assembly there.
  • a line similar to the following has been added to the onet.xml file under the 'WebFeatures' element:

    <Feature ID="67b2507c-8822-41dc-b939-3d8f34b5ad13" />


    Notably, this is the ID of the Feature which is hooked up to the Feature receiver.
  • Using Reflector on the assembly containing the Feature receiver shows that the main event-handler method performs some processing and then calls into the OnActivated method shown above, i.e. the place where VSeWSS provides for you to add your own code to execute when sites are created. This code is actually contained in the SiteProvisioning.Internal.cs file within the VS project. (If you're curious as to what on earth all the code in here is doing, the answer as far as I can tell is nothing when site definitions are created with the VSeWSS project template. However, this code is also found when Solution Generator is used to extract a site definition - in that case there are some fixups which need to be done, and this is the code which is used.)

So in summary, VSeWSS creates a hidden Feature is added to the 'WebFeatures' section of the onet.xml so that it is automatically activated when the definition is used to create a web*. The Feature is hooked up to a Feature receiver which calls the OnActivated method where your custom code lives.

*(Note that if the definition is used to create a site definition, the root web is also created automatically so the Feature would also be activated then. Also note the feature needs to be already installed in the farm for it to be activated in this way).

What we can derive from this is that there's no 'special place' in the site provisioning process to inject custom code, but it can be accomplished by use of a Feature receiver. So if you don't want to use VSeWSS to create site definitions, this is the technique to use to add your custom code to the site creation process.

In terms of what that code might look like, a 'Hello World' example could be:

public void OnActivated(SPFeatureReceiverProperties properties)

{

     SPWeb currentWeb = null;

     SPSite currentSite = null;

     object oParent = properties.Feature.Parent;

 

     if (properties.Feature.Parent is SPWeb)

     {

         currentWeb = (SPWeb)oParent;

         currentSite = currentWeb.Site;

     }

     else

     {

         currentSite = (SPSite)oParent;

         currentWeb = currentSite.RootWeb;

     }

 

     currentWeb.Title = "Set from provisioning code at " +  DateTime.Now.ToString();

     currentWeb.Update();

}


Hopefully this illustrates that it's quite simple to write code which sets properties on sites created from the definition. Generally the SPWeb object is the entry point, and any property which can be modified can be modified using the API. So, this is a pretty powerful technique which can be used in many scenarios.

If you have this type of requirement, I'd definitely recommend using VSeWSS to simplify the process. It's certainly possible to hook everything up manually and package it into a Solution, but the tool does save a large amount of hassle. However as usual with VSeWSS, the price of this is some flexibility. As my sample code in the final article will show, it's sometimes useful to pass data into Features by using Feature properties, and this unfortunately is not supported by VSeWSS. So in case it's useful, the following link provides a zip file containing a Solution/Feature which uses the above technique, without using VSeWSS:

http://sharepointchris.googlepages.com/customcodewithsitedefinitions

In the next and final article, I'll cover the specifics of using the API to modify site permissions as sites are created. As is hopefully clear, this is in conjunction with the technique detailed here so the net result is that the specific permissions are set 'automatically', courtesy of the Feature which is automatically activated against a site when it is created.

8 comments:

Katrien said...

Hello Chris,

I wanted to congratulate you on your blog. You have posted some fantastic articles.
Keep up the good work!

Katrien

Chris O'Brien said...

Hi Katrien,

Thanks for the feedback!

I went to take a quick look at your blog and ended up reading many of the articles. High quality stuff there - good work yourself!

Chris.

Cyfred said...

Chris,

Thanks. I learned a lot from your great articles and demo projects. However, I still can't figure out how to get custom code to execute after a site (or list) is first created.

I am trying to set custom permissions on sub folders of a document library during the site provisioning process. Of course, when I tried doing it using a feature receiver as in your example, the code executed before the site and library were created and therefore the folders could not be modified.

I tried using a ListEventReceiver and an ItemEventReceiver but I can’t find appropriate methods to overload that are activated only after the list or item is created. Any ideas?

Chris O'Brien said...

Cyfred,

Hmm that surprises me - it's been a long time since I looked at this, but I think you should be able to access the lists in a Feature receiver which runs when sites are created. I show this in Automatically setting custom permissions on new sites.

I can't think of a way to do something like this with a list template (so it would happen when new lists [rather than sites] are created), but it should be possible for site creation.

Can you post the code you used?

Thanks,

Chris.

Tim Stewart said...

Chris,
I'm working on a Site Definition to be deployed via a SharePoint solution. I'm trying to associate an event receiver with a list that was created in my ONET.xml file. I'm interested in handling the addition of new items.

My event receiver is part of a feature that is activated via a WebFeatures entry in my site's ONET.xml file.

Even though the event receiver feature is activated when the site is created, the event handler is not being called. If I deactivate the event handler feature and then reactivate it, the event handler will be called. Have you seen this problem before?

Thanks.

Chris O'Brien said...

Hi Tim,

Sorry for the delay in replying. I'm not sure why this wouldn't work properly, and it's interesting you say the event receiver has been associated with the list.

How about trying a different approach - in a 'standard' Feature receiver, hook up the event receiver in code, rather than using Feature XML to do this. I'm just interested to know if there's something about the sequence of things which could be having an effect.

I'd also be thinking about using the API to write out the details of the event receiver after each approach, so I can compare what SharePoint believes the definition to be.

Best of luck,

Chris.

Marten said...

Hi Chris, Thanks for the articles. They are always helpful.
I am using VS2WSS 1.2, not sure if you've had a play around yet or not. I am facing a few issues:
1. When I create a feature to deploy contenttypes, VSeWSS picks up the contenttype name to be the feature name, rather than the folder name. Even if I modify the folder name and feature name in the manifest, it is still problematic. I had to place it under a different feature for sitecolumns.
2. A site definition cannot be added to a solution, it must be created as a new solution.
3. (Important one for me now) A new site definition solution can have features in the same solution for deployment. However, if the features have feature receivers, VSeWSS creates a new feature with GUID 00000000-0000-0000-0000-000000000000 and that dummy feature just calls your feature receiver for your feature. It looks like the developers created the template to scan all feature receivers and assume they will be placed in new separate features. And even if, why on earth are they assigned the GUID 00000000-0000-0000-0000-000000000000!? :|
4. VS hangs when significant change has been made to a feature (or even item inside a feature). CPU goes to 80-90CPU for devenv. Then the sharepoint process also reaches high CPU and doesn't let you deactivate the problematic feature. It just hangs.

I don't know if it is just me or are others facing the same issues.

Any ideas? especially for number 3 above :)

Thanks,
Marten

Chris O'Brien said...

Hi Marten,

Sorry for the delay in replying.

I'm afraid I haven't looked at version 1.2 yet, but hopefully someone who has might respond to your points. I'm surprised that VSeWSS appears to enforce a certain way of factoring Features, since AFAIK little guidance exists on this - I wonder if you are just encountering bugs which slipped through testing.

In terms of the GUID weirdness, remember that is the default value for a GUID (Guid.Empty) - but yes, it could be that code in VSeWSS is expecting to replace these. Does your end solution/Feature have default GUIDs after building from F5?

Perhaps you should try splitting your artifacts across different VSeWSS projects to see if that works around the issues.

In the end however, I have to say I'm personally not sure VSeWSS is the best approach for WCM sites. I discuss my thoughts in the end paragraphs of SharePoint deployment options: Features or Content Deployment, though I also intend to discuss this again soon. In summary for my most recent projects I have used the SharePoint Content Deployment Wizard to deploy everything which is not on the filesystem, and use a Solution package for those files. This seems to work well and avoids the need to 'featurize' everything - I also avoid site definitions unless users really will create many sites from a template.

HTH,

Chris.