Implementing a State Machine Workflow
实现状态机工作流
In the example that follows, you will implement an application that uses a state machine workflow.
For this example, I chose a familiar subject to model: operating a car. While you wouldn’t normally
model something like this in a real application, it does illustrate the basic concepts of states, events,
and transitions that are important in a state machine workflow. And the subject area is something
that most of us can easily relate to.
在下面的例子中,你可以实现一个使用状态机工作流的应用程序.这个例子中,我选择了一个比较常见的命题作为模型:操作汽车.
然后,你不能把这个模型看作一个真是的应用,它只是用来展现状态机工作流中那些重要的状态/事件/切换的基础概念.
在这个命题下可以将她们轻松的联系在一起.
Designing the Car State Machine
设计汽车状态机
If you think about operating a car, you will immediately think of several states that could be modeled
in a workflow. Here are the states that I’ve chosen for this example:
如果你回想如何操作一辆汽车,你会马上想起在这个工作流模型中的几个状态.我准备选择下面几个状态作为这个例子.
? Not Running: In this state, you are in the car but the engine is not running.
未启动:在这个状态中,你已经在汽车中,但是却没有发动引擎.
? Running: You’ve now started the engine, but you’re not moving.
已启动:你已经启动了引擎,但是没有行驶.
? Moving Forward: The car is moving forward.
向前行驶:
? Moving in Reverse: The car is moving in reverse.
向后行驶:
? Done with the Car: You are finished with the car.
停车
There are many other states that you could model, but this list is enough to provide a substantial
example. The next step is to identify the events that can occur while you are in each state.
Table 9-1 lists the events that are allowed for each state, along with the planned state transitions as
each event is handled.
State Event State Transition
Not Running Start the Engine Running
Not Running Leave the Car Done with the Car
Running Go Forward Moving Forward
Running Go in Reverse Moving in Reverse
Running Stop Engine Not Running
Moving Forward Stop Moving Running
Moving in Reverse Stop Moving Running
All states Beep Horn None
If you look at the events outlined in Table 9-1, you will see that they very naturally fall into place
once you’ve identified the states. For instance, if the car is in the Not Running state, you can either
Start the Engine or Leave the Car. You can’t Go Forward or Go in Reverse because you haven’t started the
engine yet. Likewise, if you are in the Moving Forward state, the only thing you can do is Stop Moving.
You can’t Stop Engine or Leave the Car while it is moving. Of course this example could be enhanced
to allow other events that control the speed or direction of movement.
如果你看过Table 9-1的事件大概,你会发现它们非常自然的和状态切合在一起,比如,如果汽车处于Not Running状态,
那么要么你已发动汽车要么离开汽车.你不能向前行驶或者向后行驶,因为还没有启动引擎.另外,如果正处于Moving Forward
状态,那么接下来能做的就是Stop Moving,在Moving Forward状态中你不能关闭引擎(Stop Engine)或者离开汽车.
Of course this example could be enhanced
to allow other events that control the speed or direction of movement.
Notice that the Beep Horn event is available regardless of the current workflow state. This means
that the Beep Horn event will have to be defined at the workflow level instead of within one of the states.
需要注意的是Beep Horn event(喇叭响事件)不管工作流处于何种状态都是有效的.这意味着
The workflow that you will implement for this state machine will handle all of these events
using instances of HandleExternalEventActivity. The events will be defined as local service events
and raised by the host application. As the workflow receives an event, it will call a local service method
to send a simple String message back to the host. The messages will provide feedback to the driver
of the vehicle about the current state of the workflow. After sending a message to the host, the workflow
will transition to the appropriate state.
在你准备实现的状态机工作流中你会用到 HandleExternalEventActivity 实例来处理这些事件,这些事件会被定义
成本地服务事件并且被host应用唤醒.作为工作流接收事件,它会调用本地服务方法向host应用发送一个简单的字符串消息.
The messages will provide feedback to the driver
of the vehicle about the current state of the workflow
给Host发送消息后,工作流将过渡到合适的状态.
The host application will be a simple Windows Forms application. It will include a number of
buttons that enable you to raise the local service events.
这个host应用可以是简单的windows应用,它有几个按钮,允许你通过它们调用本地服务事件.
Defining the Local Service Interface
定义本地服务接口
To begin coding the example, create a new project using the Empty Workflow Project template and
name the project SharedWorkflows. This creates a DLL assembly that can be referenced by the Windows
Forms demonstration application developed later in the “Implementing the Host Application” section.
现在开始开发这个例子,使用一个空的工作流项目模版创建新的项目,项目名称SharedWorkflows.这个dll的程序集在
以后的“Implementing the Host Application”章节中也会用到.
Now add a new C# interface to this project and name it ICarServices. This interface defines
the events and methods that you want to expose to the workflow via a local service. Listing 9-1 shows
the complete code that you need for the ICarServices.cs file.
现在添加一个ICarServices接口,这个接口定义了你希望通过本地服务暴露给工作流的事件和方法.清单9-1展示了这个
ICarServices.cs文件的完整代码:
using System;
using System.Workflow.Activities;
namespace SharedWorkflows
{
/// <summary>
/// Define the contract for operating a vehicle
/// </summary>
[ExternalDataExchange]
public interface ICarServices
{
/// <summary>
/// Start the engine
/// 发动引擎
/// </summary>
event EventHandler<ExternalDataEventArgs> StartEngine;
/// <summary>
/// Stop the engine
/// 停止引擎
/// </summary>
event EventHandler<ExternalDataEventArgs> StopEngine;
/// <summary>
/// Stop movement of the vehicle
/// 停止车辆
/// </summary>
event EventHandler<ExternalDataEventArgs> StopMovement;
/// <summary>
/// Move the vehicle forward
/// </summary>
event EventHandler<ExternalDataEventArgs> GoForward;
/// <summary>
/// Move the vehicle in reverse
/// </summary>
event EventHandler<ExternalDataEventArgs> GoReverse;
/// <summary>
/// Done with the car
/// </summary>
event EventHandler<ExternalDataEventArgs> LeaveCar;
/// <summary>
/// Beep the horn
/// </summary>
event EventHandler<ExternalDataEventArgs> BeepHorn;
/// <summary>
/// Send a message to the host application
/// 向host应用发送消息
/// </summary>
/// <param name="message"></param>
void OnSendMessage(String message);
}
}
The interface is decorated with the ExternalDataExchange attribute that identifies it as a local
service interface, making it available to workflows. All of the events pass an instance of
ExternalDataEventArgs as their event arguments. None of these events need to pass any additional
data with the event, so this base argument class is sufficient.
通过修饰符ExternalDataExchange属性定义这个接口是本地服务接口,使之为工作流可用(有点词不达意).
所有的事件都传递ExternalDataEventArgs实例作为它们的事件参数.因为这些事件都不需要其他的数据,
这些基础参数是足够的(好像不太对)
The interface also defines the OnSendMessage method. This method will be invoked by the
workflow using the CallExternalMethodActivity to pass a message back to the host.
接口也定义了一个OnSendMessage方法,工作流使用CallExternalMethodActivity实体调用它(OnSendMessage)给host
传递消息.
Implementing the Local Service
实现本地服务
Next, add a new C# class to the SharedWorkflows project and name it CarService. This is the local
service that implements the ICarServices interface. Listing 9-2 shows the complete code for the
CarService.cs file.
下一步,在SharedWorkflows项目中添加一个新类,名称为CarService,CarService需要实现本地服务接口ICarServices.
Listing 9-2. Complete CarService.cs File
using System;
using System.Workflow.Activities;
using System.Workflow.Runtime;
namespace SharedWorkflows
{
/// <summary>
/// A local service that provides events used to control
/// a vehicle
/// </summary>
public class CarService : ICarServices
{
#region ICarServices Members
public event EventHandler<ExternalDataEventArgs> StartEngine;
public event EventHandler<ExternalDataEventArgs> StopEngine;
public event EventHandler<ExternalDataEventArgs> StopMovement;
public event EventHandler<ExternalDataEventArgs> GoForward;
public event EventHandler<ExternalDataEventArgs> GoReverse;
public event EventHandler<ExternalDataEventArgs> BeepHorn;
public event EventHandler<ExternalDataEventArgs> LeaveCar;
/// <summary>
/// Send a message from a workflow to the host application
/// </summary>
/// <param name="message"></param>
public void OnSendMessage(String message)
{
if (MessageReceived != null)
{
MessageReceivedEventArgs args
= new MessageReceivedEventArgs(
WorkflowEnvironment.WorkflowInstanceId,
message);
MessageReceived(this, args);
}
}
#endregion
#region Members used by the host application
public event EventHandler<MessageReceivedEventArgs> MessageReceived;
public void OnStartEngine(ExternalDataEventArgs args)
{
if (StartEngine != null)
{
StartEngine(null, args);
}
}
public void OnStopEngine(ExternalDataEventArgs args)
{
if (StopEngine != null)
{
StopEngine(null, args);
}
}
public void OnStopMovement(ExternalDataEventArgs args)
{
if (StopMovement != null)
{
StopMovement(null, args);
}
}
public void OnGoForward(ExternalDataEventArgs args)
{
if (GoForward != null)
{
GoForward(null, args);
}
}
public void OnGoReverse(ExternalDataEventArgs args)
{
if (GoReverse != null)
{
GoReverse(null, args);
}
}
public void OnBeepHorn(ExternalDataEventArgs args)
{
if (BeepHorn != null)
{
BeepHorn(null, args);
}
}
public void OnLeaveCar(ExternalDataEventArgs args)
{
if (LeaveCar != null)
{
LeaveCar(null, args);
}
}
#endregion
}
}
In addition to implementing the ICarServices interface, the service also includes a series of
methods that raise the events. These methods are invoked by the host application, not the workflow.
除实现ICarServices接口外,这个服务还包括一个调用事件的序列化方法.这些方法是被host应用调用,而不是工作流.
The implementation for the OnSendMessage method takes the String message passed from the
workflow and passes it to the host application via a MessageReceived event. This event is used only
by the host application and uses an event arguments class named MessageReceivedEventArgs. To
implement this class, add a new C# class named MessageReceivedEventArgs to the SharedWorkflows
project. Listing 9-3 shows the complete code for the MessageReceivedEventArgs.cs file.
sing System;
using System.Workflow.Activities;
namespace SharedWorkflows
{
/// <summary>
/// Passes a message from the workflow to the local service
/// </summary>
[Serializable]
public class MessageReceivedEventArgs : ExternalDataEventArgs
{
private String _message;
public MessageReceivedEventArgs(Guid instanceId, String message)
: base(instanceId)
{
_message = message;
}
public String Message
{
get { return _message; }
set { _message = value; }
}
}
}
Implementing the Workflow
实现工作流
To begin defining the workflow, add a new state machine workflow to the SharedWorkflows project
and name it CarWorkflow. Normally, I like to define any workflow variables and properties before
moving to the visual design of the workflow. However, this workflow doesn’t require a single instance
variable or property. In fact, you won’t add a single line of code to this workflow. Everything will be
done in the visual workflow designer. Listing 9-4 is the complete code listing for the CarWorkflow.cs file.
现在开始定义工作流,添加一个名字是CarWorkflow状态机工作流到SharedWorkflows项目中,通常我喜欢在切换到工作流可视化设计器前
定义工作流变量和属性,然后现在这个工作流不需要专门的属性和变量.实际上你没有必要对工作流添加一行代码.
在可视化工作流设计器上我们可用做所有需要的事情.
Listing 9-4. Complete CarWorkflow.cs File
using System;
using System.Workflow.Activities;
namespace SharedWorkflows
{
/// <summary>
/// Car state machine workflow
/// </summary>
public sealed partial class CarWorkflow
: StateMachineWorkflowActivity
{
public CarWorkflow()
{
InitializeComponent();
}
}
}
After switching back to the visual workflow designer, you should see an empty state machine
workflow that looks like Figure 9-5.
之后切换到工作流设计器,你将看到一个空的状态机工作流看起来就像Figure 9-5
Defining the States
定义状态
The first order of business is to add all of the states defined in the previous design discussion. The
first state is already created for you by the new workflow template. You only need to rename it from
the default name of CarWorkflowInitialState to NotRunningState. To add the other states, drag and
drop a StateActivity from the Toolbox to an empty area of the workflow. Name each state according to
the following list:
The first order of business is to add all of the states defined in the previous design discussion这句不知道如何翻译比较合适,
我理解的大概意思就是根据以前讨论的设计,我们首先添加所有的状态定义.
在添加新的工作流时第一个状态已经创建了.你只需要将从默认的CarWorkflowInitialState重命名为NotRunningState即可.
接下来添加其他的状态,从工具箱拖拽一个StateActivity到工作流的空白位置.每个状态的名字如下:
? RunningState
? MovingForwardState
? MovingInReverseState
? DoneWithCarState
The workflow should now look like Figure 9-6.
工作流现在看起来如Figure 9-6.
Now that the states are defined, this is a good time to identify the initial and completed states.
Switch to the Properties window for the workflow and set the InitialStateName property to
NotRunningState and the CompletedStateName property to DoneWithCarState. The InitialStateName
property identifies the initial state that the workflow will be in when it first begins execution. The
CompletedStateName property identifies the state that causes the workflow to complete. The completed
Properties window should look like Figure 9-7.
现在这些状态都已经定义完成,现在是定义initial 和 completed 状态的好时候,切换到工作流的属性窗口,设置InitialStateName属性为NotRunningState,
设置CompletedStateName属性为DoneWithCarState.工作流中设置为InitialStateName属性的初始化状态会首先执行,设置为CompletedStateName
属性的状态会导致工作流完成,这个属性窗口看起来像Figure 9-7.
Figure 9-7. Properties window with Initial and Completed states defined
Defining the First Event
定义第一个事件
You can now define the first event for the NotRunningState. Drag and drop an EventDrivenActivity
onto the NotRunningState. The workflow should now look like Figure 9-8.
你现在可以为NotRunningState定义第一个事件.拖拽EventDrivenActivity活动到NotRunningState,现在工作流看来就像 Figure 9-8.
By double-clicking the eventDrivenActivity1 that you just added, the designer view changes to
a detailed view of the activity (eventDrivenActivity1). This is shown in Figure 9-9.
双击刚才添加的eventDrivenActivity1,设计器会切换到活动(eventDrivenActivity1)详细视图.如Figure 9-9.所示
The icons at the top of the view indicate your current designer scope (you are editing the
NotRunningState of the CarWorkfow). When you are finished with this view, you can single-click either
of these icons to return to the full workflow design view.
It’s a good idea to always give the EventDrivenActivity instances a meaningful name since they
are shown in the top-level view of the workflow. This EventDrivenActivity will be used to handle the
StartEngine event so rename it to eventStartEngine.
视图上方的图标表明了你现在可以设计的范围(你可以编辑CarWorkfow的NotRunningState状态),当你完成这个视图,你可以单击上面的图标
返回工作流设计视图.
It’s a good idea to always give the EventDrivenActivity instances a meaningful name since they
are shown in the top-level view of the workflow.不知道如何翻译,也没有搞明白大概意思.
这个EventDrivenActivity将被用于操作StartEngine事件所以将他另命名为eventStartEngine
To handle the StartEngine event, you’ll need to add a set of three activities as children of
this eventStartEngine activity. First, add a HandleExternalEventActivity, followed by a
CallExternalMethodActivity and a SetStateActivity. Most of the events that you will handle in this
workflow will follow this same pattern of three activities.
要操作这个StartEngine事件,你还需要为eventStartEngine活动添加三个子活动.首先添加一个HandleExternalEventActivity,
在添加一个CallExternalMethodActivity和SetStateActivity.
Set the InterfaceType property of the HandleExternalEventActivity to SharedWorkflows.
ICarServices to identify the local service interface that defines the event. Set the EventName to
StartEngine. None of the events that you will handle require any code, so there is no need to add an
event handler.
Set the same value for the InterfaceType property of the CallExternalMethodActivity, but set
the MethodName to OnSendMessage. Enter the String literal Started Engine in the message parameter.
You can do this directly in the Parameters section of the Properties window. Figure 9-10 shows the
completed properties for this activity.
代码下载