AL MA TE RI Workflow Programming Principles RI GH TE D Business process modeling has many of the same characteristics as the traditional procedural programming model such as C#. However, it also exhibits characteristics that are fundamentally different from the traditional procedural programming model. This mismatch between the two models has always been the main stumbling block in workflow programming.
Chapter 1: Workflow Programming Principles Therefore, you can think of a business process or workflow as a logical program, just like a traditional procedural program such as a C# program, and its constituent activities as logical program statements, just like such traditional procedural program statements such as C# program statements.
Chapter 1: Workflow Programming Principles So far, I’ve discussed the characteristics that WF workflow programming has in common with traditional programming such as C#. Next, I’ll discuss the characteristics that make workflow programming fundamentally different from a traditional programming such as C#. When the program control in a single-threaded C# program reaches a program statement, the program statement executes continuously in synchronous fashion until it completes its execution.
Chapter 1: Workflow Programming Principles Clearly, the execution model of an activity or logical program statement is fundamentally different from that of a C# program statement. A C# program statement does not suspend its execution and resume it later on a different thread, process, or machine. A C# program statement cannot be resumed. That is why when a C# program crashes, you have to rerun the program.
Chapter 1: Workflow Programming Principles Program Main bool.Parse while if else Console. WriteLine Console. WriteLine bool.Parse Figure 1-1 Note that the C# program itself is the root program statement in this program statement hierarchy. Also note that this program statement hierarchy, like any other hierarchy, consists of two types of nodes. The first type includes those program statements such as “while,” “if,” and “else” that contain other program statements.
Chapter 1: Workflow Programming Principles One of the base classes from which you can inherit your leaf activity is the standard Activity base class, which is discussed thoroughly in this section. Every activity in Windows Workflow Foundation inherits from the Activity base class directly or indirectly. Listing 1-1 presents some of the methods, properties, and events of this base class.
Chapter 1: Workflow Programming Principles method of an activity returns, the activity is in a state known as Initialized, which is different from another state known as Executing, the state an activity enters when its Execute method is scheduled for execution. It is very important to understand when the Initialize and Execute methods of the activities making up a workflow are invoked. The Initialize methods are invoked when the CreateWorkflow method is invoked on the workflow run time.
Chapter 1: Workflow Programming Principles Listing 1-2 presents an example of the implementation of the Initialize method of an activity whereby the activity creates a workflow queue. Listing 1-2: A typical implementation of the Initialize method Using System; using System.Workflow.ComponentModel; using System.Workflow.
Chapter 1: Workflow Programming Principles Keep in mind that the Initialize method of an activity is invoked only once in the lifetime of the activity. This is important considering the fact that the lifetime of an activity could span multiple threads, processes, or machine boundaries and could last an indefinitely long period of time.
Chapter 1: Workflow Programming Principles Activity Uninitialization If your custom activity overrides the Initialize method of the Activity base class, it must also override the UnInitialize method to uninitialize itself. The UnIntialize method undoes what the Initialize method does. For example, if the Initialize method creates and adds a new workflow queue to the workflow queuing service, the UnInitialize method must remove the same workflow queue from the workflow queuing service.
Chapter 1: Workflow Programming Principles The Uninitialize method, which is a WF concept, is very different from the Dispose method, which is a CLR concept. You mustn’t include one-time uninitialization logic of your activity in the Dispose method because the Dispose method is invoked every time the CLR object that represents your activity is about to be disposed of — that is, every time your activity suspends its execution and is serialized into the durable storage.
Chapter 1: Workflow Programming Principles Listing 1-4: An example implementation of the Execute method Using System; using System.Workflow.ComponentModel; using System.Workflow.Runtime; namespace Chapter1 { public class CustomActivity : Activity { protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext) { WorkflowQueuingService workflowQueuingService = executionContext.GetService(); WorkflowQueue workflowQueue = workflowQueuingService.
Chapter 1: Workflow Programming Principles If not, it first registers a method named WorkflowQueue_QueueItemAvailable as event handler for the QueueItemAvailable event of the WorkflowQueue object and then returns ActivityExecutionStatus.Executing to inform the workflow run time that it hasn’t completed its execution because it needs to wait indefinitely for the external entity to deposit the data into the workflow queue.
Chapter 1: Workflow Programming Principles 3. It then invokes the GetWorkflowQueue method to access the WorkflowQueue object that represents the workflow queue that the activity created in its initialization phase. Note that the method passes the QueueName property of its second argument into the GetWorkflowQueue method. This is not important in our case because this activity creates a single workflow queue in its initialization phase.
Chapter 1: Workflow Programming Principles if (!workflowQueuingService.Exists(this.Name)) workflowQueuingService.CreateWorkflowQueue(this.Name, true); } protected override void Uninitialize(System.IServiceProvider provider) { WorkflowQueuingService workflowQueuingService = provider.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService; if (workflowQueuingService.Exists(this.Name)) workflowQueuingService.DeleteWorkflowQueue(this.
Chapter 1: Workflow Programming Principles Developing Custom Composite Activities A composite activity or logical program statement, just like a C# composite program statement such as “{ },” “for,”“while,” and so on, is a logical program statement that contains other logical program statements or activities. In general, there are two types of composite activities. The first type includes flow control constructs such as “{ },” “for,” and “while.
Chapter 1: Workflow Programming Principles As Listing 1-6 shows, the CompositeActivity activity exposes a Boolean property named CanModifyActivities. As the name suggests, you must set this property to true before you can add child activities to the Activities collection property of the composite activity or before you can modify any child activity in the Activities collection property. Note that this property is marked as protected.
Chapter 1: Workflow Programming Principles SequenceActivity Here, I’ll walk you through the implementation of the logical { } control flow construct, which does exactly what the C# { } flow control construct does. That is, it executes its constituent child activities in sequential linear fashion. This implementation provides you with a simplified duplicate of the standard WF SequenceActivity activity, as shown in Listing 1-7. Listing 1-7: The SequenceActivity activity using System.Workflow.
Chapter 1: Workflow Programming Principles SequenceActivity activity returns ActivityExecutionStatus.Closed to inform the workflow run time that it has completed its execution. A SequenceActivity activity without any child activities is the WF equivalence of an empty C# { } statement block.
Chapter 1: Workflow Programming Principles As Listing 1-7 shows, this event handler takes the following steps: 1. It unregisters the event handler: e.Activity.Closed -= this.Activity_Closed; 2. It casts the first argument to the ActivityExecutionContext type: ActivityExecutionContext executionContext = sender as ActivityExecutionContext; 3. It determines the index of the child activity that raised the Closed event and increments the index: int index = this.EnabledActivities.IndexOf(e.
Chapter 1: Workflow Programming Principles discussed earlier to schedule the execution of the Execute method of the next activity with the WF scheduler: else { Activity activity = this.EnabledActivities[index]; activity.Closed += this.Activity_Closed; executionContext.
Chapter 1: Workflow Programming Principles Listing 1-8 (continued) } void Activity_Closed(object sender, ActivityExecutionStatusChangedEventArgs e) { e.Activity.Closed -= this.Activity_Closed; foreach (Activity activity in this.EnabledActivities) { if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized || activity.ExecutionStatus != ActivityExecutionStatus.Closed) return; } ActivityExecutionContext executionContext = sender as ActivityExecutionContext; executionContext.
Chapter 1: Workflow Programming Principles foreach (Activity activity in this.EnabledActivities) { activity.Closed += Activity_Closed; executionContext.ExecuteActivity(activity); } 3. Finally, it returns ActivityExecutionStatus.Executing to inform the workflow run time that ParallelActivity has not completed its execution yet because it is waiting for its child activities to complete their execution.
Chapter 1: Workflow Programming Principles If every single child activity is in either Initialized or Closed state, the event handler calls the CloseActivity method on the ActivityExecutionContext object to inform the workflow run time that ParallelActivity has now completed its execution and is ready to transition from the Executing to the Closed state: ActivityExecutionContext executionContext = sender as ActivityExecutionContext; executionContext.
Chapter 1: Workflow Programming Principles As you’ll see in next chapter, when you initiate a workflow on a SharePoint list item, SharePoint automatically invokes the CreateWorkflow method on the workflow run time. This method, among other things, creates the global execution context for the workflow instance.
Chapter 1: Workflow Programming Principles Listing 1-9 (continued) } protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext) { if (this.EnabledActivities.Count == 0) return ActivityExecutionStatus.Closed; if (Condition != null && Condition.Evaluate(this, executionContext)) { ActivityExecutionContext childExecutionContext = executionContext.ExecutionContextManager.CreateExecutionContext( this.EnabledActivities[0]); childExecutionContext.Activity.
Chapter 1: Workflow Programming Principles The WhileActivity activity exposes a property named Condition, which is of the ActivityCondition type, as defined in Listing 1-10. As you can see, this type is an abstract type that exposes a single method named Evaluate, which takes the following two parameters and returns a Boolean value specifying whether the condition is met: ❑ activity: Pass the C# this keyword as the value of this parameter.
Chapter 1: Workflow Programming Principles 2. ❑ It invokes the Initialize method on all activities in the new activity branch copy. This basically does what a C# “while” loop does for each iteration. Recall that a C# “while” loop resets all the local variables at the beginning of each iteration. In other words, no residual effects are carried over from the previous iteration.
Chapter 1: Workflow Programming Principles Now let’s walk through the implementation of the Activity_Closed event handler. This event handler first removes itself from the list of event handlers registered for the Closed event of the new copy of the child activity of the WhileActivity activity: e.Activity.Closed -= this.Activity_Closed; Next, it casts its first argument to the ActivityExecutionContext type.
Chapter 1: Workflow Programming Principles Listing 1-11: The CustomCondition activity condition using using using using System; System.Workflow.ComponentModel; Microsoft.SharePoint.WorkflowActions; Microsoft.SharePoint; namespace Chapter1 { public class CustomCondition: ActivityCondition { public static readonly DependencyProperty ListNameProperty = DependencyProperty.Register(“ListName”, typeof(string), typeof(CustomCondition)); public string ListName { get { return (string)base.
Chapter 1: Workflow Programming Principles set { base.SetValue(SiteUrlProperty, value); } } public override bool Evaluate(Activity activity, IServiceProvider provider) { bool success = false; using (SPSite siteCollection = new SPSite(this.SiteUrl)) { using( SPWeb web = siteCollection.OpenWeb()) { SPList list = web.Lists[ListName]; SPListItem item = list.GetItemById(ItemId); string str = item[FieldName].ToString(); success = ((str != null) && str.
Chapter 1: Workflow Programming Principles Listing 1-11 follows the best practice of instantiating SPSite and SPWeb objects in the context of using statements to ensure that the Dispose methods of these objects are automatically invoked when they go out of scope. Next, we’ll design a workflow that uses the custom activities and condition that we’ve developed in this chapter, as shown in Listing 1-12.
Chapter 1: Workflow Programming Principles System.Workflow.Runtime.CorrelationToken correlationtoken1 = new CorrelationToken(); System.Workflow.ComponentModel.ActivityBind activitybind1 = new ActivityBind(); this.logToHistoryListActivity3 = new LogToHistoryListActivity(); this.sequenceActivity1 = new SequenceActivity(); this.sequenceActivity2 = new SequenceActivity(); this.logToHistoryListActivity2 = new LogToHistoryListActivity(); this.whileActivity1 = new Chapter1.WhileActivity(); this.
Chapter 1: Workflow Programming Principles Listing 1-12 (continued) this.sequenceActivity1.Name = “sequenceActivity1”; // // logToHistoryListActivity2 // this.logToHistoryListActivity2.Duration = TimeSpan.Parse(“-10675199.02:48:05.4775808”); this.logToHistoryListActivity2.EventId = SPWorkflowHistoryEventType.WorkflowComment; this.logToHistoryListActivity2.HistoryDescription = “Completed”; this.logToHistoryListActivity2.HistoryOutcome = “Completed”; this.logToHistoryListActivity2.
Chapter 1: Workflow Programming Principles // Workflow1 // this.Activities.Add(this.onWorkflowActivated1); this.Activities.Add(this.logToHistoryListActivity1); this.Activities.Add(this.whileActivity1); this.Activities.Add(this.logToHistoryListActivity2); this.Name = “Workflow1”; this.CanModifyActivities = false; } void logToHistoryListActivity3_MethodInvoking(object sender, EventArgs e) { System.Threading.Thread.Sleep(5000); } public Guid workflowId = default(System.
Chapter 1: Workflow Programming Principles Note that the workflow instantiates an instance of our CustomCondition condition, initializes its properties, and assigns it to the Condition property of the WhileActivity activity: customcondition1.FieldName = “Title”; customcondition1.ItemId = 1; customcondition1.Keywords = “Document”; customcondition1.ListName = “Documents”; customcondition1.SiteUrl = “EnterSiteUrlHere”; this.whileActivity1.
Chapter 1: Workflow Programming Principles Figure 1-3 Enter Chapter1 as the name of the workflow template and choose a local site for debugging. Click Next to navigate to the dialog shown in Figure 1-4.
Chapter 1: Workflow Programming Principles Leave the “Automatically associate workflow?” toggle checked because we want to have Visual Studio automatically create the workflow association for us. The next chapter discusses workflow associations in detail. As you can see, this dialog contains three drop-down list boxes. Select a list from the top dropdown list box to have Visual Studio associate the workflow with this SharePoint list.
Chapter 1: Workflow Programming Principles ❑ key.snk: This is the key file that is used to sign the assembly. As you can see, Visual Studio always compiles the workflow code-behind class into a strong-named assembly. Every time you build this project, Visual Studio automatically compiles the workflow into a strong-named assembly and installs this assembly in the global assembly cache (GAC).
Chapter 1: Workflow Programming Principles Press F5 to build the project. Visual Studio will automatically take the following steps: 1. 2. 3. It compiles all .cs files into a single strong-named assembly. It deploys this assembly to the global assembly cache. It creates a feature-specific subfolder named Chapter1 in the following folder in the file system of the front-end Web server: C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\ TEMPLATE\FEATURES 4. It deploys the feature.
Chapter 1: Workflow Programming Principles Select the Workflows options from the ECB menu of a list item in the list as shown in Figure 1-9. Figure 1-9 This will take you to the Workflow.aspx page shown in Figure 1-10. This page shows the workflow association added to the list. This page contains a link titled Chapter1, which is the name of the workflow association that Visual Studio created and added to the list. This is the same name that you specified earlier (refer to Figure 1-3).
Chapter 1: Workflow Programming Principles Before you click the Chapter1 link shown in Figure 1-10, open another browser window or a new tab in the existing browser window and navigate to the page that shows the SharePoint list whose name is specified in the ListName property of our custom condition, which is Documents in this case. Now go ahead and click the Chapter1 link to initiate an instance of the workflow.
Chapter 1: Workflow Programming Principles Figure 1-12 Summar y This chapter first provided an overview of the similarities and fundamental differences between WF as a logical workflow programming language and a procedural programming language such as C#. It then discussed the different types of custom activities that you can implement and showed you how to implement each type.