HowTo: Create a Custom Action for an Office SharePoint Designer Workflow - Part 1

Sorry about the long title, but I thought it may help when someone is doing a search for such a topic.  On with the show....

Invariably, if you are using Office SharePoint Designer (SPD) to create workflows, your power users are going to say 'Hey, I really like this but I want to have an Action that does xyz'.  Nobody ever seems just satisfied with what we get out of the box, but that's the beauty of the Microsoft platform, its extensible!

So in part 1, we will talk about the first steps to creating a custom action.

Scenario
Suppose we have a situation where we have a car dealership that caters to customers that purchase fleets of vehicles.  The idea here would be that whenever a new fleet customer wants to sign an order, we would want to take that order form and use it to create a unique site (or document library) for this client.  I'd like to be able to create an action where I could pick from that would allow me to pick up the customers name from the document and use that to create my new document library.

Part 1 - Creating our Custom Action

1. First, we need to setup a sample site in WSS (3.0) or MOSS to use as our base site. Go ahead and create a site https://<yourmachineanddomain.com/FleetCustomers. Create a new document library named 'Fleet Customer Doc Lib' .  In this document library, create a new custom column (single line of text) named 'Customer Name'

A custom Action is actually just a custom workflow activity that is registered with the system using an .ACTIONS file, so now we will create a custom activity.

2. Create a new workflow activity library project in Visual Studio 2008 in some location on your machine for example 'C :\MSDNBlog\SPD' Give it the name 'CreateFleetDocLib' .

3. Rename Activity.cs to FleetDocLibActivity.cs. Open the code view to the activity.

4. Change the base class from SequenceActivity to Activity.

5. Create two Dependency properties (both strings):

Property Name Data Type Category Description
Url string Input Property Url of base site
CompanyName string Input Property Used as doc lib name

This can also be done by using Insert Snippet > Other > Workflow > Dependency Property – Property.

6. To make sure we require validation on each of these entries (meaning we have to have input), put the following above each of the properties:

[ValidationOption(ValidationOption.Required)]

The code for the dependency properties should look like this:

#region Dep Props

public static DependencyProperty UrlProperty = System.Workflow.ComponentModel.DependencyProperty.Register("Url", typeof(string),typeof(FleetDocLibActivity));

[Description("Base URL of site")]
[Category("Input Property")]
[Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)[ValidationOption(ValidationOption.Required)]
public string Url
{
    get
    {
    return ((string)(base.GetValue
     (FleetDocLibActivity.UrlProperty)));
}

    set
    {
    base.SetValue(FleetDocLibActivity.UrlProperty, value);
    }
}

public static DependencyProperty CompanyNameProperty = System.Workflow.ComponentModel.DependencyProperty.Register("CompanyName",typeof(string), typeof(FleetDocLibActivity));

[Description("Name of the fleet company")]
[Category("Input Property")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[ValidationOption(ValidationOption.Required)]
public string CompanyName
{
   get
   {
    return ((string)(base.GetValue
    (FleetDocLibActivity.CompanyNameProperty)));
}

   set
   {
    base.SetValue(FleetDocLibActivity.CompanyNameProperty, value);
   }
}

#endregion

7. Add an EventLog variable to the FleetDocLibActivity class along with other variables used for the sites and webs to be accessed.

#region Member Variables

private EventLog _eventLog;

//main site
SPSite mainSite = null;
SPWeb rootweb = null;

//sub site
SPSite subSite = null;
SPWeb subWeb = null;

#endregion

8. Insert the following code inside of the FleetDocLibActivity class (after the constructor)

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
//Set up the Event Logging object
_eventLog = new EventLog("Workflow");
_eventLog.Source = "SharePoint Workflow";

    try
{
//Send the email
CreateDocLib();
}
finally
{
//Dispose of the Event Logging
// object
_eventLog.Dispose();
}
//Indicate the activity has closed
return ActivityExecutionStatus.Closed;
}

private void CreateDocLib()
{

    try
{
mainSite = new SPSite(Url);
rootweb = mainSite.OpenWeb();

        //sub site
subSite = new SPSite(Url + "/FleetCustomers");
subWeb = subSite.OpenWeb();

        //get the list collection for the main and sub
// sites

       SPListCollection lists = subWeb.Lists;
SPListCollection rootLists = rootweb.Lists;

        //we are going to create a new doc lib in the
//sub-site, create a new empty template
//type then set the type to be a doc library
SPListTemplateType listTemplateType = new SPListTemplateType();
listTemplateType = SPListTemplateType.DocumentLibrary;
lists.Add(CompanyName, "", listTemplateType);

        SPList taskList = null;

//look for the tasks lists in the root web
//also note that SPQuery could be used here
foreach (SPList lst in rootLists)
{
if (lst.Title.Equals("Tasks",
StringComparison.InvariantCultureIgnoreCase))
{
taskList = lst;
break;
}
}
if (taskList != null)
{
//add a new task item for the administrator
SPListItem newItem = taskList.Items.Add();
newItem["Title"] = "Required completion of Doc Lib " + CompanyName;
newItem["Customer Name"] = CompanyName;
newItem.Update();
_eventLog.WriteEntry("Task assigned to admin");
}

        _eventLog.WriteEntry("Workflow success: Document Library created at: " + rootweb.Title.ToString());

    }
catch (System.Exception Ex)
{
//Log exceptions in the Event Log
_eventLog.WriteEntry("Workflow Error :" + Ex.Message.ToString(), EventLogEntryType.Information);

}
finally
{

       mainSite.Dispose();
subSite.Dispose();
rootweb.Dispose();
subWeb.Dispose();
}
} //end CreateDocLib

9.  Add the following using statements to the file:

using System.Diagnostics;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;

10. Add a reference to your project to these (note that you may have to browse for this assemblies) SharePoint assemblies:

Under the .NET tab

Windows SharePoint Services
Windows SharePoint Services Workflow Actions

- If you can’t find the assemblies you can browse directly to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\
- Microsoft.SharePoint.dll
- microsoft.sharepoint.WorkflowActions.dll

11. Go into the CreateFleetDocLib project properties and create a new strong named assembly file named FleetLib.snk.

12. Build the assembly and resolve any errors.

13. Using the Visual Studio command prompt, place the library into the GAC by running the following command (NOTE: Don't forget the double quotes around the path to the assembly):

gacutil /i "C:\MSDNBlog\SPD\CreateFleetDocLib\CreateFleetDocLib\bin\Debug\CreateFleetDocLib.dll"

At this point, we have built the activity that will be used to handle our custom action.  In the Part II, we will create our custom action file.