Wednesday, August 19, 2009

How to create a SharePoint State Machine Workflow: Part 4 - Create the state machine workflow

Here in Part 4, we create the workflow and deploy it to the SharePoint server. I have done these steps with both Visual Studio 2005 and 2008. When deploying, it tells you what to do based on whether you are using 2005 or 2008. Also, I included VB as well as C# code depending on your tastes. It seems like most everybody is using C#. We use VB in-house traditionally so I figured I would stay with VB. You'll notice that there really isn't a whole lot of code here at all - just lots and lots of steps. This workflow works off of four states: InitialState, InitiatorApproval, PeerReviewerApproval and FinalState.

1. In Visual Studio 2005, File -> New -> Project -> SharePoint -> SharePoint Server State Machine, call it AgendaItemPeerReview
In Visual Studio 2008, File -> New -> Project -> Visual Basic -> Office -> 2007 -> SharePoint 2007 State Machine Workflow,
call it AgendaItemPeerReview
set location to d:\asp or whatever you want
Rename Workflow1.vb to PeerReviewWorkflow.vb and doubleclick it to open the design surface.
It will start out with a state called Workflow1InitialState. Rename Workflow1InitialState to InitialState.

Note: The workflow MUST have a different name than the solution/project. Do NOT rename file workflow1.cs (and thus the
workflow1 class) to the same name you chose as the solution/project.

2. add three more states and name them InitiatorApproval, PeerReviewerApproval and FinalState

3. right click workflow and set CompletedStateFinish property to the new state you created called FinalState
be sure InitialStateName is InitialState

We will build the workflow by adding activities to the various states.
Afterwards, we will come back and add the necessary code. I do it this way because some code is autogenerated while
other code must be written by us, and by building the whole workflow first, all the generated code will be together,
then all our code will be together below the generated code. Also, this provides more of a top-down approach to
designing (and understanding) the workflow.

4. in state InitialState, click eventDrivenActivity1 and rename it to InitialStateActivities

5. double-click this activity to drill down into it; in here we will create a sequential workflow; notice there is
already an onWorkflowActivated1 event
a. Rename onWorkflowActivated1 to onWorkflowActivated.
b. Drill down into CorrelationToken and change OwnerActivityName to PeerReviewWorkflow.
c. Drill down into WorkflowProperties and change Name to PeerReviewWorkflow.

6. in the Toolbox, find the Windows Workflow section and drag and drop a SetState event below onWorkflowActivated
a. rename setState activity to setStatePeerReviewerApproval
b. set TargetStateName property to PeerReviewerApproval
c. click on PeerReviewWorkflow to go back to workflow view; notice the line drawn from InitialState to
state PeerReviewerApproval

For the two approval states, we are going to add the same stuff, ie initialization code for when a state is entered,
code to execute while in the state, and code to execute when the state is completed.

7. for each of the two states, drag and drop into them:
a. a StateInitialization activity
b. an EventDriven activity
c. a StateFinalization activity

8. in stateInitiatorApproval, rename the activities to:
InitiatorApprovalInitialization
InitiatorApprovalActivities
InitiatorApprovalFinalization

9. in statePeerReviewerApproval, rename the activities to:
PeerReviewerApprovalInitialization
PeerReviewerApprovalActivities
PeerReviewerApprovalFinalization

We will now add the specific tasks within each of these three activities (initialization, activities, finalization) for each state.
Essentially, we will be creating sequential workflows within each of these activities.

10. in InitiatorApproval, double-click InitiatorApprovalInitialization to drill down into it

11. drag and drop a CreateTask activity and set the following properties:
a. rename it createInitiatorApprovalTask
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, click the "Bind to a new member" tab, remove the 1 from the end of the "New member name"
and click OK
d. follow the same step for TaskProperties
e. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface

12. double-click InitiatorApprovalFinalization to drill down into it

13. drag and drop a CompleteTask activity and set the following properties:
a. rename it completeInitiatorApprovalTask
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createInitiatorApprovalTask_TaskId
d. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface

14. double-click InitiatorApprovalActivities to drill down into it

15. drag and drop an OnTaskChanged activity and set the following properties:
a. rename it onInitiatorApprovalChanged
b. set CorrelationToken to InitiatorApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createInitiatorApprovalTask_TaskId
d. for AfterProperties and BeforeProperties, click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK

16. drag and drop an IfElse activity and rename it InitiatorApprovalStatus

17. click on the left branch and set the following properties:
a. rename it ifInitiatorApproved
b. for Condition, click Code Condition

18. drag and drop a setState activity under ifInitiatorApproved
a. rename it setStatePeerReviewerApproval
b. set the TargetStateName to PeerReviewerApproval


19. click PeerReviewWorkflow to go back to higher-level view of workflow

20. in PeerReviewerApproval, double-click PeerReviewerApprovalInitialization to drill down into it

21. drag and drop a CreateTask activity and set the following properties:
a. rename it createPeerReviewerApprovalTask
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
PeerReviewerApproval
c. for TaskId, click the ellipses, click the "Bind to a new member" tab, remove the 1 from the end of the "New member name"
and click OK
d. follow the same step for TaskProperties
e. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface

22. double-click PeerReviewerApprovalFinalization to drill down into it

23. drag and drop a CompleteTask activity and set the following properties:
a. rename it completePeerReviewerApprovalTask
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
PeerReviewerApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createPeerReviewerApprovalTask_TaskId
d. exit this sequential workflow view by clicking on PeerReviewWorkflow on the design surface

24. double-click PeerReviewerApprovalActivities to drill down into it

25. drag and drop an OnTaskChanged activity and set the following properties:
a. rename it onPeerReviewerApprovalChanged
b. set CorrelationToken to PeerReviewerApprovalToken; click the + next to it to set the OwnerActivityName to
InitiatorApproval
c. for TaskId, click the ellipses, on the "Bind to an existing member" tab, click createPeerReviewerApprovalTask_TaskId
d. for AfterProperties and BeforeProperties, click the ellipses, click the "Bind to a new member" tab,
remove the 1 from the end of the "New member name" and click OK

26. drag and drop an IfElse activity and rename it PeerReviewerApprovalStatus

27. click on the left branch and set the following properties:
a. rename it ifPeerReviewerApproved
b. for Condition, click Code Condition

28. drag and drop a setState activity under ifPeerReviewerApproved
a. rename it setStateFinalState
b. set the TargetStateName to FinalState

29. click on the right branch and set the following properties:
a. rename it ifPeerReviewerRejected
b. for Condition, click Code Condition

30. drag and drop a setState activity under ifPeerReviewerRejected
a. rename it setStateInitiatorApproval
b. set the TargetStateName to InitiatorApproval




Now we are ready to start adding code. First we will add code in InitialState, for when the workflow starts.

31. Go to the code-behind and find the following line (this is the VB version, but the C# version looks almost identical):
Public workflowProperties As SPWorkflowActivationProperties = New Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties

Above it, type:

VISUAL BASIC
------------
Public workflowId As System.Guid

VISUAL C#
---------
public Guid workflowId = default(System.Guid);


Below the workflowProperties line, type:

VISUAL BASIC
------------
Dim PeerReviewer As String
Dim CC As String
Dim Instructions As String

VISUAL C#
---------
private String PeerReviewer = default(String);
private String CC = default(String);
private String Instructions = default(String);


32. back on design surface, double-click InitialStateActivitites to drill down into it

33. double-click onWorkflowActivated. This will take you to the code behind and show the generated code stub
onWorkflowActivated_Invoked. Copy and paste the following code into it:

VISUAL BASIC
------------
workflowId = workflowProperties.WorkflowId

Dim serializer As XmlSerializer = New XmlSerializer(GetType(InitForm))
Dim reader As XmlTextReader = New XmlTextReader(New System.IO.StringReader(workflowProperties.InitiationData))
Dim initform As InitForm = CType(serializer.Deserialize(reader), InitForm)


Dim prPeerReviewer() As Person
prPeerReviewer = initform.gpPeerReviewer
PeerReviewer = prPeerReviewer(0).AccountId

Dim prCC() As Person
prCC = initform.gpCC
CC = prCC(0).AccountId

Instructions = initform.txtInstructions


VISUAL C#
---------
workflowId = workflowProperties.WorkflowId;

XmlSerializer serializer = new XmlSerializer(typeof(InitForm));
XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(workflowProperties.InitiationData));
InitForm initform = (InitForm)serializer.Deserialize(reader);


Person prPeerReviewer = new Person();
prPeerReviewer = (Person)initform.gpPeerReviewer[0];
//PeerReviewer = prPeerReviewer.AccountId.GetValue(0).ToString();
PeerReviewer = prPeerReviewer.AccountId;

Person prCC = new Person();
prCC = (Person)initform.gpCC[0];
//CC = prCC.AccountId.GetValue(0).ToString();
CC = prCC.AccountId;

Instructions = initform.txtInstructions;



34. go to the top of PeerReviewWorkflow.vb and add the following code:

VISUAL BASIC
------------
imports System.Xml.Serialization
imports System.Xml

VISUAL C#
---------
using System.Xml.Serialization;
using System.Xml;


Next, we will add code to the InitiatorApproval state.

35. go back to the design surface and double-click InitiatorApprovalInitialization to drill down into it

36. double-click createInitiatorApprovalTask to be taken to its MethodInvoking code stub. Copy and paste
the following code in it.


VISUAL BASIC
------------
createInitiatorApprovalTask_TaskId = Guid.NewGuid
createInitiatorApprovalTask_TaskProperties = new SPWorkflowTaskProperties
createInitiatorApprovalTask_TaskProperties.Title = "Initiator Approval of " & workflowProperties.Item.File.Name
createInitiatorApprovalTask_TaskProperties.AssignedTo = workflowProperties.Originator
createInitiatorApprovalTask_TaskProperties.Description = ""
createInitiatorApprovalTask_TaskProperties.TaskType = 0
createInitiatorApprovalTask_TaskProperties.ExtendedProperties("txtInstructions") = Instructions


VISUAL C#
---------
createInitiatorApprovalTask_TaskId = Guid.NewGuid();
createInitiatorApprovalTask_TaskProperties = new SPWorkflowTaskProperties();
createInitiatorApprovalTask_TaskProperties.Title = "Initiator Approval of " + workflowProperties.Item.File.Name;
createInitiatorApprovalTask_TaskProperties.AssignedTo = workflowProperties.Originator;
createInitiatorApprovalTask_TaskProperties.Description = "";
createInitiatorApprovalTask_TaskProperties.TaskType = 0;
createInitiatorApprovalTask_TaskProperties.ExtendedProperties["txtInstructions"] = Instructions;


37. go back to the design surface and double-click InitiatorApprovalActivities to drill down into it

38. double-click onInitiatorApprovalChanged to be taken to its Invoked code stub. Copy and paste
the following code in it.

VISUAL BASIC
------------
onInitiatorApprovalChanged_AfterProperties = onInitiatorApprovalChanged.AfterProperties
onInitiatorApprovalChanged_BeforeProperties = onInitiatorApprovalChanged.BeforeProperties

VISUAL C#
---------
onInitiatorApprovalChanged_AfterProperties = onInitiatorApprovalChanged.AfterProperties;
onInitiatorApprovalChanged_BeforeProperties = onInitiatorApprovalChanged.BeforeProperties;


39. go back to the design surface and click ifInitiatorApproved and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type InitiatorApproved and hit enter
b. you will be taken to the code stub for InitiatorApproved routine; copy and paste the following
code into it:

VISUAL BASIC
------------
If onInitiatorApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "accepted" Then
e.Result = True
End If

VISUAL C#
---------
if (onInitiatorApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "accepted")
{
e.Result = true;
}


Next, we will add code to the PeerReviewerApproval state.

40. go back to the design surface and double-click PeerReviewerApprovalInitialization to drill down into it

41. double-click createPeerReviewerApprovalTask to be taken to its MethodInvoking code stub. Copy and paste
the following code in it.


VISUAL BASIC
------------
createPeerReviewerApprovalTask_TaskId = Guid.NewGuid
createPeerReviewerApprovalTask_TaskProperties = new SPWorkflowTaskProperties
createPeerReviewerApprovalTask_TaskProperties.Title = "Peer Reviewer Approval of " & workflowProperties.Item.File.Name
createPeerReviewerApprovalTask_TaskProperties.AssignedTo = PeerReviewer
createPeerReviewerApprovalTask_TaskProperties.Description = ""
createPeerReviewerApprovalTask_TaskProperties.TaskType = 0
createPeerReviewerApprovalTask_TaskProperties.ExtendedProperties("txtInstructions") = Instructions


VISUAL C#
---------
createPeerReviewerApprovalTask_TaskId = Guid.NewGuid();
createPeerReviewerApprovalTask_TaskProperties = new SPWorkflowTaskProperties();
createPeerReviewerApprovalTask_TaskProperties.Title = "Peer Reviewer Approval of " + workflowProperties.Item.File.Name;
createPeerReviewerApprovalTask_TaskProperties.AssignedTo = PeerReviewer;
createPeerReviewerApprovalTask_TaskProperties.Description = "";
createPeerReviewerApprovalTask_TaskProperties.TaskType = 0;
createInitiatorApprovalTask_TaskProperties.ExtendedProperties["txtInstructions"] = Instructions;


42. go back to the design surface and double-click PeerReviewerApprovalActivities to drill down into it

43. double-click onPeerReviewerApprovalChanged to be taken to its Invoked code stub. Copy and paste
the following code in it.

VISUAL BASIC
------------
onPeerReviewerApprovalChanged_AfterProperties = onPeerReviewerApprovalChanged.AfterProperties
onPeerReviewerApprovalChanged_BeforeProperties = onPeerReviewerApprovalChanged.BeforeProperties

VISUAL C#
---------
onPeerReviewerApprovalChanged_AfterProperties = onPeerReviewerApprovalChanged.AfterProperties;
onPeerReviewerApprovalChanged_BeforeProperties = onPeerReviewerApprovalChanged.BeforeProperties;


44. go back to the design surface and click ifPeerReviewerApproved and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type PeerReviewerApproved and hit enter
b. you will be taken to the code stub for PeerReviewerApproved routine; copy and paste the following
code into it:

VISUAL BASIC
------------
If onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "accepted" Then
e.Result = True
End If

VISUAL C#
---------
if (onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "accepted")
{
e.Result = true;
}


45. go back to the design surface and click ifPeerReviewerRejected and set the following properties:
a. drill down into the Condition property (already set to Code Condition), and next to the second
Condition property, type PeerReviewerRejected and hit enter
b. you will be taken to the code stub for PeerReviewerApproved routine; copy and paste the following
code into it:

VISUAL BASIC
------------
If onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties("status").ToString = "rejected" Then
e.Result = True
End If

VISUAL C#
---------
if (onPeerReviewerApprovalChanged_AfterProperties.ExtendedProperties["status"].ToString() == "rejected")
{
e.Result = true;
}


46. sign the assembly
a. right-click the project, then click properties
b. click Signing
c. if Sign the assembly is not already checked...
d. check Sign the assembly
e. choose <new> from the dropdown
f. enter MyKeyFile for the key file name
g. uncheck Protect my key file with a password
h. click OK

47. follow instructions above for creating the workflow's Initiation and Task forms using Microsoft Office InfoPath

48. If developing in Visual Studio 2005: copy PeerReviewInit.xsn and PeerReviewTask.xsn to DeploymentFiles\FeatureFiles folder

49. be sure feature.xml is set up correctly
a. title: Agenda Item Peer Review feature
b. description: This feature installs the Agenda Item Peer Review workflow
c. if deploying via Visual Studio 2008:
under the <ElementManifests> tag, add:
<ElementFile Location="PeerReviewInit.xsn" />
<ElementFile Location="PeerReviewTask.xsn" />

50. be sure workflow.xml is set up correctly
a. name: AgendaItemPeerReview
b. description: This is the Agenda Item Peer Review workflow
c. CodeBesideClass: AgendaItemPeerReview.PeerReviewWorkflow
d. after CodeBesideAssembly, you must have the following:
TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"
e. after TaskListContentTypeId, you must have the following:
AssociationUrl="_layouts/CstWrkflIP.aspx"
InstantiationUrl="_layouts/IniWrkflIP.aspx"
ModificationUrl="_layouts/ModWrkflIP.aspx"
f. copy initiation form's urn into the <Association_FormURN> and <Instantiation_FormURN> tags
g. copy task form's urn into the <Task0_FormURN> tag

51. deploy solution
a. Visual Studio 2005: right click project, click Properties, click Build Events, change NODEPLOY to
DEPLOY at the end of the Post-build event command line, click Build, click Rebuild Solution
b. Visual Studio 2008: click Build, then click Deploy Solution

52. The first time you deploy it, the AgendaItemPeerReview assembly is added to the GAC. The workflow.xml
file needs the assembly's public key token.
a. Start, Settings, Control Panel, Administrative Tools, Microsoft .NET Framework 2.0 Configuration
b. click Manage the Assembly Cache
c. click View List of Assemblies in the Assembly Cache
d. right-click on AgendaItemPeerReview and click Properties
e. copy the public key token and click OK
f. paste the public key token in workflow.xml where you find PublicKeyToken
g. redeploy solution (step 51)





Another method for retrieving the assembly's public key token
-------------------------------------------------------------
If you want to skip step 52, retrieve the public key token using the sn.exe utility then
update workflow.xml (step 50) with it

1. copy sn.exe from c:\program files\microsoft visual studio 8\sdk\v2.0\bin to d:\
2. change directory to d:\asp\agendaitempeerreview\agendaitempeerreview\bin\debug
3. d:\sn -T agendaitempeerreview.dll
4. copy (or type) the resulting public key token into the workflow.xml

Posts in this series:
Part 1: Introduction
Part 2: Create the initiation form
Part 3: Create the task form
Part 4: Create the state machine workflow
Part 5: Add workflow history logging
Part 6: Add task notification emails

5 comments:

  1. In step 25b when I try to set the OwnerActivityName, InitiatorApproval is not available as an option for me to select. Did I miss (or screw up) a step?

    ReplyDelete
  2. In step 34 when I type "using system.xml" intellisense doesn't pick it up and capitalize as usual, and I get errors with XmlTextReader that say I am missing an assembly or directive.

    ReplyDelete
  3. hi,
    if i use FullTrust For TaskForm or Initiation Form ,the Status of the Form Under 'Manage Form Template' is always "Installing".
    i would slove this by giving the Trust Level to Domain.
    But my Problem is i am want to call a WebService From Task Form Which i Can't able do even in Infopath Client with Domain Trust.
    Pls Help me to do this,
    and my boubts are can we use Full Trust Forms as Task Forms if so what are the additional steps to do this,and secound one is can we call a webservice from infopath form with out FullTrust

    ReplyDelete
  4. hi, i have solved it by storing the data connection in SharePoint DataConnection Library but give me a idea that can we use FullTrust Froms as either of the TaskForm ,Initiation

    ReplyDelete
  5. Rajendra,
    I am not sure I understand. Are you saying you want to make the form Full Trust? If so, Part 2, Step 16c tells you how to make the form Full Trust. To use the forms, steps 48 - 50 in this article tell you where to deploy them and how to properly configure the feature.xml and workflow.xml.

    ReplyDelete