Windows Workflow Foundation (Basics): How to handle external events and to trigger state transitions using a HandleExternalEvent activity

 

MSDN's description for a HandleExternalEvent reads as "Defines a workflow communication activity that is used to handle an event that is raised by a local service". In this blog I will try to expand on that definition.

Imagine a simple state machine representing a moving (state = Moving) or non-moving (state = Stopped) vehicle. For simplicity, let's assume that the vehicle can only start moving if the driver accelerates and it stops when the brakes are applied:

WF's state machine runtime allows services to raise events which can be handled by the state machine. In other words, a local service could perhaps communicate with a state machine via this route.

Let’s create a simple service project which will allow us to raise both the accelerate and brake events:

- Create an empty class library called VehicleService
- Inherit from ExternalDataEventArgs and mark the inherited class as serializable (it must be marked as serializable in order to be sent to the state machine):

[Serializable]
public class MovementChangeEventArgs : ExternalDataEventArgs
{
   private MovementChangeType _movementChangeType;
   public MovementChangeType MovementChangeType
   {
      get { return _movementChangeType; }
      set { _movementChangeType = value; }
   }

 

   public MovementChangeEventArgs(Guid instanceId, MovementChangeType
movementChangeType) : base(instanceId)
   {
      _movementChangeType = movementChangeType;
      this.WaitForIdle = true;
   }
}

public enum MovementChangeType
{
   Brake,
   Accelerate
}

- Create an interface which can be shared by both the local service (the service which has the responsibility for raising the event) and the state machine.
- Add only a single event to that interface

[ExternalDataExchange]
public interface IVehicleService
{
   event EventHandler<MovementChangeEventArgs> MovementChanged;
}

- As you can see above, the interface is marked with the ExternalDataExchangeAttribute attribute which flags the interface as a local service used for data exchange purposes.
- The next step is to implement the IVehicleService interface:

public class VehicleService : IVehicleService
{
   public void RaiseEvent(MovementChangeEventArgs args)
   {
      EventHandler<MovementChangeEventArgs> movementChanged = MovementChanged;
      if (movementChanged != null)
      {
         movementChanged(null, args);
      }
   }
   public event EventHandler<MovementChangeEventArgs>MovementChanged;
}

This interface now can be registered with the state machine. For this, you will need a HandleExternalEvent activity as the first event driven activity within the workflow of both the stopped and moving states:

- VehicleService class library must be referenced by the workflow project
- Go to the stopped state and drop a HandleExternalEvent activity as the first item within the event driven workflow. You will also need to set the following properties on this activity:
   o InterfaceType: IVehicleService
   o EventName: MovementChanged
- Now attach an event handler to the Invoked event of this activity which is called when the MovementChanged event is invoked. At this stage, you have a number of options open to you. One option is to keep the result of the event using a local property. You could perhaps add an IfThenElse activity followed by a SetState to your workflow which could trigger a state transition based on that local property.

The very last step is to make the workflow runtime aware of the service:

WorkflowRuntime workflowRuntime = new WorkflowRuntime();
ExternalDataExchangeService dataExchange = new ExternalDataExchangeService();
workflowRuntime.AddService(dataExchange);
VehicleService vehicleService = new VehicleService();
dataExchange.AddService(vehicleService);

You can now simply raise events by calling the RaiseEvent method of the vehicleService instance:

vehicleService.RaiseEvent(
new MovementChangeEventArgs(instance.InstanceId,
       MovementChangeType.Brake));

Please let me know what you think about this blog entry.