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

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

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

932CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

used by our Code activities (ShowInstructions() and NameNotValid() specifically), you may have noticed that they are called via WF events that are making use of the System.EventHandler delegate (given the incoming parameters of type object and System.EventArgs).

Because this .NET delegate demands the registered event handler and takes System.Object and System.EventArgs as arguments, you may wonder how to pass in custom parameters to be used by a Code activity. In fact, you may be wondering how to define custom arguments that can be used by any activity within the current workflow instance.

As it turns out, the WF engine supports the use of custom parameters using a generic Dictionary<string, object> type. The name/value pairs added to the Dictionary object must then be associated to (identically named) properties on your workflow instance. Once you’ve done this, you can pass these arguments into the WF runtime engine when you start your workflow instance. Using this approach, you are able to get and set custom parameters throughout a particular workflow instance.

Note The names defined within a Dictionary object must map to public properties, not public member variables! If you attempt to do so, you will generate a runtime exception.

To try this out firsthand, begin by updating the code within Main() to define a Dictionary<string, object> containing two data items. The first item is a string that represents the error message to display if the name is too long; the second item is a numeric value that will be used to specify the maximum length of the user name. To register these parameters with the WF runtime engine, pass in your Dictionary object as a second parameter to the CreateWorkflow() method. Here are the relevant updates:

using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

{

...

//Define two parameters for use by our workflow.

//Remember! These must be mapped to identically named

//properties in our workflow class type.

Dictionary<string, object> parameters = new Dictionary<string, object>(); parameters.Add("ErrorMessage", "Ack! Your name is too long!"); parameters.Add("NameLength", 5);

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

//and pass in parameters.

WorkflowInstance instance = workflowRuntime.CreateWorkflow(

typeof(UserDataWFApp.ProcessUsernameWorkflow), parameters); instance.Start();

waitHandle.WaitOne();

}

Note In the preceding code, the values assigned to the ErrorMessage and NameLength dictionary items are hard-coded. A more dynamic approach is to read these values from a related *.config file, or perhaps from incoming command-line arguments.

If you try running your program at this point, you will encounter a runtime exception, as you have yet to associate these incoming values to public properties on your workflow type. Once you have done so, however, the runtime will invoke them upon workflow creation. After this point,

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

933

you can use these properties to get and set the underlying data values. Here are the relevant updates to the ProcessUsernameWorkflow class type:

public sealed partial class ProcessUsernameWorkflow : SequentialWorkflowActivity

{

...

// These properties map to the names within the Dictionary object. public string ErrorMessage { get; set; }

public int NameLength { get; set; }

private void GetAndValidateUserName(object sender, ConditionalEventArgs e)

{

Console.Write("Please enter name, which much be less than {0} chars: ", NameLength);

UserName = Console.ReadLine();

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

}

private void NameNotValid(object sender, EventArgs e)

{

Console.WriteLine(ErrorMessage);

}

...

}

Beyond the fact that you have added two new automatic properties, notice that the GetAndValidateUserName() method is now checking for the length specified by the NameLength property, while the error message prints out the value found within the ErrorMessage property. In both cases, these values are determined via the Dictionary object passed in at the time the workflow instance was created. Figure 26-10 shows some possible output for this modified example.

Figure 26-10. The workflow in action, now with custom parameters

Source Code The UserDataWFApp example is included under the Chapter 26 subdirectory.

934 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

Invoking Web Services Within Workflows

WF provides several activities that allow you to interact with XML web services during the lifetime of your workflow-enabled application. When you wish to simply call an existing web service, you can make use of the InvokeWebService activity.

Note Given that WCF is the preferred API to build services, this edition of the text does not cover the construction of XML web services in any great detail (Chapter 25 broached the topic in passing); therefore the web service we will be calling here is intentionally simple.

Creating the MathWebService

The first task is to build an XML web service that can be utilized by a workflow-enabled application. To do so, create a brand-new XML web service project by accessing the File New Web Site menu option. Select the ASP.NET Web Service icon and be sure to select the HTTP location option in order to create this web service within a new IIS virtual directory mapped to http://localhost/ MathWebService (see Figure 26-11).

Figure 26-11. Adding an XML web service project to the WF application

This XML web service will allow external callers to perform basic mathematical operations on two integers using the following [WebMethod]-adorned public members:

[WebService(Namespace = "http://intertech.com/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class MathService : System.Web.Services.WebService

{

[WebMethod]

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

935

public int Add(int x, int y)

{return x + y; } [WebMethod]

public int Subtract(int x, int y)

{return x - y; }

[WebMethod]

public int Multiply(int x, int y) { return x * y; }

[WebMethod]

public int Divide(int x, int y)

{

if (y == 0) return 0;

else

return x / y;

}

}

Notice that we have accounted for a division by zero error by simply returning 0 if the y value is in fact zero. Also notice that we have renamed this service to MathService, and therefore we must also update the Class attribute in the *.asmx file as so:

<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs"

Class="MathService" %>

At this point you can test your XML web service by running (Ctrl+F5) or debugging (F5) the project. When you do so, you will find a web-based testing front end that allows you to invoke each web method (see Figure 26-12).

Figure 26-12. Testing our MathWebService

At this point you can close down the web service project.

Source Code The MathWebService example is included under the Chapter 26 subdirectory.

936 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

Building the WF Web Service Consumer

Now create a new Sequential Workflow Console Application project named WFMathClient and rename your initial C# file to MathWF.cs. This application will ask the user for data to process and which operation they wish to perform (addition, subtraction, etc.). To begin, open your code file and define a new enum type named MathOperation:

public enum MathOperation

{

Add, Subtract, Multiply, Divide

}

Next, define four automatic properties in your class, two of which represent the numerical data to process, one of which represents the result of the operation, and one of which represents the mathematical operation itself (note the default constructor of MathWF sets the value of the Operation property to MathOperation.Add):

public sealed partial class MathWF : SequentialWorkflowActivity

{

// Properties.

public int FirstNumber { get; set; } public int SecondNumber { get; set; } public int Result { get; set; }

public MathOperation Operation { get; set; }

public MathWF()

{

InitializeComponent();

// Set default Operation to addition.

Operation = MathOperation.Add;

}

...

}

Now, using the WF designer, add a new Code activity named GetNumericalInput that is mapped to a method named GetNumbInput(), by setting the ExecuteCode value via the Properties window. Within this method, prompt the user to enter two numerical values that are assigned to your FirstNumber and SecondNumber properties:

public sealed partial class MathWF : SequentialWorkflowActivity

{

...

private void GetNumbInput(object sender, EventArgs e)

{

//For simplicity, we are not bothering to verify that

//the input values are indeed numerical.

Console.Write("Enter first number: "); FirstNumber = int.Parse(Console.ReadLine());

Console.Write("Enter second number: "); SecondNumber = int.Parse(Console.ReadLine());

}

}

Add a second Code activity named GetMathOpInput mapped to a method named GetOpInput() in order to ask the user how he or she wishes to process the numerical data. To do so,

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

937

we will assume the user will specify the letters A, S, M, or D, and based on this character value, set the MathOperation property to the correct enumeration value. Here is one possible implementation:

private void GetOpInput(object sender, EventArgs e)

{

Console.WriteLine("Do you wish to A[dd], S[ubtract],"); Console.Write("Do you wish to M[ultiply] or D[ivide]: "); string option = Console.ReadLine();

switch (option.ToUpper())

{

case "A":

Operation = MathOperation.Add; break;

case "S":

Operation = MathOperation.Subtract; break;

case "M":

Operation = MathOperation.Multiply; break;

case "D":

Operation = MathOperation.Divide; break;

default:

numericalOp = MathOperation.Add; break;

}

}

At this point we have the necessary data. Now let’s check out how to pass it to our XML web service for processing.

Configuring the IfElse Activity

Given that fact that our numerical data can be processed in four unique manners, we will use an IfElse activity to determine which web method of the service to invoke. When you drag an IfElse activity onto your designer, you will automatically be given two branches. To add additional branches to an IfElse activity, right-click the IfElse activity icon and select the Add Branch menu option. Figure 26-13 shows the current WF designer (note that each branch and the entire IfElse activity have been given proper names).

Each branch of an IfElse activity can contain any number of internal activities that represent what should take place if the decision logic results in moving the flow of the application down a given path. Before we add these subactivities, however, we first need to add the logic that allows the WF engine to determine which branch to take by setting the Condition value to each IfElseBranch activity.

Recall that the Condition event can be configured to establish a code condition or a declarative rule condition. The first example project in this chapter already illustrated how to create a code condition, so in this example we will opt for rule conditions. Starting with the AddBranch, set the Condition value to Declarative Rule Condition using the Visual Studio Properties window. Next, click the ellipsis button for the ConditionName subnode, and click the New button from the resulting dialog box. Here, you are able to author a code expression that will determine the truth or falsity of the current branch. For this first branch, the expression is as follows:

this.Operation == MathOperation.Add

938 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

Figure 26-13. A multibranching IfElse activity

You’ll notice that this dialog box supports IntelliSense, which is always a welcome addition (see Figure 26-14).

Figure 26-14. Defining a declarative rule condition

Once you have set each rule, you will now notice that a new file with a *.rules extension has been added to your project (see Figure 26-15).

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

939

Figure 26-15. *.rules files hold each declarative rule you have established for a WF.

If you were to open this file, you would find a number of <RuleExpressionCondition> elements that describe the conditions you have established.

Configuring the InvokeWebService Activities

The final tasks are to pass the incoming data to the correct web method and print out the result. Drag an InvokeWebService activity into the leftmost branch. Doing so will automatically open the Add Web Reference dialog box, where you can specify the URL of the web service (which for this example is http://localhost/MathWebService/Service.asmx) and click the Add Reference button (see Figure 26-16).

Figure 26-16. Referencing our XML web service

940 CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

When you do so, the IDE will generate a proxy to your web service and use it as the value to the InvokeWebService’s ProxyClass property. At this point, you can use the Properties window to specify the web method to invoke via the MethodName property (which is the Add method for this branch), and map the two input parameters to your FirstNumber and SecondNumber properties and the return value to the Result property. Figure 26-17 shows the full configuration of the first InvokeWebService activity.

Figure 26-17. A fully configured InvokeWebService activity

You can now repeat this process for the remaining three IfElse branches, specifying the remaining web methods. Do be aware that even though the Add Web Reference dialog box will appear for each InvokeWebService activity, the IDE is smart enough to reuse the existing proxy, as each activity is communicating with the same endpoint.

Last but not least, we will add one final Code activity after the IfElse logic that will display the result of the user-selected operation. Name this activity DisplayResult, and set the ExecuteCode value to a method named ShowResult(), which is implemented as so:

private void ShowResult(object sender, EventArgs e)

{

Console.WriteLine("{0} {1} {2} = {3}",

FirstNumber, Operation.ToString().ToUpper(), SecondNumber, Result);

}

For simplicity, we are using the textual value of the Operation property to represent the selected mathematical operator, rather than adding additional code to map MathOperation.Add to a + sign

CHAPTER 26 INTRODUCING WINDOWS WORKFLOW FOUNDATION

941

and MathOperation.Subtract to a - sign, and so on. In any case, Figure 26-18 shows the final design of our workflow; Figure 26-19 shows one possible output.

Figure 26-18. The completed web service–centric workflow

Figure 26-19. Communicating with XML web services from a WF application

Communicating with WCF Services Using SendActivity

To complete this example, let’s examine how a workflow-enabled application can communicate with WCF services. The .NET 3.5–centric SendActivity and ReceiveActivity types allow you to build workflow-enabled applications that communicate with WCF services. As the name implies, the SendActivity type can be used to make calls on WCF service operations, while ReceiveActivity provides a way for the WCF service to make calls back on the WF (in the case of a duplex calling contract).