Workflow Services

This article provides some information about construction of services that can be added to the workflow runtime, and also a sample service that you can use to save your fingers when writing a simple workflow application.

When you construct the WorkflowRuntime object within your code, you can optionally specify a set of services that are available to any workflows that are executing. Typical examples are the persistence service or the tracking service. You can also add your own services into the runtime, and these are typically used to communicate to other applications outside the scope of the workflows. A service is simply an instance of a class that implements some user defined functionality - the workflow runtime neither knows or cares about your service implementation - it's just an object that's available in the context of a running workflow.

There are two main ways to add services - you can construct them in code or use an application configuration file. Using code is all fair and good for simple examples, but more frequently services are defined within the application configuration file (be that App.Config for a Windows application or Web.Config for a web site). I'll use the config file approach here as it's easier to swap in/out services if all you have to do is alter the .config file. The first example here shows the app.config file for a simple workflow application, and shows how to add a service into the runtime just using the configuration based approach...

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <section name="WF" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime,
                             Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
  </configSections>

  <WF>
    <Services>
      <add type="WFServices.RuntimeEventWriter, WFServices" Console="true" Debug="true"/>
    </Services>
  </WF>

</configuration>

This config file declares the reader object (WorkflowRuntimeSection) that will read the data within the section entitled "WF", and within this section I've declared a service that is implemented in the download code - more on this service later in the article.

When you construct the runtime within code, you can optionally specify the section that will be read from the configuration file - the code below constructs an instance of the WorkflowRuntime that reads its configuration from the "WF" section of the config file.

using(WorkflowRuntime runtime = new WorkflowRuntime("WF"))
{
}

When the runtime is constructed in this manner, the named section in the configuration file is parsed and and services added within the <Services> element will be constructed and added to the runtime. This construction process is somewhat different from that of service construction in ASP.NET so I'll discuss it here.

Within .NET 2.0, Microsoft added the concept of services to ASP.NET, so that you could swap implementations of things like Membership, Profiles etc simply by changing a line in the application configuration file. A service is implemented by a service provider, and Microsoft added the abstract ProviderBase class that a service can derive from. Having a base class for services makes a lot of sense - especially as each service may potentially need to read configuration data from the application configuration file. So, ProviderBase has a method called Initialize which is used to pass a collection of name/value pairs to the service.

You can imagine the code used to construct a service would create an instance of that service (using Activator.CreateInstance), and then check if the instance was derived from ProviderBase, and if so call its Initialize method, passing any attributes defined in the config file to the service.

Now, I bring this up as service construction in Windows Workflow doesn't adhere to this model. With WF, a service needs to have one of four public constructors defined. These are as shown below...

public YourService(IServiceProvider provider, NameValueCollection parameters);
public YourService(NameValueCollection parameters);
public YourService(IServiceProvider provider);
public YourService();

You can choose any - or all of these constructors. The code within the workflow runtime uses Reflection to find the appropriate constructor, and calls one of the above to create the service. The order of construction is as shown above as most preferred at the top to least preferred at the bottom. The object that implements IServiceProvider being passed in is the WorkflowRuntime instance. So, if you want to parse parameters passed to a workflow service, you'll need to use one of the constructors that takes a NameValueCollection. Personally I would have preferred the folks designing this API to have used the two stage construction approach used in ASP.NET (i.e. use the ProviderBase class), but it is the way it is so now you know.

With that little lot out of the way, the next thing to know is that (to paraphrase a well known book), "All services are created equal, but some are more equal than others".

When a service is constructed and added to the services collection maintained by the workflow runtime, we check if that service is derived from the WorkflowRuntimeService class. If so it gets some special treatment by the runtime, in that your service is provided with a link to the WorkflowRuntime that it resides within, and you can also override the OnStarted and OnStopped methods to do appropriate startup or teardown code. Most services may need a hook back to the workflow runtime, so deriving from WorkflowRuntimeService is very useful. Obviously if you are not derived from WorkflowRuntimeService you can still get hold of the runtime by using one of the constructors that is passed the IServiceProvider instance.

The RuntimeEventWriter custom service

The point of writing this article was to present a (semi!) useful service, and to that end I've come up with the RuntimeEventWriter service. When you write a Workflow Application, it is common to hook up code to handle events on the runtime such as WorkflowStarted, WorkflowCompleted etc. I've lost count of the number of times I've written the same code (Console.WriteLine ( "Workflow x started")), so thought that a service would be useful here.

My service implementation just hooks each event raised by the runtime, and writes out a message to one of three places when that event occurrs. These three places are the Console, the Degug stream and the Trace stream. You may have noticed the attributes on the earlier service configuration - Console="true" and Debug="true". All these do is make the service output to the appropriate place when an event is raised. The three attributes are (somewhat unsurprisingly) called Console, Debug and Trace. Each should have a value of "true" to enable output to that destination, and anything else if output should be disabled.

Once you add this service into your workflow runtime, you'll never need to hook the WorkflowCompleted or other events again - as the service does this for you. Less code more smiles as a friend of mine used to say. There's nothing terribly interesting about the implementation of this service - it just hooks each Runtime event in the OnStart method and unhooks them all again in the OnStop method.

IStartWorkflow

Have you ever wondered how you can start a workflow from within a workflow service? If so, you'll appreciate knowing about the IStartWorkflow interface. You can access this from the ActivityExecutionContext passed into your activities Execute method. If however you want to start another workflow inside a custom service (such as the one I've presented here), you simply need to derive from WorkflowRuntimeService and you can then access the Runtime as a property on your service. Coolio.

Downloads

There are two downloads here - the first being just the .cs implementaton of the RuntimeEventWriter class, the second being a project that contains all the code an simple test application.

RuntimeEventWriter.cs

WFServices.zip