源代码下载:OrderEntryCode.zip
这一部分中,我们来看一看如何向工作流添加自定义逻辑。自定义逻辑在这里指的是向工作流添加我们自己的代码。完成这一目标,可以有两种机制:使用CodeActivity和开发自定义活动。在后面我们会看到这两种方法是如何实现的。这里用到的示例,是一个订货的流程。我们假定它包含了下述的几个步骤:
l 验证发送订单的帐号ID,并且查询它的信用卡余额;
l 验证待购商品的ID,并获取它的价格;
l 如果帐号和商品都是有效的,并且帐户余额足以支付商品的价格,那么就执行这份订单。
使用CodeActivity
首先建立一个Sequential Workflow Console Application项目,命名为OrderEntryCode。
为了处理一个订单,此工作流需要两个参数:AccountId和SalesItemId,分别标识下单者和待售商品。这些参数可以有两种方法在工作流中定义,即通常的.NET类属性(前面已经使用过),或者一个从属属性(dependency property)。从属属性的属性值集中存放在一起,而不是作为一个普通的成员变量定义在类中。这样做的好处就在于允许在运行时将属性值绑定到实例。
为了支持从属属性,这个类必须派生自DependencyObject类。所幸的是我们的System.Workflow.ComponentModel.Activity根类就是派生自它,因此所有的工作流和活动类都支持从属属性。
实际操作一下,我们定义AccountId属性作为此工作流的从属属性,添加下面的代码到Workflow1.cs文件中:
public static DependencyProperty AccountIdProperty = System.Workflow.ComponentModel.DependencyProperty.Register("AccountId", typeof(int), typeof(Workflow1));
///<summary>
///标识一个帐户
///</summary>
[Description("标识帐户")]
[Category("CodeActivity 示例")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int AccountId
{
get
{
return ((int)(base.GetValue(Workflow1.AccountIdProperty)));
}
set
{
base.SetValue(Workflow1.AccountIdProperty, value);
}
}
可以看到,get和set通过调用GetValue和SetValue方法来获取和更新属性值。这些属性值都被存放在由静态的DependencyProperty域作为键值的库中。这个库很像一个Dictionary对象,基于一个唯一的键值来保存每一个属性。
Visual Studio还提供了专门的snippet来插入从属属性的代码。在你想要插入从属属性的地方右击编辑区,选择Insert SnippetàWorkflowàDependencyProperty – Property,然后一个模板似的代码段就为你准备好了。So good~~~
随后向工作流添加一些私有字段,参见示例源码。
切换到工作流设计模式,拖放一个CodeActivity,这个代码活动负责验证作为输入参数传递进来的AccountId属性。如果AccountId幼小,那么isAccountVerified变量设置为true,并且availableCredit变量设置为信用卡的余额。
使用属性窗口,将CodeActivity的名称改为codeLookupAccount。双击它,填入下面的代码:
private void codeLookupAccount_ExecuteCode(object sender, EventArgs e)
{
// 模拟查询账户
switch (AccountId)
{
case 1001:
isAccountVerified = true;
availableCredit = 100.00M;
break;
case 2002:
isAccountVerified = true;
availableCredit = 500.00M;
break;
default:
isAccountVerified = false;
availableCredit = 0;
break;
}
}
实际应用中,这里应该是进行数据库查询。这个例子中我们就从简了。切换回设计界面,在CodeActivity的下面再添加一个IfElseActivity,它可以根据isAccountVerified变量的值确定AccountId是否有效。左边的分支代表帐户有效的处理,右边代表无效。将左边的分支改名为ifAccountVerified,把它的Condition属性改为Declarative Rule Condition之后展开它,设置ConditionName为checkIsAccountVerified(注意,工作流里的所有规则条件都必须拥有一个独立的名称)。点击Expression属性旁的省略号,输入下面的条件:
this.isAccountVerified == true
点击确定,这时候的属性窗口看起来应该是下面的样子:
在继续补充左侧分支之前,先完善右边的分支。将其改名为ifAccountInvalid,拖放一个CodeActivity进去,改名为codeBadAccountId,然后双击添加代码:
private void codeBadAccountId_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("AccountId {0} 无效", AccountId);
}
继续来看左侧的分支,拖放另一个CodeActivity上去,这段代码的目的就是验证待购商品的ID,并且获取其价格。如果SalesItemId属性有效,那么isSalesItemVerified就设置为true,并且salesItemAmount设置为商品的价格。将CodeActivity的名称改为codeLookupItem,双击添加代码:
private void codeLookupItem_ExecuteCode(object sender, EventArgs e)
{
// 模拟商品查询
switch (SalesItemId)
{
case 501:
isSalesItemVerified = true;
salesItemAmount = 59.95M;
break;
case 502:
isSalesItemVerified = true;
salesItemAmount = 199.99M;
break;
default:
isSalesItemVerified = false;
salesItemAmount = 0;
break;
}
}
下面我们还要对购买者的信用卡余额进行检查。拖放IfElseActivity到codeLookupItem活动下面,将它内部的分支分别改名为ifCreditAvailable和ifItemProblems。对左侧分支添加Declarative Rule Condition,以checkAvailableCredit为名,输入如下的Expression:
this.isSalesItemVerified == True && this.salesItemAmount <= this.availableCredit
在上面放置一个CodeActivity,命名为codeEnterOrder,添加代码:
private void codeEnterOrder_ExecuteCode(object sender, EventArgs e)
{
// 模拟下单
Console.WriteLine("{0} 的订单已下,商品 ID 为 {1},价格 {2}", AccountId, SalesItemId, salesItemAmount);
}
向右侧的分支也添加一个CodeActivity,代码如下:
private void codeInsufficientCredit_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("商品 {0} 无效或者 AccountId {1} 的信用额度不足 {2}", SalesItemId, AccountId, availableCredit);
}
最终的工作流看起来应该是下面的样子。
完整代码参见示例。下面我们在Program.cs的Main方法中加入测试代码,执行这个工作流。修订后的Program.cs代码如下,所有的解释和说明以注释形式添加在代码中。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
namespace OrderEntryCode
{
///<summary>
///执行带有CodeActivity 的 OrderEntry 工作流
///</summary>
class Program : IDisposable
{
private WorkflowRuntime _workflowRuntime;
private AutoResetEvent _waitHandle = new AutoResetEvent(false);
public Program()
{
InitializeWorkflowRuntime();
}
#region IDisposable Members
public void Dispose()
{
_workflowRuntime.StopRuntime();
_workflowRuntime.Dispose();
}
/// Program 类实现了IDisposable接口,这并不是一个严格的要求,但为了在Main()方法中使用
/// using 语法,还是这么做了。在Dispose方法中,StopRuntime和Dispose是从WorkflowRuntime
///中调用的。这就确保了工作流运行时引擎被释放,并且允许垃圾回收
#endregion
///<summary>
///启动工作流运行时
///</summary>
private void InitializeWorkflowRuntime()
{
_workflowRuntime = new WorkflowRuntime();
_workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
{
_waitHandle.Set();
};
_workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
{
Console.WriteLine(e.Exception.Message);
_waitHandle.Set();
};
}
///<summary>
///运行工作流
///</summary>
///<param name="wfArguments"></param>
public void RunWorkFlow(Dictionary<string, object> wfArguments)
{
// 创建一个工作流实例并启动它
WorkflowInstance instance = _workflowRuntime.CreateWorkflow(typeof(OrderEntryCode.Workflow1), wfArguments);
instance.Start();
// 等待工作流完成
_waitHandle.WaitOne();
}
static void Main(string[] args)
{
using (Program instance=new Program())
{
// 创建一个带有输入参数的字典
Dictionary<string, object> wfArguments = new Dictionary<string, object>();
wfArguments.Add("AccountId", 1001);
wfArguments.Add("SalesItemId", 501);
// 运行工作流
instance.RunWorkFlow(wfArguments);
// 修改参数为另一个帐户和商品,再次运行工作流
wfArguments.Clear();
wfArguments.Add("AccountId", 2002);
wfArguments.Add("SalesItemId", 502);
instance.RunWorkFlow(wfArguments);
// 这一次帐户的余额不足
wfArguments.Clear();
wfArguments.Add("AccountId", 1001);
wfArguments.Add("SalesItemId", 502);
instance.RunWorkFlow(wfArguments);
// 使用一个无效的帐户运行工作流
wfArguments.Clear();
wfArguments.Add("AccountId", 9999);
wfArguments.Add("SalesItemId", 501);
instance.RunWorkFlow(wfArguments);
Console.WriteLine("Press Enter to exit");
Console.ReadLine();
}
}
}
}