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.