In order to start a workflow, it is necessary to create an instance of the WorkflowRuntime class. This is typically done once within your application, and this object is usually defined as a static member of the application so that it can be accessed anywhere within the application.
When you start the runtime, it can then reload any workflow instances that were executing the last time the application was executed by reading these instances from the persistence store. This uses a service called the persistence service, which is defined in the following section.
The runtime contains six various CreateWorkflow methods that can be used to construct workflow instances. The runtime also contains methods for reloading a workflow instance and enumerating all
The runtime also has a number of events that are raised while workflows are executing – such as WorkflowCreated (raised-when a new workflow instance is constructed), Workflowldled (raised when a workflow is awaiting input such as in the expense-processing example shown earlier), and WorkflowCompleted (raised when a workflow has finished).
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. Some standard services are . provided to your workflow by the runtime, and you can optionally construct your own services to be consumed by running workflows. .
This section describes two of the standard services provided by the runtime. It then shows how you can create your own services and some instances of when this is necessary.
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:
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.
Two methods are available for adding services to the runtime. You can construct the services in code and then add them to the runtime by calling the Add Service method. Or, you can define services within the application configuration file, and these ~ill 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:
Here are constructed instances of the Sql Work flow Persistence Service, which is used by the runtime to store workflow state, and an instance of the Sql Tracking Service, 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 and then add services to this section as show here:
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 here:
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 workflow Persistence Service 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 Database such as SQLServer.
The workflow libraries contain an implementation of the persistence service, which stores data within a SQL Server database – this is the Sql Workflow Persistence Service. In order to use this service, you need to run two scripts against you 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. S\Windows Workflow Foundation\SQL\EN directory.
The scripts to execute against the database are SqlPersistenceProviderSchema. sql and SqlPersistenceProviderLogic. 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: Instance State and Completed Scope. 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 Instance State 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 accessible by only 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 Sql Workflow Persistence Service to the runtime. The following code shows an example using declarative code:
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 Instance State table. The 05 Workflow Persistence example contains the preceding code and executes a delay within a 20 second period.
The parameters passed to the constructor of the persistence service are shown in the following table.
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 If Else Activity 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 Tracking Service, 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 Sql Tracking Service.
To record data about the state of a workflow, it is necessary to define a Tracking profile. This defines which events should be recorded, so you could. for example, record just 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 work flow and each activity in that workflow 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. H one is found, 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 43-19.The class includes collection properties for activity, user, and workflow track points. A track point is an object (such as workflow Track Point) 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 Workflow Track Point, which will record some data when the workflow is created, and another to record some data when the workflow is completed.
Once this data has been recorded, it may be necessary to display the execution path of a workflow, as in Figure 43-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.
To read the data stored by the Sql Tracking Service, you could execute queries against the SQL database directly however, Microsoft has provided the Sql Tracking Query class defined within the System. workflow. Runtime. Tracking namespace for this purpose. The following example code shows how to retrieve a list of all workflows tracked between two dates:
This uses the Sql Tracking Query Options class, which defines the query parameters. You can define other properties of this class to further constrain the workflows retrieved.
In Figure 43-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 43-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.
In addition to built-in services such as the persistence service and the tracking service, you can add your own objects to the services collection maintained by the Workflow Runtime, These services are typically defined using an interface and an implementation, so that you can replace the service-without recording the workflow.
The state machine presented earlier in the chapter uses the following interface:
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 External Data Exchange 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, because the workflow runtime also contains a service called the External Data Exchange Service, which acts as a proxy for these events. This proxy is used when the event is raised, because the workflow may not be loaded in memory at the time the event is delivered. So the event is first routed to the external data exchange service, which checks to see if the workflow is loaded, and, if not, re hydrates it from the persistence store and then passes the event on into the workflow.
The code used to construct the External Data Exchange Service and to construct proxies for the events defined by the service is shown here:
This constructs an instance of the external data exchange service and adds it to the runtime. It then
creates an instance of the DoorService (which “!tselfimplements IDoorService) and adds this to the
external data exchange service.
The ExternalDataExchangeService. Addmethod 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, 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, because 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 dtrive a class from ExternalDataEventArgs and add these values as properties
to that class.