SPD 2010 Custom Workflow Activity

In a recent project, I needed to build a custom workflow activity that would be used in SharePoint Designer. In my workflow I need to retrieve a list of users based on certain criteria. So I need a custom Activity that query a custom list as shown below containing a user field collection column called Users and other fields: Category and Range where it will be used for rule of determining the users.

Category Range Users

Before going into implementation detail, the picture below shows the end result of my custom activity.

SharePoint Designer Custom ActivityIts name is Lookup Worklow Approvers. It has 3 parameters:

  • List name: is the custom list where the activity lookups up for the users
  • Rule: is telling the activity how to retrieve users from the list. The rule is simple, for the custom list above it would be Field=Category, Field=Range, Result=Users. The activity would build a CAML query based on the rule.
  • the return values is coma separated user accounts.

Here is the steps to build this custom Activity:

  1. First create an actions file, an xml file use by SharePoint for defining Workflow Actions/Activities used by SharePoint Designer. It must be deployed in {hive 14}\TEMPLATE\1033\Workflow. The picture shows how it structures in VS SharePoint project. In this example, I created MyCustomActivities.actions.
    Below is the content of my actions file.
    <?xml version="1.0" encoding="utf-8" ?>
    <WorkflowInfo>
      <Actions>
        <Action Name="Lookup Workflow Approvers"
            ClassName="MyCustom.Workflow.Actions.LookupApproversActivity"
            Assembly="MyCustom, Version=1.0.0.0, Culture=neutral, PublicKeyToken=accb8d1cf1b32c7f"
            Category="My Custom Activities"
            AppliesTo="all">
          <RuleDesigner Sentence="Get Approver from List %1 with rule %2 (output to %3)">
            <FieldBind DesignerType="ListNames" Id="1" Text="list name" Field="ListValue" />
            <FieldBind DesignerType="Stringbuilder" Id="2" Text="rules" Field="RuleValue" />
            <FieldBind DesignerType="ParameterNames" Id="3" Text="approvers" Field="ReturnValue" />
          </RuleDesigner>
          <Parameters>
            <Parameter Name="ListValue" Type="System.String, mscorlib" Direction="In" DesignerType="ListNames" Description="Approver matrix list name." />
            <Parameter Name="RuleValue" Type="System.String, mscorlib" Direction="In" DesignerType="Stringbuilder" Description="Approver matrix fields." />
            <Parameter Name="ReturnValue" Type="System.String, mscorlib" Direction="Out" DesignerType="WritableFieldNames" Description="Workflow variable output by this action." />
            <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In" DesignerType="Hide" />
          </Parameters>
        </Action>
      </Actions>
    </WorkflowInfo>
    The explanation of the xml can be found here. The Rule Designer will be the text displayed on SPD where the variables are marked by %. The variables are mapped to Id attributes of the FieldBind elements. The FieldBind elements map the SPD designer variables to code behind variables defined in Parameters elements. One of the parameter element is __Context, this is the Workflow Context that would be passed into the codebehind.
  2. Next step, is the code behind. Please see this msdn article for step by step of creating a custom workflow activity.
         public class LookupApproversActivity : Activity
        {
            #region Fields
            public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(LookupApproversActivity));
            public static DependencyProperty ReturnValueProperty = DependencyProperty.Register("ReturnValue", typeof(string), typeof(LookupApproversActivity));
            public static DependencyProperty ListValueProperty = DependencyProperty.Register("ListValue", typeof(string), typeof(LookupApproversActivity));
            public static DependencyProperty RuleValueProperty = DependencyProperty.Register("RuleValue", typeof(string), typeof(LookupApproversActivity));
            #endregion
    
        }
        

    After declaring the Dependency Properties, we defined the Properties, they must match Parameter elements defined in the action xml.

     #region Properties
            [ValidationOption(ValidationOption.Optional)]
            public WorkflowContext __Context
            {
                get
                {
                    return (WorkflowContext)base.GetValue(__ContextProperty);
                }
                set
                {
                    base.SetValue(__ContextProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string ReturnValue
            {
                get
                {
                    return (string)base.GetValue(ReturnValueProperty);
                }
                set
                {
                    base.SetValue(ReturnValueProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string ListValue
            {
                get
                {
                    return (string)base.GetValue(ListValueProperty);
                }
                set
                {
                    base.SetValue(ListValueProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string RuleValue
            {
                get
                {
                    return (string)base.GetValue(RuleValueProperty);
                }
                set
                {
                    base.SetValue(RuleValueProperty, value);
                }
            }
    
            #endregion
    

    And then the implementation of Execute method: The __Context would be used to retrieve current web, current item as shown in the first 2 lines. Then it retrieves the custom List containing the users using Workflow Helper class GetListGuid method passing in ListValue variable and __Context. Next is parsing the RuleValue variable to retrieve the fields. As mentioned above the rule is field=<fieldname> for field and result=<fieldname> for the user field. At line 27, it performs CAML query to the rule list and then parse the result of the query to the ReturnValue variable. Note. There are several classes are my own utility classes such as QueryData and Utility.

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
       SPWeb currentWeb = this.__Context.Web;
       SPListItem currentItem = currentWeb.GetListItem(this.__Context.CurrentItemUrl);
       SPList ruleList = currentWeb.Lists[Helper.GetListGuid(this.__Context,ListValue)];
       var rules = from rule in RuleValue.Split(new string[] { "\r\n", "\n", "\r", "," }, StringSplitOptions.RemoveEmptyEntries)
          let r = rule.Trim().Split(new char[] { '=' })
          select new
          {
             Type = r[0].ToLower().Trim(),
             Field = r[1].Trim(),
          };
    
      string approverFieldName = string.Empty;
      List qData = new List();
      foreach (var r in rules)
      {
         if (string.Compare(r.Type, "field", true) == 0)
         {
            SPField field = ruleList.Fields.GetField(r.Field);
            qData.Add(new QueryData() { Field = field.InternalName, Value = currentItem.GetItemProperty(r.Field), Type = field.TypeAsString });
          }
          if (string.Compare(r.Type, "result", true) == 0)
               approverFieldName = r.Field;
      }
    
      SPListItem val = ruleList.GetItems(new SPQuery() { Query = QueryData.BuildQuery(qData) }).Cast().FirstOrDefault();
    
      this.ReturnValue = Utility.ParseUserValues(val[approverFieldName] as SPFieldUserValueCollection));
    
      return ActivityExecutionStatus.Closed;
    }
    
  3. The last configuration that need to be done is registering the workflow as authorized type in the web.config under System.Workflow.ComponentModel.WorkflowCompiler element add this
    <authorizedType Assembly="MyCustom, Version=1.0.0.0, Culture=neutral, PublicKeyToken=accb8d1cf1b32c7f" Namespace="MyCustom.Workflow.Actions" TypeName="*" Authorized="True" />
    
    After deploying the solution, perform IIS Reset. Then our custom activity would be available in SPD Workflow Actions drop down.

In the next post I will show how to modify this custom activity to feed approvers to the SharePoint 2010 OOB Approval Workflow.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, ASP.NET

6 thoughts on “SPD 2010 Custom Workflow Activity

  1. Can you please include specific steps in VS 2010 from scratch?
    “First create an actions file.” – create where in what context?

    Thank you

    [Reply]

    Daniel Laksana\ Reply:

    Hi, I’ve put a screen shot of the VS 2010 project. I hope it is clearer now. The development steps in VS should follow a normal SharePoint development steps.

    [Reply]

  2. Hi! Thanks for you effort!

    I wrote down the your code step by step.
    But, I can’t figured out the your utility classes such as QueryData and Utility methods.
    Could you share your utility classes?

    Best regards!

    [Reply]

    Daniel Laksana Reply:

    Hi,

    The QueryData just a utility to build CAML query and the ParseUserValue is a another utility to transform the SPFiedUserValueCollection to xml representation used by the approval workflow. I left this out for reader’s practise.

    [Reply]

  3. This is exactly what I needed for my current project. Thanks
    I made it with less parameters but for some reason it errors before Execute (or so I think because the debugger does not get there).
    To debug I attach to OWStimer.exe right?
    Any trubleshooting edvice?

    [Reply]

    Daniel Laksana Reply:

    Yes, attach OWSTimer

    [Reply]

Leave a Reply

Your email address will not be published. Required fields are marked *


4 + = seven

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">