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