1. Trang chủ
  2. » Công Nghệ Thông Tin

microsoft press windows workflow foundation step by step phần 10 pptx

77 306 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 77
Dung lượng 1,22 MB

Nội dung

392 Part IV External Data Communication HandleExternalEvent activities. We used the wca.exe tool to create custom activities for us based on our interface. (We could have used the CallExternalEvent and HandleExternalEvent activities directly, providing each with the interface and method or event to process, but creating custom activities highlights their use in our workflows.) With the interface in hand, we then created a local service that we plugged in to the workflow runtime to manage our local communications needs. The local service consisted of a data con- nector and a service class. When the application needed to send data to the workflow, it requested the service from the workflow runtime and then raised the events supported by the interface. Your workflow instance would then handle those events, assuming you dropped the event handler into the workflow and invoked the event at an appropriate time (for example, when the workflow anticipated the event and was ready and waiting with the correct event handler). The workflow, on the other hand, had no such need to query the workflow runtime for the local communication service. By dropping instances of CallExternalMethod in your workflow’s processing path, the host would automatically be told of the data’s arrival—again assuming the host application hooked an event handler into the local communication service for the received data event. The workflow runtime keeps track of workflow instances and their association with the local communication service and therefore the host application. Correlation Consider that last paragraph again. The workflow instance didn’t need to rummage around to find the service that communicates with the host application. Yet the host application did need to query for the local communication service. Although this, in part, is due to the nature of host interaction with the workflow runtime, the process also underscores the one-to-many relationship between the host application and workflow instances. The host application needs to identify which workflow instance it wants to communicate with because there might be many to choose from. However, a workflow instance has no such choice—there is only one host application. I’m not saying the need for correlated data flow drove the WF team to architect the host appli- cation’s access to the local communication service in this way. The host always queries the workflow runtime for services in this fashion, and the local communication service is just one service you might want to access. However, the reverse is certainly true. The workflow is bound to the local communication service with no regard for host application identification, and this is a direct result of an architecture designed around having many workflow instances in one host application. There can be no more than one host application, so there is no need to identify it. Therefore, the workflow runtime provides the workflow instance with the local communication service, and the workflow instance calls external methods at will. Chapter 17 Correlation and Local Host Communication 393 So is it enough for the host to use the workflow instance identifier as the way to correlate data flow? That is, if you keep track of a workflow instance and try to send data back and forth to and from that workflow, wouldn’t merely having the workflow’s instance ID be enough to uniquely identify the workflow and the data? Yes, if you had but a single data flow. But it’s possible to have multiple data paths into and out of your workflow. For this reason, correlation was born. When you use correlated workflow communication, in the end the workflow runtime creates a storage container for bits of information necessary to identify the workflow and data in ques- tion. This correlation token is consulted when the host and workflow pass data back and forth. If the correlation token indicates that both sides of the conversation are in sync, meaning the correct workflow instance and bound activity are communicating the correct piece of data, communication can proceed. However, if the correlation token indicates a problem, the work- flow runtime does not allow the data communication to proceed and throws an exception. Problems might include using an incorrect workflow instance, communicating the wrong data, calling an activity bound to a different correlation token, or trying to send data without first creating the correlation token. The correlation token is maintained by the CorrelationToken class. When you drop copies of the CallExternalMethod or HandleExternalEvent activity into your workflow, and if correlation is involved, you need to assign a correlation token. Correlation tokens are shared by name, so by assigning a correlation token with the same name to more than one correlated activity, you effectively bind those activities together from a data-conversation perspective. The token’s name is nothing more than a string, the value of which is meaningless. It only matters that activities that share a correlation token share it by identifying the token using the same name. A good question now is, Why didn’t correlation tokens come into play earlier in the book? After all, we certainly have used both CallExternalMethod and HandleExternalEvent activities in previous work. The answer is we chose not to invoke correlation. Correlation isn’t required in all cases, and this was true for every workflow you created up until this chapter. When you have one-to-one mapping between the application and workflow instance, correlation is unnecessary over- head, and happily you can omit it and enjoy slightly increased performance. Even when you have multiple workflow instances working with a single host application, you can work without correlation. However, when you use correlation, WF prevents you from inadvertently mixing up data, and in many cases, this is a very desirable feature. To activate the correlation infrastructure, you use specific WF-based attributes when you create your host communication interface. The good news is the process to perform the host communication doesn’t change by much. The effect the change has on your workflow can be dramatic, however. 394 Part IV External Data Communication The CorrelationParameter Attribute If you think about situations where a single host application might orchestrate multiple work- flow instances, you will probably find that methods and events that pass data also pass some sort of unique identifier. An order processing system might pass a customer ID, or a packag- ing system might pass a lot number. This type of unique identifier is a perfect candidate for identifying unique instances of data, and in fact, that’s precisely what happens. When you design the methods and events in your communication interface, you design into their signatures a data correlation ID. The data correlation ID doesn’t have to be unique for all space and time, like a Guid. However, if it isn’t a Guid, it must be used uniquely for the dura- tion of the workflow instance’s execution. Note Perhaps surprisingly, it isn’t an error if you create two correlated workflow instances that run simultaneously using the same correlation parameter value (akin to creating two workflows working with the same customer ID). Correlation merely associates a single workflow instance with a single correlation parameter value. Calling methods or events to exchange data with a workflow created with one correlation parameter value using a differ- ent correlation value is where the error lies, and this is where WF helps you keep things straight. You tell WF what method parameter carries this data correlation ID value by including the CorrelationParameter attribute in your interface definition (placed there alongside the ExternalDataExchange attribute). WF can then examine the contents of the parameter as the data is moved about the system. If your logic attempts to mix customers or lot numbers, for example, WF will throw the System.Workflow.Activity.EventDeliveryFailedException. This exception is your friend, because it indicates processing logic on your part that could conceivably cross-match data. One customer could be charged for another’s purchase, for instance. Obviously, this result is not desirable. If you receive this exception, you need to check your application logic for incorrect logical operation. The CorrelationParameter attribute accepts a string in its constructor. This string represents the name of the parameter used throughout your interface to contain the unique ID. If you elect to rename the parameter for a given method, you can rename it for a selected event or method using the CorrelationAlias parameter. You’ll read more about this parameter later in the chapter. The CorrelationInitializer Attribute WF also needs to initialize the correlation token when data communications commence. To facilitate this, you place the CorrelationIntializer attribute on the method or event that kicks off the data communication, and there might be more than one. Any attempt to send correlated Chapter 17 Correlation and Local Host Communication 395 data back and forth before executing the method or event marked as the correlation initializer results in an exception. The CorrelationAlias Attribute When you build correlated services, the CorrelationParameter attribute identifies by name the method parameter that is used to convey the data correlation identifier. For your interface methods, this means you must have a method parameter named using the same name as the correlation parameter name. But this can break down for events. If your delegate is created such that the correlation parameter is in the delegate definition, there is no problem. It’s baked into the event handler’s method signature just as if it were any other interface method. The problem arises when you use a delegate that includes event arguments, and those event arguments convey the correlated parameter. For example, imagine your correlation parameter was named customerID. Then consider this delegate: delegate void MyEventHandler(object sender, MyEventArgs e); If the event that used this delegate were placed into your communication interface, the customerID parameter wouldn’t appear in the event handler and WF would throw an excep- tion stating correlation was misapplied when you executed your workflow. However, if MyEventArgs has a property that contains the customer ID, you can use the CorrelationAlias attribute to identify that. For this example, if the MyEventArgs customer ID property were named CustomerID, the correlation parameter’s alias would be e.CustomerID. An important thing to keep in mind is that once you initialize a correlated data path for a single workflow instance, you cannot change the data correlation ID for the lifetime of the workflow instance without generating an error. For example, once you communicate with a workflow data associated with one customer ID, you can’t later communicate data regarding another customer ID to the same workflow instance. What this means is that if your process involves creating customer IDs, such as when inserting information into new database table rows, you need to pre-create the customer ID. You can’t allow the database to generate those for you because your communications would be initialized using no customer ID, or a default “empty” one, only to later begin using the newly created ID. The IDs in question would differ, and your workflow would throw an exception. Building Correlated Workflows When it comes right down to it, in this chapter I’ve introduced the concept of correlation and mentioned only three attributes. Is this all there is to it? 396 Part IV External Data Communication In a word, yes. However, our local service grows a bit more complex because we must account for different data flows. Remember, the local communication service is a singleton service in the workflow runtime, so all data requests to the various workflow instances are made through this one local communication service. By necessity, that service has to keep track of the known workflow instances and correlation parameters so that when the host requests data from a given workflow, the service returns the correct data. Note How you architect your local communication service is up to you. I’ll show you how I build them later in the chapter, but in the end there is no rule that says you have to build your services as I do. The only requirement is that you return the correctly correlated data from your service. So that you understand the bigger picture, I’ll first introduce the application you’ll modify and explain why it uses correlation. The classic example of a correlated workflow is an order-processing system that uses unique customer IDs to keep track of customer orders. But I wanted a different example if only to be different. This chapter’s sample application simulates an application a trucking company might use to track its vehicles. Today, many long-haul trucks are equipped a with Global Positioning System (GPS) that is able to report the truck’s position to the shipping company. Wherever the truck happens to be, you can track it and monitor its progress towards its destination. This sample simulates that type of tracking application, the user interface for which is shown in Figure 17-1. Four trucks are shown, traveling to various destinations (as indicated by the Active Trucks list). The trucks themselves are animated, moving from origin to destination. As they arrive at their destination, they’re removed from the list of active trucks. Figure 17-1 The TruckTracker application user interface Chapter 17 Correlation and Local Host Communication 397 Each truck you see is supported by its own workflow instance. (Because it might be difficult to see the trucks in a two-color book, I circled them.) The heart of the workflow asynchronously updates the truck’s geographic location. When updates are made, the workflow communi- cates the new coordinates to the host application, which then visually updates the truck’s position in the user interface. Of course, we’re simulating the GPS reception, and the simula- tion moves the trucks at a speed far greater than real vehicles could sustain. (It would be silly to run the sample for four days just to see whether a truck actually made it to New Jersey from California.) The true point of the application is to use correlated workflow instances when communicating data with the host application. The trucks follow specific routes to their destinations, driving though other cities on the map. You select the truck’s route when you click Add Truck, the supporting dialog box for which is shown in Figure 17-2. The routes themselves are stored in an XML file that is read as the appli- cation loads. The trip from Sacramento to Trenton, for example, has the truck pass through waypoints Phoenix, Santa Fe, Austin, and Tallahassee. Figure 17-2 The Add Truck dialog box The application’s main program has been completed for you. What remains is completing the service and creating the workflow. We’ll begin by creating the service interface. Adding a correlated communications interface to your application 1. You should find the TruckTracker application in the \Workflow\Chapter17\ TruckTracker directory. As usual, I placed two different versions of the application in the Chapter17 directory—an incomplete version for you to work with, and a completed version that you can execute right now. If you want to follow along but don’t want to actually perform the steps, open the solution file for the completed version. (It will be in the TruckTracker Completed directory.) If you do want to work through the steps, open the TruckTracker version. To open either solution, drag the .sln file onto an executing copy of Microsoft Visual Studio to open the solution for editing and compilation. 2. The solution contains two projects: TruckTracker (the main application) and TruckService. Looking at the TruckService project in Visual Studio’s Solution Explorer window, open the ITruckService.cs file for editing by double-clicking the filename or selecting it and clicking the View Code button on the Solution Explorer toolbar. Note you might have to expand the TruckService project’s tree control node to see the project files. 398 Part IV External Data Communication 3. Between the braces delimiting the interface, add this code: // Workflow-to-host communication [CorrelationInitializer] void ReadyTruck(Int32 truckID, Int32 startingX, Int32 startingY); void UpdateTruck(Int32 truckID, Int32 X, Int32 Y); void RemoveTruck(Int32 truckID); // Host-to-workflow communication [CorrelationAlias("truckID", "e.TruckID")] event EventHandler<CancelTruckEventArgs> CancelTruck; [CorrelationInitializer] [CorrelationAlias("truckID", "e.TruckID")] event EventHandler<AddTruckEventArgs> AddTruck; 4. Just prior to the ExternalDataExchange attribute (which you should find decorating the interface), insert the CorrelationParameter attribute: [CorrelationParameter("truckID")] 5. Save the file. Looking back at the code you inserted in step 3, which is repeated in Listing 17-1, you see each of the attributes discussed in this chapter. The truckID method parameter carries a unique truck identifier, and it’s present in all methods in the interface. The CorrelationParameter attribute, then, tells WF that this method parameter is the one to use for correlation purposes. Listing 17-1 ITruckService.cs completed using System; using System.Collections.Generic; using System.Text; using System.Workflow.ComponentModel; using System.Workflow.Activities; namespace TruckService { [CorrelationParameter("truckID")] [ExternalDataExchange] public interface ITruckService { // Workflow-to-host communication [CorrelationInitializer] void ReadyTruck(Int32 truckID, Int32 startingX, Int32 startingY); void UpdateTruck(Int32 truckID, Int32 X, Int32 Y); void RemoveTruck(Int32 truckID); // Host-to-workflow communication [CorrelationAlias("truckID", "e.TruckID")] event EventHandler<CancelTruckEventArgs> CancelTruck; [CorrelationInitializer] [CorrelationAlias("truckID", "e.TruckID")] event EventHandler<AddTruckEventArgs> AddTruck; } } Chapter 17 Correlation and Local Host Communication 399 The two events, AddTruck and CancelTruck, use a CorrelationAlias attribute to reassign the correlation parameter from truckID to the name e.TruckID because the event arguments carry the correlation identifier for those events. e.TruckID is used for this sample, but any event argument that carries the correlation parameter could have been used. That is, you could alias truckID to any parameter that also carries the correlation value to the workflow. And there are two ways to initialize the correlation mechanism in this interface: the workflow can call ReadyTruck, or the host application can invoke the AddTruck event. Either one kicks off correlated communications because both are decorated with the CorrelationInitializer attribute. Invoking any other method or event prior to the initialization results in a workflow runtime exception. The service project typically carries with it the local communication service, and this sample application is no different. Because the connector class TruckServiceDataConnector is derived from ITruckService, it makes sense to complete that class at this time. Completing the correlated data connector 1. Turning again to the TruckService project, look for the TruckServiceDataConnector.cs file and open it for editing. 2. The TruckServiceDataConnector class is empty, but it clearly derives from ITruckService. So, at the very least, you add the methods and events from the interface to this class. Before you do, however, let’s add some supporting code. First, add the following fields just after the opening class brace: protected const string KeyFormat = "{0}.Truck_{1}"; protected static Dictionary<string, string> _dataValues = new Dictionary<string, string>(); protected static Dictionary<string, WorkflowTruckTrackingDataService> _dataServices = new Dictionary<string, WorkflowTruckTrackingDataService>(); private static object _syncLock = new object(); 3. Because the data connector keeps track of data items and is a singleton in the workflow runtime, we’ll add a pair of static methods to both register and retrieve registered data services. Add these methods following the fields you just inserted: public static WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(Guid instanceID, Int32 truckID) { string key = String.Format(KeyFormat, instanceID, truckID); WorkflowTruckTrackingDataService serviceInstance = null; if (_dataServices.ContainsKey(key)) { // Return the appropriate data service. serviceInstance = _dataServices[key]; } // if 400 Part IV External Data Communication return serviceInstance; } public static void RegisterDataService(WorkflowTruckTrackingDataService dataService) { string key = String.Format(KeyFormat, dataService.InstanceID.ToString(), dataService.TruckID); lock (_syncLock) { _dataServices.Add(key, dataService); } // lock } 4. Once a data service is registered, which occurs in the main application when a new work- flow instance is started (one data service per workflow instance), it stores correlated data in the data connector. We need to have a way to retrieve the data. Previously, we used a property, but that won’t work for us now because we have to pass in both a workflow instance ID and the correlation value (a truck identifier in this case). To retrieve data, then, add this method following the static registration methods: public string RetrieveTruckInfo(Guid instanceID, Int32 truckID) { string payload = String.Empty; string key = String.Format(KeyFormat, instanceID, truckID); if (_dataValues.ContainsKey(key)) { payload = _dataValues[key]; } // if return payload; } 5. With that last method, the housekeeping code is complete. Now let’s add the methods from the ITruckService interface. These follow the data-retrieval method from the preced- ing step: // Workflow-to-host communication methods public void ReadyTruck(Int32 truckID, Int32 startingX, Int32 startingY) { // Pull correlated service. WorkflowTruckTrackingDataService service = GetRegisteredWorkflowDataService( WorkflowEnvironment.WorkflowInstanceId, truckID); // Place data in correlated store. UpdateTruckData(service.InstanceID, truckID, startingX, startingY); Chapter 17 Correlation and Local Host Communication 401 // Raise the event to trigger host activity. if (service != null) { service.RaiseTruckLeavingEvent(truckID, startingX, startingY); } // if } public void UpdateTruck(Int32 truckID, Int32 X, Int32 Y) { // Pull correlated service. WorkflowTruckTrackingDataService service = GetRegisteredWorkflowDataService( WorkflowEnvironment.WorkflowInstanceId, truckID); // Update data in correlated store. UpdateTruckData(service.InstanceID, truckID, X, Y); // Raise the event to trigger host activity. if (service != null) { service.RaiseRouteUpdatedEvent(truckID, X, Y); } // if } public void RemoveTruck(Int32 truckID) { // Pull correlated service. WorkflowTruckTrackingDataService service = GetRegisteredWorkflowDataService( WorkflowEnvironment.WorkflowInstanceId, truckID); // Remove truck from correlated store. string key = String.Format(KeyFormat, service.InstanceID, truckID); if (_dataValues.ContainsKey(key)) { // Remove it. _dataValues.Remove(key); } // if // Raise the event to trigger host activity. if (service != null) { service.RaiseTruckArrivedEvent(truckID); } // if } [...]... new WorkflowRuntime(); // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID); if (RouteUpdated != null) { RouteUpdated(this, new TruckActivityEventArgs(_instanceID, truckID, X, Y)); } // if } public void RaiseTruckArrivedEvent(Int32 truckID) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID);... object return WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(instanceID, truckID); } // lock } public static WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(Guid instanceID, Int32 truckID) { lock (_syncRoot) { WorkflowTruckTrackingDataService workflowDataService = TruckServiceDataConnector.GetRegisteredWorkflowDataService( instanceID, truckID); if (workflowDataService... startingY) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID); if (TruckLeaving != null) { TruckLeaving(this, new TruckActivityEventArgs(_instanceID, truckID, startingX, startingY)); } // if } public void RaiseRouteUpdatedEvent(Int32 truckID, Int32 X, Int32 Y) { if (_workflowRuntime == null) _workflowRuntime... and configure it within the workflow runtime, and another to retrieve a registered service instance: public static WorkflowTruckTrackingDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime, Int32 truckID) { lock (_syncRoot) { // If we're just starting, save a copy of the workflow // runtime reference if (_workflowRuntime == null) { _workflowRuntime = workflowRuntime; } // if... object return WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(instanceID, truckID); } // lock } public static WorkflowTruckTrackingDataService GetRegisteredWorkflowDataService(Guid instanceID, Int32 truckID) { lock (_syncRoot) { WorkflowTruckTrackingDataService workflowDataService = TruckServiceDataConnector GetRegisteredWorkflowDataService(instanceID, truckID); if (workflowDataService... { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); 413 414 Part IV External Data Communication // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID); if (RouteUpdated != null) { RouteUpdated(this, new TruckActivityEventArgs(_instanceID, truckID, X, Y)); } // if } public void RaiseTruckArrivedEvent(Int32 truckID) { if (_workflowRuntime == null) _workflowRuntime... Clean up _workflowRuntime = null; _dataExchangeService = null; _dataConnector = null; } public string Read() { return _dataConnector.RetrieveTruckInfo(InstanceID, TruckID); } public void RaiseTruckLeavingEvent(Int32 truckID, Int32 startingX, Int32 startingY) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID);... _truckID; } set { _truckID = value; } } public static WorkflowTruckTrackingDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime, Int32 truckID) { lock (_syncRoot) { 412 Part IV External Data Communication // If we're just starting, save a copy of the workflow // runtime reference if (_workflowRuntime == null) { _workflowRuntime = workflowRuntime; } // if // If we're just starting,... (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances _workflowRuntime.GetWorkflow(_instanceID); if (TruckArrived != null) { TruckArrived(this, new TruckActivityEventArgs(_instanceID, truckID)); } // if } } } Creating the correlated data exchange workflow ● Creating the workflow in this case is no different from how you created workflow projects in the past Simply... (workflowDataService == null) { workflowDataService = new WorkflowTruckTrackingDataService(instanceID, truckID); TruckServiceDataConnector.RegisterDataService( workflowDataService); } // if return workflowDataService; } // lock } 6 Now add a constructor and destructor: private WorkflowTruckTrackingDataService(Guid instanceID, Int32 truckID) { this._instanceID = instanceID; this._truckID = truckID; } ~WorkflowTruckTrackingDataService() . Int32 startingY) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances. _workflowRuntime.GetWorkflow(_instanceID); if (TruckLeaving. Int32 X, Int32 Y) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances. _workflowRuntime.GetWorkflow(_instanceID); if (RouteUpdated. RaiseTruckArrivedEvent(Int32 truckID) { if (_workflowRuntime == null) _workflowRuntime = new WorkflowRuntime(); // Loads persisted workflow instances. _workflowRuntime.GetWorkflow(_instanceID); if (TruckArrived

Ngày đăng: 06/08/2014, 02:20