Tuesday, July 17, 2007

Adding services to the Windows Workflow runtime and a simple activity implementation

There are two groups of Windows Workflow service that you'll come across. The first group are the well-known services that are used by the runtime itself (well-known because they are well-known to the runtime not necessarily to the developer using the runtime...). These are things like the persistence service, tracking service, data exchange service etc. You can use these services out of the box or you can inherit from them and modify them as required. Fortunately a lot of the methods in these services are virtual so you can quite easily hook in new functionality (and quite possibly get yourself in a whole heap of trouble as well).

The second group of services are services that can be used by your own custom activities. At this point it's worth considering when using your own service makes sense. For instance I downloaded an email sender activity that required me to configure the SMTP host address and port as properties on the activity. Since it was a standalone activity, this was probably the sensible option, since it simplified usage. But if I were to use the activity in many different workflows and the SMTP host changed I'd need to update every workflow to work with the new SMTP server. So if I was to write the activity myself it may well make more sense to handle all this kind of configuration in an email sender service. This also has the advantage of being able to pull the plug on emails being sent out if a poorly written workflow was sending out too many emails. So it depends on what you're trying to achieve whether services make sense or not.

To actually add your own service to the runtime is pretty straightforward. Define an interface and mark it with the ExternalDataExchange attribute.

    public interface ISendEmail
      void Send(string toAddress, string message);

Next write a class that implements the interface.

    public class EmailSenderService : ISendEmail
      public void Send(string toAddress, string message)  
        // send it...

I'm not entirely sure whether you have to use an interface or if you can just use a class straight off but I've only used interfaces myself and there are at least a couple of good reasons why this is the best way to go. First, it decouples your service implementation from the activity that is calling it. For the email sender example, this means you could replace the SMTP sender with a new implementation using Exchange (or whatever) to send the email without needing to change your activity implementation. Second, if you want to use the CallExternalMethod activity to call into the service, this only accepts an interface for the InterfaceType property.

After that, all you need to do is add the service to the runtime.

    // external data exchange service
    ExternalDataExchangeService dataService = new ExternalDataExchangeService();
    // email sender service
    EmailSenderService emailService = new EmailSenderService();

To call the ISendEmail methods, you have two choices, hook up a CallExternalMethod activity or write a custom activity to call the method. Since I've not yet shown an activity that does anything useful, I'm going down the latter route. Create a new activity via the Add/Activity... popup menu item and add some code as follows.

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
      // generate alerts
      ISendEmail sendEmail = (ISendEmail)executionContext.GetService(typeof(ISendEmail));
      if (sendEmail != null)
        sendEmail.Send("doogal@doogal.co.uk", "blah");

      return ActivityExecutionStatus.Closed;

This is the simplest activity that you can implement, it executes some code then returns saying "I'm done". When I work out how to implement some more complex activities, I'll write about it.

No comments: