Windows Workflow (WF) 作为 WCF 服务
顺序工作流作为 WCF 服务。创建工作流自定义活动,从父级调用子工作流。使用配置文件配置工作流运行时服务。规则的基本思想,使用规则编辑器创建规则。在运行时应用规则。
引言
在 Windows Communication Foundation (WCF) 上处理 Windows Workflow Foundation (WF) 时,我找不到任何“实用”的文档/示例来一步一步地解释需要做什么以及“为什么”。本文档介绍如何将顺序 WF 创建为 WCF 服务。
那么,我们在这里将学到什么?
- 在 Windows Communication Foundation 上创建顺序工作流服务库。
- 使用“ReceiveActivity” - 创建契约、绑定属性等。
- 创建一个网站将库公开给外界(Web 配置等)。
- 创建一个自定义工作流运行时服务并在服务模型部分进行配置。
- 使用 DependencyProperty 创建带有自定义事件和属性的自定义活动。
- 创建自定义活动以从另一个工作流调用工作流。另外,如何传递参数。
- 规则的基本思想,如何使用规则编辑器创建规则集。在运行时应用规则。
背景
正如我所提到的,WCF、WF、规则是非常有趣的技术,但同时,从 MSDN 和其他网站收集所有必需的基础信息也是一项耗时的工作。我只是想涵盖该领域内所有必要的功能。
使用代码
一个简单的业务场景会让这一切更有趣。所以,让我们来定义它
- 预订单详情(或原始订单详情)来自外部源。
- 客户端调用“订单处理器”来处理数据。
- “订单处理器”对原始数据应用业务规则,并决定接受哪些订单,拒绝哪些订单。
- 它还对每个流程(订单)应用特定的业务规则(现有客户的运费折扣),并将订单和运费详情保存到数据库。
- 业务用户可以随时更改规则。
- 可选地,调用者还可以定义输入文件是否需要存档。[仅仅是为了让我们的主工作流更有趣。]
从技术角度来说
- 订单处理器是我们在 WCF 上的主工作流。工作流将保持活动状态(活着),直到我们显式关闭它,以便它可以按需处理我们的请求。
- 主工作流对文件级别(所有记录)应用规则。在此示例中,如果订单金额为零,则拒绝订单(数据质量规则)。
- 在处理文件级别规则后,它将调用另一个子工作流(将是自定义活动)来处理工作流,并传递完整的“订单”对象。[在这里我们将看到如何使用“DependencyProperty”定义属性以及如何使用它。]
- “Worker”工作流对订单级别(ShipmentCostRule)应用规则,并计算运费。最后,它使用 LinQ 将订单保存到数据库。
- 这两个工作流都将使用自定义工作流运行时服务 ExternalRuleSetService,该服务将用于从数据库检索规则。
- 我们还将创建两个自定义活动,“InvokeWorkflow” - 用于调用另一个工作流,以及“PolicyFromService” - 用于应用规则,它内部将使用工作流运行时服务从数据库拉取规则。这还将有一个自定义活动事件,该事件将在应用策略后触发。
- 我们将使用规则编辑器来创建规则。
参考文献
我从 Microsoft samples 中借用了一些示例,其中包含规则编辑器、PolicyFromService、示例用法等。
入门
好了!让我们先确定我们的文件夹结构。
- Lib - 将包含各种应用程序在运行时使用的所有二进制文件。
- Src/Libraries - 将包含所有库(自定义活动、自定义工作流运行时服务等)。
- Src/RuleEditor - 来自 Microsoft 示例的规则编辑器,我没有对此做任何特殊处理。
- Src/Services - 我们在 WCF 服务上的主工作流,以及用于托管该服务的一个网站。
- Src/TestClient - 用于调用服务的测试客户端。
创建主工作流
- 创建新项目 - > WCF - > 顺序工作流服务库。
- 将创建一个顺序工作流,其中包含一个“ReceiveActivity”和一个接口“
IWorkflow1
”。将工作流“Workflow1”重命名为“ProcessManager”,将接口“IWorkflow1
”重命名为“IProcessManagerServices
”,因为这将是我们向外部公开的服务。 - 打开
IProcessManagerServices
。将现有操作契约重命名为string Start(bool fileToBeArchived)
。 - 打开“ProcessManager”,您会注意到 receiveActivity 上有一个红色的感叹号。别担心,这是因为我们重命名了契约“getData”。
- 更正契约操作名称,属性 --> ServiceOperationInfo --> 选择“Start”操作。
- 我们需要绑定输入参数“fileToBeArchived”。
- ProcessManager --> 查看代码。
- 定义一个变量
public bool FileToBeArchived;
- 回到设计模式。
- 点击“Promote Bindable Properties”。
- 您应该会看到“
FaultMessage
”、“fileToBeArchived
”和“OperationValidation
”属性已填充。 - 将“
FaultMessage
”和“OperationValidation
”属性删除并留空。我们将在下一节中学习它们。转到代码视图。删除 VS IDE 添加到您代码中的所有相关代码。您应该会看到类似这样的内容。 - 点击“
fileToBeArchived
”并与我们定义的变量“FileToBeArchived
”绑定。 - 我们希望我们的服务(主工作流)保持活动状态(始终在线)。为此,我们将使用“While”活动。在放置它之前,让我们先创建一个另一个变量
public bool SwitchedOn
。 - 在 receive activity 之上创建一个“Code”活动,并将其命名为“SwitchOn”。在它下面放置一个“While”活动,然后将 Start “Receive”活动拖到“While”活动内。将其命名为“WhileSwitchedOn”。
- 在 execute code 方法中,编写
this.SwitchedOn = true;
。 - 让我们放置另一个“Receive”活动,并将其命名为“SwitchOff”。创建另一个操作契约“SwitchOff”并相应地绑定它。
- 所以,我们的工作流将一直保持活动状态,直到我们不显式关闭它。
- 客户端将调用工作流“Start”操作,并带有一个参数,指示输入文件是否需要存档,只是为了向您展示一些“ifelse”活动在主工作流级别执行一些任务。
创建库
好了……现在,让我们在这里休息一下,创建一些其他有趣的东西!让我们创建工作流库以及所需的自定义活动“InvokeWorkflow
”和“PolicyFromService
”。
自定义活动需要继承自 System.Workflow.ComponentModel.Activity
,并重写其 Execute
方法,仅此而已!如此简单!
PolicyFromService
– 我只是使用了 Microsoft 的示例,没有对其做任何特殊处理,只是添加了一个自定义事件,该事件将在应用策略(规则集)后触发。InvokeWorkflow
– 重写Execute
方法时,您只需要做以下事情:- 获取服务,例如
IStartWorkflow IStartWorkflow startWorkflow = executionContext.GetService(typeof(IStartWorkflow)) as IStartWorkflow;
- 启动工作流
this.InstanceId = startWorkflow.StartWorkflow(this.TargetWorkflow, this.Parameters)
。Targetflow
和Parameters
将由“调用工作流”或“父工作流”填充。 - 定义一个静态
DepedencyProperty
,public static readonly DependencyProperty InstanceIdProperty = DependencyProperty.Register("InstanceId", typeof(Guid), typeof(InvokeWorkflow));
。 - 创建一个公共属性来“
get
”和“set
”(base
)值 - 定义一个静态
DependencyProperty
- 创建一个公共事件来“
add
”和“remove
”事件处理程序 - 在需要的地方引发事件。
使用 DependencyProperty 的自定义属性
[Category("Activity")]
[Description("InstanceId of of the invoked workflow")]
public Guid InstanceId
{
get
{
return (Guid)base.GetValue(InstanceIdProperty);
}
set
{
base.SetValue(InstanceIdProperty, value);
}
}
public static DependencyProperty AfterPolicyAppliedEvent =
DependencyProperty.Register("AfterPolicyApplied",
typeof(EventHandler), typeof(PolicyFromService));
public event EventHandler AfterPolicyApplied
{
add { base.AddHandler(AfterPolicyAppliedEvent, value); }
remove { base.RemoveHandler(AfterPolicyAppliedEvent, value); }
}
base.RaiseEvent(PolicyFromService.AfterPolicyAppliedEvent,this, EventArgs.Empty);
自定义工作流运行时服务
ExternalRuleSetService – 我没有对其做任何特殊处理,只是使用了 Microsoft 的示例。大家试着理解代码的作用。我们将配置此工作流运行时服务,以便在运行时(通过“PolicyFromService”活动)可以使用它。
最后,编译解决方案“Library.sln”并将**二进制文件复制**到root\Lib文件夹,这将由我们的主解决方案引用。
回到主工作流
要使用我们的自定义库,请转到工具箱 --> 右键单击 --> 选择项。从Lib文件夹中选择我们的库。您应该会在工具栏上看到我们的组件,如下所示。
对于我们的业务场景,在处理完文件上的初始任务(存档等)后,我们需要对已完成的输入记录应用业务规则,并相应地接受和拒绝记录。
放置“PolicyFromService”活动,命名为“DataQuality”,并设置 RuleSetName 为“DataQuality”。提醒我使用规则编辑器创建此规则**:)**
子工作流
好的!我们已经过滤了记录(预订单详情),我们需要通过应用业务规则来处理每个订单并将它们保存到数据库。为此,我们将创建另一个顺序工作流“Worker”,然后放置“Code”活动来初始化 Worker,然后应用规则,“PolicyFromService”活动,最后放置“Code”活动将处理后的订单保存到数据库。相应地命名它们,并将“PolicyFromService”活动的 RuleSetName 设置为“ShipmentCostRule”…提醒我创建此规则**:)**
我们将从主“ProcessManager”工作流调用此子“Worker”工作流。这将一直持续到所有订单都处理完毕。所以,让我们放置一个“While”活动,将 Condition 选择为“Declarative Rule Condition”,并将表达式设置为 this.OrderQueue.Count != 0
。将“InvokeWorflow”活动放置在“While”活动内部。将 TargetWorkflow 设置为“Worker”(记住我们现在正在使用 DependencyProperty
),通过浏览类型。
最后…我们最终的主工作流看起来像…
我们需要将必需的参数从主工作流传递到子工作流。在这种情况下,我们将传递一个完整的“订单”对象给“Worker”。我们通过“worker”工作流的“BeforeInvoke
”事件来完成。
从队列中出列订单并将其作为参数传递。
OrderDetail thisOrder = this.OrderQueue.Dequeue();
Type targetWorkflow = typeof(Worker);
(sender as InvokeWorkflow).Parameters.Add("Order", thisOrder);
[注意 - 我使用了 .NET 的 Queue
来对所有记录进行排队,并逐个出列进行处理。]
创建规则
好的… :) 我们的应用程序差不多完成了,只需要使用规则编辑器创建两个规则集。希望你一直跟着我。
- DataQuality – 忽略所有金额为 0 的订单。我创建了一个扩展方法
FilterZeroAmountOrders
来过滤所有此类记录,我们将在本规则中使用它。 - ShipmentCostRule – 如果会员已购买超过 1000 美元的商品,我们将给予他 50% 的折扣。另一个扩展方法
TotalAmountOrderedTillDate
将帮助我们。
Condition – this.RawOrders.FilterZeroAmountOrders().Count > 0
。
Then Action – this.Orders = this.RawOrders.FilterZeroAmountOrders()
。
Condition – this.Order.TotalAmountOrderedTillDate() > 1000
。
Then Action – this.Order.ShippingCost = this.Order.ShippingCost * 0.5
。
我们不关心 else 部分,可以将其留空。**保存规则**。
托管
让我们通过向解决方案中添加一个空的网站来托管我们的服务。您可以删除所有内容,除了web.config。我也建议删除web.config的所有内容,以便我们知道运行此解决方案确切需要什么。这很重要…让我们将任务分为两部分。
- 在 WCF 服务上配置或公开我们的服务契约。
- 为此,我们将向网站添加一个新项“WCF Service”并将其命名为ProcessManagerService.svc。删除所有其他项和 VS IDE 创建的任何文件夹。
- 在ProcessManagerService.svc中,我们需要说明我们要公开哪个服务。我们的服务实际上是一个名为“ProcessManager”的工作流。连同其 Factory 一起提及,该 Factory 负责将工作流作为服务公开的内部“事物”。
- 根据旧的 WCF “ABC”规则定义其终结点。有关详细信息,请查看web.config。
- 在服务模型下配置我们的自定义
WorkflowRuntime
服务,因为 WCF 是我们工作流的主机。此配置节位于 <system.serviceModel
> --> <serviceBehaviors
> --> <behavior
> --> <workflowRuntime
> - 按如下方式添加工作流运行时服务。
Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory"
Service="BigDs.WF.Services.ProcessManager"
<workflowRuntime>
<services>
<add type="BigDs.WF.WorkFlow.ExternalRuleSetService, BigDs.WF.WorkFlow" />
<services>
<commonParameters>
<add name="ConnectionStringName" value="cs" />
</commonParameters>
</workflowRuntime>
将网站设置为启动项目并运行它。点击ProcessManagerService.svc以验证我们的服务是否已正确配置。
我们差不多准备好了,这次我是认真的 **:)**,只需要创建一个客户端来调用。
客户端
创建一个简单的控制台应用程序,添加一个服务引用,并按如下方式调用 WCF 方法……
ProcessManagerServicesClient psc = new ProcessManagerServicesClient();
psc.Start(true);
仅此而已,各位……运行您的网站,执行客户端来测试应用程序。请告诉我您对本文的看法,或者如何改进/增强它。
让我们快速总结一下今天学到的所有内容。
- WCF 顺序工作流 (WF) 服务库。
- 将其公开为 WCF 服务,并将其托管在网站上。
- 将其与工作流运行时服务一起配置。
- 创建自定义工作流运行时服务。
- 创建自定义活动。
- 使用
DependencyProperty
创建自定义属性。 - 使用
DependencyProperty
创建自定义事件。 - 规则集的基本概念。
- 如何使用规则编辑器创建规则集。
- 从数据库获取规则并通过工作流运行时服务在运行时应用它。
- 创建自定义活动以调用子工作流。
- 如何将参数传递给子工作流。
- 如何使用扩展方法并在规则编辑器中创建规则。
关注点
我们可以在 <workflowRuntime>
下定义 <commonParameters>
。我仍在尝试找到一种方法来从配置中读取它。唉!搜索这些小东西真的很烦人。也许以后再说。
运行代码
- 解压文件。
- 执行Script文件夹下的脚本。这将创建两个表,其中包含一些现有数据(两条规则和三个现有订单详情)。
- 更新 RuleSet 表的
AssemblyPath
以匹配您的路径。这很重要。 - 在web.config中更新
InputFileLocation
为您的路径。
历史
- 1st 版本,于 2009 年 7 月 16 日发布。