Tuesday, January 14, 2014

Spinning SharePoint Timer Jobs from Site Collection Configuration

My recent content management engagement required to have several long running jobs or tasks that must run in background. For instance:

1. Analyzing Checked out files and sending reminders to the individuals if the number of days (since the file was checked out) exceed certain threshold limits

2. Removing links from other content when a particular content is removed or archived from the system

3. User wants to see all the alerts that he subscribed in all webs in the site collection

4. Sending a reminders to authors to review the content when a review time was specified in the content and that date is approaching

wow, the list goes on…

Well, we could have created one timer job for each scenario and scheduled them at different time. While that is perfectly fine, had some additional design goals for which this approach fall short. Our primary goals were:

1. Ability to monitor the background job and check its status right from site collection, without logging on to server on which it was run and go through ULS logs

2. Ability to run the job on demand without requiring farm level permissions

3. Ability to look into all individual tasks that the job performed and receive an email if anything unusual happened during the execution.

4. Ability to spin up a one time timer job based on an action by an end user and the timer job sends the required information or processes the request later.

So, I came up with a custom architecture which satisfies all of the above goals and achieve even more. While, I won’t be able to share the code because of confidentiality, I can share the details of the architecture. Please feel free to leave comments or contact me if you like the approach and need additional detail.

Here are the main components of this timer framework:

1. When a feature is enabled at Site Collection level, the framework creates two lists: Task Definition List, Task Trace List.

In task The Task definition list contains at least following information:

  • Title of the Task as it appears in the timer job definitions on central admin
  • GUID to track this definition
  • Schedule – to store an XML based schedule definition for the timer job
  • Schedule Type: matches with SharePoint Schedule Types such as one time, hourly, weekly etc.
  • Trace Level – one of the few trace level that my framework supports. A concept similar to OTB diagnostic logging
  • Provisioning Status – a status which shows whether a corresponding SharePoint timer job is created or not
  • Assembly – Full Name of the assembly which contains the execution code of this timer job
  • Class – Full name of the class which is derived from our custom interface ITimerTask interface
  • TaskData – A random XML data that your timer job needs. Framework will automatically make this data available when above Class is called when the timer job runs
  • NotifyTo – Can contains email address of person(s) who is interested in knowing the status of the timer job. The framework takes care of sending the email notification when developer sets the status of the job to anything other than “Success”

The Task Trace list contains at least following columns:

  • GUID – Identifies the corresponding timer job definition
  • ExecutionDateTime – Date and time when the timer job was executed
  • Status – Custom Status such as Success, Warning, Failed, SuccessWithWarning, FailedWithWarning etc.
  • Trace – Contains detailed trace information registered by developer while implementing the execute method of custom ITimerTask interface. Framework takes care of putting the trace information in this list. All that developer needs to to is call a method. For example, .RegisterTrace (“required list was not found”, TraceLevel.Warning)

2. A Web Application level feature is installed by the framework which creates two timer job definitions at Web Application level: Timer Task Scheduler Job and Timer Task Cleanup job.

Let’s see what exactly they do!

TimerSchedulerJob runs at a regular interval (say 5 minutes) and looks for any un-provisioned job in the The Task definition list that I mentioned above. Once it finds, it create a SharePoint Timer Job and schedules it as specified in the definition list. This newly created timer job is a generic timer job, all it does is loads the assembly and the class specified in the definition, prepares the required data for the job and calls the execute method. That means developer’s implementation of Execute method of my custom interface gets called.

Developer of this framework is responsible for:

  • Getting the required data passed as Execute method parameter, along with lot of other information like an instance of SPSite object in which the definition was created
  • Write core logic
  • Set the final status of the job
  • Register all required trace messages by calling method parameter’s RegisterMessage method

After calling the execute method, framework looks at the registered trace messages and the status of the task set by developer. The framework sends email notification specified in the definition. The framework also creates an entry in the Timer Task Trace list at the site collection level that I specified above.

Aha! so finally all dots are connected!

Finally, the Timer Task Trace clean up job removes the trace older than pre configured number of days.

So here is the experience of creating a new timer job for a developer, once the framework is enabled using Web App and Site Collection level feature:

  1. Create a class derived from ITimerTask class of my framework which has just one method called Execute
    • The core responsibility of a developer in order to use the framework effectively are specified above..
  2. Deploy the assembly to GAC
  3. Create a definition in the Timer Task Definition list at site collection level. Alternatively, create the onetime definition dynamically on click of a user action
  4. Monitor the log and see what happened when the framework executed the timer job defined in the definition.

This is one of the most useful framework that I have built and the developers in the team love it because it opens up a lot of possibilities. My developers don’t need to learn how to develop a timer job but they are able to create one and execute them with so much ease. Without a doubt this is my favorite too!

Tuesday, July 17, 2012

SharePoint: Showing Last Published Version to Authors

Quick 5 minute post! Hope it helps!

Recently, our content authors wanted to see the most recent published version without going to Version History. Unfortunately, they hate to use SharePoint’s Version History and let me mention – they hate the ribbon too Sad smile.

Anyways, they were okay with a button put in the side bar of the page which can only be displayed to Authors. And they wanted to view the page in a new window so that they can compare with draft version (thank god, they were okay with comparing the content manually Smile)

Rather than accessing the versions on server side, we decided to access the version information only when a button is clicked. Once again the wonderful Client Side Object model saved a lot of work for us. The script on click of button would look this (and that’s it for today)



this.ctx = SP.ClientContext.get_current();
this.page = ctx.get_web().getFileByServerRelativeUrl(window.location.pathname);
ctx.load(page);

ctx.executeQueryAsync(Function.createDelegate(
this, this.ShowVersion), Function.createDelegate(this, this.ShowError));
function ShowVersion() {
var versionID = page.get_uiVersion();
if (versionID < 512)
SP.UI.Notify.addNotification(
'This page was never published.', false);
else if (versionID % 512 == 0)
SP.UI.Notify.addNotification(
'You are currently viewing Published Version.', false)
else{
window.open(window.location
+ '?PageVersion=' + 512*(versionID % 512), '_blank')
}

}
function ShowError() {
SP.UI.Notify.addNotification(
'Unable to perform the action. Please report the issue.', false)
}

Saturday, January 21, 2012

Where is my Rating Feature?

Many of you know new new Rating FEAURE of SharePoint 2010. There are many resources out there to learn about using Rating Feature but here, I would talk about what it takes to make the Rating Feature available to you.

I will not waste time, ALL of following must be TRUE for rating feature to be available for your lists:

1. You must be using SharePoint Server product. According to Compare SharePoint Editions, Rating is NOT available to SharePoint Foundation that comes free with Windows Server 2008.

2. You must have configured User Profile Service in Central Administration.

3. The User Profile Service must be associated with your Web Application

Note: Step 2 and 3 are described here (and many other places, I am sure)

http://dxjo.net/dxit/2011/09/activate-and-enable-rating-in-sharepoint-2010/

4. This is perhaps not documented at many places, but you must NOT have created your site from a Blank site template. If yes, you are out of luck. But wait… there is one last chance, if you started with blank site template and you are okay with enabling Publishing Features at site collection level, you can still make it available! 

DISCLAIMER: I do not recommend and I have not tested the side effects of enabling publishing feature on sites created from blank site template. Personally, I would try to avoid it!

Alternatively, you can just try enabling the hidden rating FEATURE by using following PowerShell command. When I tested following, I was able to enable rating on announcement rating and save my rating, the average rating was also calculated depending upon the scheduled jobs. I did not test further:

Enable-SPFeature 915c240e-a6cc-49b8-8b2c-0bff8b553ed3 –URL http://yoururl.com

Also note that SharePoint staples the Rating FEATURE to following Site Templates:




<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="STS#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="STS#2" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="MPS#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="MPS#1" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="MPS#2" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="MPS#3" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="MPS#4" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="WIKI#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="BLOG#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SGS#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="BDR#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="OFFILE#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="OFFILE#1" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="PWA#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="PWS#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPS#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSMSITE#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSTOC#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSTOPIC#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSNEWS#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSNHOME#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSSITES#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSBWEB#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSCOMMU#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSREPORTCENTER#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSPORTAL#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SRCHCEN#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="PROFILES#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="CMSPUBLISHING#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSPORTAL#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPS#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSREPORTCENTER#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSTOC#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSTOPIC#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSNEWS#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSNHOME#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SPSSITES#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="SRCHCEN#0" />

<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="CMSPUBLISHING#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="BLANKINTERNET#0" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="BLANKINTERNET#1" />
<FeatureSiteTemplateAssociation Id="915c240e-a6cc-49b8-8b2c-0bff8b553ed3" TemplateName="BLANKINTERNET#2" />


The Rating FEATURE is hidden and Publishing Feature depends on it and that’s the reason it becomes available to you when you activate Publishing Feature.

Thursday, January 12, 2012

Sending Emails to SharePoint Group

Recently, I came across a situation where I had to include SharePoint group in To field of “Send Email” activity in a reusable workflow designed in SharePoint Designer. I just included it thinking that it would work (just like hundreds of other developers I guess) but I was wrong. Well, that was my last activity in the workflow. After it was deployed, it would NOT send emails to members of the SharePoint Group and worse than that, it would not show any error, the workflow appears completed but it does NOT terminate itself but the page gets published. As a result, content authors cannot start a new workflow because previous workflow was not terminated even though item was published. SharePoint not sending emails is understood but I would be happier it it logged something in the workflow history or terminated the workflow. It took me many change/deploy cycles before I could figure it out.

I posted a question on here: Definitive answer to sending emails to SP Group from Workflow and on MSDN forum too, but did not get an answer for couple of days and then thought about figuring out solution that works for me (yes, there could be other solutions out there).

Solution and Assumptions: It was safe to assume in my situation that SharePoint users will directly be added to the group to which I need to send emails.

So the simplest solution was to get develop a custom activity which can be consumed by SharePoint designer. The activity outputs list of emails given the SharePoint group or individual’s name. The designer can take the output and store it in Workflow variable. And finally, the variable is used in “To” field of the Send Email Activity.

So following solution needs to be revisited (or tested) if the SharePoint group includes other groups or active directory groups.

Code

Here is my activity looks like:

using System;
using System.ComponentModel;
using System.Linq;
using System.Workflow.ComponentModel;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;
namespace MyProduct.Workflows.Activities
{
public class ExpandSharePointGroup : Activity
{
public static DependencyProperty __ContextProperty = System.Workflow.ComponentModel.DependencyProperty.Register("__Context",
typeof(WorkflowContext), typeof(ExpandSharePointGroup));

[Description(
"Context")]
[Browsable(
true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext __Context
{
get
{
return ((WorkflowContext)(base.GetValue(__ContextProperty)));
}
set
{
base.SetValue(__ContextProperty, value);
}
}


public static DependencyProperty GroupNameProperty = DependencyProperty.Register("GroupName",
typeof(string), typeof(ExpandSharePointGroup));

[Description(
"SharePoint Group Name")]
[Category(
"MyProduct Workflow Activities")]
[Browsable(
true)]
[DesignerSerializationVisibility
(DesignerSerializationVisibility.Visible)]
public string GroupName
{
get
{
return ((string)
(
base.GetValue(GroupNameProperty)));
}
set
{
base.SetValue(GroupNameProperty, value);
}
}

public static DependencyProperty FieldValueProperty = DependencyProperty.Register("FieldValue",
typeof(string), typeof(ExpandSharePointGroup));

[Description(
"Field Value")]
[Category(
"MyProduct Workflow Activities")]
[Browsable(
true)]
[DesignerSerializationVisibility
(DesignerSerializationVisibility.Visible)]
public string FieldValue
{
get
{
return ((string)
(
base.GetValue(FieldValueProperty)));
}
set
{
base.SetValue(FieldValueProperty, value);
}
}



protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{

SPWeb web
= (SPWeb)__Context.Web;

try
{
if (string.IsNullOrEmpty(GroupName))
{

return ActivityExecutionStatus.Closed;
}

string strOutput = string.Empty;
strOutput
= Helper.ResolveToEmailName(__Context, GroupName);
FieldValue
= strOutput;
}
catch (Exception ex)
{
// TODO: Log error here
return ActivityExecutionStatus.Faulting;
}


return ActivityExecutionStatus.Closed;
}

}

}

In order to make sure that above activity is available to SharePoint designer, we need to deploy a .Actions file to {SharePointRoot}\Template\1033\Workflow which is shown below:


<?xml version="1.0" encoding="utf-8"?>
<WorkflowInfo Language="en-us">
<Actions>
<Action Name="Expand SharePoint Group Members"
ClassName
="FULL CLASS NAME"
Assembly
="FULL ASSEMBLY NAME"
AppliesTo
="all"
Category
="My Custom Workflow Activities">
<RuleDesigner Sentence="Expand Email for %1 it in: %2">
<FieldBind Field="GroupName" Text="SharePoint Group" DesignerType="TextBox" Id="1"/>
<FieldBind Field="FieldValue" Text="Field Value" DesignerType="ParameterNames" Id="2"/>
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext" Direction="In" DesignerType="Hide" />
<Parameter Name="GroupName" Type="System.String, mscorlib" Direction="In" />
<Parameter Name="FieldValue" Type="System.String, mscorlib" Direction="Out" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>

Finally, you need to add a web.config entry under authorizedTypes section:



<authorizedType Assembly="Full Assembly Name" Namespace="Your Name space" TypeName="Class Name or *" Authorized="True" />


Note: After your assembly is deployed in GAC, Actions file is deployed on SharePoint root and web.config entry is added (as shown above), you need to close the SharePoint Designer and open it again before you can see your activity in list of available actions.


Hope this helps!

Tuesday, December 13, 2011

Expand-collapsible FAQ for your Publishing Pages

I have come across an expand-collapsible FAQs requirement as part of publishing pages a few times. While there are many ways to achieve this including a custom field or custom control/web part accessing external list, in my most recent project, I thought about and implemented a much simplified solution. The solution looks like:

1. Have a FAQ field of type: “Full HTML with Formatting and Constraints of Publishing”

2. Provide custom two custom styles to choose from: Question and Answer

3. In display mode, hide the Answers using jQuery when the page loads

4. In display mode, attach click event on question to toggle the answer

5. And finally, provide required training to your content authors so that they can author FAQs on publishing pages.

In following sections, I will expand the steps above and provide necessary technical details.

I am assuming that you have a column of type “Full HTML with Formatting and Constraints of Publishing” added to your Publishing Page Content Type and corresponding Page Layout has been provisioned to Master Page Gallery.

We need two CSS files provisioned (preferably) in “Style Library”.

FAQDisply.css

SNAGHTML2b1380

FAQEdit.css


SNAGHTML2c3c64 


Note: Feel free to use different styles but make sure that you follow the naming convention of styles as required by the Rich HTML editor, failing to do so will NOT make the styles available to you in the editor toolbar.


Also make sure that in your page layout, above CSS files are included in “PlaceHolderAdditionalPageHead” content placeholder. FAQEdit should be included only in edit mode and it should be referred after FAQDsply.css file as shown below:


image


Now, let’s look at the required jQuery code (assuming jQuery library is already plugged in your Page Layout/Master Page). The code is commented below for an understanding:


SNAGHTML3a3ed4





Once above infrastructure is in place, go ahead and create a new publishing page using your Page Layout. If everything is working fine, you should see Two styles available when you edit the rich HTML field as shown below:


SNAGHTML416671


Typing Question/Answer and choosing appropriate styles may look like below. Note that answer is not hidden in Edit mode because 1) our Jquery does not execute in edit mode and 2) In edit mode, we are making sure that the DIV tag containing the answer has display=block style.


SNAGHTML49787a


When you view the page in Display Mode, the answer will not be displayed until you click on the answer.


Hope this helps you implement similar requirements…

Thursday, September 22, 2011

Quickly Testing if JQuery is referenced properly on your page

I have observed a lots of developers quickly test the jQuery or SPServices references by putting a script like and make sure it works:

<script type="text/javascript" language="javascript">

$(document).ready(function() {
alert("jQuery");
alert($().SPServices.SPGetCurrentSite());
});
</script>

While this approach works, it requires to put the actual script on the page, only to be removed later.

There is a better way to check it instead of actually writing script on the page. Thanks to the IE Developer Toolbar. Here are the quick steps:

1. Reference your jQuery (and SPServices) in your page

2. Open your page in Internet Explorer on which IE Developer toolbar is installed. Note that you have IE 8 and above, it comes with it by default

3. Hit F12 to activate it the IE Developer Toolbar

4. Click on Script Tab, the beauty of IE Developer toolbar is that it allows you to run Ad-hoc JavaScript on your page as shown below:

SNAGHTMLaadd08

To make sure jQuery is referenced properly and it is functional on your page is to type a simple jQuery function like alert($(‘title’).html()). If it displays browser title like shown below, you are all set with the wonder of jQuery!

image

Many developers use SPServices library for some wonderful enhancements to the Forms (and for everything else it provides, of course). To test integration of SPServices (and jQuery as well because SPServices depend on jQuery), you may use similar approach but a different function to test and here it is:

alert($().SPServices.SPGetCurrentSite());

Hope this helps!

Wednesday, September 7, 2011

The Extensible and Reusable FEATURE Framework

As we all know there are two methods to develop/deploy various artifacts using SharePoint FEATURES : Declaratively using Feature.xml and element files and Programmatically.

Most examples that use programming approach either directly writes code in Feature Activated, Feature Deactivating etc. or use helper methods to promote the reusability of the code. While the second approach ensures reusability, it is not extensible, meaning, you must change code to change the actions that you want to perform in the Feature Receiver class. In my recent SharePoint implementation, I came across the need for extensibility and ended up writing a mini framework for the same.

Following are the elements of the framework and how they are connected to each other:

1. IFeatureAction class which has one method with following signature:

void Execute(object target, XmlElement actionData)

2. A Class named FeatureActionReceiver which is derived from SPFeatureReceiver. Overrides FeatureActivated and FeatureDeactivating methods.

3. The overriden FeatureActivated method looks for FeatureActivated.xml file under {Feature Root}\Actions directory and processes the file (more on processing later). Similarly, FeatureDeactivating looks for FeatureDeactivating.xml file under {Feature Root}\Actions directory

4. The Actions file (FeatureActivated.xml or FeatureDeactivating.xml) contains one of more Actions in following format:

<Actions>

<Action ActionName=”” Class=”” Assembly=””>

<AnyValidXML>

</Action>

<Action ActionName=”” Class=”” Assembly=””>

</Action>

</Actions>

4. Action Processing: the framework class reads the action one after another and for each action, it loads the specified Assembly and Specified class which must implement IFeatureAction class. After creating instance of the Action, it gives an opportunity of providing the target object (on which action needs to be performed) to the parent FeatureReceiver class by calling a virtual method “ProvideTargetObject”. The framework passes action name so that the feature’s receiver class can write a switch statement and provide appropriate target object to the framework. If no object is provided by Feature’s Receiver class, by default, framework will pass Feature’s Parent (depending on scope of feature) to the Execute method of the Action.

5. Now your Feature’s Receiver class must derive from FeatureActionReceiver  instead of SPFeatureReceiver and override FeatureActivated and FeatureDeactivating methods. Here, the Feature’s receiver must call Base.FeatureActivated and Base.FeatureDeactivating methods so that framework can process those actions. Apart from that, as usual other code can be written in the Feature Receiver.

Now, everything that I want to do in Feature Receiver is externalized in a set of Action Classes and those Action classes must get the parameters from XML file (the parameters varies from one project to another) while performing the actions. So that’s about reusability. In addition, performing a new action means writing a new action (if existing action does not satisfy the need, of course) and updating those XML files so that the action will be executed in FeatureActivated or FeatureDeactivating. That’s extensible!!

To give you a few examples, I developed following actions:

1. Create List – It would create list instance, add content types, set other properties, set the views and add fields to the view

2. Create Terms – Accepts a set of Term Groups, Term Sets and a hierarchical Terms data. This will ensure that all terms and term sets required by your application are provisioned using Features

3. Set Master Page – Sets the specified master page for the specified web

4. Create Web – creates the webs using specified Title and Web template

5. Add Feature – Activates a Feature. In my case, it was required to activate some features after creating the web so that my content authors do not need to perform the action

.. the list goes on and on..

I hope all dots are connected. Unfortunately, I cannot share the code here but let me know if you need more information about it.