在 Visual Studio 2005 中创建顺序工作流 - 示例






3.25/5 (7投票s)
本文介绍了使用 .NET Framework 3.0 中的 Windows Workflow Foundation 在 Visual Studio 2005 中创建顺序工作流的步骤。
作者笔记
这篇分三部分的文章是 MSDN 库中关于创建顺序工作流教程的改编版( http://msdn2.microsoft.com/en-us/library/ms734794.aspx)。在此重新发布相同示例的主要目的是让读者更清晰地理解执行创建工作流的步骤。我的许多朋友和社区同行都表示对理解工作流创建过程中定义的步骤感到非常不满。他们引用了一些示例页面,这些页面非常不精确,不足以清楚地遵循这些步骤。
MSDN 教程包含以下 URL 中的三个练习:
- http://msdn2.microsoft.com/en-us/library/ms735860.aspx
- http://msdn2.microsoft.com/en-us/library/ms735914.aspx
- http://msdn2.microsoft.com/en-us/library/ms734523.aspx
在本文中,在保持主要练习不变的情况下,我详细解释了步骤、任务和编码部分,并包含了一些图示,以便任何人都可以轻松地开始创建顺序工作流。
引言
此示例应用程序是一个 **简单的费用报告**,包含一个用于输入金额的文本字段和一个用于提交费用报告的按钮。该工作流使用规则来评估金额,并在金额小于 1000 时要求主管批准,或者在金额大于或等于 1000 时要求经理批准。如果需要批准,工作流会与应用程序通信,并显示一个包含“批准”和“拒绝”按钮的下拉面板。当点击其中一个按钮时,应用程序会将响应通知工作流,工作流将继续处理该事件。
此演练包含三个主要步骤以及每个步骤下的子任务。
- 创建费用报告项目。
- 创建 Windows 应用程序
- 创建顺序工作流
- 托管顺序工作流
- 创建费用报告服务。
- 定义工作流参数
- 定义 IExpenseReportService 接口
- 创建费用报告顺序工作流类库
- 创建 CallExternalMethod 活动
- 创建 HandleExternalEvent 活动
1. 创建费用报告项目
创建 Windows 应用程序
在 Visual Studio 2005 中,选择“新建项目”;在“C# 项目”类型下,选择“Windows 应用程序”。
将项目中出现的默认窗体的名称更改为“MainForm
”,根据需要拖放控件,并按照下图所示设计布局。
下面是窗体中定义的控件及其属性的表格。
Control |
属性 |
值 |
|
|
|
|
简单的费用报告 |
|
|
|
|
|
金额 |
|
|
|
|
|
结果 |
|
|
|
|
|
||
|
|
|
|
||
|
|
|
|
审批 |
|
|
|
|
|
提交 |
|
|
|
|
|
批准 |
|
|
|
|
|
拒绝 |
|
|
|
|
将 approveButton
和 rejectButton
添加到 panel1
中。
为金额文本框的 KeyPress
和 TextChanged
事件添加两个事件处理程序方法。
private void amount_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsControl(e.KeyChar) && (!Char.IsDigit(e.KeyChar)))
e.KeyChar = Char.MinValue;
}
private void amount_TextChanged(object sender, EventArgs e)
{
submitButton.Enabled = amount.Text.Length > 0;
}
确保已替换 Program.cs 中运行应用程序默认窗体的代码。
Application.Run(new MainForm());
最后,要完成第一步,请添加对以下程序集的引用:
- System.Workflow.Activities
- System.Workflow.ComponentModel
- System.Workflow.Runtime
构建应用程序。
创建顺序工作流
在 Visual Studio 2005 中,从菜单中选择“新建项目”;在“C# 项目”类型下,选择“工作流”,然后在右侧模板面板中选择“顺序工作流库”。
将项目命名为 ExpenseReportWorkflow。
在默认创建的工作流设计器文件 (Workflow1.designer.cs) 中,如下修改 IntializeComponent
方法中的代码:
this.Name = "ExpenseReportWorkflow";
this.CanModifyActivities = false;
构建项目。
托管顺序工作流
要执行工作流,您必须创建一个 WorkflowRuntime
类的实例。然后,创建一个宿主类,并通过 CreateWorkflow
方法传入工作流的类型规范。然后,您可以调用返回的 WorkflowInstance
对象的 Start
方法来开始执行工作流。由于顺序工作流不依赖于外部事件来执行,因此它将立即开始。
托管 Windows Workflow 运行时引擎
回到您的 Windows 应用程序,并添加对您在上一步中创建的类库 (ExpenseReportWorkflow.dll) 的引用。
在 MainForm
类中,声明字段如下:
private WorkflowRuntime workflowRuntime = null;
private WorkflowInstance workflowInstance = null;
在构造函数方法中,调用 InitializeComponent
方法后,添加以下代码。这将启动运行时,但在应用程序通知它启动工作流之前不会执行任何工作流。
this.workflowRuntime = new WorkflowRuntime();
workflowRuntime.StartRuntime();
为 workflowRuntime
对象的 WorkflowCompleted
事件创建一个通用事件处理程序。
workflowRuntime.WorkflowCompleted +=
new EventHandler<WorkflowCompletedEventArgs>
(workflowRuntime_WorkflowCompleted);
同时,添加相应的事件处理程序方法。
void workflowRuntime_WorkflowCompleted(object sender,
WorkflowCompletedEventArgs e)
{
}
在 submitButton_Click
方法中,创建一个本地 Type
对象,使其等于您在上一步中创建的工作流的类型。创建该类型的工作流对象并启动它。
Type type = typeof(ExpenseReportWorkflow);
// Start the workflow
workflowInstance = workflowRuntime.CreateWorkflow(type);
workflowInstance.Start();
现在,再次构建项目。
2. 创建费用报告服务
为了实现 Windows 窗体应用程序和顺序工作流之间的通信,请定义一个标记有 ExternalDataExchangeAttribute
的接口,并在您的窗体类中实现该接口。为了简化窗体和工作流之间的通信,您将使用 ExternalDataExchangeService
,通过将 Windows 窗体类添加为工作流可以访问的服务。
定义工作流参数
此练习中的第一个任务是在工作流中创建两个属性。这些属性允许您从 Windows 窗体应用程序传递参数,或从工作流接收参数。
宿主应用程序可以在工作流执行之前设置该属性,方法是在调用 CreateWorkflow
方法时传入参数集合。要在工作流完成执行后将参数返回宿主应用程序,您可以为该属性定义一个 get
方法。然后,宿主应用程序可以访问传递到 WorkflowCompleted
事件的 WorkflowCompletedEventArgs
对象。WorkflowCompletedEventArgs
对象包含工作流中具有 get
方法的所有属性。
在 ExpenseReportWorkflow 类库项目的工作流类中,包含一个名为“amount
”的属性,并带有一个 set
访问器。
private int reportAmount = 0;
public int Amount
{
set
{
this.reportAmount = value;
}
}
包含另一个名为 Result
的属性,并带有一个 get
访问器。
private string reportResult = "";
public string Result
{
get
{
return this.reportResult;
}
}
回到您的 Windows 应用程序 SimpleExpenseReport;在 submitButton_Click
中,修改代码如下。
Type type = typeof(ExpenseReportWorkflow.ExpenseReportWorkflow);
// Construct workflow parameters
Dictionary<string, object> properties =
new Dictionary<string, object>();
properties.Add("Amount", Int32.Parse(this.amount.Text));
// Start the workflow
workflowInstance = workflowRuntime.CreateWorkflow(type, properties);
workflowInstance.Start();
新代码将为 ExpenseReportWorkflow
类型创建工作流实例,并在 Dictionary
对象中添加 amount
属性。
在 MainForm
类中的 workflowRuntime_WorkflowCompleted
方法中,显示从工作流返回的结果并清除金额文本字段。
if (this.result.InvokeRequired)
{
this.result.Invoke(new EventHandler<WorkflowCompletedEventArgs>
(this.workflowRuntime_WorkflowCompleted), sender, e);
}
else
{
this.result.Text = e.OutputParameters["Result"].ToString();
// Clear fields
this.amount.Text = string.Empty;
}
再次构建项目以检查任何错误。
定义 IExpenseReportService 接口
在此第二项任务中,定义前面提到的服务接口,并实现工作流用于调用在 Windows 窗体类中定义的方法的方法。此外,为宿主应用程序定义了两个事件,以在发生某些事件时通知工作流。这将使宿主应用程序和工作流能够相互通信。
通过选择“项目”->“添加新项”->“接口”,在 ExpenseReportWorkflow 项目中添加一个新的接口文件。
要使用 ExternalDataExchange
属性修饰该接口,请导入以下命名空间。
using System.Workflow.Activities;
[ExternalDataExchange]
public interface IExpenseReportService
{
void GetLeadApproval(string message);
void GetManagerApproval(string message);
event EventHandler<ExternalDataEventArgs> ExpenseReportApproved;
event EventHandler<ExternalDataEventArgs> ExpenseReportRejected;
}
修改 MainForm
类,使其实现 IExpenseReportService
接口。
public class MainForm : Form, IExpenseReportService
添加以下委托声明,并声明在窗体运行期间发生的事件。请记住,我们在窗体上有两个按钮“批准”和“拒绝”来引发相应的事件。
private delegate void GetApprovalDelegate(string message);
private event EventHandler<ExternalDataEventArgs> reportApproved;
private event EventHandler<ExternalDataEventArgs> reportRejected;
同时,添加这两个事件的事件处理程序。
public event EventHandler<ExternalDataEventArgs> ExpenseReportApproved
{
add
{
reportApproved += value;
}
remove
{
reportApproved -= value;
}
}
public event EventHandler<ExternalDataEventArgs> ExpenseReportRejected
{
add
{
reportRejected += value;
}
remove
{
reportRejected -= value;
}
}
下一步非常重要。由于窗体是服务 IExpenseReportService
的实现,因此应将其添加到 ExternalDataExchangeService
类中。
为此,请在 MainForm
类中为 ExternalDataExchangeService
创建一个实例。
private ExternalDataExchangeService exchangeService = null;
在构造函数方法中修改代码如下。
this.workflowRuntime = new WorkflowRuntime();
this.exchangeService = new ExternalDataExchangeService();
workflowRuntime.AddService(exchangeService);
exchangeService.AddService(this);
workflowRuntime.StartRuntime();
在 MainForm
类中实现 IExpenseReportService
中定义的两个方法。
public void GetLeadApproval(string message)
{
}
public void GetManagerApproval(string message)
{
}
构建项目并检查错误。
创建费用报告顺序工作流类库
工作流以 "IfElseActivity
" 开始,以确定是必须由经理还是主管批准工作流开始执行时设置的 Amount
属性中指定的值。
确定后,工作流将调用宿主应用程序中定义的方法,请求用户批准或拒绝该金额的输入。当点击“批准”或“拒绝”按钮时,宿主应用程序将引发相应的事件,该事件由工作流正在等待的一个 HandleExternalEventActivity
活动处理。一旦引发一个事件并由工作流处理,工作流将设置 Result
属性并完成其处理。
创建 CallExternalMethod 活动
打开 ExpenseReportWorkflow 项目中的工作流设计器,然后从工具箱中拖动 "IfElseActivity
" 控件。
从工具箱中拖动 CallExternalMethodActivity
并将其放置在 IfElseActivity
的 if 分支中。从工具箱中拖动另一个 CallExternalMethodActivity
并将其放置在 IfElseActivity
的 else 分支中。
按如下方式为活动命名:
类型 |
名称 |
|
|
|
|
|
|
|
|
|
|
选择 elseLeadApprove
矩形框,然后按 F4 打开属性窗口。将“Condition
”属性设置为“Code Condition”。展开“Condition
”属性。
将 Condition
的值设置为 DetermineApprovalContact
。
在同一名称下编写一个辅助方法,如下所示:
void DetermineApprovalContact(object sender, ConditionalEventArgs e)
{
if (this.reportAmount < 1000)
{
e.Result = true;
}
else
{
e.Result = false;
}
}
现在,也为 CallExternalMethodActivity
框设置属性。
选择 invokeLeadApproval
框,并设置以下属性:
属性 |
值 |
|
|
|
"需要主管批准" |
|
|
选择 invokeManagerApproval
框,并设置以下属性:
属性 |
值 |
|
|
|
"需要经理批准" |
|
|
在 MainForm
类中的 IExpenseReportService
中定义了两个方法,添加如下代码:
public void GetLeadApproval(string message)
{
if (this.approvalState.InvokeRequired)
this.approvalState.Invoke(new GetApprovalDelegate
(this.GetLeadApproval), message);
else
{
this.approvalState.Text = message;
this.approveButton.Enabled = true;
this.rejectButton.Enabled = true;
// expand the panel
this.Height = this.MinimumSize.Height + this.panel1.Height;
this.submitButton.Enabled = false;
}
}
public void GetManagerApproval(string message)
{
if (this.approvalState.InvokeRequired)
this.approvalState.Invoke(new GetApprovalDelegate
(this.GetManagerApproval), message);
else
{
this.approvalState.Text = message;
this.approveButton.Enabled = true;
this.rejectButton.Enabled = true;
// expand the panel
this.Height = this.MinimumSize.Height + this.panel1.Height;
this.submitButton.Enabled = false;
}
}
构建项目并运行应用程序。您应该会看到窗体显示,但“批准”和“拒绝”按钮此时不起作用。
创建 HandleExternalEvent 活动
在此任务中,您将创建一个 ListenActivity
活动,其中包含两个 HandleExternalEventActivity
活动,以捕获宿主应用程序引发的批准或拒绝事件。当引发批准或拒绝事件时,工作流将继续,设置结果,然后完成。
修改 ExpenseReportWorkflow
类,使其能够侦听宿主应用程序引发的特定事件。
创建一个接受一个名为 sender
的对象和一个名为 e
的 ExternalDataEventArgs
作为参数的新方法。
void approveEvent_Invoked(object sender, ExternalDataEventArgs e)
{
this.reportResult = "Report Approved";
}
private void rejectEvent_Invoked(object sender,ExternalDataEventArgs e)
{
this.reportResult = "Report Rejected";
}
在工作流设计器中,通过从工具箱拖动来放置 ListenActivity 图。
从工具箱中拖动 HandleExternalEvent
活动,并将其放置在两个 EventDrivenActivity
中。
按如下方式为活动命名:
类型 |
名称 |
|
|
|
|
|
|
|
|
|
|
选择 ApproveEvent
活动框,然后按 F4 显示属性窗口。
属性 |
值 |
|
|
|
|
|
|
选择 RejectEvent
活动框,然后按 F4 显示属性窗口。
属性 |
值 |
|
|
|
|
|
|
下一步是从宿主应用程序引发事件。为此,请向 approvebutton_Click
处理程序添加代码。
// Raise the ExpenseReportApproved event back
// to the workflow reportApproved(null, new ExternalDataEventArgs
(this.workflowInstance.InstanceId));
this.Height = this.MinimumSize.Height;
this.submitButton.Enabled = true;
同样,向 rejectbutton_Click
处理程序添加代码。
// Raise the ExpenseReportRejected event back to the workflow
reportRejected(null, new ExternalDataEventArgs (
this.workflowInstance.InstanceId));
this.Height = this.MinimumSize.Height;
this.submitButton.Enabled = true;
构建并运行应用程序。
当您提交费用报告金额时,窗体将展开以显示“**批准**”和“**拒绝**”按钮,以及从工作流收到的消息。点击其中一个按钮会执行以下操作:
- 折叠窗体。
- 将事件引发回工作流。
- 从工作流中检索
Results
属性。 - 将其显示在窗体上。
这是创建顺序工作流演练的结束。