Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

922 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

The most straightforward workflow type is sequential. As its name implies, a sequential workflow allows you to model a business process where each activity executes in sequence until the final activity completes. This is not to say that a sequential workflow is necessarily linear or predictable in nature—it is entirely possible to build a sequential workflow that contains various branching and looping activities, as well as a set of activities that execute in parallel on separate threads.

The key aspect of a sequential workflow is that it has a crystal-clear beginning and ending point. Within the Visual Studio 2008 workflow designer, the path of execution begins at the top of the WF diagram and proceeds downward to the end point. Figure 26-3 shows a simple sequential workflow that models a partial business process for verifying a given automobile is in stock.

Figure 26-3. Sequential workflows have a clear starting point and ending point.

Sequential workflows work well when the workflow models interactions with various systemlevel entities, and when there is no requirement for backtracking in the process. For example, the business process modeled in Figure 26-3 has two possible outcomes: the car is in stock or it isn’t. If the car is indeed in stock, the order is processed using some block of custom code (whatever that may be). If the car isn’t in stock, we send a notification e-mail, provided that we have the client’s e-mail address at our disposal.

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

923

In contrast to sequential workflows, state machine workflows do not model activities using a simple linear path. Instead, the workflow defines a number of request states and a set of related events that trigger transitions between these states. Figure 26-4 illustrates a simple state machine WF diagram that represents the processing of an order. Don’t worry about the details of what each activity is doing behind the scenes, but do notice that each request state in the workflow can flow across various states based on some internal event.

Figure 26-4. State machine workflows do not follow a fixed, linear path.

State machine workflows can be very helpful when you need to model a business process that can be in various states of completion, typically due to the fact that human interaction is involved to move between states. Here, we have a request state that is waiting for an order to be created.

Once that occurs, an event forces the flow of activity to the order open state, which may trigger an order processed state (or loop back to the previous open state), and so forth.

Note In this introductory chapter, I don’t dig into the details of building state machine workflows. Consult the

.NET Framework 3.5 SDK documentation for further information if you are interested.

924 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

WF Assemblies, Namespaces, and Projects

From a programmer’s point of view, WF is represented by three core assemblies:

System.Workflow.Activities.dll: Defines the intrinsic activities and the rules that drive them

System.Workflow.Runtime.dll: Defines types that represent the WF runtime engine and instances of your custom workflows

System.Workflow.ComponentModel.dll: Defines numerous types that allow for design-time support of WF applications, including construction of custom designer hosts

While these assemblies define a number of .NET namespaces, many of them are used behind the scenes by various WF visual design tools. Table 26-3 documents some key WF-centric namespaces to be aware of.

Table 26-3. Core WF Namespaces

Namespace

Meaning in Life

System.Workflow.Activities

This is the core activity-centric namespace, which defines

 

type definitions for each of the items on the Windows

 

Workflow Toolbox. Additional subnamespaces define the

 

rules that drive these activities as well as types to configure

 

them.

System.Workflow.Runtime

This namespace defines types that represent the WF

 

runtime engine (such as WorkflowRuntime) and an instance

 

of a given workflow (via WorkflowInstance).

System.Workflow.Runtime.Hosting

This namespace provides types to build a host for the WF

 

runtime, which make use of custom WF services (tracking,

 

logging, etc.). As well, this namespace provides types to

 

represent the out-of-the-box core WF services.

 

 

.NET 3.5 WF Support

With the release of .NET 3.5, the base class libraries now ship with a fourth WF-centric assembly named System.WorkflowServices.dll. Here you will find additional types that allow you to build WF-enabled applications that integrate with the Windows Communications Foundation APIs. The most important aspect of this assembly is that it augments the System.Workflow.Activities namespace with new types to support WCF integration.

Note When you build any WF-aware Visual Studio 2008 project, the IDE will automatically set references to each of the Windows Workflow Foundation assemblies.

Visual Studio Workflow Project Templates

As you would expect, the Visual Studio 2008 IDE provides a good number of WF project templates. First and foremost, when you select the File New Project dialog box, you will find a Workflow node under the C# programming category (see Figure 26-5). Here you will find projects that allow you to build sequential and state machine workflows contained within a simple console or custom *.dll assembly.

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

925

Figure 26-5. The core WF project templates

In addition, you may recall from Chapter 26 that the Windows Communication Foundation (WCF) node of the New Project dialog box also provides a set of WF templates. Here you will find two project templates (Sequential Workflow Service Library and State Machine Workflow Service Library), which allow you to build a WCF service that internally makes use of workflows. We will not make use of this group of WF project templates in this chapter; however, do remember that when you are building WCF services, you can elect to integrate WF functionality when creating a new project.

Getting into the Flow of Workflow

Before we dive into our first code example, allow me to point out a few final thoughts regarding the “workflow mind-set.” When programming with the WF API, you must keep in mind that you are ultimately attempting to model a business process; therefore, the first step is to investigate the business process itself, including all the substeps within the process and each of the possible outcomes. For example, assume you are modeling the process of registering for a training class online. When a request comes in, what should you do if the salesperson is out of the office? What if the class is currently full? What if the class has been canceled or moved to a new date? How can you determine if the trainer is available, is not on vacation, is not teaching a class that same week, or whatnot?

Depending on your current background, the process of gathering these requirements may be a very new task, as figuring out a business process may be “someone else’s problem.” However, in small companies, the act of determining the necessary business processes may fall on the shoulders of the developers themselves. Larger organizations typically have business analysts who take on the role of discovering (and often modeling) the business processes.

926 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

In any case, do be aware that working with WF is not simply a “jump in and start coding” endeavor. If you do not take the time to clearly analyze the business problem you are attempting to solve before coding, you will most certainly create a good amount of unnecessary pain. In this chapter, you will concentrate on the basic mechanics of workflow design, the use of activities, and how to work with the visual WF designers. However, don’t be too surprised when your real-world workflows become substantially more complex.

Building a Simple Workflow-Enabled Application

To get your feet wet with the process of building workflow-enabled applications, this first WF example will model a very simple sequential process. The goal is to build a workflow that prompts the user for his or her name and validates the results. If the results do not jibe with our business rules, we will prompt for input again until we reach success.

To begin, create a Sequential Workflow Console Application project named UserDataWFApp. Once the project has been created, use Solution Explorer to rename the initial WF designer file from

Workflow1.cs to the more fitting ProcessUsernameWorkflow.cs.

Examining the Initial Workflow Code

Before we add activities to represent our business process, let’s take a look at how this initial diagram is represented internally. If you examine the ProcessUsernameWorkflow.cs file using Solution Explorer, you will notice that much like other designer-maintained files (forms, windows, web pages), a WF diagram consists of partial class definitions. When you open the core *.cs file, you will find a class type that extends the SequentialWorkflowActivity type and a default constructor that makes a call to the InitializeComponent() method:

public sealed partial class ProcessUsernameWorkflow : SequentialWorkflowActivity

{

public ProcessUsernameWorkflow()

{

InitializeComponent();

}

}

Note One of the tenets of WF development is that workflows are singular, atomic entities. Given this fact, notice that the workflow class type is explicitly sealed, thereby preventing it from functioning as a parent class for derived types.

If you now open the related *.Designer.cs file, you will find that InitializeComponent() has set the Name property accordingly:

partial class ProcessUsernameWorkflow

{

[System.Diagnostics.DebuggerNonUserCode] private void InitializeComponent()

{

this.Name = "ProcessUsernameWorkflow";

}

}

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

927

As you make use of the Windows Workflow Toolbox to drag various activities onto the designer surface and configure them using the Properties window (or the inline smart tags), the *.Designer. cs file will be updated automatically. Like other IDE-maintained files, you can typically ignore the code within this file completely and keep focused on authoring code within the primary *.cs file.

Adding a Code Activity

The first activity you will add in the sequence is a Code activity. To do so, drag a Code activity component from the Windows Workflow Toolbox and drop it onto the line connecting the starting and ending points of the workflow. Next, use the Properties window to rename this activity as ShowInstructionsActivity. At this point, your designer should look like Figure 26-6.

Figure 26-6. A (not quite ready for prime time) Code activity

As you can see, the designer is currently reporting an error, which you can view by clicking the red exclamation point on top of the Code activity. The error informs you that the ExecuteCode value has not been set, which is a mandatory step for all Code activity types. Not too surprisingly, ExecuteCode establishes the name of the method to execute when this task is encountered by the WF runtime engine.

Using the Properties window, set the value of ExecuteCode to a method named ShowInstructions. Once you press the Enter key, the IDE will update the primary *.cs code file with the following stub code:

public sealed partial class ProcessUsernameWorkflow : SequentialWorkflowActivity

{

public ProcessUsernameWorkflow()

{

InitializeComponent();

}

private void ShowInstructions(object sender, EventArgs e)

{

}

}

928 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

Truth be told, ExecuteCode is an event of the CodeActivity class type. When the WF engine encounters this phase of the sequential workflow, the ExecuteCode event will fire and be handled by the ShowInstructions() method. Implement this method with a handful of Console.WriteLine() statements that display some basic instructions to the end user:

private void ShowInstructions(object sender, EventArgs e)

{

ConsoleColor previousColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("*******************************************"); Console.WriteLine("***** Welcome to the first WF Example *****");

Console.WriteLine("*******************************************\n"); Console.WriteLine("I will now ask for your name and validate the data...\n"); Console.ForegroundColor = previousColor;

}

Adding a While Activity

Recall that our sequential process will prompt the end user for his or her name until the input can be validated against a custom business rule (that is yet to be defined). Such looping behavior can be represented using the While activity. Specifically, a While activity allows us to define a set of related activities that will continuously execute until a specified condition is true.

To illustrate, begin by dragging a While activity from the Windows Workflow Toolbox directly below the previous Code activity and rename this new activity to AskForNameLoopActivity. The next step is to define the condition that will be used to exit the loop itself by setting the Condition value from the Properties window.

The Condition value (which is a common property of many activities) can be set in two key ways. First of all, you can establish a code condition. As the name implies, this option allows you to specify a method in your class that will be called by the activity in order to determine if it should proceed. To inform the activity of this fact, the method specified will eventually need to return a Boolean value (true to repeat, false to exit).

The second way the Condition value can be set is by establishing a declarative rule condition. This option can be useful in that you are able to specify a single code statement that evaluates to true or false; however, this statement is not hard-coded in your assembly, but is instead offloaded to a *.rules file. One benefit of this approach is that it makes it possible to modify rules in a declarative manner.

Our condition will be based on some custom code that we have yet to author; however, the first step is to select the Code Condition option from the Condition value, and then specify the name of the method that will perform the test. Again using the Properties window, name this method

GetAndValidateUserName (see Figure 26-7).

As soon as you specify the name of the code condition used to test the While activity, the IDE will generate a method stub where the second parameter is of type ConditionalEventArgs. This type contains a property named Result, which can be set to true or false based on the success or failure of the condition you are modeling.

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

929

Figure 26-7. The configured While activity

Add a new automatic property of type string named UserName to your ProcessUsernameWorkflow class. Within the scope of the GetAndValidateUserName() method, ask the user to enter his or her name, and if the name consists of fewer than ten characters, set the Result property accordingly. Here are the updates in question:

public sealed partial class ProcessUsernameWorkflow : SequentialWorkflowActivity

{

// Use C# automatic property. public string UserName { get; set; }

private void GetAndValidateUserName(object sender, ConditionalEventArgs e)

{

Console.Write("Please enter name, which must be less than 10 chars: "); UserName = Console.ReadLine();

// See if name is correct length, and set the result. e.Result = (UserName.Length >= 10);

}

...

}

The final task to complete the While activity involves adding at least a single activity within the scope of the While logic. Here we will add a new Code activity named NameNotValidActivity, which has been connected to a method named NameNotValid via the ExecuteCode value. Figure 26-8 shows the final workflow diagram. The implementation of NameNotValid() is intentionally simple:

private void NameNotValid(object sender, EventArgs e)

{

Console.WriteLine("Sorry, try again...");

}

930 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

Figure 26-8. The final workflow design

At this point, you may compile and run this workflow-enabled application. When you execute the program, purposely enter more than ten characters a few times. You will notice that the runtime engine forces the user to reenter data until the business rule (a name of fewer than ten characters) is honored. Figure 26-9 shows one possible output.

Figure 26-9. The workflow-enabled application in action

Examining the WF Engine Hosting Code

While our first example executes as expected, we have yet to examine the code that actually instructs the WF runtime engine to execute the tasks that represent the current workflow. To understand this aspect of WF, open the Program.cs file that was created when you defined your initial project. Within the Main() method, you will find code that makes use of two primary types,

WorkflowRuntime and WorkflowInstance.

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

931

As the names suggest, the WorkflowRuntime type represents the WF runtime engine itself, while WorkflowInstance is used to represent an instance of a given (pardon the redundancy) workflow instance. Here is the Main() method in question, annotated with my various code comments:

static void Main(string[] args)

{

// Ensure the runtime shuts down when we are finished. using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

AutoResetEvent waitHandle = new AutoResetEvent(false);

//Handle events that capture when the engine completes

//the workflow process and if the engine shuts down with an error. workflowRuntime.WorkflowCompleted

+= delegate(object sender, WorkflowCompletedEventArgs e)

{

waitHandle.Set();

};

workflowRuntime.WorkflowTerminated

+= delegate(object sender, WorkflowTerminatedEventArgs e)

{

Console.WriteLine(e.Exception.Message);

waitHandle.Set();

};

// Now, create a WF instance that represents our type.

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(UserDataWFApp.ProcessUsernameWorkflow)); instance.Start();

waitHandle.WaitOne();

}

}

First of all, notice that the WorkflowCompleted and WorkflowTerminated events of WorkflowRuntime are handled using C# anonymous method syntax. The WorkflowCompleted event fires when the WF engine has completed executing a workflow instance, while WorkflowTerminated fires if the engine terminates with an error.

Strictly speaking, you are not required to handle these events, although the IDE-generated code does so in order to inform the waiting thread these events have occurred using the AutoResetEvent type. This is especially important for a console-based application, as the WF engine is operating on a secondary thread of execution. If the workflow logic did not make use of some sort of wait handle, the main thread might terminate before the WF instance was able to perform its work.

The next point of interest regarding the code within Main() is the creation of the WorkflowInstance type. Notice that the WorkflowRuntime type exposes a method named CreateWorkflow(), which expects type information representing the workflow to be created. At this point, we simply call Start() from the returned object reference. This is all that is required to fire up the WF runtime engine and begin the processing of our custom workflow.

Adding Custom Startup Parameters

Before we move on to a more interesting workflow example, allow me to address how to define application-wide parameters. If you examine the signature of the designer-generated methods