Experiences building SharePoint Designer Custom Workflow Actions


Introduction

For some reason I have shyed away from creating custom SharePoint Designer Workflow Actions. However, one of my clients uses a fair amount of SharePoint designer based workflows.

The requirement was to retrieve the workflow id so that they could provide a user a link to the workflow status page.

With the out of the box workflow actions this is not possible. So the only method would be to build a custom workflow action. Initially, I thought the process was going to be pretty straightforward. Over the last few years I have read plenty of articles about how to build custom actions including a few of the pitfalls. However, the process wasn’t as straight forward as I thought and I hit a few problems. This blog entry will hopefully share my experiences and
explain how I figured them out. In the end the final error was a stupid eror! Though it came about through the process of fixing a previous error I had. Hopefully my experience will help others to figure this out quicker, so here goes.

Using Visual Studio I created a new Visual Studio project using the Workflow Activity project type. The first discussion to make was whether to build from the SequentialActivity or Activity base class. I decided to use a SequentialActivity base class.

The following code was created:-

public partial class GetWorkflowInstanceInfoActivity : SequenceActivity
    {
        #region Dependency Properties

        //The SharePoint workflow properties activation object
        public static DependencyProperty __ActivationPropertiesProperty = DependencyProperty.Register("__ActivationProperties", typeof(SPWorkflowActivationProperties), typeof(GetWorkflowInstanceInfoActivity));

        // Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty.  This enables animation, styling, binding, etc...
        public static DependencyProperty WorkflowInstanceIdVariableProperty = DependencyProperty.Register("WorkflowInstanceIdVariable", typeof(string), typeof(GetWorkflowInstanceInfoActivity));

        // Using a DependencyProperty as the backing store for WorkflowInstanceIdProperty.  This enables animation, styling, binding, etc...
        public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(GetWorkflowInstanceInfoActivity));

        [Description("The WorkflowInstanceId for the workflow.")]
        [ValidationOption(ValidationOption.Required)]
        public string WorkflowInstanceIdVariable
        {
            get { return (string)(base.GetValue(WorkflowInstanceIdVariableProperty)); }
            set { base.SetValue(WorkflowInstanceIdVariableProperty, value); }
        }

        [Description("The Workflow Properties")]
        [ValidationOption(ValidationOption.Required)]
        public SPWorkflowActivationProperties __ActivationProperties
        {
            get { return (SPWorkflowActivationProperties)(base.GetValue(__ActivationPropertiesProperty)); }
            set { base.SetValue(__ActivationPropertiesProperty, value); }
        }

        [Description("The Workflow Context object")]
        [ValidationOption(ValidationOption.Optional)]
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public WorkflowContext __Context
        {
            get
            {
                return (WorkflowContext)(base.GetValue(__ContextProperty));
            }

            set
            {
                base.SetValue(__ContextProperty, value);
            }
        }
        #endregion

        public GetWorkflowInstanceInfoActivity()
        {
            InitializeComponent();
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            ActivityExecutionStatus status = ActivityExecutionStatus.Faulting;
            try
            {
                //build up the workflow information class.
                if (__ActivationProperties != null)
                {
                    WorkflowInstanceIdVariable = __ActivationProperties.WorkflowId.ToString();
                }

                status = ActivityExecutionStatus.Closed;
            }
            catch (Exception ex)
            {
                status = ActivityExecutionStatus.Faulting;

                throw;
            }
            return status;
        }
    }

In order for project to work two references were added to the project, these were Microsoft.SharePoint.dll and Microsoft.SharePoint.WorkflowActivities.dll.

Within the code there are two special properties, __ActivationProperties and __Context. Both these properties are always made available by the SharePoint Designer workflow actions framework. This post by Phil Allen is incredibly useful and explains a lot about how the SharePoint Designer workflow actions work: http://blogs.msdn.com/b/sharepointdesigner/archive/2007/09/30/getting-to-workflow-information.aspx

The following properties are provided by the SharePoint Designer workflow action framework:

Property Name Value it is set to .Net type
__ActivationProperties __initProps, the SPWorkflowActivationProperties that was passed in via OnWorkflowActivated Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties
__ListId __list, the name of the SPList the workflow is executing on. This is typically a string datatype which contains the text of a System.Guid. System.String
__ListItem __item, the integer value of the list item in the list that the workflow is running on. System.Int32
__Context __context, the object that contains many helper methods and objects for use as part of workflow actions Microsoft.SharePoint.WorkflowActions.WorkflowContext

Once the workflow assembly compiles then it needs to be signed as the assembly needs to be deployed to the GAC.

Next a .ACTIONS file is created. This file describes how SharePoint Designer displays the Custom Action within the SharePoint Designer Workflow builder.

This ACTION file should be deployed in the 12HIVE\Templates\1033\Layouts\Workflow folder. To help you understand the ACTIONS file take a look at the WSS.ACTIONS which describes the out of the box actions.

Our action file should look like this:-

  <?xml version=”1.0″ encoding=”utf-8″ ?> <WorkflowInfo> <Actions Sequential=”then” Parallel=”and”> <Action Name=”Get WorkflowInstanceId”
ClassName=”ITSP.Foundation.SPDActivities.GetWorkflowInstanceInfoActivity”
Assembly=”ITSP.Foundation.SPDActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a52a37e312a5558″
AppliesTo=”all”
CreatesTask =”false”
Category=”Custom”> <RuleDesigner Sentence=”Store WorkflowInstanceID in %1″ AppliesTo=”all” Name=”AssignWorkflowInstanceVariable”> <FieldBind Field=”WorkflowInstanceIdVariable” Text=”variable” Id=”1″ DesignerType=”ParameterNames” /> </RuleDesigner> <Parameters> <Parameter Name=”WorkflowInstanceIdVariable” Type=”System.String, mscorlib” Direction=”Out” /> <Parameter Name=”__Context” Type=”Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions” Direction=”In” /> <Parameter Name=”__ActivationProperties” Type=”Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePoint” Direction=”Out” /> </Parameters> </Action> </Actions> </WorkflowInfo>  

Once the ACTION file has been created then we need to deploy our custom activity using the SharePoint solution framework. For this I recommend using WSPBuilder (http://wspbuilder.codeplex.com).

Once the Custom Action has been built as a solution deploy the solution file then deploy using stsadm –o addsolution and deploy with stsadm –o deploysolution.

The final two steps are :-

  • Ensure SharePoint can load our assembly reference to the web.config
  • Ensure that SharePoint Designer can trust the assembly

To add our assembly to the web.config add the following line within the <assemblies> </assemblies> elements :-

<add assembly=”ITSP.Foundation.SPDActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a52a37e312a5558″ />

This should be added to the web.config file which belongs to the web application in which the workflow will run.

To ensure that SharePoint Designer is able to trust the assembly then need to add another configuration setting to the same web.config file.

This should be added to the <authorisedTypes></authorisedTypes> elements.

  <authorizedTypes> <authorizedType Assembly=”ITSP.Foundation.SPDActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a52a37e312a5558″ Namespace=”ITSP.Foundation.SPDActivities” TypeName=”*” Authorized=”True” /> </authorizedTypes>  

Take special notice to the Namespace tag, I have seen examples on the web that state this should be ITSP.Foundation.SPDActivities.* when this was used then SharePoint Designer could not add the action to the workflow.

Once the solution has been deployed and the assemblies setup within the web.config we are ready to the test the workflow.

Fire up SharePoint Designer and open up the site and then:-

  • Click New->Workflow
  • Choose the Blank Workflow
  • Give the workflow a name
  • Click Next
  • Give the workflow step a name such as Get WorkflowID
  • Click on the Actions button and click the last list item to display all workflow actions available. From the categories you should have a custom group, choose Get Workflow Instance ID
  • This should now display the action and allow you to assign a variable to store the variable into.
  • Finally create another action, this time use Build a Dynamic String. This will be used to build a string and store our concatenated string to the workflow history log. Create a variable called LoggingVariable to store the results of the Build Dynamic String Action
  • Click on the f(x) button and then type “This is the workflow id: “ and then insert the variable that was used to retrieve the workflow instance id.
  • Finally create another action using “Log to History” to log the LoggingVariable into the history log
  • Click Check Workflow, everything should be ok and then click Finish.

Finally test the workflow.

All of this took a while to get working and I liked to explains some of the issues I had and then the method that was used to fix them.

Issues

Issue 1

Once the action file was created it did not appear in SharePoint designer. The fix: restart SharePoint designer and add an entry to SharePoint’s web application web.config within the <authorizedTypes> tag.

Issue 2

When the custom action did show up in SharePoint Designer, and was selected to be added as a Workflow Action nothing would show. This was because of a problem with Namespace attribute in the <authorizedType> element.

The value was ITSP.Foundation.SPDActivities.* as soon as this was changed to ITSP.Foundation.SPDActivities then the action was entered in correctly. This didn’t require a restart of SharePoint designer.
eg.

  <authorizedTypes> <authorizedType Assembly=”ITSP.Foundation.SPDActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a52a37e312a5558″ Namespace=”ITSP.Foundation.SPDActivities” TypeName=”*” Authorized=”True” /> </authorizedTypes>  

Issue 3

This problem took the longest to solve and I have seen loads of people post similar problems. This issue seemed to only affect any output fields, particularly ones which used a Field Binding with DesignerType=”ParameterNames”. The symptoms would be that once the action was added in a workflow step and you tried to select a variable to store the action’s result in it would reset back to nothing. That is the variable would never be stored properly.

The problem was caused by the action file assembly details being incorrect. The ActivityClass was set to ITSP.Foundation.SPDActivities.GetWorkflowInfo and not ITSP.Foundation.SPDActivities.GetWorkflowInfoActivity.

<Action Name=”Get WorkflowInstanceID”      ClassName=”ITSP.Foundation.SPDActivities.GetWorkflowInstanceInfoActivity”            Assembly=”ITSP.Foundation.SPDActivities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a52a37e312a5558″
AppliesTo=”all”
CreatesTask =”false”
Category=”Custom”>

This issue came about because of changes made when trying to fix issue 2.

Once again Phil Allen’s article helped a lot and I ended up also updating the Actions parameter file to include the following:-
The important part in the <Parameters> elements is the Type field which should include both Class and Assembly.
<Parameters>
<Parameter Name=”WorkflowInstanceIdVariable” Type=”System.String, mscorlib” Direction=”Out” />
<Parameter Name=”__Context” Type=”Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions” Direction=”In” />
<Parameter Name=”__ActivationProperties” Type=”Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties, Microsoft.SharePoint” Direction=”Out” />
</Parameters>

Note: because I was using a Parameter which had the class SPWorklowActivationProperties I had to include a reference to Microsoft.SharePoint.dll.

So how did I resolve issue 3? Well when a workflow is created within SharePoint Designer there is a moment where SharePoint downloads details of the workflow actions which are available. In doing this it cache’s the various assemblies and creates a proxy stub which is stored on your machine.
This is the reason why you need to restart SharePoint designer after you make changes to the assembly.
This proxy stub is found in your user’s %userprofile%\Local Settings\Application Data\Microsoft\WebsiteCache\ directory. Within this directory is a directory for each website that there are assembly caches for. I opened up the assembly proxy in .NET Reflector. Once opened this allowed me to workout that there were two classes in this assembly. The class that my ACTIONS file was pointing to was the wrong one!
So there were two classes being :-

  • ITSP.Foundation.SPDActivities.GetWorkflowInstanceInfoActivity
  • ITSP.Foundation.SPDActivities.GetWorkflowInstanceInfo.

It turns out that I had forgotten to rename the class properly. I had updated the workflow activity code file but not the designer.cs file!!

Once the assembly was fixed, solution redeployed and SharePoint Designer restarted then everything worked beautifully.

You can download the projects here:

 

Please let me know if you found this post useful!

Good luck

Simon

>Custom Exception Objects in Workflows


>The more you use Windows Workflow Foundation, the more you get to understand it. In the past, I have been a bit lazy with exception handling and just used out of the box .NET exception types.

Anyway to improve the resilency of a workflow I decided to implment a custom exception. Pretty straight forward I thought.

However when I got to test it the exception was raised correctly and all looked well an error appeared in the workflow history. However when the workflow went to rehydrate it errored out with the following error:-

System.Workflow.Activities.EventDeliveryFailedException: Event “OnTaskChanged”
on interface type “Microsoft.SharePoint.Workflow.ITaskService” for instance id
“80742821-239e-4740-b19b-7ad2885ff6ba” cannot be delivered. —>
System.Runtime.Serialization.SerializationException: The constructor to
deserialize an object of type
‘ITSP.CC.SimpleApprovalWorkflow.TaskNotUpdatedByAssignedUser’ was not found.
—> System.Runtime.Serialization.SerializationException: The constructor to
deserialize an object of type
‘ITSP.CC.SimpleApprovalWorkflow.TaskNotUpdatedByAssignedUser’ was not
found. at
System.Runtime.Serialization.ObjectManager.GetConstructor(Type t, Type[]
ctorParams) at
System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object
obj, Serializatio…

Anyway I wrote a document which has been used as the base of this post so here it is.

What do I do now?
This article will describe a couple of issues that were discovered when implementing custom exceptions within a SharePoint / Windows Workflow Foundation workflow.
The custom exception was used in a fault handling activity and assigned as one of the types of exceptions that could be handled by a fault handler.
When selecting the exception you choose the type of exception and also bind a property to the fault property for that exception handler.
This is fine, though workflows generally do not start and finish in one go, they normally have to pause and wait for some user interaction or system interaction. In order to not waste system resources the workflows hydrate (go to sleep) and dehydrate (wake up) when the workflow has done all the work that it needs to do.
When the workflow hydrates and dehydrates it calls each of the objects serialization routines. This is normally fine as in order to make an object serializable we add the [Serializable] attribute above the class definition.
Now the workflow is able to serialize the class … great.

Great?
Well not quite this works brilliantly when all the properties (member variables) are public as the serialization engine just serializes them, though for member variables that are protected or private the engine needs some assistance in case it records some data, making it public when its really sensitive.
So lets not do anything and see what happens… well when the workflow wakes up to process an event an error occurs:-

System.Workflow.Activities.EventDeliveryFailedException: Event “OnTaskChanged”
on interface type “Microsoft.SharePoint.Workflow.ITaskService” for instance id
“80742821-239e-4740-b19b-7ad2885ff6ba” cannot be delivered. —>
System.Runtime.Serialization.SerializationException: The constructor to
deserialize an object of type
‘ITSP.CC.SimpleApprovalWorkflow.TaskNotUpdatedByAssignedUser’ was not found.
—> System.Runtime.Serialization.SerializationException: The constructor to
deserialize an object of type
‘ITSP.CC.SimpleApprovalWorkflow.TaskNotUpdatedByAssignedUser’ was not
found. at
System.Runtime.Serialization.ObjectManager.GetConstructor(Type t, Type[]
ctorParams) at
System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object
obj, Serializatio…

To fix this we need to provide a constructor to deserialize the object and also a method so that the runtime and properly serialize the object.
Serialization support is provided by implementing the ISerializable interface and this interface requires to functions.
These are handled by the following functions.

public virtual void GetObjectData(SerializationInfo info, StreamingContext
context)·

protected MyObject(SerializationInfo info,
StreamingContext context)

GetObjectData is called to get the serialization data and this is where we tell the object to also put the private / protected member variable definitions.
The protected constructor provides a way to get the serialized information in SerializationInfo object back into the private/protected member variables.

Now with the Custom exceptions we have added complexity in that the System.Exception object already implements the ISerialize interface so we need to override these functions and also call the base functions whilst handing our custom exception objects data.
The example below describes this:-

The deserization constructor is as follows:

protected TaskNotUpdatedByAssignedUser(SerializationInfo info, StreamingContext
context)

:
base
(info,context)

{

m_sUser
=
info.GetString(“m_sUser”);

m_sAssignedUser =
info.GetString(“m_sAssignedUser”);

m_sErrorMessage =
info.GetString(“m_sErrorMessage”);

}

The GetObjectData function is as follows:-

[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(“m_sUser”, m_sUser);
info.AddValue(“m_sAssignedUser”, m_sAssignedUser);
info.AddValue(“m_sErrorMessage”, m_sErrorMessage);
}

Once these functions have been implemented and the new code loaded then the workflow rehydrates as expected.

One tip:

remember to recycle the timer service as well as the app pool.

Use:

net stop sptimerv3 and net start sptimerv3.

Hope someone finds this useful!