WF活动是工作流程序的构建块。每个活动一般都执行一个任务,比如调用一个类的一个方法,调用一个网络服务或者调用其他程序。WF附带了很多内建活动,开发人员可以通过继承基类活动中的一个来创建它们自己的活动。
至少有四种方式从WF调用一个服务: 使用发送活动,写一个自定义活动,使用InvokeWebService 活动,或者使用代码活动。发送活动最适合用来调用WCF或者其他交互操作的网络服务,它在.NET 3.5 的时候被专门引入。一个自定义活动是一个用来压缩可以很容易地在不同工作流之间回收代码的轻量级架构。InvokeWebService活动当调用ASMX 网络服务时很有用,但是相对发送活动来说没有任何优势。一个代码活动是好的代码。这四个,这一章主要讲述发送活动和写一个自定义活动。首先我们将使用一个发送活动来调用一个网络服务,然后我们将写一个非常简单的自定义活动并用它来代替。
列表11.1 显示了将会在例子中使用的服务接口。GetPrice接收一个简单的类型作为输入并返回一个复杂的类型,StockPrice.
在Visual Studio 2008 中使用工作流文件夹中的”顺序工作流控制台应用程序”模板创建一个工程。这个模板包含一个实现了工作流和类文件(workflow1.cs)和一个初始化工作流运行时并启动工作流类的实例的主程序(Program.cs).
[ServiceContract] interface IStockPrice { [OperationContract] StockPrice GetPrice(string ticker); } public class StockService : IStockPrice { #region IStockPrice Members public StockPrice GetPrice(string ticker) { StockPrice price = new StockPrice(); price.price = 39.20; price.calls = 1; return price; } #endregion } [DataContract] public class StockPrice { [DataMember] public double price; [DataMember] public int calls; }
列表11.1 WF调用StockService
使用一个发送活动
发送活动是Visual Studio 2008 中内建活动之一。它的目的是使用一个WCF代理来调用一个网络服务。设计环境支持需要被配置来调用一个网络服务的公共属性,包括终结点信息。属性节和设计器也用来绑定WF变量到服务操作参数。额外的,活动可以重载服务的URI地址。
访问网络服务之前,工程必须首先包含你想要调用的网络服务代理。代理可以通过在Visual Studio中添加服务引用工具来生成或者通过使用svcutil.exe工具。
为了使用发送活动,从工具栏中把它拖动到工作流设计面,其他活动类似。图片11.2 显示了已经添加发送活动到工作流的工作流设计器。
图片11.2 添加一个发送活动到一个工作流设计界面
发送活动必须配置来确定调用哪个服务操作,WF变量将提供输入并接收服务操作的输出,以及终结点名字。
首先,通过设置ServiceOperation属性选择服务操作。当你选择忽略属性配置节时,你选择的服务操作将会弹出一个对话框。如果这是你第一次从这个工作流引用服务,你需要从WCF生成的代理将类型导入到WF中。选择导入按钮并在工程中浏览到那个代理。图片11.3显示当代理类型从WF工程被导入以后的对话框。在这个例子中,GetPrice操作被选择。在服务操作被选择了以后,属性配置节展开来包含参数并返回服务操作的值。
下一步是将WF变量绑定到代理调用。当你选择忽略变量名(在这个例子中,Ticker和RetrunValue)旁边的属性配置节,将会弹出一个对话框让你选择或者创建适当类型的WF变量。你可以创建一个WF字段或者一个WF属性;字段是工作流类的本地变量,而一个属性在WF设计环境中有更广的可见能力。
图片11.3 导入代理类型所以可以选择一个操作
提示 属性或者字段 属性就是一个可以在一个工作流实例启动时被初始化的字段。你可以通过定义一个字典对象并将它传递给workflowRuntime.CreateWorkflow作为可选的第二个参数来在启动时给工作流发送一个值。如果你定义了一个属性,那么传递给工作流的值可以沿着顺序服务传递或者其他从工作流调用的活动传递。 |
图片11.4显示了WF变量,sendActivity1_RetrunValue_1, 绑定到getPrice操作的返回值上。
最后,你必须配置服务终结点属性以便于发送活动格式化消息并将消息发送给正确的位置。这是通过配置ChannelToken属性实现的,ChannelToken有三个元素: Name, EndpointName, OwnerActivityName. OwnerActivityName 指ChannelToken的范围并可以从一个下拉列表选择。EndpointName必须符合由添加服务应用或者svcutil.exe生成的app.config 文件中的配置名字。
图片11.4 绑定WF变量到服务操作参数
写一个自定义活动
WF中的自定义活动是一个压缩商业活动的很好的方式。通过在自定义活动中提供正确的抽象层次和粒度,一个WF开发人员可以通过连接那些能力来为一个应用建模。尽管使用发送活动来调用任何交互操作网络服务是一个好方法,它要求WF开发人员了解他们想使用的商业能力是什么样子的,事实上,是一个网络服务。自定义活动压缩知识以便于开发人员可以对应用建模。
在最简单的示例中,一个自定义活动是一个继承自System.Workflow.ComponentModel.Activity 的.NET 类。有很多子类继承于它,来专门研究顺序化或者状态机模型,或者负责的活动。只有那个类中的一个方法,Execute 是必须的。Execute的返回值是一个ActivityExecutionStatus 集合。如果设置为Closed, 表示活动完成了。如果不是,WF管理活动实例直到活动通知WF运行时它完成了。
自定义活动可以在运行时向WF设计器暴露属性。属性可以在Visual Studio 中设计阶段设置并绑定到WF的运行时可用的变量上。这些属性,实际上是自定义活动的接口。在自定义活动上的Execute方法仅要求上下文作为一个参数,因为接口通过属性完成。为了创建一个自定义活动,你可以使用Visual Studio工作流活动库模板。模板创建一个SequenceActivity 类型的自定义活动,继承自Activity.
列表11.2 显示了一个自定义活动。注意除了构造函数,仅有一个方法,Execute. 这个方法是你插入代码调用WCF服务的入口。在这个例子中,我们创建代理和取消代理的方法。当代理生成以后,WCF在本地工程中创建一个app.config 文件。在运行时,当调用活动时,WCF将会在当前应用程序配置文件中寻找服务模型信息。因此,服务模型配置必须被拷贝到用来作为工作流宿主的app.config 文件。
定义了两个属性,分别是ticker 和 price. 它们都可以从Visual Stuido 设计界面和运行时代码中看到。
列表11.2 实现一个自定义WF活动
自定义活动中的调用是封装起来的。图片11.5显示一个使用GetPrice 自定义活动的工作流。
namespace MyActivityLibrary { public partial class GetPriceActivity: Activity { public GetPriceActivity() { InitializeComponent(); } protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { StockPriceClient proxy = new StockPriceClient(); StockPrice price = proxy.GetPrice("MSFT"); return ActivityExecutionStatus.Closed; } public static DependencyProperty tickerProperty = DependencyProperty.Register("ticker", typeof(System.String), typeof(GetPriceActivity)); [DescriptionAttribute("Please specify a stock symbol ")] [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] [BrowsableAttribute(true)] public StockPrice ticker { get { return ((StockPrice)(base.GetValue(GetPriceActivity.tickerProperty))); } set { base.SetValue(GetPriceActivity.tickerProperty, value); } } public static DependencyProperty priceProperty = DependencyProperty.Register("price", typeof(StockPrice), typeof(GetPriceActivity)); [DescriptionAttribute("tradePrice")] [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)] [BrowsableAttribute(true)] public StockPrice price { get { return ((StockPrice)(base.GetValue(GetPriceActivity.priceProperty))); } set { base.SetValue(GetPriceActivity.priceProperty, value); } } } }
图片11.5 在一个工作流中使用自定义活动