Developing Custom Workflow Templates


The primary goal of this chapter is to teach you how to create a custom workflow template for WSS and MOSS by using Visual Studio 2005. As you will see, you can use the workflow designer within Visual Studio to develop a WF program for each workflow template you want to produce. However, the Visual Studio project for a custom workflow template contains other source files, such as Collaborative Application Markup Language (CAML)-based XML files, to define the feature that will be used to deploy the workflow template. The project can optionally contain a set of .aspx pages used to create custom workflow input forms.

Once you successfully install WSS 3.0 on your development workstation, you already have all of the runtime support required to run workflows within WSS sites because installation of the .NET 3.0 framework is a prerequisite to installing WSS 3.0. Also note that the basic installation of WSS 3.0 adds several assemblies critical to the WSS workflow architecture, such as Microsoft.SharePoint.Workflows.dll and Microsoft.SharePoint.WorkflowActions.dll.

As mentioned earlier in this chapter, you need to install the Visual Studio Extensions for Windows Workflow Foundation before you begin to develop custom WF programs. This is the add-in created by the WF team, and it installs the workflow designer to Visual Studio. You can use this workflow designer to create WF programs whether your development efforts are targeting WSS or not.

Once you install the Visual Studio Extensions for Windows Workflow Foundation, your next step is to install one of the workflow starter kits created by the Microsoft Office team to assist with creating SharePoint-targeted workflow templates. Which SDK and workflow starter kit you download depends on whether you are developing workflow templates for WSS or for MOSS. The following items are what you need to consider.

If you want to develop custom workflow templates that run in a WSS environment whether MOSS is installed or not, you should download the Windows SharePoint Services 3.0: Software Development Kit (SDK), which includes the Workflow Developer Starter Kit. This SDK contains two Visual Studio Project Templates for WSS-targeted WF programs titled Sequential Workflow Library and State Machine Workflow Library. In addition to these two project templates, the SDK also includes a single sample project named ASPXCollectFeedback, which demonstrates how to build a custom workflow template that includes workflow input forms built using .aspx pages.

If you want to develop custom workflow templates that run exclusively in a MOSS environment, you should download the Microsoft Office SharePoint Server 2007 Software Development Kit (SDK), which includes the Enterprise Content Management Starter Kit. This includes the same two Visual Studio project templates that come with the WSS SDK, but also adds support for integrating InfoPath forms into your workflow templates. Instead of including only one sample project to demonstrate the creation of a custom workflow template such as the WSS SDK, the Enterprise Content Management Starter Kit provides over one dozen sample projects that all involve using InfoPath forms instead of.aspx pages for creating workflow input forms.

The obvious caveat to using the Enterprise Content Management Starter Kit to build custom workflow templates is that they can pick up a dependency on MOSS that prevents them from working correctly within an environment in which WSS (but not MOSS) is installed. In this book, we focus on developing custom workflow templates with the WSS SDK and the Workflow Developer Starter Kit to ensure that the templates run in a WSS environment even when MOSS is not installed.

image from book
Create Ad-hoc Workflow Associations with the SharePoint Designer

Note that the SharePoint Designer makes it possible to create custom workflow associations directly on a list or document library. This support for creating ad hoc custom workflow associations is not actually intended for professional developers as much as it is for sophisticated users in the role of a business analyst or Web designer.

The advantage of creating ad hoc workflows is that they eliminate the need for custom development of WF programs and workflow input forms. They also remove the need to create a solution package to deploy and install all of the pieces required by a workflow template on each front-end Web server.

The limitations of creating ad hoc workflow associations involve a lack of flexibility and an inability to effectively reuse custom workflow logic. Once a user creates an ad hoc workflow association on a particular list with the SharePoint Designer, there is no supported technique that allows the user to reuse that ad hoc customization on another pre-existing list. However, it is possible to create an ad hoc workflow association on a list and then create a list template from the list. Any workflow association created on a list that is used to create a list template is automatically created on lists that are created from the list template.

Although creating ad hoc custom workflow associations with the SharePoint Designer is an interesting and valuable topic that might warrant your attention, it is not developer focused and, therefore, is not covered in this book.

image from book

Creating the “Hello World” Workflow Template

At this point, we assume that you have already installed the Visual Studio Extensions for Windows Workflow Foundation as well as the WSS SDK that includes the Workflow Developer Starter Kit. Having these pieces of software installed on your development workstation allows you to create and test your first custom workflow template. During this initial walkthrough, we will follow the guidelines for creating a classic Hello World sample.

When you create a new project in Visual Studio, you should notice that the New Project dialog box contains a SharePoint folder of project templates for both Microsoft Visual Basic and C#, as shown in Figure 8-8. Within the SharePoint folder, you should see two Visual Studio project templates named Sequential Workflow Library and State Machine Workflow Library. In this walk-through, we are going to create a new C# project named HelloWorkflow by using the Sequential Workflow Library project template.

image from book
Figure 8-8: The Workflow Starter Kit of the WSS SDK provides Visual Studio project templates for creating new custom workflow templates that target WSS.

Once you create a new Visual Studio project based on the SharePoint Sequential Workflow Library project template, you are provided with several different source files as a starting point. These source files include feature.xml, workflow.xml, install.bat, Workflow1.cs, and Workflow1.designer.cs, as shown in Figure 8-9.

image from book
Figure 8-9: New projects created with the Workflow Starter Kit templates provide a WF program as well as the starting files used to create features.

The two files named feature.xml and workflow.xml are important when it’s time to deploy your workflow template. These two files are used to define a feature that makes it possible to install the workflow template so that it is recognized by WSS. The provided install.bat file contains command-line instructions to copy the required source files into the TEMPLATE directory of WSS and install the feature containing the workflow template by using the stsadm.exe utility. We will revisit these three files at the end of this section because they are an essential part of installing the workflow template, which is a required step before you can conduct any testing or debugging.

We now turn our attention to the two source files named Workflow1.cs and Workflow1. designer.cs. These two files are used together to define the WF program that is at the heart of your workflow template. This WF program is defined as a class named Workflow1. You can observe that Workflow1 inherits from the standard base class named SequentialWorkflowActivity from the WF programming model. Note that Workflow1 is defined as a partial class that is spread out across both Workflow1.cs and Workflow1.designer.cs.

As the author of a custom workflow template, you are required to work with WF program files, such as Workflow1.cs, in both Designer view and Code view. When you are working in Designer view, the WF workflow designer provides you with the convenience of dragging and dropping activities from the Toolbox onto the design surface of Workflow1.cs, which is how you compose the flow of activities for your WF program. Designer view also makes it possible to modify an activity by selecting it and then modifying its properties using a standard property sheet.

Tip 

Figure 8-9 demonstrates what the Visual Studio Toolbox should look like when you are working with a WF program in Designer view with all WSS-specific activities. However, it’s possible that you might not see these WSS-specific activities in the Visual Studio Toolbox even after installing the WSS SDK and the Workflow Starter Kit. To get these WSS-specific activities to show up, you have two choices. First, you can modify the class for your workflow program to inherit from SharePointSequentialWorkflowActivity instead of SequentialWorkflowActivity. This will be described in the next section of this chapter titled “Working with Activities.”

The second approach is to manually add the WSS-specific activities to your toolbox. You can accomplish this by right-clicking the Toolbox and selecting the Choose Items command, which brings up a dialog box that makes it possible for you to choose the components you would like to add. From the tab labeled .NET Framework Components, sort the components by namespace and select all components that have the Microsoft.SharePoint.WorkflowActions namespace. This adds the WSS-specific activities to the Toolbox so that you can drag and drop them onto your WF programs.

Let’s take a moment and describe the relationship between the two source files named Workflow1.cs and Workflow1.designer.cs. Although you can work directly with Workflow1.cs, it is unnecessary to make direct edits to Workflow1.designer.cs. Instead, the code generator built into the workflow designer makes the necessary changes to Workflow1.designer.cs for you behind the scenes as you work on Workflow1.cs in Designer view.

For example, when you drag and drop a new activity onto the design surface of Workflow1.cs, the code generator that’s part of the workflow designer automatically adds code to Workflow1. designer.cs to create a new activity instance. It also adds the code required to initialize the properties of the new activity instance and add it to the Activities collection of Workflow1. Furthermore, when you select an activity in Designer view and modify it by using the property sheet, the code generator makes the necessary updates to Workflow1.designer.cs to track your changes.

Warning 

It is recommended that you avoid making direct edits to Workflow1.designer.cs unless you truly know what you are doing. If you add code that isn’t formatted correctly, you can prevent the code generator from working properly as well as prevent the designer from opening.

Let’s begin working on Workflow1.cs to start development of a WF program. You should start by double-clicking Workflow1.cs within the project explorer, which opens Workflow1.cs in Designer view, as shown earlier in Figure 8-9. The next thing you must do is become comfortable switching Workflow1.cs back and forth between Designer view and Code view, which is something you will do on a regular basis as you develop WF programs with the workflow designer.

If you right-click the window of the workflow designer, you see context menu items that allow you to toggle back and forth between Designer view and Code view. When in Designer view, there is a context menu item titled View Code. When in Code view, there is a context menu item titled View Designer. Practice moving back and forth between these two views.

You should observe that the context menu in the workflow designer window provides several other important options, as shown in Figure 8-10. A Generate Handlers menu item is used to quickly create event handlers for the WF program as well as for child activities. Other menu items make it possible to navigate between the main SequentialWorkflow view and two other views, Cancel Handler and Fault Handler.

image from book
Figure 8-10: Right-clicking the workflow designer window provides several important context menu items.

Working with Activities

When you open Workflow1.cs in Designer view, you notice that it already contains an existing activity named onWorkflowActivated1 that was created using the onWorkflowActivated activity type. This is an important requirement for any WF program targeting WSS or MOSS. More specifically, the first activity within the WF program running in WSS must be of the OnWorkflowActivated activity type. For this reason, you should never remove the activity instance named onWorkflowActivated1, nor should you add any other activities in front of it.

Before we make any modifications to Workflow1.cs, let’s switch over to Code view and examine the code that is provided as a starting point. The code you see should be roughly the equivalent of the following code:

 // using statements omitted for clarity namespace HelloWorkflow {   public sealed partial class Workflow1 : SequentialWorkflowActivity {     public Workflow1() {       InitializeComponent();     }     public Guid workflowId = default(System.Guid);     public SPWorkflowActivationProperties workflowProperties;     workflowProperties = new SPWorkflowActivationProperties();   } }

Tip 

When you create a new sequential workflow project by using the Visual Studio templates installed by the WSS SDK, it creates the class for your workflow program by inheriting from the standard SequentialWorkflowActivity class defined by the WF base activity library. However, if you use the Visual Studio templates installed by the Office SharePoint Server SDK, you will notice that it creates a class that inherits from a different base class created by the Office Server team named SharePointSequentialWorkflowActivity. Note that the SharePointSequentialWorkflowActivity class inherits from the SequentialWorkflowActivity class.

You should understand that there is no real difference in the behavior between these two classes. The only difference is that the SharePointSequentialWorkflowActivity class has been created with attributes that tell Visual Studio to display the WSS-specific activities on the toolbox when a class of type SharePointSequentialWorkflowActivity is displayed in Design View. If you find it more convenient, you can substitute the SharePointSequentialWorkflowActivity class for the SequentialWorkflowActivity immediately after creating a new sequential workflow project with the WSS SDK.

The class named Workflow1 is defined as a partial class because the other half of the class definition for Workflow1 exists within Workflow1.designer.cs. You can also see that the preprovided constructor for the Workflow1 class calls a method from Workflow1.designer.cs named InitializeComponent. The InitializeComponent method contains the code created by the code generator of the workflow designer that creates and initializes the activities added to the WF program while in Designer view.

We now turn our attention to the two public fields that have already been defined in the Workflow1 class: workflowId and workflowProperties. These fields need to be initialized by the OnWorkflowActivated activity whenever a new workflow instance is created from the WF program named Workflow1. As it turns out, the Visual Studio project template that you used to create the current project has already added what is needed to initialize the workflowProperties field.

Initialization of the workflowProperties field is accomplished through data binding. The activity named onWorkflowActivated1 contains a data-binding–enabled property named WorkflowProperties. While in Designer view, select onWorkflowActivated1 and inspect the property named WorkflowProperties in the Visual Studio property sheet. You should be able to verify that the value of this property is preconfigured with the following property value, which constitutes a data-binding expression that is evaluated at run time.

 Activity=Workflow1, Path=workflowProperties

As mentioned earlier in the chapter, the entire idea behind data binding within a WF program is to facilitate the declarative flow of data across activities. In this case, the WorkflowProperties property of the activity named onWorkflowActivated1 is data bound to the workflowProperties field of Workflow1. When the WF runtime creates the activity instance named onWorkflowActivated1, this child activity initializes the new workflow instance. After onWorkflowActivated1 completes its work, the workflowProperties field in Workflow1 references a fully initialized SPWorkflowActivationProperties object that can then be used in any event handler to retrieve information about the current workflow instance.

Now that you have seen an example of data binding with an activity property, it’s time to create an event handler for an activity. In particular, let’s create an event handler for the activity named onWorkflowActivated1 to initialize the other field in the Workflow1 class named workflowId.

Switch Workflow1.cs back to Designer view and then right-click the activity named onWorkflowActivated1. Select the Generate Handlers menu item, which creates an event handler method inside Workflow1.cs named onWorkflowActivated1_Invoked. Note that this event handler executes as part of the initialization sequence for any workflow instance created from this WF program.

It’s important to understand that the event handler for an activity of type onWorkflowActivated runs after the activity has completed its work, which means that the event handler runs after the data binding has initialized the workflowProperties field in Workflow1. Therefore, you can initialize the workflowId field by using the workflowProperties field.

 private void onWorkflowActivated1_Invoked(...) {   workflowId = this.workflowProperties.WorkflowId; }

To complete the first phase of our Hello World sample, let’s add a second activity to Workflow1 that writes a message into the workflow history table. Navigate to Designer view. In the Toolbox, locate the activity type LogToHistoryListActivity. Drag this activity type onto the designer surface to create a new activity instance immediately after onWorkflowActivated1. After you create this activity, select it and examine its properties in the Visual Studio property sheet.

The new activity is named logToHistoryListActivity1. You can leave the name of this activity as is or change it to something more descriptive, such as logActivated. You should also note that two other properties are visible within the property sheet named HistoryDescription and HistoryOutcome. Whenever an activity of type LogToHistoryListActivity writes an entry into the workflow history table, it writes the values of these two properties so that users can see them.

While it is possible to assign static values for the HistoryDescription and HistoryOutcome properties directly inside the property sheet, it is more flexible if you create two new string fields inside the Workflow1 class and then data-bind them to the HistoryDescription and HistoryOutcome properties. Start by adding two fields to the Workflow1 class named HistoryDescription and HistoryOutcome.

 public sealed partial class Workflow1 : SequentialWorkflowActivity {   // other members removed for clarity   public String HistoryDescription;   public String HistoryOutcome; }

Once you add these two public string fields to Workflow1, you can bind them to any data-binding–enabled properties of child activities as long as these properties are based on the string type. Switch Workflow1.cs back into Designer view and select the activity you created from the LogToHistoryListActivity activity type. You can now data-bind the HistoryDescription and HistoryOutcome properties from this activity to the two fields you just added to Workflow1.

To configure data binding for a property, click the little blue circle to the right of its name, which causes the workflow designer to invoke a special dialog box that makes it possible to data-bind property value to public fields or public properties exposed by another activity, such as Workflow1. Once you have data-bound the HistoryDescription and HistoryOutcome properties to the two fields in Workflow1, your property sheet should look like the one shown in Figure 8-11.

image from book
Figure 8-11: The properties of activities can be bound to fields within the WF program.

Tip 

As you have just seen, the dialog box shown on the far right of Figure 8-11 makes it possible to data-bind an activity property to an existing public field or property such as the HistoryDescription field defined within the workflow class named Workflow1. You should also note that there is a tab in this dialog box with the caption Bind To A New Member that provides the means to quickly create new public fields or properties inside the workflow program class. When you create a new public field or property by using this dialog box, it is automatically data-bound to the current activity property. Once you become comfortable with this dialog box, you will likely favor using this technique as opposed to manually adding public fields and properties to the workflow class.

Once you successfully add the LogToHistoryListActivity activity and data-bind its properties, it’s time to add another event handler. Right-click the LogToHistoryListActivity activity and click Generate Handlers. This creates an event handler that fires just before the LogToHistoryListActivity activity writes its entry into the workflow history table. Therefore, you can write code in this event handler to assign a value to each of the two fields before data binding takes place. Write the event handler using the following code:

  private void logActivated_MethodInvoking(object sender, EventArgs e) {   // Generate message using information of current item   SPListItem item = workflowProperties.Item;   // determine whether workflow is running on a standard item or a document   if (item.File == null) {     HistoryDescription = "Workflow started on item " + item.Title;   }   else {     HistoryDescription = "Workflow started on document " + item.File.Name;   }   HistoryOutcome = "Workflow activation complete"; } 

The previous example demonstrates a few important things about writing code within the event handler for a WF program targeting WSS. First, you see it’s very simple to program against the item or the document on which the workflow is running because the workflowProperties field exposes an Item property that, in turn, exposes a File property. The code in this example checks to see whether the File property is null to determine whether the workflow instance started on a standard list item or on a document.

Tip 

Note that the WF programs run under the security context of the SHAREPOINT\system account. If you want to log history list activity under the name of the current user, you must assign a value to the UserId property of the LogToHistoryListActivity with a value other than –1. For example, you can log the history list entries by using the account name of the user that initiated the current workflow instance by binding the UserId property of the LogToHistoryListActivity to workflowProperties.OriginatorUser.ID.

The second important observation about this example is that the event handler assigns values to fields that are data bound to properties within the LogToHistoryListActivity activity. The timing is important here because the LogToHistoryListActivity activity does its work of evaluating its data-bound expressions and writing its entry into the workflow history after the event handler fires. This is the opposite of the timing involved with the onWorkflowActivated activity, which fires its event handler after completing its work of initializing the new workflow instance and assigning values back to the workflowProperties field in the Workflow1 class as a result of a data-bound property setting.

In the previous simple example, you saw the declarative data flow controlled through data binding that worked in both directions. The first example shows how data-binding the WorkflowProperties property of the onWorkflowActivated activity enables the flow of data from a child activity to the top-level activity named Workflow1. The second example shows you how to assign values to fields within Workflow1 that flow to a child activity.

Deploying a Workflow Template for Testing

At this point, the WF program named Workflow1 with the HelloWorkflow project has enough functionality that we can move through the steps of deployment to test our work. This section walks you through the details of installing the workflow template, creating an association, and initiating a workflow instance.

Note that WSS requires that an assembly DLL containing WF programs associated with a workflow template be installed in the GAC prior to use. That means that you must add a signing key to your assembly project so that its output assembly DLL is built with a strong name. Go ahead and add a strong name key file to the HelloWorkflow project so that the output assembly HelloWorkflow.dll is built with a strong name that makes it possible to deploy it in the GAC.

Next, you need to modify the two files named feature.xml and workflow.xml to define an installable feature that contains a Workflow element used to define a workflow template. After you complete this step, you should be able to install the feature with WSS by using stsadm.exe. Begin by opening the file named feature.xml, which is initially empty of content. Add the following CAML-based content to define the high-level feature attributes.

  <Feature   Id=""   Title="Hello World Workflow Template Feature"   Description="This feature installs our Hello World Workflow Template"   Version="12.0.0.0"   Scope="Site"   xmlns="http://schemas.microsoft.com/sharepoint/">   <ElementManifests>     <ElementManifest Location="workflow.xml" />   </ElementManifests> </Feature> 

Within the Feature element, you can see it has typical attribute settings, such as a new unique GUID for its Id attribute along with a Title and Description. You should take note that the Scope attribute has a value of Site, which means that this feature is scoped at the site collection level as opposed to the site level. This is important because workflow templates can be defined only within features that are defined at Site Collection scope.

Tip 

The Workflow Starter Kit of the SDK provides code snippets that enable you to generate the starting XML-based content for feature.xml and workflow.xml.

Next, open the workflow.xml file. You can see that this file is also initially empty of content. You need to add a Workflow element that defines a workflow template. Add the following XML-based content into workflow.xml:

  <Elements xmlns="http://schemas.microsoft.com/sharepoint/">   <Workflow     Id=""     Name="Hello World Workflow Template"     Description="This workflow templates has Hello World functionality"     CodeBeside     CodeBesideAssembly="HelloWorkflow, [four-part assembly name]" >     <Categories/> <!-- no catagories needed -->     <MetaData /> <!-- no metadata needed -->   </Workflow> </Elements> 

As you add this Workflow element into workflow.xml to define a workflow template, you must create a new GUID and assign it the Id attribute. Note that this GUID is different from the GUID used to identify the feature itself. You should also note that you are not limited to one workflow template per feature.

For example, assume that you want to add three WF programs to your Visual Studio project. Each one of these WF programs must be defined as a separate workflow template and requires its own Workflow element within the workflow.xml file. Of course, each of these workflow templates needs its own unique GUID to serve as its Id.

In addition to the Id, the Workflow element used to define a workflow template requires a Name, a Description, and two other attributes named CodeBesideClass and CodeBesideAssembly that should be configured to point to a class name of a WF program that has been compiled into an assembly DLL and installed in the GAC. At this point, we have done enough within workflow.xml to install our Hello World workflow template. Later in the chapter, in the section titled “Integrating Workflow Input Forms,” we will revisit the topic of working with the Workflow element when it’s time to integrate workflow input forms with our workflow template.

Now that we are finished editing feature.xml and workflow.xml, you should open the install.bat file and examine what’s inside. You need to make some edits to the install.bat file so that it contains the proper command-line instructions required to copy the feature files to the correct location and install the feature with the stsadm.exe utility. The install.bat file should also be written to install HelloWorkflow.dll into the GAC. Comments found inside install.bat provide instructions on what needs to be done. In most cases, all you really need to do is find and replace the text “MyFeature” with the name of your feature. This find and replace operation will take place about 14 different times. You’ll need to do more only if you change the default file names for your XML files or deploy to a non-root site collection.

Tip 

The install.bat file generated by the Visual Studio project templates from the WSS Workflow Starter kit does not include a fully qualified path to the STSADM.EXE commandline utility. When you try to run install.bat, you might get an error saying it cannot find STSADM.EXE. To fix this problem, you can either add a System path to your development workstation or update install.bat as discussed in the sidebar in Chapter 1 titled “The STSADM.EXE Command Line Utility.”

After editing install.bat, you are now ready to deploy the workflow template for testing. First, build the HelloWorkflow project to ensure that the assembly DLL is up to date. Next, run install.bat from the command line or by double-clicking it within Windows Explorer. Note that installation of the assembly DLL in the GAC fails if you have not compiled HelloWorkflow.dll with a strong name. However, if install.bat runs successfully, you can then move to the next step of the workflow template test.

Testing the Hello World Workflow Template

Once you have installed the feature that contains the Hello World workflow template, you should be able to activate it with a site collection for testing. Create or navigate to a test site collection that you can use to test your work. Once you get to the top-level site of the site collection, go to the Site Settings page and click the Site Collection Features link. At the Site Collection Features page, you should see the feature that defines your workflow template. Activating the feature makes your workflow template available to create workflow associations on lists, document libraries, and other content types throughout the site collection.

It’s now time to create a workflow association. Create or navigate to a list. Go to the List Settings page for the list and click the Workflow Settings link. If there are no existing workflow associations on the list, clicking the Workflow Settings link should take you to a page named AddWrkfl.aspx, which allows you to create new workflow associations. If there are already existing workflow associations on the list, clicking the Workflow Settings link should take you to the standard Workflow Settings page named WrkSetng.aspx. The WrkSetng. aspx.page displays existing workflow associations and provides a link that redirects you to a page that allows you to create a new workflow association.

Click the Add A Workflow link, which takes you to the Add A Workflow page named AddWrkfl.aspx. On this page, you can see all of the workflow templates that are activated with the current site collection, including your Hello World workflow template. Select the Hello World workflow template and use it to create a new workflow association named Hello World Workflow Association. When you click the OK button, it creates the new association and returns you to the Workflow Settings page.

Now that the workflow association is created, it’s time to create a workflow instance on an item within the list. Return to the main page for the list named AllItems.aspx so that you can create a new list item. Once you create the new item, drop down the ECB menu for that list item and click the ECB menu item titled Workflows. Clicking the Workflow ECB menu item should take you to the Workflow.aspx page, where you should have the option of starting a new workflow instance by using your Hello World workflow association. Start a new workflow instance by clicking Hello World Workflow Association.

After starting the workflow instance, return to the AllItems.aspx page for the list and look at the item for which you just started the new workflow instance. There should be a new column for the workflow association and a Completed link indicating that the workflow ran and was able to complete. Click this link so that you can drill down into the workflow status page named WrkStat.aspx. You should see information about the workflow instance, including the entry written to the workflow history table by the LogToHistoryListActivity activity that you added to your workflow. If you followed all of the steps correctly, the workflow status page should look like the page shown in Figure 8-12.

image from book
Figure 8-12: Once you successfully deploy and test your Hello World workflow template, you should see your message in the workflow history table for a specific workflow instance.

We have now walked through the steps for creating, deploying, and testing a very simple workflow template. However, a typical workflow template in the real world does not run to completion as soon as it is initiated. Instead, it performs one or more tasks and then uses events to go to sleep until the user(s) responsible for modifying those tasks performs additional work, which is the topic of our next section.

Creating and Waiting On Tasks

Earlier in this chapter, we explained at a high level how WSS adds a human dimension on top of the WF for task-driven activities. However, to take advantage of this dimension, you must learn how to develop WF programs that create WSS tasks associated with workflow instances. It is equally important for you to learn how to put a workflow instance to sleep in a way that allows it to wake up and resume its execution when a user updates one of the tasks associated with it.

When you create a task that’s associated with a workflow instance, you don’t do it by calling the Items.Add method on an SPList object as you would when creating new list items in other WSS development scenarios. Instead, you must go through the WSS Workflow Services API so that each new task is created with a subscription. The subscription is something that associates a new task with a workflow instance by wiring up event handlers that react to users who modify or delete the task.

Fortunately, it is not necessary for you to program against the WSS Workflow Services API directly. Instead, you can create WF programs using activities based on activity types such as CreateTask, OnTaskCreated, UpdateTask, OnTaskChanged, and CompleteTask. These activity types contain the code that encapsulates the necessary calls into the WSS Workflow Services API. When you use these activity types, you can create a WF program that creates a task and then puts the current workflow instance to sleep. At a later time when the task is updated by a user, an event handler that has been registered by the WSS Workflow Services API fires and brings the workflow instance back to life to continue its execution.

It is important that you begin to distinguish between method activities and event activities. The activities created from method activity types, such as CreateTask, UpdateTask, CompleteTask, and DeleteTask, are represented with blue shapes in the workflow designer, whereas activities created from event activity types, such as OnTaskCreated, OnTaskChanged, and OnTaskDeleted, are represented with green shapes.

A method activity is one that performs an action, such as creating or updating a task. An event activity is one that runs in response to an action occurring. Event activities are particularly important in creating episodic WF programs because they can provide the blocking behavior required when a workflow instance needs to wait for some external event before continuing with its execution.

Let’s continue working with the WF program named Workflow1 from the HelloWorkflow project that we began developing earlier in this chapter. We are now going to extend Workflow1 to create and wait on a task, and we begin by adding two new activities. First, we create a new activity by using the CreateTask method activity type. Next, we create a second new activity after it by using the OnTaskCreated event activity type. After creating these two activities, the Designer view window of Workflow1.cs should look like the image in Figure 8-13.

image from book
Figure 8-13: Method activities such as CreateTask perform actions, whereas event activities such as OnTaskCreated respond to actions.

When several different method activities and event activities within a WF program work on the same task, it’s important for all of these activities to be able to identify this task. The WSS workflow infrastructure relies on correlators to accomplish this. A correlator involves the use of both a GUID and a correlation token. The following text explains what’s required to properly set up the correlator that allows several different activities to all work on the same task.

We begin by creating a new correlation token for the task. Navigate to the property sheet for the CreateTask activity and take a look at the CorrelationToken property. The value for this property is initially empty, yet it needs to be assigned a valid value before the CreateTask activity can function correctly. If you look at the drop-down list for this property value, you see an existing CorrelationToken value of workflowToken that you can select. However, you don’t want to select workflowToken because it identifies the workflow instance and not the task that is being created. Instead, you must create a new CorrelationToken object.

It’s actually fairly simple to create the new CorrelationToken object for the new task being created. Simply place your cursor into the property sheet and type in a new string value for the CorrelationToken, such as “taskToken,” and then hit the ENTER key. The code generator of the workflow designer responds by adding the code into Workflow1.designer.cs to create a new CorrelationToken object named taskToken. The code generator also does the work to initialize the taskToken object and bind it to the CorrelationToken property of the CreateTask activity.

Note that after you create the new CorrelationToken object named taskToken and assign it to the CorrelationToken property of the CreateTask activity, you must still assign its OwnerActivityName property value as shown in Figure 8-13. The OwnerActivityName property value can point back to the parent activity, which in this case is the top-level WF program itself named Workflow1.

Once you create the CorrelationToken named taskToken within the property sheet for the CreateTask activity, you can use it as the CorrelationToken property value for other taskrelated activities within the same WF program. For example, you can select the other activity named OnTaskCreated and, from its property sheet, assign taskToken to its CorrelationToken property by selecting it from the drop-down menu.

After creating the correlation token for the task, the next thing you must do is add two new fields to the Workflow1 class that will be data bound to properties of the CreateTask activity. The first field is used to track a new GUID that passes to the CreateTask activity so that it can properly initialize its correlation token. The second field should be defined by using the SPWorkflowTaskProperties class. You use this field to pass a reference to the CreateTask activity that points to an SPWorkflowTaskProperties object that holds the initial values for the task to be created. Add the following two fields to the Workflow1 class:

 public Guid taskId = default(System.Guid); public SPWorkflowTaskProperties taskProperties = new SPWorkflowTaskProperties();

Next, you must create an event handler for the CreateTask activity. Note that CreateTask is a method activity, which means that its event handler fires before it calls the WSS Workflow Services API to perform its work. This gives you a chance to initialize fields within the WF program that can then be passed to the CreateTask activity through data binding.

Switch back to Designer view, right-click the CreateTask activity, and select the Generate Handlers command. This generates an event handler with a name such as createTask1_ MethodInvoking. Add the following code to this event handler to initialize the taskId field with a new GUID and initialize the SPWorkflowTaskProperties object referenced by the taskProperties field.

 private void createTask1_MethodInvoking(object sender, EventArgs e) {   // generate new GUID used to initialize task correlation token   taskId = Guid.NewGuid();   // assign initial properties prior to task creation   taskProperties.Title = "Task for " + workflowProperties.Item.Title;   taskProperties.Description = "Please review and approve this item.";   taskProperties.AssignedTo = @"LITWAREINC\BrianC";   taskProperties.PercentComplete = 0;   taskProperties.StartDate = DateTime.Today;   taskProperties.DueDate = DateTime.Today.AddDays(2); }

As you can see, this code is simply initializing the data that the CreateTask activity uses to perform its work when it calls the WSS Workflow Services API. The final step is to data-bind the taskId and taskProperties fields to properties in the CreateTask activity. Navigate back to Designer view and select the CreateTask activity. Proceed to the property sheet and configure data binding so that the TaskId property of the CreateTask activity is initialized with the taskId field in Workflow1. Configure data binding so that the TaskProperties property of the CreateTask activity is initialized with the taskProperties field in Workflow1.

You should now be through working with the CreateTask activity. Select the event activity named OnTaskCreated and look at its property sheet. If you haven’t already done so, assign the CorrelationToken object named taskToken to its CorrelationToken property. Next, databind the same two fields from the Workflow1 class to the OnTaskCreated activity that you already data-bound to the CreateTask activity. In particular, data-bind the TaskId property of the OnTaskCreated activity to the taskId field in Workflow1, and data-bind the TaskProperties property of the OnTaskCreated activity to the taskProperties field in Workflow1.

One subtle yet important point must be understand concerning how method activities and event activities work. WSS batches all modifications made by a sequence of method activities into a single transaction and writes them all to the content database at once. This means that the work for a method activity is not committed when the activity finishes its work. If you don’t understand when the transaction for one or more method activities is committed, it is possible to get into trouble.

For example, what would happen if you added a Code activity in between the CreateTask and OnTaskCreated activities? This Code activity would not be able to see the just-created task in a committed state because WSS has not yet written the task to the content database. WSS does not write the task to the database until the workflow instance is put to sleep by encountering an event activity, such as OnTaskCreated. However, if you add a Code activity after the OnTaskCreated activity, things will be different because you are at a point where WSS has already saved the task to the content database.

If you look at the property sheet for the OnTaskCreated activity, you notice that it has a data-binding–enabled property named TaskAfterProperties. You can use this property to get hold of an object that allows you to see the state of the new task after it is committed to the content database. Start by adding a new field to Workflow1 named taskAfterProperties based on the SPWorkflowTaskProperties type. Initialize this field with a new instance by using the following code:

 public SPWorkflowTaskProperties taskAfterProperties =                                 new SPWorkflowTaskProperties();

Once you add this new field to the Code view of Workflow1.cs, navigate back to Designer view and go to the property sheet for the OnTaskCreated activity. Now data-bind the field you just created to the AfterProperties property of the OnTaskCreated activity. Next, create an event handler for this activity and add the following code:

  void onTaskCreated1_Invoked(object sender, ExternalDataEventArgs e) {   HistoryDescription = "Task created and assigned to " +                        taskAfterProperties.AssignedTo;   Guid TaskStatusFieldId = new Guid("");   string TaskStatus =          taskAfterProperties.ExtendedProperties[TaskStatusFieldId].ToString();   HistoryOutcome = "Task status: " + TaskStatus; } 

This code displays an example of inspecting the after properties of a task during the event handler for an OnTaskCreated activity. Note that the taskAfterProperties field of type SPWorkflowTaskProperties makes it possible to access standard task item fields, such as AssignedTo, through strong-typed properties. Also note that some task fields, such as TaskStatus, are not available through a strong-typed property and must be accessed by using the ExtendedProperties property that exposes a collection of name/value pairs. The previous code listing demonstrates how to access the TaskStatus field by using the GUID used to define its underlying site column.

Now that you have created a task, it’s time to employ the OnTaskChanged activity type to put the workflow instance to sleep. Whenever the WF program encounters an OnTaskChanged event activity, it puts the current workflow instance to sleep and registers an event handler to wake it up and resume execution whenever it is changed. In our simple Hello World example, we want our WF program to examine the TaskStatus field and determine whether it has been updated to a value of Completed. If the TaskStatus field is updated to a value of Completed, we want the workflow instance to complete its life cycle. If the TaskStatus field is not updated with a value of Completed, we want to put the workflow instance back to sleep and have it wake up the next time a user updates the task.

We can use a While activity in this example to get the proper control of flow. If the user who updates the task assigns a value of Completed to the TaskStatus field, we want to break out of the While activities loop behavior and end the life cycle of the workflow instance. However, when a user updates the task and does not assign a value of Completed to the TaskStatus field, we want to put the workflow instance back to sleep until the task is updated again in the future.

Add a While activity just after the OnTaskCreated activity, and then add an OnTaskChanged activity as a child activity inside of it, as shown in Figure 8-14. Once you add these two activities, you must then create the logic so that the While activity loops around when it should and then breaks out when a user assigns the TaskStatus field with a value of Completed.

image from book
Figure 8-14: Placing an OnTaskChanged activity inside a While activity makes it possible to achieve the correct control-of-flow logic to ensure that a task is properly updated before continuing.

Tip 

Note that a While activity can contain only a single child activity. In our example, we are using the OnTaskChanged activity. However, in situations in which you want a sequence of several child activities inside a While activity, you must use a slightly different approach. You can add an activity by using the Sequence activity type as a child activity to the While activity. You can then add multiple child activities to the Sequence activity so that you can have several child activities execute inside the scope of a While activity.

You should start by adding a new field to the Workflow1 class that is used to control the looping behavior of the While activity. In particular, add a public field named TaskNotCompleted that is based on a Boolean type and initialized to a value of true.

 public bool TaskNotCompleted = true;

Now that you have created the TaskNotCompleted field in Workflow1, you can use it to control looping behavior of the While activity. Switch back to Designer view, select the While activity, and inspect its property sheet. Locate the property named Condition and drop down its combo box to view the available values. You should see two possible values named Declarative Rule Condition and Code Condition. Select a value of Declarative Rule Condition, and then put your cursor into the property field of the Condition name. If you then click the ellipsis, you should see the Rule Condition Editor as shown in Figure 8-15, where you can simply add the field name of TaskNotCompleted. You might notice that the Rule Condition Editor provides IntelliSense just as if you were typing C# code inside a code editor window.

image from book
Figure 8-15: You can use the Rule Condition Editor to add a Declarative Rule Condition that controls the looping behavior of a While activity.

Once you add the code condition to control the While activity, the final step is to properly configure the OnTaskChanged activity. Make sure to assign the taskToken to its correlation token property and bind its properties to the fields in Workflow1 just as you bound the properties of the other task-related activities. Finally, add an event handler to the OnTaskChanged activity by clicking the Generate Handlers command. Once you generate the event handler, add the following code:

  private void onTaskChanged1_Invoked(object sender, ExternalDataEventArgs e) {   TaskStatus = taskAfterProperties.ExtendedProperties[TaskStatusFieldId].ToString();   if (TaskStatus.Equals("Completed")) {     // update variable to break out of while loop     TaskNotCompleted = false;   }   // generate info for history list entry   HistoryDescription = "Task updated";   HistoryOutcome = "Task status: " + TaskStatus; } 

At this point, you should be able to test your work. Recompile your WF program and then run the install.bat file to install the new version in the GAC, refresh the feature installation, and reset IIS. After doing this, you should be able to test the new version of your workflow template. If you are successful, you should be able to initiate new workflow instances that create tasks and then go to sleep while they are still in progress. Once you initiate a workflow instance, you should drill down to the workflow status page to see whether the task has been created. If a task is created, the workflow status page makes it possible to open this task for editing. Try editing the task to see whether changing the Task Status column value to Completed advances the workflow instance to the end of its life cycle.

This completes our walk-through of the creation of a simple workflow template that interacts with users by using workflow tasks. While this sample does not deal with many of the scenarios you might deal with in the development of real-world WF programs, it should give you an appreciation for what is required to work with tasks. You create tasks with the CreateTask method activity and then use event activities, such as OnTaskCreated and OnTaskChanged, to inspect the task after it is saved. You will typically also be required to manage the execution of WF programs that create tasks by using control-of-flow activities, such as the While activity.




Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
EAN: N/A
Year: 2007
Pages: 92

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net