Skip to main content

Cloud

Creating a MOSS State Machine Workflow (Part 3 of 4: Workflow Development)

In the first two posts in this series (here, here, and there’s an important addendum here), I discussed some of the initial analysis and InfoPath form creation that go into creating a SharePoint state-machine workflow. With those items done, it’s time to crack open Visual Studio and complete the coding.

A QUICK WORD OF ADVICE (AGAIN): If you haven’t done any workflow development before, don’t start here. Nick Swan has a good walkthrough for a basic sequential workflow. My opinion is that the sequential workflow is a much better place to cut your teeth. Once you’ve nailed the concepts there, come back and build your first state machine.

The first step is to create the Visual Studio project. To create a SharePoint workflow, you’ll need to have a couple of items installed in addition to VS2005:

Once you’ve got all that installed, you’ll see that there’s a SharePoint State Machine Workflow project template. Name your project State Machine Demo.

(While I highly recommend going through these steps to increase your familiarity with the process, I’m also making the code available at the bottom of this post, so feel free to save yourself some typing if you desire.)

Rename the Workflow1.cs file to DemoWorkflow.cs and then open up the workflow design surface.

A state machine workflow is a collection of states in which a process is in one and only one state at a given point in time. Its boundaries are a beginning state and an ending state; until it reaches its ending state, it can “live” indefinitely.

IMPORTANT NOTE: For the sake of keeping this walkthrough fairly simple and linear, I’m going to demonstrate creating all of your workflow’s states up front and then adding the code for each of them. I would not recommend doing this on a normal project: a better approach would be to get the workflow functioning in small chunks, testing as you go to make sure that all of the moving parts are working. (Anyone out there writing unit tests for workflows? I’d be interested in your thoughts.)

OK, caveat aside, the next step is to create the five states that reflect steps in the flow chart we created earlier. You can see that the initial state – Workflow1InitialState – which fires when the workflow is instantiated is already created for you. Rename this state to DemoWorkflowInitialState to match your class name. Finally, drag four additional states from the toolbox onto the design surface and name them as you see here.

As I mentioned, the initial state is already defined for you, but you have to tell the workflow which state is the “completed” state so that it knows when to stop executing.

You created a state called FinalState. To set this to be the “completed state, right-click on the FinalState state and select Set as completed state from the context menu. You’ll now see a little red indicator in the icon for the state on the design surface letting you know that this state is the end of the road for the workflow.

So far you’ve defined your states and specified the initial and completed states. The next step is to start connecting the dots so that the workflow accurately enforces the specified business process.

The first step is to do a little work when the workflow starts. Rename the eventDrivenActivity1 in the DemoWorkflowInitialState to WorkflowActivatedActivity, then double-click on it. Doing this will change the scope on your design surface to show you the workflow activities within that event-driven activity.

Next, select the onWorkflowActivated1 activity and set the properties as shown here.

The next step is to add the code for the OnWorkflowActivated_Invoked method.

In this method, you want to get the information that the user specified in both the association form and the initiation form. In the association form, the SharePoint administrator specified who the approver should be for all proposals for this workflow; in the initiation form, the owner of the proposal added comments about the proposal, and you’ll want to make sure those are logged appropriately.

The reason you went through the process of generating a C# class from the myschema.xsd files created by the InfoPath form was so that you can deserialize the XML contained in the AssociationData and InitiationData properties of the SPWorkflowActivationProperties class into a friendly class within your C# code; it’s also possible to simply go after that XML directly and extract the information you care about.

In order to implement the deserialization, you’ll need some classes in namespaces that aren’t currently being used, so add the necessary “using” statements to the DemoWorkflow class.

using System.Xml.Serialization;
using System.Xml;
using System.IO;

Next, choose to add an existing file to your solution. Browse to the location of the myschema.cs that you created when building the InfoPath forms and add it, then rename it to Main.cs. Remember, since the association and initiation forms use the same schema, you can deserialize the information from each of them using this same class.

So with all that said, here’s the implementation of the OnWorkflowActivated_Invoked method:

private void OnWorkflowActivated_Invoked(object sender, ExternalDataEventArgs e)

{

// get association data from the workflow

XmlSerializer serializer = new XmlSerializer(typeof(Main));

XmlTextReader reader = new XmlTextReader(new StringReader(this.workflowProperties.AssociationData));

Main assocForm = null;

assocForm = (Main)serializer.Deserialize(reader);

_approver = assocForm.Approver;

reader = new XmlTextReader(new StringReader(this.workflowProperties.InitiationData));

Main startForm = null;

startForm = (Main)serializer.Deserialize(reader);

LastComments = startForm.Comments;

}

One of the things that we want to do throughout this workflow is log any comments left by the users to the workflow history. Fortunately, there’s a LogToHistory activity that makes this pretty easy. The next step, then, is to return to the workflow design surface, drag a LogToHistory activity onto the surface immediately after the OnWorkflowActivated activity.

Several of the steps in the workflow allow the user to specify comments, and since this is a state machine, there’s no limit to how many times each of those steps might potentially execute. An easy way to log comments is to create a public field on your class that always contains the most recent comments left by the last user. Setting the value for this field will be done in the Invoking method for your task handlers (more on that later), but doing this allows for very easy configuration of the LogToHistory activities.

Add this code to the DemoWorkflow class:

public string LastComments = string.Empty;

With that done, set the properties on the LogToHistory activity as shown below.

Set the name to DemoWorkflow and the path to LastComments. When the LogToHistory activity executes, it will read the value in the LastComments field and log that data to the workflow history list. You can repeat this exact activity a number of times throughout the workflow, as each time that it appears, the value in LastComments could be different.

That’s all you need to do for the workflow initialization: grab some information from the association and initiation forms and log any comments to the workflow history.

Now it’s time to move on to the next state: pending manager review. State machines “hand off” control to another state by using the SetState activity.

Drag a SetState activity onto the design surface and position it immediately after the LogToHistory activity that was just created, and then set the properties as shown.

Note that the TargetStateName property allows you to specify which state will receive “control.” When you navigate back up one level on your workflow (done using the breadcrumbs on the design surface), you’ll also see that the WF designer has now drawn a line from the initial state to the ManagerReviewState; it’s done this because of how you set the properties in the SetState activity.

Next up is the Manager Review state. The initial and completed states are special cases: most of the “interior” states of the state machine – including the Manager Review state that you’re starting – have three components. The first is a StateInitialization, which is a set of activities that execute when the workflow enters that state. The next is one or more EventDriven activities that are triggered by events on tasks or other items of which the workflow is aware. The last is a StateFinalization, which is a set of activities that execute when the machine is changing states; the finalization activities of the current state fire before the initialization activities of the new state.

To bring it home a little, the pattern you’re building in this workflow is that a task is created for a user in the StateInitialization, an EventDriven activity listens for the TaskChanged event and responds to the input of the user from the task form, and the task is marked as complete in the StateFinalization.

So with all that said, the next step is to add the StateInitialization for the ManagerReviewState. Do this by right-clicking on the ManagerReviewState on the design surface and clicking Add StateInitialization from the context menu.

Rename the StateInitializationActivity to StateInit_ManagerReview.

SIDE NOTE: For those of you experienced with workflow development: I haven’t found a naming convention for workflow development that strikes a chord with me yet; have any suggestions?

As I mentioned earlier, the role of the initialization activity is to create a task. To do this, double-click on the StateInit_ManagerReview activity, then drag a CreateTask activity from the toolbar onto the design surface and set its properties.

Take special note of the CorrelationToken property. This property is what allows the workflow engine to match up events on items over the life of the workflow. For example, we will create additional activities to listen for changes to this task and eventually to complete it. The way that the engine knows to do those things for this specific task and not for any of the others that might be in the list is by having a consistent correlation token. You’ll see that be expanding that property, you can also set an OwnerActivityName, which is essentially setting the scope for the correlation. In this example, we won’t have any tasks that cross states, so I’ve set all the correlation tokens to be scoped to the local state. If you were going to have a task that crossed transitions between states in your state machine, you might want to scope it to the workflow.

Next, you will need to specify values for the TaskId and TaskProperties properties. To do this, click the button for each property, then click the Bind to a new member tab. You will see dialogs as shown below. My recommendation is to use the Create Field option, as this leaves your code simpler; if you want to experiment with the Create Property option, do it after you’ve nailed your first workflow. In both cases, you can see that all you need to do is specify the name of the new field, and the wizard takes care of defining the type and creating the necessary code.

It’s time to take one step back: note that we added a value for the MethodInvoking property of the task creation event. Jump into code view and add the code for the method specified there.

private void CreateTask_ReviewTask_Invoked(object sender, EventArgs e)

{

ReviewTaskId = Guid.NewGuid();

ReviewTaskProperties.Title = "Review Proposal";

ReviewTaskProperties.AssignedTo = _approver;

ReviewTaskProperties.Description = LastComments;

ReviewTaskProperties.DueDate = DateTime.Today.AddDays(2);

ReviewTaskProperties.TaskType = TaskType_Review;

}

You can see in this method that you created a new task ID, set the title, approver, etc. You also set a property called TaskType. This property is an integer that you use in concert with values in your workflow.xml file (see next post) to specify which InfoPath form to load for each type of task. For this example, I suggest creating a set of constants to make your code a little more descriptive than simply setting a property to an integral value.

private const int TaskType_InProgress = 0;

private const int TaskType_Review = 1;

private const int TaskType_Customer = 2;

So now you’ve got a workflow that creates a new task and assigns it to the proposal approver. The next step is to add an activity to listen for when the approver actually gets around to reviewing the proposal. To do this, return to the “main” view of the entire state machine on the workflow design surface, then right-click on the ManagerReviewState and select Add EventDriven from the context menu.

Change the name to EventDriven_ReviewTaskChanged (in the properties panel), then drag an OnTaskChanged activity from the toolbox onto the design surface and set its properties.

Note that values for the AfterProperties and BeforeProperties. To set these, follow the same process you did above for creating the TaskProperties on the task creation step. Choose to create a new member and create a new field.

Also note that the value for the CorrelationToken is exactly the same as on the CreateTask activity created above.

What’s happening here? Well, the proposal approver has viewed the proposal approval InfoPath form and made a decision: that’s what triggers the OnTaskChanged activity. Now the workflow needs to take action. If the approver approved the proposal, the state should be set to the “Customer Review” state; if the approver denied the proposal, the state should be set back to the “In Progress” state so that changes can be made to the proposal and allow it to be resubmitted for approval.

To accomplish this, declare a private member called _approved that will store the value internally of whether or not the approver approved the proposal.

Now add the code for the method specified in the Invoked method.

private bool _approved = false;

private void OnReviewTaskChanged_Invoked(object sender, ExternalDataEventArgs e)

{

_approved = bool.Parse(ReviewTaskAfterProps.ExtendedProperties["Approved"].ToString());

}

What you’ve done here is read the value from the InfoPath form into your private variable. The ExtendedProperties property of the activities AfterProperties contains name/value pairs of the data that came from your InfoPath form. Since there is a field on the approval InfoPath form called “Approved,” it will be available to the workflow via the ExtendedProperties.

The next step is to branch based on whether or not the proposal was approved and set the state accordingly. To do this, drag an IfElse activity from the toolbar and place it directly after the OnTaskChanged activity. You’ll see that the IfElse activity has two child IfElseBranch activities: a left side and a right side. The left side represents the “if” part of the branch, while the right side represents the “else” side of it.

Next, drag a SetState activity into each of the if/else branches as shown here.

Now click on the left IfElseBranchActivity activity and set the properties as shown here.

If the condition you specify here evaluates to true, the left activity will be executed, otherwise the right one will be run.

The code for the ProposalApproved method looks like this:

private void ProposalApproved(object sender, ConditionalEventArgs e)

{

e.Result = _approved;

}

Note that it’s simply returning the value that you’ve already stored in the _approved variable. What you’ve done is said that if the value coming out of your InfoPath form indicated that the proposal was approved then execute the first IfElseBranchActivity; otherwise, execute the second (right) one.

Given that, set the properties on the two SetState activities as shown below.

The last step in the Manager Review state is to add the state finalization activity. In this example, you’ll add an activity to complete the review task to make sure that the task list gets cleaned up. Remember that your InfoPath form either requires a manager to approve or deny the proposal, so in either result, the task is complete.

To add the state finalization, right-click on the ManagerReviewState on the main workflow design surface and select Add StateFinalization from the context menu.

Rename the new activity to StateFinal_Review. Drag a CompleteTask activity onto the design surface.

Then set the properties for the CompleteTask property as shown below.

Remember that the CorrelationToken property is critical, as this is what tells the workflow engine that you want to complete the manager review task that was created.

For the sake of keeping this post somewhat readable, I’m not going to go through the same detail for each of the other states. The pattern is exactly the same:

  1. Create a state initialization, and within that create a task using the CreateTask activity
  2. Add an event-driven activity, and within that add an OnTaskChanged activity
  3. Store the results from the InfoPath form using the ExtendedProperties collection on the AfterProperties property of the OnTaskChanged activity
  4. Branch based on the results of the form and use SetState activities to drive the state machine to the next state. When the workflow is complete, use a SetState activity to set it to the state that you marked as the final state for the workflow.
  5. Add a state finalization, and within that complete the task using a CompleteTask activity.

In the end, your workflow design surface should look something like this:

At this point, assuming everything compiles, you’re ready to deploy and test. My next post (the last in this series) will briefly walk you through the deployment steps.

The code for this example can be found here, and the InfoPath forms can be found here. I hope it helps as you dive in to state machine workflow development.

Thoughts on “Creating a MOSS State Machine Workflow (Part 3 of 4: Workflow Development)”

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Matthew Morse

More from this Author

Follow Us