WF 工作流(4)
在第一章,我已经向你展示了在工作流中怎么使用variables(变量)和arguments(参数)。跟编码类似,variables类似于类成员,而arguments类似于方法的参数。你已经在前三章使用过variables了,在这一章,我将向你展示怎样使用input(输入)、output(输出)arguments(参数)和arguments(参数)是怎么在workflow和宿主程序之间传递。
创建一个新的解决方案
创建一个新的Workflow Console Application,如图Figure4-1。命名这个项目为OrderProcess,同时命名这个解决方案为Chapter04。
在这个项目中,你将定义一个货物订单,然后把订单传递到工作流中。工作流将会计算订单的费用,然后把费用返回给应用程序。
定义订单类
第一步是定义订单的数据类型。在Solution Explorer(解决方案)中右击项目,选择Add > Class,如图Figure4-2所示。
在Add New Item对话框中(如图Figure4-3),选择Class模板(它应该已经被默认地选择了),指定它的名字为Order.cs,点击Add。
Solution Explorer(解决方案窗口)应该如图Figure4-4所示。
在Order类中输入以下代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OrderProcess
{
public class OrderItem
{
public int OrderItemID { get; set; }
public int Quantity { get; set; }
public string ItemCode { get; set; }
public string Description { get; set; }
}
public class Order
{
public Order()
{
Items = new List<OrderItem>();
}
public int OrderID { get; set; }
public string Description { get; set; }
public decimal TotalWeight { get; set; }
public string ShippingMethod { get; set; }
public List<OrderItem> Items { get; set; }
}
}
Order类包含了一些公有成员(OrderID,Description,TotalWeight,ShippingMethod)和一个OrderItem类的集合。工作流会根据这些内容区决定订单的金额。点击F6去生成解决方案。这会编译生成Order类,后面我们需要用到。
实现工作流
模板为我们创建了一个工作流文件,名叫Workflow1.xaml。在Solution Explorer中,右击Workflow1.xaml文件,选择Rename(重命名),如图Figure4-5所示。命名为OrderWF.xaml。
你同样要打开OrderWF.xaml的code view。在第一行,修改Class属性为:
x:Class="OrderProcess.OrderWF"
定义Arguments
打开OrderWF.xaml文件(design(设计)视图)。现在定义输入输出arguments。点击工作流设计器的左下角的Argument按钮。设计器将会弹出以下窗口(如图Figure4-6)。
提示:你可能会想起第一章中定义variables时是需要指定范围的,范围可能是整个工作流或者是特定的一个活动(和他的子活动)。对于arguments,然而,是不需要定义范围的,因为它已经被默认的指定为对整个工作流有效,因为它是指定来传入传出数据到工作流的。因此,当定义argument是是没有Scope属性的。
点击Create Argument链接。输入Name为OrderInfo。Direction为In。点击ArgumentType,将会弹出一个下拉菜单,如图Figure4-7所示。
选择最后一条(Browse for Types)。这时会弹出一个对话框,如图Figure4-8所示。
展开OrderProcess引用,选择Order类,点击OK。
提示:如果在OrderProcess引用中看不见Order类,你必须生成解决方案。点击Cancel按钮,点击F6生成解决方案(这时有可能报错“The type or namespace name ‘Workflow1’ could not be found (are you missing a using directive or an assembly reference?)”)
,然后就可以设置Argument类型了。
再次点击Create Argument链接,创建另外一个argument。输入Name为TotalAmount,Direction为Out,ArgumentType为Decimal。Decimal类型不再下拉列表中,你需要导航到这个类型(正如第一个argument定义那样)。Decimal类型可以在mscorlib引用的System命名空间中找到。
提示:你不必通过浏览引用和命名空间来找你需要的类型。你可以直接在对话框的最上面输入类型的命名空间和类型。例如,需要Decimal类型,只是需要输入System.Decimal。实际上,你甚至不需要输入特定的命名空间,你只要输入Decimal,之后下面就会出现匹配的类型。
设计工作流
现在你可以设计活动来处理传进来的订单了。首先拖拉一个Sequence活动到工作流设计器上,然后拖放一个WriteLine活动到Sequence中。设置Text属性为“Order Received”。拖放一个Assign活动到WriteLine活动的下面。设置DisplayName为Initialize Total。To属性为TotalAmount,Value属性为0。Assign的属性窗口如图Figure4-9所示。
这个活动简单的把TotalAmount初始化为0。
Switch活动
Switch活动的工作原理跟C#中的switch类似。它允许你执行一系列的活动根据表达式的值。你将会使用Switch活动去验证ShippingMethod而去决定收费。在Toolbox中,Switch活动显示为Switch<T>。这意味着他是一个模板类和他可以操作不同的数据类型。拖放一个Switch活动到Assign活动的下面。你应该可以看到弹出了一个对话框,如图Figure4-10所示,这个对话框要求你指定数据类型。ShippingMethod是一个string类型,因此选择String类型。
点击OK。在属性窗口中,设置DisplayName为Handling Charges。Handling Charges活动应该如图Figure4-11所示。
Switch活动有一个Expression属性,一个default分支,和一些用户定义的分支。输入expression为OrderInfo.ShippingMethod。点击Add new case链接(在活动的底部)。输入case的值为NextDay。再次点击Add new case,输入case为2ndDay。这时活动如图Figure4-12所示。
设计器显示了所有的case(分支),和其中一个分支是展开的,以便你可以看到这个分支中有哪些活动。你可以点击Add an activity链接展开一个收缩的分支。
Expression活动
到目前为止,你已经定义了NextDay和2ndDay分支到Switch活动中,default分支是用来当ShippingMethod不是其中一个分支的值时来执行的。现在你需要指定每一个分支要执行的活动。对于这个项目,你将使用Add活动。
提示:在System.Activities.Expressions命名空间中包含了很多工作流活动。包括Add,Subtract,Multiply和Divide活动。它也包括Equal,GreaterThan,And和Or活动,你可以直接在你的工作流中使用它们。
不幸运的是,Add活动不能在toolbox中,你需要直接在.xaml代码中输入这个活动。保存项目。在Solution Explorer中右击OrderWF.xaml,选择View Code。它会弹出一个窗口询问你是否关闭现有窗口,点击Yes。Switch活动定义如下:
<Switch x:TypeArguments="x:String" DisplayName="Handling Charges" Expression="[OrderInfo.ShippingMethod]" sap:VirtualizedContainerService.HintSize="481,260">
<x:Null x:Key="NextDay" />
<x:Null x:Key="2ndDay" />
</Switch>
注意到NextDay和2ndDay分支的属性x:Null,这表明这个分支没有包含活动,用以下代码覆盖这两行代码:
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" x:Key="NextDay" sap:VirtualizedContainerService.HintSize="461,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[15]" />
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" x:Key="2ndDay" sap:VirtualizedContainerService.HintSize="461,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[10]" />
在上面代码之前加上:
<Switch.Default>
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" sap:VirtualizedContainerService.HintSize="463,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[5]" />
</Switch.Default>
注意:以上代码跟原文有不一致,因为我试过原文的方法会报错,所以我直接把Add活动添加到toolbox中,然后拖放到相应的分支中,上面的代码是自动生成的。
Add活动有三个属性:Left,Right和Result。Right的值加上Left的值然后赋值给Result属性,Left和Result属性设置为TotalAmount参数,Right属性被指定为一个常数。
Switch活动的完整定义如下:
<Switch x:TypeArguments="x:String" DisplayName="Handling Charges" Expression="[OrderInfo.ShippingMethod]" sap:VirtualizedContainerService.HintSize="481,258">
<Switch.Default>
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" sap:VirtualizedContainerService.HintSize="463,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[5]" />
</Switch.Default>
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" x:Key="NextDay" sap:VirtualizedContainerService.HintSize="461,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[15]" />
<Add x:TypeArguments="x:Decimal, x:Decimal, x:Decimal" x:Key="2ndDay" sap:VirtualizedContainerService.HintSize="461,100" Left="[TotalAmount]" Result="[TotalAmount]" Right="[10]" />
</Switch>
保存项目,右击OrderWF.xaml文件,选择View Designer,Switch活动如图Figure4-13所示。
当ShippingMethod为NextDay是,TotalAmount将会增加15$;当为2ndDay是,将增加10$。当为其他值时,将增加5$。如果你点击其中的一个switch分支,你将看到分支中包含什么活动,如图Figure4-14所示。
点击Add活动,它的属性将在属性窗口出现(如图Figure4-15所示),如果需要,你可以在这里直接修改它的属性。
拖放另外一个Assign活动到Switch活动的下面,DisplayName为Freight Charges。To属性为TotalAmount,Value属性为:
TotalAmount + (OrderInfo.TotalWeight * 0.50D)
拖放一个WriteLine活动到“Freight Charges”活动的下面,设置Text属性为:
“That Total amount is: $” + TotalAmount.ToString()
这展示了订单的总金额。最后的工作流图应该如图Figure4-16所示。
引用工作流
在这个项目,控制台程序自动的引用了工作流,用下面代码覆盖Program.cs:
using System;
using System.Linq;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
namespace OrderProcess
{
class Program
{
static void Main(string[] args)
{
Order myOrder = new Order()
{
OrderID = 1,
Description = "Need some stuff",
ShippingMethod = "2ndDay",
TotalWeight = 100
};
IDictionary<string, object> input = new Dictionary<string, object>
{
{"OrderInfo", myOrder}
};
//execute the workflow
IDictionary<string,object> output = WorkflowInvoker.Invoke(new OrderWF(), input);
//Get the TotalAmount returned by workflow
decimal total = (decimal)output["TotalAmount"];
Console.WriteLine("Workflow return ${0} for my order total", total);
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
}
}
首先创建了一个Order对象,在Order对象中定义了测试的数据。然后创建了一个Dictionary对象,用来存储Order对象。调用了WorkflowInvoker类的静态方法Invoke(),在Invoke()方法中传递了Dictionary对象。Invoke()方法创建和执行一个工作流实例在应用程序的线程中。
在Dictionary中传递数据是可以同时传多个参数的。这是非常重要的去匹配dictionary的key和工作流中的传入参数,而且dictionary中对象的类型也要与工作流中传入参数匹配。
Invoke()方法返回一个Dictionary对象,这个dictionary对象包含了工作流的所有传出和传出/传入参数。
运行程序
点击F5运行程序,结果应该如下所示:
为了去验证结果是否正确,你可以手动的去计算一下。ShippingMethod是2ndDay,这代表要加上$10。TotalWeight为100,价格为$0.50一磅,这就是一共$50。加上前面的$10就是$60。