Custom Workflows for SharePoint Solutions
- rbenton's blog
- Login or register to post comments
`
Recently, I’ve had to develop some custom workflows for a SharePoint solution. A lot of the examples you find on MSDN or the web for developing custom workflows for SharePoint involve writing some customer ACTIONS which you then use in your custom WORKFLOW. An ACTION is the thing the workflow will do, like “Send Email”, “Approve Invoice”, “Do Laundry”, etc. In going down this route, I found that once I wrote the ACTION, I didn’t really need to write the whole workflow. I could just use SharePoint Designer to create a workflow which used my custom ACTION.
The end users or site administrators generally seem to prefer using SharePoint Designer workflows anyway because they are fairly easy to understand, create, and the SharePoint Designer “wizard-like” interface for workflows gives some measure of control and understanding of what you’re actually using the workflow for in the first place.
Here’s an example. I had to write a solution in which adding an item to a SharePoint list would create two SharePoint User Groups to which users could be added so access would be granted to a particular section of the site. In looking at the “out-of-the-box” capabilities of SharePoint workflows, no action allowing the creation of a user group existed. I decided I needed to write a custom workflow. To do this, I first needed to write a custom action which took the Title of the list item and then used that title to create two user groups with the title as the prefix, creating “OfficeName-Updater” and “OfficeName-Viewers”. After writing it, I then started writing the workflow which would use the action. Then I realized, “Eureka!”. Why am I writing a workflow? I can just use this action in a SharePoint Designer workflow! SPD workflows can use any custom action you register to the site!
I began applying this thinking to several other workflows I had to write, and found very few cases in which just writing the action didn’t do the trick. For example, I wrote an action to send emails to a list of multiple recipients. I wrote another one to call a web service and create an InfoPath form. The list goes on and one. There wasn’t a single case where I had to write a whole workflow to use the actions. Furthermore, if I really needed to write a whole workflow, I could still have it use the actions I wrote.
There is some configuration and maintenance necessary to allow a custom Action to be used by SharePoint Designer within your SharePoint site, a link to how to do this is provided here: http://msdn.microsoft.com/en-us/library/bb629922.aspx . However, I find that the process to do this is no more complicated than making a custom workflow available. In fact, I personally find it a little easier.
Here is some code I used to create a custom workflow action that creates user groups based on the title passed in from a list item. This would exist in the Activity1.designer.cs file of your custom action:
namespace CreateUserGroup{
public partial class AddUserGroup: SequenceActivity
{
#region Activity Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[System.Diagnostics.DebuggerNonUserCode]
private void InitializeComponent()
{
this.Name = "AddUserGroup";
}
public static DependencyProperty OfficeNameProperty = System.Workflow.ComponentModel.DependencyProperty.Register("OfficeName", typeof(string), typeof(AddUserGroup));[Description("OfficeName")]
[Category("Office")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string OfficeName
{
get
{
return ((string)(base.GetValue(AddUserGroup.OfficeNameProperty)));
}
set
{
base.SetValue(AddUserGroup.OfficeNameProperty, value);
}
}public static DependencyProperty ErrorReturnProperty = System.Workflow.ComponentModel.DependencyProperty.Register("ErrorReturn", typeof(string), typeof(AddUserGroup));
[Description("Error Returned")]
[Category("Errors")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string ErrorReturn
{
get
{
return ((string)(base.GetValue(AddUserGroup.ErrorReturnProperty)));
}
set
{
base.SetValue(AddUserGroup.ErrorReturnProperty, value);
}
}public static DependencyProperty __ContextProperty = System.Workflow.ComponentModel.DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(AddUserGroup));
[Description("The site context")]
[Category("User")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext __Context { get { return ((WorkflowContext)(base.GetValue(AddUserGroup.__ContextProperty))); } set { base.SetValue(AddUserGroup.__ContextProperty, value); } }protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
//get the title of the office and create the User Groups for the Office Viewers and Updatersstring officeNamestr = string.Empty;
officeNamestr = OfficeName;string Errors = string.Empty;
string officeUpdaterGrp = string.Empty;
string officeViewerGrp = string.Empty;if ((null != officeNamestr) && (!officeNamestr.Equals(string.Empty)))
{
officeUpdaterGrp = officeNamestr + "Updaters";
officeViewerGrp = officeNamestr + "Viewers";
}try
{
if (!officeUpdaterGrp.Equals(string.Empty))
{
SPWeb tmpweb = __Context.Web;
SPSite site = new SPSite(tmpweb.Url);
SPWeb topSite = site.OpenWeb();topSite.AllowUnsafeUpdates = true;
SPGroup groupObj= null;
SPMember oMember = null;try
{
oMember = topSite.Users[topSite.Author.LoginName];
groupObj= topSite.SiteGroups[officeUpdaterGrp];
}
catch (Exception ex1) { Errors += "officeNameStr = " + officeNamestr + " Exception: " + ex1.ToString(); }if (groupObj== null)
{
topSite.SiteGroups.Add(officeUpdaterGrp, oMember, topSite.Author, officeUpdaterGrp);
groupObj= topSite.SiteGroups[officeUpdaterGrp];SPRoleDefinition oRole = topSite.RoleDefinitions.GetByType(SPRoleType.Contributor);
SPRoleAssignment oRoleAssignment = new SPRoleAssignment(grouppi);
oRoleAssignment.RoleDefinitionBindings.Add(oRole);
topSite.RoleAssignments.Add(oRoleAssignment);
topSite.Update();}
groupObj= null;
try
{
oMember = topSite.Users[topSite.Author.LoginName];
groupObj= topSite.SiteGroups[officeViewerGrp];
}
catch { }if (groupObj== null)
{
topSite.SiteGroups.Add(officeViewerGrp, oMember, topSite.Author, officeViewerGrp);
groupObj= topSite.SiteGroups[officeViewerGrp];SPRoleDefinition oRole1 = topSite.RoleDefinitions.GetByType(SPRoleType.Contributor);
SPRoleAssignment oRoleAssignment1 = new SPRoleAssignment(grouppi);
oRoleAssignment1.RoleDefinitionBindings.Add(oRole1);
topSite.RoleAssignments.Add(oRoleAssignment1);
topSite.Update();}
topSite.AllowUnsafeUpdates = false;
}
}
catch (Exception ex)
{
Errors += " 2nd exception: " + ex.ToString();ErrorReturn = Errors;
}return ActivityExecutionStatus.Closed;
}#endregion
}
}
// Best wishes, Ryan Benton


Technorati Tags: