组织并执行一系列的操作或者活动的最自然的方式——那就是工作流——同时也是构造一个工作流程的可执行表现形式的最佳途径。
Windows Workflow Foundation( 以下简称WWF)提供了一个编程框架和工具以开发和执行各种不同的基于工作流的应用程序,比如文档管理、线型的商业应用、贸易单据流程、IT管理、B2B应用以及消费者应用。
![]()
1
public ExpenseReportWorkflow()
2![]()
3![]()
![]()
{
4![]()
5
InitializeComponent();
6![]()
7
}
8![]()
9
10![]()
11
private void InitializeComponent()
12![]()
13![]()
![]()
{
14![]()
15
System.Workflow.ComponentModel.ParameterDeclaration Amount = new System.Workflow.ComponentModel.ParameterDeclaration();
16![]()
17
System.Workflow.ComponentModel.ParameterDeclaration Result = new System.Workflow.ComponentModel.ParameterDeclaration();
18![]()
19
//
20![]()
21
// Workflow Parameters
22![]()
23
//
24![]()
25
Amount.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
26![]()
27
Amount.Name = "Amount";
28![]()
29
Amount.Type = typeof(int);
30![]()
31
Amount.Value = null;
32![]()
33
Result.Direction = System.Workflow.ComponentModel.ParameterDirection.Out;
34![]()
35
Result.Name = "Result";
36![]()
37
Result.Type = typeof(string);
38![]()
39
Result.Value = null;
40![]()
41
this.Parameters.Add(Amount);
42![]()
43
this.Parameters.Add(Result);
44![]()
45
}
46![]()
47
Windows Workflow Foundation( 以下简称WWF)提供了一个编程框架和工具以开发和执行各种不同的基于工作流的应用程序,比如文档管理、线型的商业应用、贸易单据流程、IT管理、B2B应用以及消费者应用。
有状态的、持久化的、不间断运行的应用程序
WWF
简化了创造有状态的,不间断运行的异步工作流应用程序的过程。WWF运行时引擎管理工作流的运行,为工作流的长期运行提供保障,并能抵抗机器的重启。WWF运行时服务提供了一系列的附加功能,例如WWF服务为能温和且正确的处理错误提供了事务和持久化。
工作流模型
WWF
为开发人员提供了一个工作流模型,来描述应用程序所需要的处理过程。通过使用工作流模型所提供的流程控件、状态管理、事务和同步器,开发人员可以分离应用程序逻辑和业务逻辑,构造一个高层次的抽象,达到提高开发者效率的目的。
组件的重用
WWF
为开发者提供了一系列的活动——活动是一种包含了工作单元的可配置逻辑结构。这种结构封装了开发者可能经常性用到的一些部件,这样就节省了开发者的时间。
如果遇到一些特殊的需求或场景,WWF同样为开发自定义的活动提供了简单的方法。
通过将工作流引擎载入进程,WWF可以使任何应用程序和服务容器运行工作流。
运行时服务组件被设计成可插件形式的,这个可使应用程序以最合适的方式来提供它们的服务。WWF还提供了一组运行时服务的默认实现,这些服务能满足大部分类型的应用程序。
另外,WWF还提供了对ASP.NET的out-of-the-box(啥意思?)支持,让构造和运行能在IIS和ASP.NET环境的工作流变得简单。
如果遇到一些特殊的需求或场景,WWF同样为开发自定义的活动提供了简单的方法。
通过将工作流引擎载入进程,WWF可以使任何应用程序和服务容器运行工作流。
运行时服务组件被设计成可插件形式的,这个可使应用程序以最合适的方式来提供它们的服务。WWF还提供了一组运行时服务的默认实现,这些服务能满足大部分类型的应用程序。
另外,WWF还提供了对ASP.NET的out-of-the-box(啥意思?)支持,让构造和运行能在IIS和ASP.NET环境的工作流变得简单。
顺序工作流(sequentialworkflow)是为执行一种由一系列预定义的步骤组成的任务而设计的。这种体系结构是模拟基于过程的应用程序的。这一节将用几个步骤来编写一个简单的开支报告程序,这个小程序使用WinFrom做界面,用顺序工作流做业务逻辑。
这个小程序有一个TextBox来输入开支报告的总数,一个Button点击提交报告。工作流将评估开支,如果开支小于1000则提请领班审批,如果大于等于1000则提请经理审批。之后,工作流会发送一个审批意见,此时,出现一个Label显示审批意见,两个Button分别表示通过和拒绝审批。当某一个按钮被点击的时候,应用程序会通知回应工作流,工作流继续处理发生的事件。
开始构造顺序工作流
创建工作流类
WWF SDK中定义了一个SequentialWorkFlow类,我们定义一个ExpenseRoportWorkflow类,并继
承自SequentialWorkflow,这样就创建一个顺序工作流。如:
1
using
System;
2
using
System.Workflow.Activities;
3
using
System.Workflow.Activities.Rules;
4![]()
5
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow
6![]()
![]()
{
7
[RuleConditionsAttribute(typeof(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.ExpenseReportWorkflow))]
8
public sealed partial class ExpenseReportWorkflow : System.Workflow.Activities.SequentialWorkflow
9![]()
{
10
public ExpenseReportWorkflow()
11![]()
{
12![]()
13
}
14
}
15
}
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
声明工作流参数
在一个工作流运行时,它可以从宿主应用程序中接收参数。参数是ParameterDeclaration类型的对象,一旦工作流初始化完成,参数的值就能通过工作流的Parameters集合来访问。
这里的开始报告程序用了两个参数。第一个参数是开支的总数;第二个是一个传出参数,用来放置审批意见。
定义一个新的方法InitializeComponent,并在构造ExpenseRoportWorkflow类的构造函数中调用它。一下的例子示范了怎样定义两个参数并把它们加到Parameters集合中。
使用IfElse活动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
IfElse活动用条件表达式来控制工作流中流程的运行。工作流将根据条件表达式的结果来决定执行条件分支(IfElseBranch)中的哪一个活动。
例子中将使用IfElse活动。通过判断从宿主应用程序中传入的Amount参数的值是否小于1000,来决定是否将审报发送到领班,否则发送到经理。
创建IfElse活动
1.定义4个私有变量
类型
|
名称
|
IfElse
|
evaluateExpenseReportAmount
|
IfElseBranch
|
ifNeedsLeadApproval
|
IfElseBranch
|
elseNeedsManagerApproval
|
CodeCondition
|
ifElseLogicStatement
|
2.在InitializeComponent中用默认构造函数实例以上4个对象。
以下的代码示例了怎样创建IfElse活动,并用IfElseBranch活动联系两个逻辑分支。你需要把以下代码放到InitializeComponent方法底部。 WWF在IfElse活动中,有两种评估条件表达式的方式。一种是RoleCondition,这个对象通过使用一组规则来判断条件表达式的结果;另一种就是使用CodeCondition活动。CodeCondition使用一个回调方法,这个回调方法返回一个代表评估结果的布尔值。上面的例子就是使用CodeCondition来决定条件表达式的值。如果Amount参数小于1000,回调方法返回true,否则返回false。以下的代码就是这个回调函数的定义,你可以把它加到工作流类的定义中。构造IfElse分支(IfElseBranch)活动
1
//
2![]()
3
//
EvaluateExpenseReportAmount
4![]()
5
//
6
7
this.EvaluateExpenseReportAmount.Activities.Add(this
.ifNeedsLeadApproval);
8![]()
9
this.EvaluateExpenseReportAmount.Activities.Add(this
.elseNeedsManagerApproval);
10![]()
11
this.EvaluateExpenseReportAmount.ID = "EvaluateExpenseReportAmount"
;
12![]()
13
//
14![]()
15
//
ifNeedsLeadApproval
16![]()
17
//
18
19
this.ifNeedsLeadApproval.Activities.Add(this
.invokeGetLeadApproval);
20![]()
21
ifElseLogicStatement.Condition += new System.Workflow.Activities.ConditionalExpression(this
.DetermineApprovalContact);
22![]()
23
this.ifNeedsLeadApproval.Condition =
ifElseLogicStatement;
24![]()
25
this.ifNeedsLeadApproval.ID = "ifNeedsLeadApproval"
;
26![]()
27
//
28![]()
29
//
elseNeedsManagerApproval
30![]()
31
//
32
33
this.elseNeedsManagerApproval.Activities.Add(this
.invokeGetManagerApproval);
34![]()
35
this.elseNeedsManagerApproval.Condition = null
;
36![]()
37
this.elseNeedsManagerApproval.ID = "elseNeedsManagerApproval"
;
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1
private bool DetermineApprovalContact(object
sender, EventArgs e)
2![]()
3![]()
![]()
{
4![]()
5
if ( Convert.ToInt32(this.Parameters["Amount"].Value) < 1000 )
6![]()
7
return true;
8![]()
9
10![]()
11
return false;
12![]()
13
}
14
2
3
4
5
6
7
8
9
10
11
12
13
14
创建完IfElse活动之后,我们来构造IfElseBranch活动
在这个例子中,每一IfElse活动的分支都使用InvokeMethodActivity活动来通知宿主程序——工作流需要领班或经理的审批才能继续执行。InvokeMethodActivity被设计成调用一个在WWF运行时中的服务接口。我们在同一份代码文件中定义了这个接口。当我们在之后构造宿主程序时,宿主类将实现这个接口,以便能建立工作流和宿主程序的通信(这一段文档上写的很模糊,我reflect后看了源码才明白过来,在最后将补充描述一下)。
构建IfElseBranch活动
构建IfElseBranch活动
1. 在类中定义两个私有字段
类型
|
名称
|
InvokeMethodActivity
|
invokeGetLeadApproval
|
InvokeMethodActivity
|
invokeGetManagerApproval
|
2. 在InitializeComponent中用默认构造函数实例化这两个对象
以下的代码示例了怎样在父活动(IfElse)中创建IfElseBranch活动,并把两个的InvokeMethodActivity联系到对应的IfElseBranch活动上,每个InvokeMethodActivity将调用定义在IExpenseReportService接口中的方法,接口会在稍微实现。你需要把以下代码放到InitializeComponent方法底部。 以下代码定义了IExpenseReportService接口监听宿主事件
1
//
2![]()
3
//
invokeGetLeadApproval
4![]()
5
//
6
7
this.invokeGetLeadApproval.ID = "invokeGetLeadApproval"
;
8![]()
9
this.invokeGetLeadApproval.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
10![]()
11
this.invokeGetLeadApproval.MethodName = "GetLeadApproval"
;
12![]()
13
//
14![]()
15
//
invokeGetManagerApproval
16![]()
17
//
18
19
this.invokeGetManagerApproval.ID = "invokeGetManagerApproval"
;
20![]()
21
this.invokeGetManagerApproval.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
22![]()
23
this.invokeGetManagerApproval.MethodName = "GetManagerApproval"
;
24![]()
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1
using
System;
2![]()
3
using
System.Workflow.ComponentModel;
4![]()
5
using
System.Workflow.Runtime.Messaging;
6![]()
7
8![]()
9
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow
10![]()
11![]()
![]()
{
12![]()
13
[DataExchangeService]
14![]()
15
public interface IExpenseReportService
16![]()
17![]()
{
18![]()
19
void GetLeadApproval();
20![]()
21
void GetManagerApproval();
22![]()
23
event EventHandler<WorkflowMessageEventArgs> ExpenseReportApproved;
24![]()
25
event EventHandler<WorkflowMessageEventArgs> ExpenseReportRejected;
26![]()
27
}
28![]()
29
}
30![]()
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在这个阶段,工作流已经从宿主程序接受了两个参数(译者注:其中一个为out参数,此时设为null),评估了Amount参数,作出了到底该提请谁确认审批的决定,并通知了宿主程序在继续接下来的处理之前,确认审批。这里,Listen活动和EventSinkActivity活动往往配合使用,来监听宿主程序触发指定的事件。接着,一个approval或rejection事件被引发,工作流继续执行,返回审批结果Result,并终止流程。
Listen活动的每个分支是一个EventDriven活动。EventDriven活动只能使用实现了IEventActivity接口的活动。Listen活动的每个分支中的EventDriven各有一个EventSinkActivity,它们是用来监听宿主程序触发的ExpenseReportApproved或者ExpenseReportRejected事件的。这种工作流和宿主的通信方法其实类似于之前的InvokeMethodActivity的过程,只不过前者是工作流监听宿主事件,而后者是宿主事件工作流中注册的接口。
我们已经在前面的步骤中,把接口和两个事件的定义都完成了。在这里,我们将创建一个Listen活动并和两个EventDriven分支建立连接。每个分支包含一个EventSinkActivity活动,每个EventSink监听一种对应的事件。此外,我们还将创建一些事件处理程序,来处理AfterInvoke事件(译者注:这里的AfterInvoke事件应为Invoked事件)。这些事件处理程序将会把Result参数的值设为approval或者rejected。
构造监听活动
1.在工作流类中定义5个私有字段
类型
|
名称
|
Listen
|
listenApproveReject
|
EventDriven
|
approveEventDriven
|
EventDriven
|
rejectEventDriven
|
EventSinkActivity
|
approveEvent
|
EventSinkActivity
|
rejectEvent
|
2. 在InitializeComponent中实例化。
以下的代码示例了怎样在创建Listen活动和EventSinkActivity活动,来监听宿主程序发出的事件。你需要把以下代码放到InitializeComponent方法底部。使用EventSinkActivity时,为了在工作流中加入一些附加逻辑,你可以为Invoked事件创建一个事件处理程序。一下是事件处理程序的代码
完成顺序工作流
完成顺序工作流
1
//
2![]()
3
//
listenApproveReject
4![]()
5
//
6
7
this.listenApproveReject.Activities.Add(this
.approveEventDriven);
8![]()
9
this.listenApproveReject.Activities.Add(this
.rejectEventDriven);
10![]()
11
this.listenApproveReject.ID = "listenApproveReject"
;
12![]()
13
//
14![]()
15
//
approveEventDriven
16![]()
17
//
18
19
this.approveEventDriven.Activities.Add(this
.approveEvent);
20![]()
21
this.approveEventDriven.ID = "approveEventDriven"
;
22![]()
23
//
24![]()
25
//
approveEvent
26![]()
27
//
28
29
this.approveEvent.EventName = "ExpenseReportApproved"
;
30![]()
31
this.approveEvent.ID = "approveEvent"
;
32![]()
33
this.approveEvent.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
34![]()
35
this.approveEvent.Roles = null
;
36![]()
37
this.approveEvent.Invoked += new System.EventHandler(this
.approveEvent_Invoked);
38![]()
39
//
40![]()
41
//
rejectEventDriven
42![]()
43
//
44
45
this.rejectEventDriven.Activities.Add(this
.rejectEvent);
46![]()
47
this.rejectEventDriven.ID = "rejectEventDriven"
;
48![]()
49
//
50![]()
51
//
rejectEvent
52![]()
53
//
54
55
this.rejectEvent.EventName = "ExpenseReportRejected"
;
56![]()
57
this.rejectEvent.ID = "rejectEvent"
;
58![]()
59
this.rejectEvent.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
60![]()
61
this.rejectEvent.Roles = null
;
62![]()
63
this.rejectEvent.Invoked += new System.EventHandler(this
.rejectEvent_Invoked);
64![]()
65
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
1
private void approveEvent_Invoked(object
sender, EventArgs e)
2![]()
3![]()
![]()
{
4![]()
5
this.Parameters["Result"].Value = "Report Approved";
6![]()
7
}
8![]()
9
10![]()
11
private void rejectEvent_Invoked(object
sender, EventArgs e)
12![]()
13![]()
![]()
{
14![]()
15
this.Parameters["Result"].Value = "Report Rejected";
16![]()
17
}
18![]()
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这个工作流包括两个主要的步骤:第一,监听宿主程序的递交审批事件,并把传入的值作为工作流参数;第二,监听通过或拒绝审批消息。一下的代码示例了怎样通过把之前创建好的活动加到工作流活动集中,来完成工作流的构造。
创建宿主程序
1
//
2![]()
3
//
ExpenseReportWorkflow
4![]()
5
//
6
7
this.Activities.Add(this
.EvaluateExpenseReportAmount);
8![]()
9
this.Activities.Add(this
.listenApproveReject);
10![]()
11
this.DynamicUpdateCondition = null
;
12![]()
13
this.ID = "ExpenseReportWorkflow"
;
14![]()
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
WWF需要一个宿主程序来运行工作流。当程序开始运行,WWF运行时引擎也随之启动。而之前构造好的工作流,则到用户点击了Submit按钮后才真正启动。
建立一个新的源文件,取名Program。以下的代码包含了完整的WinForm应用程序。IExpenseReportService接口GetLeadApproval和GetmanagerApproval方法已经定义在另一个文件中。宿主程序实现了这个接口。在approval和rejected按钮的click事件中,将引发ExpenseReprotApproval或ExpenseReprotRejected事件。
1
using
System;
2![]()
3
using
System.ComponentModel;
4![]()
5
using
System.Drawing;
6![]()
7
using
System.Windows.Forms;
8![]()
9
using
System.Collections.Generic;
10![]()
11
using
System.Workflow.Runtime;
12![]()
13
using
System.Workflow.Runtime.Hosting;
14![]()
15
using
System.Workflow.Runtime.Messaging;
16![]()
17
18![]()
19
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflowHost
20![]()
21![]()
![]()
{
22![]()
23
public class MainForm : Form, Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService
24![]()
25![]()
{
26![]()
27
private System.Windows.Forms.Label label1;
28![]()
29
private System.Windows.Forms.TextBox result;
30![]()
31
private System.Windows.Forms.Label label2;
32![]()
33
private System.Windows.Forms.Button submitButton;
34![]()
35
private System.Windows.Forms.Label approvalState;
36![]()
37
private System.Windows.Forms.Button approveButton;
38![]()
39
private System.Windows.Forms.Button rejectButton;
40![]()
41
private System.Windows.Forms.TextBox amount;
42![]()
43
private System.Windows.Forms.Panel panel1;
44![]()
45
46![]()
47
private System.ComponentModel.IContainer components = null;
48![]()
49
50![]()
51
private delegate void GetApprovalDelegate();
52![]()
53
private WorkflowRuntime workflowRuntime = null;
54![]()
55
private WorkflowInstance workflowInstance = null;
56![]()
57
58![]()
59
public MainForm()
60![]()
61![]()
{
62![]()
63
InitializeComponent();
64![]()
65
66![]()
67
// Collapse approve/reject panel
68![]()
69
this.Height -= this.panel1.Height;
70![]()
71
72![]()
73
workflowRuntime = new WorkflowRuntime();
74![]()
75
workflowRuntime.AddService(this);
76![]()
77
workflowRuntime.StartRuntime();
78![]()
79
80![]()
81
workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
82![]()
83
}
84![]()
85
86![]()
87
protected override void Dispose(bool disposing)
88![]()
89![]()
{
90![]()
91
if (disposing && (components != null))
92![]()
93![]()
{
94![]()
95
components.Dispose();
96![]()
97
}
98![]()
99
base.Dispose(disposing);
100![]()
101
}
102![]()
103
104![]()
105
private void InitializeComponent()
106![]()
107![]()
{
108![]()
109
this.label1 = new System.Windows.Forms.Label();
110![]()
111
this.result = new System.Windows.Forms.TextBox();
112![]()
113
this.label2 = new System.Windows.Forms.Label();
114![]()
115
this.submitButton = new System.Windows.Forms.Button();
116![]()
117
this.approvalState = new System.Windows.Forms.Label();
118![]()
119
this.approveButton = new System.Windows.Forms.Button();
120![]()
121
this.rejectButton = new System.Windows.Forms.Button();
122![]()
123
this.amount = new System.Windows.Forms.TextBox();
124![]()
125
this.panel1 = new System.Windows.Forms.Panel();
126![]()
127
this.panel1.SuspendLayout();
128![]()
129
this.SuspendLayout();
130![]()
131
//
132![]()
133
// label1
134![]()
135
//
136![]()
137
this.label1.AutoSize = true;
138![]()
139
this.label1.Location = new System.Drawing.Point(13, 13);
140![]()
141
this.label1.Name = "label1";
142![]()
143
this.label1.Size = new System.Drawing.Size(39, 13);
144![]()
145
this.label1.TabIndex = 1;
146![]()
147
this.label1.Text = "Amount";
148![]()
149
//
150![]()
151
// result
152![]()
153
//
154![]()
155
this.result.Location = new System.Drawing.Point(13, 69);
156![]()
157
this.result.Name = "result";
158![]()
159
this.result.ReadOnly = true;
160![]()
161
this.result.Size = new System.Drawing.Size(162, 20);
162![]()
163
this.result.TabIndex = 1;
164![]()
165
this.result.TabStop = false;
166![]()
167
//
168![]()
169
// label2
170![]()
171
//
172![]()
173
this.label2.AutoSize = true;
174![]()
175
this.label2.Location = new System.Drawing.Point(13, 54);
176![]()
177
this.label2.Name = "label2";
178![]()
179
this.label2.Size = new System.Drawing.Size(33, 13);
180![]()
181
this.label2.TabIndex = 3;
182![]()
183
this.label2.Text = "Result";
184![]()
185
//
186![]()
187
// submitButton
188![]()
189
//
190![]()
191
this.submitButton.Enabled = false;
192![]()
193
this.submitButton.Location = new System.Drawing.Point(56, 95);
194![]()
195
this.submitButton.Name = "submitButton";
196![]()
197
this.submitButton.Size = new System.Drawing.Size(75, 23);
198![]()
199
this.submitButton.TabIndex = 2;
200![]()
201
this.submitButton.Text = "Submit";
202![]()
203
this.submitButton.Click += new System.EventHandler(this.submitButton_Click);
204![]()
205
//
206![]()
207
// approvalState
208![]()
209
//
210![]()
211
this.approvalState.AutoSize = true;
212![]()
213
this.approvalState.Location = new System.Drawing.Point(10, 9);
214![]()
215
this.approvalState.Name = "approvalState";
216![]()
217
this.approvalState.Size = new System.Drawing.Size(45, 13);
218![]()
219
this.approvalState.TabIndex = 4;
220![]()
221
this.approvalState.Text = "Approval";
222![]()
223
//
224![]()
225
// approveButton
226![]()
227
//
228![]()
229
this.approveButton.Enabled = false;
230![]()
231
this.approveButton.Location = new System.Drawing.Point(11, 25);
232![]()
233
this.approveButton.Name = "approveButton";
234![]()
235
this.approveButton.Size = new System.Drawing.Size(75, 23);
236![]()
237
this.approveButton.TabIndex = 0;
238![]()
239
this.approveButton.Text = "Approve";
240![]()
241
this.approveButton.Click += new System.EventHandler(this.approveButton_Click);
242![]()
243
//
244![]()
245
// rejectButton
246![]()
247
//
248![]()
249
this.rejectButton.Enabled = false;
250![]()
251
this.rejectButton.Location = new System.Drawing.Point(86, 25);
252![]()
253
this.rejectButton.Name = "rejectButton";
254![]()
255
this.rejectButton.Size = new System.Drawing.Size(75, 23);
256![]()
257
this.rejectButton.TabIndex = 1;
258![]()
259
this.rejectButton.Text = "Reject";
260![]()
261
this.rejectButton.Click += new System.EventHandler(this.rejectButton_Click);
262![]()
263
//
264![]()
265
// amount
266![]()
267
//
268![]()
269
this.amount.Location = new System.Drawing.Point(13, 29);
270![]()
271
this.amount.MaxLength = 9;
272![]()
273
this.amount.Name = "amount";
274![]()
275
this.amount.Size = new System.Drawing.Size(162, 20);
276![]()
277
this.amount.TabIndex = 0;
278![]()
279
this.amount.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.amount_KeyPress);
280![]()
281
this.amount.TextChanged += new System.EventHandler(this.amount_TextChanged);
282![]()
283
//
284![]()
285
// panel1
286![]()
287
//
288![]()
289
this.panel1.Controls.Add(this.approvalState);
290![]()
291
this.panel1.Controls.Add(this.approveButton);
292![]()
293
this.panel1.Controls.Add(this.rejectButton);
294![]()
295
this.panel1.Location = new System.Drawing.Point(3, 124);
296![]()
297
this.panel1.Name = "panel1";
298![]()
299
this.panel1.Size = new System.Drawing.Size(172, 66);
300![]()
301
this.panel1.TabIndex = 8;
302![]()
303
//
304![]()
305
// MainForm
306![]()
307
//
308![]()
309
this.AcceptButton = this.submitButton;
310![]()
311
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
312![]()
313
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
314![]()
315
this.ClientSize = new System.Drawing.Size(187, 201);
316![]()
317
this.Controls.Add(this.panel1);
318![]()
319
this.Controls.Add(this.amount);
320![]()
321
this.Controls.Add(this.submitButton);
322![]()
323
this.Controls.Add(this.label2);
324![]()
325
this.Controls.Add(this.result);
326![]()
327
this.Controls.Add(this.label1);
328![]()
329
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
330![]()
331
this.MaximizeBox = false;
332![]()
333
this.MinimizeBox = false;
334![]()
335
this.Name = "MainForm";
336![]()
337
this.Text = "Simple Expense Report";
338![]()
339
this.panel1.ResumeLayout(false);
340![]()
341
this.panel1.PerformLayout();
342![]()
343
this.ResumeLayout(false);
344![]()
345
this.PerformLayout();
346![]()
347
348![]()
349
}
350![]()
351
352![]()
353
private void submitButton_Click(object sender, EventArgs e)
354![]()
355![]()
{
356![]()
357
Type type = typeof(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.ExpenseReportWorkflow);
358![]()
359
360![]()
361
// Construct workflow parameters
362![]()
363
Dictionary<string, object> properties = new Dictionary<string, object>();
364![]()
365
properties.Add("Amount", Int32.Parse(this.amount.Text));
366![]()
367
properties.Add("Result", string.Empty);
368![]()
369
370![]()
371
// Start the workflow
372![]()
373
workflowInstance = workflowRuntime.StartWorkflow(type, properties);
374![]()
375
}
376![]()
377
378![]()
379
void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
380![]()
381![]()
{
382![]()
383
if (this.result.InvokeRequired)
384![]()
385
this.result.Invoke(new EventHandler<WorkflowCompletedEventArgs>(this.workflowRuntime_WorkflowCompleted), sender, e);
386![]()
387
else
388![]()
389![]()
{
390![]()
391
this.result.Text = e.OutputParameters["Result"].ToString();
392![]()
393
394![]()
395
// Clear fields
396![]()
397
this.amount.Text = string.Empty;
398![]()
399
400![]()
401
// Disable buttons
402![]()
403
this.approveButton.Enabled = false;
404![]()
405
this.rejectButton.Enabled = false;
406![]()
407
}
408![]()
409
}
410![]()
411
412![]()
413
private void approveButton_Click(object sender, EventArgs e)
414![]()
415![]()
{
416![]()
417
// Raise the ExpenseReportApproved event back to the workflow
418![]()
419
ExpenseReportApproved(null, new WorkflowMessageEventArgs(this.workflowInstance.InstanceId));
420![]()
421
this.Height -= this.panel1.Height;
422![]()
423
this.submitButton.Enabled = true;
424![]()
425
}
426![]()
427
428![]()
429
private void rejectButton_Click(object sender, EventArgs e)
430![]()
431![]()
{
432![]()
433
// Raise the ExpenseReportRejected event back to the workflow
434![]()
435
ExpenseReportRejected(null, new WorkflowMessageEventArgs(this.workflowInstance.InstanceId));
436![]()
437
this.Height -= this.panel1.Height;
438![]()
439
this.submitButton.Enabled = true;
440![]()
441
}
442![]()
443
444![]()
445![]()
IExpenseReportService Members#region IExpenseReportService Members
446![]()
447
448![]()
449
public void GetLeadApproval()
450![]()
451![]()
{
452![]()
453
if (this.approvalState.InvokeRequired)
454![]()
455
this.approvalState.Invoke(new GetApprovalDelegate(this.GetLeadApproval));
456![]()
457
else
458![]()
459![]()
{
460![]()
461
this.approvalState.Text = "Lead approval needed";
462![]()
463
this.approveButton.Enabled = true;
464![]()
465
this.rejectButton.Enabled = true;
466![]()
467
468![]()
469
// expand the panel
470![]()
471
this.Height += this.panel1.Height;
472![]()
473
this.submitButton.Enabled = false;
474![]()
475
}
476![]()
477
}
478![]()
479
480![]()
481
public void GetManagerApproval()
482![]()
483![]()
{
484![]()
485
if (this.approvalState.InvokeRequired)
486![]()
487
this.approvalState.Invoke(new GetApprovalDelegate(this.GetManagerApproval));
488![]()
489
else
490![]()
491![]()
{
492![]()
493
this.approvalState.Text = "Manager approval needed";
494![]()
495
this.approveButton.Enabled = true;
496![]()
497
this.rejectButton.Enabled = true;
498![]()
499
500![]()
501
// expand the panel
502![]()
503
this.Height += this.panel1.Height;
504![]()
505
this.submitButton.Enabled = false;
506![]()
507
}
508![]()
509
}
510![]()
511
512![]()
513
public event EventHandler<WorkflowMessageEventArgs> ExpenseReportApproved;
514![]()
515
public event EventHandler<WorkflowMessageEventArgs> ExpenseReportRejected;
516![]()
517
518![]()
519
#endregion
520![]()
521
522![]()
523
private void amount_KeyPress(object sender, KeyPressEventArgs e)
524![]()
525![]()
{
526![]()
527
if (!Char.IsControl(e.KeyChar) && (!Char.IsDigit(e.KeyChar)))
528![]()
529
e.KeyChar = Char.MinValue;
530![]()
531
}
532![]()
533
534![]()
535
private void amount_TextChanged(object sender, EventArgs e)
536![]()
537![]()
{
538![]()
539
submitButton.Enabled = amount.Text.Length > 0;
540![]()
541
}
542![]()
543
}
544![]()
545
546![]()
547
static class Program
548![]()
549![]()
{
550![]()
551![]()
/**//// <summary>
552![]()
553
/// The main entry point for the application.
554![]()
555
/// </summary>
556![]()
557
[STAThread]
558![]()
559
static void Main()
560![]()
561![]()
{
562![]()
563
Application.EnableVisualStyles();
564![]()
565
Application.Run(new MainForm());
566![]()
567
}
568![]()
569
}
570![]()
571
}
572![]()
573
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
Ps:上面还有个问题没有解释清楚,我reflect了一下,看了源码才知道个大概。
那就是IfElseBranch中的InvokeMethodActivity。
那就是IfElseBranch中的InvokeMethodActivity。
InvokeMethodActivity中有一个Type类型的InterfaceType属性,使用时,需要设置这个属性,并把MethodName设为这个接口中的一个方法的名称。运行时,工作流引擎(也就是WorkflowRuntime)将通过反射调用这个接口。
但引擎怎么知道调用接口的哪个实现呢?你看
workflowRuntime = new WorkflowRuntime();
workflowRuntime.AddService(this);
workflowRuntime.StartRuntime();
原来,初始化引擎时,我们已经把实现了这个interface的类型的实例(this)注册到工作流中了。运行时,引擎就遍历所有已经注册的服务,如果实现了这个接口,这调用它。
另外,注册服务不一定要用AddService,也可以用WorkflowRuntime.StartWorkflow(Type)。