翻译自 ms-help://MS.WinWF.v1.EN/WinWF_GettingStarted/html/9c3e5551-4eff-4977-89ac-f81ab092d996.htm
顺序工作流(sequential workflow)是为执行一种由一系列预定义的步骤组成的任务而设计的。这种体系结构是模拟基于过程的应用程序的。这一节将用几个步骤来编写一个简单的开支报告程序,这个小程序使用WinFrom做界面,用顺序工作流做业务逻辑。
这个小程序有一个TextBox来输入开支报告的总数,一个Button点击提交报告。工作流将评估开支,如果开支小于1000则提请领班审批,如果大于等于1000则提请经理审批。之后,工作流会发送一个审批意见,此时,出现一个Label显示审批意见,两个Button分别表示通过和拒绝审批。当某一个按钮被点击的时候,应用程序会通知回应工作流,工作流继续处理发生的事件。
开始构造顺序工作流
创建工作流类
WWF SDK中定义了一个SequentialWorkFlow类,我们定义一个ExpenseRoportWorkflow类,并继承自SequentialWorkflow,这样就创建一个顺序工作流。如:
using System;2
using System.Workflow.Activities;3
using System.Workflow.Activities.Rules;4

5
namespace Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow6
{7
[RuleConditionsAttribute(typeof(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.ExpenseReportWorkflow))]8
public sealed partial class ExpenseReportWorkflow : System.Workflow.Activities.SequentialWorkflow9
{10
public ExpenseReportWorkflow()11
{12

13
}14
}15
}16

声明工作流参数
在一个工作流运行时,它可以从宿主应用程序中接收参数。参数是ParameterDeclaration类型的对象,一旦工作流初始化完成,参数的值就能通过工作流的Parameters集合来访问。
这里的开始报告程序用了两个参数。第一个参数是开支的总数;第二个是一个传出参数,用来放置审批意见。
定义一个新的方法InitializeComponent,并在构造ExpenseRoportWorkflow类的构造函数中调用它。一下的例子示范了怎样定义两个参数并把它们加到Parameters集合中。
使用IfElse活动
IfElse活动用条件表达式来控制工作流中流程的运行。工作流将根据条件表达式的结果来决定执行条件分支(IfElseBranch)中的哪一个活动。
例子中将使用IfElse活动。通过判断从宿主应用程序中传入的Amount参数的值是否小于1000,来决定是否将审报发送到领班,否则发送到经理。
创建IfElse活动
1.定义4个私有变量
|
类型 |
名称 |
|
IfElse |
evaluateExpenseReportAmount |
|
IfElseBranch |
ifNeedsLeadApproval |
|
IfElseBranch |
elseNeedsManagerApproval |
|
CodeCondition |
ifElseLogicStatement |
2.在InitializeComponent中用默认构造函数实例以上4个对象。
以下的代码示例了怎样创建IfElse活动,并用IfElseBranch活动联系两个逻辑分支。你需要把以下代码放到InitializeComponent方法底部。
// 2

3
// EvaluateExpenseReportAmount4

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
// ifNeedsLeadApproval16

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
// elseNeedsManagerApproval30

31
// 32

33
this.elseNeedsManagerApproval.Activities.Add(this.invokeGetManagerApproval);34

35
this.elseNeedsManagerApproval.Condition = null;36

37
this.elseNeedsManagerApproval.ID = "elseNeedsManagerApproval";38

WWF在IfElse活动中,有两种评估条件表达式的方式。一种是RoleCondition,这个对象通过使用一组规则来判断条件表达式的结果;另一种就是使用CodeCondition活动。CodeCondition使用一个回调方法,这个回调方法返回一个代表评估结果的布尔值。上面的例子就是使用CodeCondition来决定条件表达式的值。如果Amount参数小于1000,回调方法返回true,否则返回false。以下的代码就是这个回调函数的定义,你可以把它加到工作流类的定义中。
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

构造IfElse分支(IfElseBranch)活动
创建完IfElse活动之后,我们来构造IfElseBranch活动
在这个例子中,每一IfElse活动的分支都使用InvokeMethodActivity活动来通知宿主程序——工作流需要领班或经理的审批才能继续执行。InvokeMethodActivity被设计成调用一个在WWF运行时中的服务接口。我们在同一份代码文件中定义了这个接口。当我们在之后构造宿主程序时,宿主类将实现这个接口,以便能建立工作流和宿主程序的通信(这一段文档上写的很模糊,我reflect后看了源码才明白过来,在最后将补充描述一下)。
构建IfElseBranch活动
1. 在类中定义两个私有字段
|
类型 |
名称 |
|
InvokeMethodActivity |
invokeGetLeadApproval |
|
InvokeMethodActivity |
invokeGetManagerApproval |
2. 在InitializeComponent中用默认构造函数实例化这两个对象
以下的代码示例了怎样在父活动(IfElse)中创建IfElseBranch活动,并把两个的InvokeMethodActivity联系到对应的IfElseBranch活动上,每个InvokeMethodActivity将调用定义在IExpenseReportService接口中的方法,接口会在稍微实现。你需要把以下代码放到InitializeComponent方法底部。
// 2

3
// invokeGetLeadApproval4

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
// invokeGetManagerApproval16

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

以下代码定义了IExpenseReportService接口
using System;2

3
using System.Workflow.ComponentModel;4

5
using System.Workflow.Runtime.Messaging;6

7
8

9
namespace Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow10

11
{12

13
[DataExchangeService]14

15
public interface IExpenseReportService16

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

监听宿主事件
在这个阶段,工作流已经从宿主程序接受了两个参数(译者注:其中一个为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方法底部。
// 2

3
// listenApproveReject4

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
// approveEventDriven16

17
// 18

19
this.approveEventDriven.Activities.Add(this.approveEvent);20

21
this.approveEventDriven.ID = "approveEventDriven";22

23
// 24

25
// approveEvent26

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
// rejectEventDriven42

43
// 44

45
this.rejectEventDriven.Activities.Add(this.rejectEvent);46

47
this.rejectEventDriven.ID = "rejectEventDriven";48

49
// 50

51
// rejectEvent52

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

使用EventSinkActivity时,为了在工作流中加入一些附加逻辑,你可以为Invoked事件创建一个事件处理程序。一下是事件处理程序的代码。
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
// ExpenseReportWorkflow4

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

创建宿主程序
WWF需要一个宿主程序来运行工作流。当程序开始运行,WWF运行时引擎也随之启动。而之前构造好的工作流,则到用户点击了Submit按钮后才真正启动。
建立一个新的源文件,取名Program。以下的代码包含了完整的WinForm应用程序。IExpenseReportService接口GetLeadApproval和GetmanagerApproval方法已经定义在另一个文件中。宿主程序实现了这个接口。在approval和rejected按钮的click事件中,将引发ExpenseReprotApproval或ExpenseReprotRejected事件。
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.SequentialWorkflowHost20

21
{22

23
public class MainForm : Form, Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService24

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 panel68

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
// label1134

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
// result152

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
// label2170

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
// submitButton188

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
// approvalState208

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
// approveButton226

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
// rejectButton246

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
// amount266

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
// panel1286

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
// MainForm306

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 parameters362

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 workflow372

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
else388

389
{390

391
this.result.Text = e.OutputParameters["Result"].ToString();392

393
394

395
// Clear fields396

397
this.amount.Text = string.Empty;398

399
400

401
// Disable buttons402

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 workflow418

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 workflow434

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 Members520

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 Program548

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

Ps:上面还有个问题没有解释清楚,我reflect了一下,看了源码才知道个大概。
那就是IfElseBranch中的InvokeMethodActivity。
InvokeMethodActivity中有一个Type类型的InterfaceType属性,使用时,需要设置这个属性,并把MethodName设为这个接口中的一个方法的名称。运行时,工作流引擎(也就是WorkflowRuntime)将通过反射调用这个接口。
但引擎怎么知道调用接口的哪个实现呢?你看
workflowRuntime = new WorkflowRuntime();
workflowRuntime.AddService(this);
workflowRuntime.StartRuntime();
原来,初始化引擎时,我们已经把实现了这个interface的类型的实例(this)注册到工作流中了。运行时,引擎就遍历所有已经注册的服务,如果实现了这个接口,这调用它。



浙公网安备 33010602011771号