Workflow Services


A workflow doesn’t exist on its own - as described in the previous section, a workflow is executed within the WorkflowRuntime and this runtime provides services to running workflows.

A service is any class that may be needed while executing the workflow. There are some standard services that are provided to your workflow by the runtime, and you can optionally construct your own services to be consumed by running workflows.

In this section, I will describe two of the standard services provided by the runtime, and then show how you can create your own services and some instances of when this is necessary.

When an activity runs, it is passed some contextual information via the ActivityExecutionStatus parameter of the Execute method.

  protected override ActivityExecutionStatus Execute     (ActivityExecutionContext executionContext) {     ... } 

One of the methods available on this context parameter is the GetService<T> method. This can be used as shown in the following code to access a service attached to the workflow runtime.

 protected override ActivityExecutionStatus Execute     (ActivityExecutionContext executionContext) {     ICustomService myService = executionContext.GetService<ICustomService>();     ... Do something with the service }

The services hosted by the runtime are added to the runtime prior to calling the StartRuntime method - an exception is raised if you attempt to add a service to the runtime once it has been started.

There are two methods available for adding services to the runtime - you can construct the services in code and then add them to the runtime by calling the AddService method, or you can define services within the application configuration file and these will be constructed for you and added to the runtime.

The following code snippet shows how to add services to the runtime in code - the services added are those described later in this section.

  using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) {     workflowRuntime.AddService(         new SqlWorkflowPersistenceService(conn, true, new TimeSpan(1,0,0),                                           new TimeSpan(0,10,0)));     workflowRuntime.AddService(new SqlTrackingService(conn));     ... } 

Here are constructed instances of the SqlWorkflowPersistenceService, which is used by the runtime to store workflow state, and an instance of the SqlTrackingService, which records the execution events of a workflow while it runs.

To create services using an application configuration file, you need to add a section handler for the workflow runtime, then add services to this section, as shown below:

  <?xml version="1.0" encoding="utf-8" ?> <configuration>   <configSections>     <section name="WF"       type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection,       System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,       PublicKeyToken=31bf3856ad364e35" />   </configSections>   <WF Name="Hosting">     <CommonParameters/>       <Services>         <add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService,                    System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,                    PublicKeyToken=31bf3856ad364e35"           connectionString="Initial Catalog=WF;Data Source=.;                             Integrated Security=SSPI;"           UnloadOnIdle="true"           LoadIntervalSeconds="2"/>         <add type="System.Workflow.Runtime.Tracking.SqlTrackingService,                    System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,                    PublicKeyToken=31bf3856ad364e35"           connectionString="Initial Catalog=WF;Data Source=.;                             Integrated Security=SSPI;"           UseDefaultProfile="true"/>       </Services>   </WF> </configuration> 

Within the configuration file, you have added the WF section handler (the name is unimportant but must match the name given to the later configuration section) and then created the appropriate entries for this section. The <Services> element can contain an arbitrary list of entries that consist of a .NET type and then parameters that will be passed to that service when constructed by the runtime.

To read the configuration settings from the application configuration file, you call another constructor on the runtime, as shown below:

  using(WorkflowRuntime workflowRuntime = new WorkflowRuntime("WF")) {     ... } 

This constructor will instantiate each service defined within the configuration file and add these to the services collection on the runtime.

The following sections describe some of the standard services available with WF.

The Persistence Service

When a workflow executes, it may reach a wait state - this can occur when a delay activity executes or when you are waiting for external input within a listen activity. At this point, the workflow is said to be idle and as such is a candidate for persistence.

Let’s assume that you begin execution of 1,000 workflows on your server, and each of these instances becomes idle. At this point, it is unnecessary to maintain data for each of these instances in memory, so it would be ideal if you could unload a workflow and free up the resources in use. The persistence service is designed to accomplish this.

When a workflow becomes idle the workflow runtime checks for the existence of a service that derives from the WorkflowPersistenceService class. If this service exists, it is passed the workflow instance and the service can then capture the current state of the workflow and store it in a persistent storage medium. You could store the workflow state on disk in a file, or store this data within a database such as SQL Server.

The workflow libraries contain an implementation of the persistence service, which stores data within a SQL Server database - this is the SqlWorkflowPersistenceService. In order to use this service, you need to run two scripts against your SQL Server instance - one of these constructs the schema and the other creates the stored procedures used by the persistence service. These scripts are, by default, located in the C:\Windows\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN directory.

The scripts to execute against the database are SqlPersistenceService_Schema.sql and SqlPersistenceService_Logic.sql. These need to be executed in order, with the schema file first and then the logic file. The schema for the SQL persistence service contains two tables: InstanceState and CompletedScope: these are essentially opaque tables, and they are not intended for use outside the SQL persistence service.

When a workflow idles, its state is serialized using binary serialization, and this data is then inserted into the InstanceState table. When a workflow is reactivated, the state is read from this row and used to reconstruct the workflow instance. The row is keyed on the workflow instance ID and is deleted from the database once the workflow has completed.

The SQL persistence service can be used by multiple runtimes at the same time - it implements a locking mechanism so that a workflow is only accessible by one instance of the workflow runtime at a time. When you have multiple servers all running workflows using the same persistence store, this locking behavior becomes invaluable.

To see what is added to the persistence store, construct a new workflow project and add an instance of the SqlWorkflowPersistenceService to the runtime. The code below shows an example using declarative code:

  using(WorkflowRuntime workflowRuntime = new WorkflowRuntime()) {     workflowRuntime.AddService(         new SqlWorkflowPersistenceService(conn, true, new TimeSpan(1,0,0),                                           new TimeSpan(0,10,0)));     // Execute a workflow here... } 

If you then construct a workflow that contains a DelayActivity and set the delay to something like 10 seconds, you can then view the data stored within the InstanceState table. The 05 WorkflowPersistence example contains the above code and executes a delay with a 20-second period.

The parameters passed to the constructor of the persistence service are shown in the table below.

Open table as spreadsheet

Parameter

Description

Default

ConnectionString

The database connection string used by the persistence service.

None

UnloadOnIdle

Determines whether a workflow is unloaded when it idles. This should always be set to true otherwise no persistence will occur.

False

InstanceOwnershipDuration

This defines the length of time that the workflow instance will be owned by the runtime that has loaded that workflow.

None

LoadingInterval

The interval used when polling the database for updated persistence records.

2 Minutes

These values can also be defined within the configuration file.

The Tracking Service

When a workflow executes it might be necessary to record which activities have run, and in the case of composite activities such as the IfElseActivity or the ListenActivity, which branch was executed. This data could be used as a form of audit trail for a workflow instance, which could then be viewed at a later date to prove which activities executed and what data was used within the workflow. The tracking service can be used for this type of recording and can be configured to log as little or as much information about a running workflow instance as is necessary.

As is common with WF, the tracking service is implemented as an abstract class called TrackingService, so it is easy to replace the standard tracking implementation with one of your own. There is one concrete implementation of the tracking service available within the workflow assemblies - this is the SqlTrackingService.

To record data about the state of a workflow, it is necessary to define a TrackingProfile. This defines which events should be recorded, so you could for example just record the start and end of a workflow and omit all other data about the running instance. More typically, you will record all events for the workflow and each activity in that workflow, so as to provide a complete picture of the execution profile of the workflow.

When a workflow is scheduled by the runtime engine, the engine checks for the existence of a workflow tracking service. If one is found, then it asks the service for a tracking profile for the workflow being executed, and then uses this to record workflow and activity data. You can in addition define user tracking data and store this within the tracking data store without needing to change the schema.

The tracking profile class is shown in Figure 41-19 below. The class includes collection properties for activity, user, and workflow track points. A track point is an object (such as WorkflowTrackPoint) that typically defines a match location and some extra data to record when this track point is hit. The match location defines where this track point is valid - so for example, you could define a WorkflowTrackPoint, which will record some data when the workflow is created, and another to record some data when the workflow is completed.

image from book
Figure 41-19

Once this data has been recorded, it may be necessary to display the execution path of a workflow, as in Figure 41-20. This shows the workflow that was executed, and each activity that ran includes a glyph to show that it executed. This data is read from the tracking store for that workflow instance.

image from book
Figure 41-20

In order to read the data stored by the SqlTrackingService, you could execute queries against the SQL database directly; however, Microsoft have provided the SqlTrackingQuery class defined within the System.Workflow.Runtime.Tracking namespace for this purpose. The example code below shows how to retrieve a list of all workflows tracked between two dates:

  public IList<SqlTrackingWorkflowInstance> GetWorkflows   (DateTime startDate, DateTime endDate, string connectionString) {   SqlTrackingQuery query = new SqlTrackingQuery (connectionString);   SqlTrackingQueryOptions queryOptions = new SqlTrackingQueryOptions();   query.StatusMinDateTime = startDate;   query.StatusMaxDateTime = endDate;   return (query.GetWorkflows (queryOptions)); } 

This uses the SqlTrackingQueryOptions class, which defines the query parameters. You can define other properties of this class to further constrain the workflows retrieved.

In Figure 41-20 you can see that all activities have executed - this might not be the case if the workflow were still running or if there were some decisions made within the workflow so that different paths were taken during execution.

The tracking data contains details such as which activities have executed, and this data can be correlated with the activities to produce the image in Figure 41-20. It is also possible to extract data from the workflow as it executes, which could be used to form an audit trail of the execution flow of the workflow.

Custom Services

In addition to built-in services such as the persistence service and the tracking service, you can also add your own objects to the services collection maintained by the WorkflowRuntime. These services are typically defined using an interface and an implementation, so that you can replace the service without recoding the workflow.

The state machine presented earlier in the chapter utilizes the following interface:

  [ExternalDataExchange] public interface IDoorService {     void LockDoor();     void UnlockDoor();     event EventHandler<ExternalDataEventArgs> RequestEntry;     event EventHandler<ExternalDataEventArgs> OpenDoor;     event EventHandler<ExternalDataEventArgs> CloseDoor;     event EventHandler<ExternalDataEventArgs> FireAlarm;     void OnRequestEntry(Guid id);     void OnOpenDoor(Guid id);     void OnCloseDoor(Guid id);     void OnFireAlarm(); } 

The interface consists of methods that are used by the workflow to call the service, and events raised by the service that are consumed by the workflow. The use of the ExternalDataExchange attribute indicates to the workflow runtime that this interface is used for communication between a running workflow and the service implementation.

Within the state machine, there are a number of instances of the CallExternalMethodActivity that are used to call methods on this external interface. One example is when the door is locked or unlocked - the workflow needs to execute a method call to the UnlockDoor or LockDoor methods, and the service responds by sending a command to the door lock to unlock or lock the door.

When the service needs to communicate with the workflow, this is done by using an event, as the workflow runtime also contains a service called the ExternalDataExchangeService, which acts as a proxy for these events. This proxy is used when the event is raised, as the workflow may not be loaded in memory at the time the event is delivered, and so the event is first routed to the external data exchange service, which checks to see if the workflow is loaded, and if not rehydrates it from the persistence store and then passes the event on into the workflow.

The code used to construct the ExternalDataExchangeService and to construct proxies for the events defined by the service is shown below:

  WorkflowRuntime runtime = new WorkflowRuntime(); ExternalDataExchangeService edes = new ExternalDataExchangeService(); runtime.AddService(edes); DoorService service = new DoorService(); edes.AddService(service); 

This constructs an instance of the external data exchange service and adds it to the runtime, then creates an instance of the DoorService (which itself implements IDoorService) and adds this to the external data exchange service.

The ExternalDataExchangeService.Add method constructs a proxy for each event defined by the custom service so that a persisted workflow can be loaded prior to delivery of the event. If you don’t host your service within the external data exchange service, then when you raise events there will be nothing listening to these events, so they will not be delivered to the correct workflow.

Events use the ExternalDataEventArgs class, as this includes the workflow instance ID that the event is to be delivered to. If there are other values that need to be passed from an external event to a workflow, you should derive a class from ExternalDataEventArgs and add these values as properties to that class.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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