用于路由服务的 WorkflowChannel。






4.67/5 (5投票s)
本文描述了用于通过路由服务从存储库调用工作流活动的自定义 WCF 通道的设计、实现和使用。
目录
特点
- IOutputChannel
- IRequestChannel
- 发送
- Begin/EndSend
- 同步模式下的环境事务流
- 单向和请求-回复消息交换模式
- 用于输入/输出活动的非类型化消息
- 不需要托管 xaml 活动
- Uri 资源 (xaml) 寻址(包括通过路由服务)
- 从存储库(数据库或文件系统)提取 xaml 活动
- 存储库或文件夹的多种配置
- 不支持持久性
- ETW 跟踪
- 由 IIS/WAS、WinForm、Silverlight、Console 等托管的任何客户端使用。
- .Net 4 技术
引言与概念
本文是我的文章 WCF4 路由管理器以及之前文章如 可管理服务 和 可管理服务的契约模型 的延续。在这些文章中,我详细描述了模型驱动架构,其中逻辑模型集中(存储)在存储库中,然后去中心化(部署)到目标以进行运行时投影。
在本文中,我将向您展示另一种在运行时进行业务模型组合的方式,例如从存储库中提取工作流活动。这与工作流服务相反,工作流服务是部署服务,然后使用 WCF 连接模型进行调用。在提取场景中,我们有一个业务 xaml 活动存储库,可以在调用者域中提取这些活动以进行执行。
由于 WCF 范式在通道级别的可扩展性,可以构建任何虚拟通道。对于从存储库中提取和执行 xaml 活动的概念,我们需要虚拟化客户端通道。使用相同的通道模式(透明代理模式)使我们能够以松散耦合的方式在业务层透明地调用工作流活动。这个自定义通道被称为 WorkflowChannel
。关于自定义进程内 WCF 通道的更多详细信息可以在我的 WCF 空传输 文章中找到,该文章已用于实现此 WorkflowChannel
。
创建 WorkflowChannel
的想法是由 WCF4 路由服务提出的。路由服务是一个出色的架构组件,用于消息路由、协议桥接、消息交换模式、版本控制、负载平衡、错误处理、前置和后置消息调解等。路由服务的用途非常通用。从架构的角度来看,它可以代表一个用于运行时业务组合的组件,类似于由路由表中存储的元数据驱动的本地服务总线。该服务(与其他服务不同)具有内置能力,可以在不回收主机进程的情况下在运行时进行修改。换句话说,路由表可以即时远程更改。
更准确地说,我们只能动态更改路由服务中的出站通道和路由规则。入站通道的更改将需要回收主机进程。对于服务中的任何监听器/接收器来说,这确实有意义,不是吗?
路由服务具有通过拦截器调解转发消息的出色能力。拦截器可以通过路由表插入到路由过程中。请注意,拦截器服务必须在其使用之前部署,就像另一个服务一样。拦截器中的任何更改都需要回收其进程或部署新版本并更新路由表。
本文向您展示了一种不同的方法,其中拦截器(调解器)可以部署到存储库中,并且路由服务可以根据 url 地址以透明的方式将其作为任何资源提取。
下图显示了路由服务消费 xaml 活动(例如消息调解器)的概念图
上图显示了根据消息交换模式将入站消息转发到 xaml 活动。在 RequestReply
消息交换模式下,消息会像来自“真实”服务一样以完全透明的方式返回到路由器。
下图显示了与前一幅图相比更详细的内容,您可以看到出站消息的自定义通道
上图显示了 WorfklowChannel
在此场景中的位置。WorkflowChannel
仅针对客户端工厂的特定协议构建,以从存储库(数据库)获取 xaml 资源并以同步或异步方式执行它。换句话说,WorkflowChannel
没有监听器,因此它只能用于出站消息,例如客户端。
从架构的角度来看,带有自定义 WorkflowChannel
的路由服务代表了一个用于托管存储在存储库中的未知声明性活动的托管服务。活动可以通过外部消息调用,然后通过单个活动链式连接。这种组合由路由元数据声明,路由元数据也可以存储在存储库中,供主机进程引导。
正如我前面提到的,活动资源是根据 url 查询从存储库中提取的。下图显示了将入站路由器消息转发到自定义出站 WorkflowChannel
如上图所示,活动的名称(本例中为:Test.ZipCodeByPass
)是 url 查询表达式的一部分,它被转发到自定义 WorkflowChannel
(net.wf
)。请注意,本文中此 url 查询表达式已简化。我们可以创建更复杂的查询表达式,包括主题、版本等。此实现支持从存储库(例如数据库和/或文件系统)中提取资源。
通过自定义 WorkflowChannel
调用工作流活动可以通过任何业务层完成。下图显示了通过自定义 BindingScope
包装的 Send
活动,通过自定义 WorkflowChannel
调用 xaml 活动
如您所见,Send 活动配置为自定义绑定 wfTransport
,它是我们的自定义 WorkflowChannel
。请求消息传递到 xaml 活动的 _inMessage
参数。当 xaml 活动完成时,输出参数 _outMessage
作为回复消息传递给 Receiver 活动。请注意,_inMessage
和 _outMessage
是此 RequestReply 消息交换模式的必需参数。这些参数是未类型化消息,因此 xaml 活动负责反序列化和序列化(或创建)它们的有效负载。
说到 WorkflowChannel
的消息契约,有两种非类型化契约。一种用于 OneWay
,另一种用于 RequestReply
。这两种契约都是非类型化消息,类似于路由服务使用的契约。请注意,IOutputChannel
和 IRequestChannel
处理非类型化消息,但透明代理可以是强类型的。
基本上,Send 活动可以使用任何内容(例如 Message、DataContract、MessageContract 或参数)来创建消息正文,但在另一方面,ReceiverReply 应该使用非类型化消息。
另一个消费 WorkflowChannel
的例子。下图显示了通过 WorkflowChannel
从 Windows 窗体调用 xaml 活动
最后,根据上述描述,下图显示了运行时存储库(活动容器)的策略:
上图显示了两个不同的范围。第一个是设计时(包括部署时),负责将业务模型分解为小的业务导向活动并将其部署到运行时存储库。另一个是运行时范围,它从存储库中提取活动并由 WorkflowServices、Services、Activities 等消费。
回到我们的路由服务。在下面的图片中,我们可以详细了解消息如何通过自定义 WorkflowChannel
进行转发(拦截)以进行调解
下图显示了一个简单的 OneWay
模式,其中入站端点收到的消息可以直接转发到出站端点,或者转发到出站自定义 WorkflowChannel
进行调解。在这种情况下,自定义通道实现了一个协议 IOutputChannel
(同步和异步模式),该协议根据 URL 查询表达式从存储库中提取 xaml 资源,将资源反序列化为 Activity
对象,并由 System.Activities.WorkflowApplication
实例执行此 Activity
对象。活动调解的一部分是将消息发送回路由器以转发到特定的出站端点。消息调解的一个例子是,为路由到目标服务添加一个头。这是一个 OneWay 模式,因此如果调解器中发生任何事情,不会有异常消息返回给调用者。请注意,只有跟踪和日志显示这里发生了什么。
如您所见,上图向您展示了一个非常简单的模式,用于将入站消息重新路由到调解器进行预处理,然后返回到路由器以将消息转发到目标出站。此更改可以在不影响当前路由器的情况下插入。我们不需要重新部署包等。此外,如果拥有正确的存储库工具系统,消息可以以完全透明的方式进行拦截,并且管理员/开发人员/业务人员可以根据需要即时调解消息。
下图显示了通过实现 IRequestChannel
的自定义 WorkflowChannel
将请求-回复消息转发到出站端点。通道处理与之前类似,不同之处在于通道等待活动完成并将响应回复给调用者。这是一个与活动紧密耦合的模式,包括将消息转发到出站端点,接收其响应并返回给调用者。此场景具有前置和后置消息调解的能力。换句话说,活动可以拦截请求和回复消息。此外,如下图所示,拦截器活动可以直接从存储库(以事务方式)或间接通过路由服务调用另一个活动。
关于介绍和概念就到这里。我希望您对 WorkflowChannel
的需求有了一个很好的理解和理由。
让我们开始 WorkflowChannel
的设计和实现。我假设您对 WCF 和 WF 技术有一定的了解和经验。
设计与实现
WorkflowChannel
的概念和设计基于 WCF 模型在客户端通道级别的可扩展性。请注意,WorkflowChannel
功能仅为客户端通道构建,其中工厂通道将提取工作流 xaml 活动以执行,并根据消息交换模式将结果回复给调用者或处理 OneWay 输出。我将跳过如何构建自定义 WCF 通道的解释。您可以在我的 WCF 空传输 文章中找到更多详细信息。我将只关注与 WorkflowChannel
相关的特定功能。
下图显示了从配置、绑定和通道角度来看 WorkflowChannel
的模型
如您所见,IOutputChannel
和 IRequestChannel
这两个通道只有一个通用通道工厂。
以下屏幕截图显示了它们的模型类
这两个模型都非常简单,特别是 WorkflowOutputChannel
,它只有一个单向输出模式。两个模型都支持同步和异步方式的消息处理。它们之间存在一些不同的实现。这种差异基于托管(执行)工作流活动。在同步过程的情况下,模型使用 System.Activities.WorkflowInvoker
类执行活动。原因是这个过程可以以事务方式完成。
以下代码片段显示了 Send 方法的实现
public void Send(Message message, TimeSpan timeout) { // double check base.ThrowIfDisposedOrNotOpen(); // add remote address to the message header base.RemoteAddress.ApplyTo(message); /* Note: Transaction doesn't flow through the WorkflowApplication! // load and create instance of workflow WorkflowApplication wfa = WfHelper.CreateWorkflowApplication(message, this.connectionString); // run workflow wfa.Run(timeout); */ // load and invoke a workflow activity in synch manner (the same thread!) WfHelper.WorkflowInvoker(message, this.connectionString, timeout); }
对于异步消息处理,模型通道将使用 System.Activities.WorkflowApplication
类来执行工作流活动。由于这个强大的类,它使得我们的 WorkflowChannel
实现非常轻量和优雅。
public IAsyncResult BeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state) { // double check base.ThrowIfDisposedOrNotOpen(); // add remote address to the message header base.RemoteAddress.ApplyTo(message); // load and create instance of workflow WorkflowApplication wfa = WfHelper.CreateWorkflowApplication(message, this.connectionString); return wfa.BeginRun(timeout, callback, state); } public void EndSend(IAsyncResult result) { WfHelper.EndRun(result); } public Message EndRequest(IAsyncResult result) { return WfHelper.EndRun(result); }
在上面的代码片段中,您还可以看到 EndSend
和 EndRequest
方法之间的区别,其中通道需要将回复消息返回给调用者。
好的,这看起来是一个非常直接的实现,不是吗?正如您在上述方法中看到的,实现已被封装到 WfHelper
静态类中。
在我们深入了解 WfHelper
工具类的实现细节之前,让我们看看 xaml 活动存储在哪里以及我们如何对其进行寻址。
寻址资源 (xaml Activity)
WorkflowChannel
的概念基于从存储库中提取资源。此时的资源是一个 xaml 活动,它是一种 xml 格式的文本。如您所知,根据之前的描述,WorkflowChannel
是为在调用者域中工厂化和执行资源而设计和构建的。它是一个进程内(本地)通道。该通道经过简化,没有编码器,传输层虚拟化用于本地输入/输出消息传递。因此,RESTfull 风格用于资源寻址,其中 uri 描述了权限、路径和资源查询。
下图显示了 WorkflowChannel
寻址的语法
授权部分显示了存储库的位置。本文内置了两种存储库,第一种是存储在数据库中的存储库,另一种是存储在文件系统中的存储库,称为文件夹。
Uri 寻址的路径是可选的。如果没有此值,则访问资源的连接字符串在 wfTransport 的绑定部分中定义。在文件夹的情况下,它是进程程序集(exe 文件)或虚拟目录(iis/was)的位置。此默认路径可以通过配置选项覆盖,其中可以使用到存储库或文件夹(或子文件夹)的不同连接字符串。您将在本文后面看到更多示例。
Uri 寻址的查询是强制性的,它用于查询存储库中的特定资源。其格式以资源的键名(例如 xaml
)开头。查询非常简单,但可以包含主题、版本等表达式,从而更丰富。
下图显示了 WorkflowChannel
寻址的一些示例
请注意,通过路由器寻址 WorkflowChannel
略有不同,如上例所示。在这种情况下,查询部分位于端点地址之后。正如您稍后将看到的,在 WfHelper
实用程序中实现了一个“技巧”以在路由器的出站通道处获取此查询。
自定义绑定
WorkflowChannel
绑定使用自定义绑定来设置其“虚拟传输”。以下代码片段显示了 WorfklowChannel
绑定的一个示例,用于标准跟踪配置文件,指定数据库存储库和消息版本 Soap12
<bindings> <customBinding> <binding name="wfAgentBinding"> <textMessageEncoding messageVersion="Soap12"/> <wfTransport trackingProfileName="" repositoryConnectionString="...."/> </binding> </customBinding> </bindings> <tracking/>
wfTransport
绑定元素有两个属性,即 trackingProfileName
和 repositoryConnectionString
。第一个允许指定自定义跟踪配置文件。当跟踪部分和 profileName 为空时,使用标准 TrackingProfile
。第二个属性 repositoryConnectionString
允许设置到存储 xaml 活动的存储库的特定数据库连接。
配置选项
存储库的默认位置可以通过 .config 文件中的配置来覆盖。下图显示了文件夹和存储库两种编写方式的示例。
这是 WorkflowChannel
的一个很棒的功能,它允许我们使用资源寻址的别名并使用配置文件映射到物理资源。
Xaml 资源(活动)
xaml 资源是xml格式文本(资源)声明的工作流活动。它以xml类型存储在存储库中。在 WorkflowChannel
和 Activity
之间定义了一个非类型化消息的契约。传入消息存储在 _inMessage
参数中,传出消息存储在 _outMessage
参数中。这两个参数是 WorkflowChannel
调用的 Activity 的强制参数。以下屏幕截图显示了 ByPass
Activity 的一个示例,其中输入消息被分配给输出消息。
请注意,活动有责任为输出参数创建适当的消息,例如版本、操作等。此消息是返回给 WorfklowChannel
调用者的回复消息。在使用强类型通道工厂(无非类型化消息)的情况下,输出消息格式必须与强类型契约匹配。
下图显示了使用自定义活动 CreateMessage
根据契约优先模型生成消息的示例。有一个编辑器用于导入请求的契约 (wsdl)、选择操作、版本等,以生成一个 xslt 参数化模板,该模板将在运行时生成适当的 soap 消息。有关此自定义活动和用于消息调解的其他活动的更多详细信息,请参阅我的文章 WF4 用于消息调解的自定义活动。
再提一点,请记住,WorkflowChannel
基本上是一个活动调用器,用于短时处理,不具备持久性和书签。其主要优点是使用通用的 WCF 范例将业务模型分解为存储在存储库中的小型可重用业务导向活动(调解器)。
存储库 - 文件夹
文件夹存储库表示 xaml 资源的简单容器。下图显示了项目中文件夹存储库的示例。App_Data 文件夹是 xaml 活动的容器,可以由 Visual Studio 设计。请注意,配置文件需要映射此路径文件夹。
基本上,任何文件夹都可以物理映射,以便 WorkflowChannel
可以对其进行寻址。该文件夹可以显式寻址,也可以相对寻址到 exe 进程的位置。
存储库 - 数据库
将数据库用于 xaml 活动存储库为我们提供了企业级更多功能。我们可以控制资源的签入/签出、预验证、回滚、版本控制等。为了展示 WorkflowChannel
的功能,我将使用我的 WF4 企业变量 文章中的存储库,其中详细描述了该设计和工具。基本上,此存储库的模型如下图所示。
本文包含一个批处理文件,用于创建和预加载存储库数据库中的一个小表。还有一个批处理文件用于清理数据库。
以下代码片段展示了从存储库获取 xaml 资源是多么简单
private static string GetXamlFromRepository(string name, string connectionString) { string xaml = null; using (var repository = new System.Data.Linq.DataContext(connectionString)) { var query = repository.ExecuteQuery(typeof(string), "SELECT runtimeValue FROM Variables WHERE Category=1 and Name={0}", name); if (query == null) { string errText = string.Format("Workflow '{0}' doesn't exist in the Repository", name); Trace.WriteLine(errText); throw new Exception(errText); } xaml = query.Cast<string>().FirstOrDefault(); if (string.IsNullOrEmpty(xaml)) { string errText = string.Format("Workflow '{0}' is empty", name); Trace.WriteLine(errText); throw new Exception(errText); } } return xaml; }
如果存储库模型发生更改,例如使用存储过程等,则将放置上述行。
WfHelper 工具
RKiss.WorkflowChannel.WfHelper
是一个实用程序类,用于处理运行时从活动到活动的所有同步和异步消息处理功能。下图显示了该类模型
WorkflowChannel
的共同部分是它对 xaml 资源的寻址。以下代码片段显示了此实现。这部分的挑战在于为任何直接或间接消费者(例如通过路由服务)找出此查询。此实现是通过使用反射来获取原始操作上下文,然后获取 IncomingVia
属性的值来完成的。
private static Dictionary<string, string> GetQuery(Message message) { string query = string.Empty; // in the case of routing service using (var os = new OperationContextScope((OperationContext)null)) { BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance; var originalScope = os.GetType().GetField("originalScope", flags).GetValue(os); if (originalScope != null) { OperationContext originalContext = originalScope.GetType().GetField("originalContext", flags).GetValue(originalScope) as OperationContext; if (originalContext != null && originalContext.IncomingMessageProperties.ContainsKey("IncomingVia")) query = (originalContext.IncomingMessageProperties["IncomingVia"] as Uri).Query.Trim('?'); } } if (string.IsNullOrEmpty(query)) query = message.Headers.To.Query.Trim('?'); if (string.IsNullOrEmpty(query)) throw new Exception("The uri.query is empty"); var arr = query.Split(new[] { ';', '&' }, StringSplitOptions.RemoveEmptyEntries); var dic = arr.Select(x => x.Split('=')).ToDictionary(i => i[0].ToLower(), i => i[1]); if (dic.ContainsKey("xaml") == false) throw new Exception("Missing 'xaml' value in the uri.query expression"); return dic; }
正如我之前提到的,对于异步消息处理,我们使用 System.Activities.WorkflowApplication
类来执行活动。在创建此托管实例之前,我们需要做几件事,例如从存储库中提取 xaml 资源,将 xaml 资源反序列化为 clr 类型并复制传入消息。以下代码片段显示了创建 WorfklowApplication
实例以请求 xaml 资源的方法。
public static WorkflowApplication CreateWorkflowApplication(Message message, string connectionString, string trackingProfileName) { WorkflowApplication wfa = null; var query = GetQuery(message); string name = query["xaml"]; // get xaml resource string xaml = null; string authority = message.Headers.To.Authority.Trim('/').ToLower(); string localpath = message.Headers.To.LocalPath.Trim('/').ToLower(); if(authority == "repository") xaml = GetXamlFromRepository(name, localpath, connectionString); else if(authority == "folder") xaml = GetXamlFromFile(name, localpath); else throw new Exception( ... ); // serialize xaml into clr type Activity activity = null; using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) { activity = System.Activities.XamlIntegration.ActivityXamlServices.Load(ms); } // create message copy Message copyMessage = null; using (MessageBuffer buffer = message.CreateBufferedCopy(Int32.MaxValue)) { copyMessage = buffer.CreateMessage(); } var inputs = new Dictionary<string, object>(); inputs.Add("_inMessage", copyMessage); wfa = new WorkflowApplication(activity, inputs); // tracking SetEtwTracking(wfa.Extensions, trackingProfileName); #region events // ... #endregion return wfa; }
请注意,异步消息处理在启动执行活动级别产生。在活动中进行大型复杂调解的情况下,解析和加载工作流定义实际上是以同步方式完成的。此外,此公共文章中的解决方案不包含 xaml 资源和/或 clr 类型工作流定义的缓存实现。
异步模式下工作流处理的结束通过在特定的 WorkflowApplication 托管实例上调用 EndRun
方法来定义。以下代码片段显示了其包装器实现。
public static Message EndRun(IAsyncResult result) { ManualResetEvent wfc = new ManualResetEvent(false); Message message = null; // workaround for accessing Workflow Instance BindingFlags bflags = BindingFlags.NonPublic | BindingFlags.Instance; Type type = typeof(System.Activities.WorkflowApplication).GetNestedType("RunAsyncResult", bflags); WorkflowApplication wfa = type.BaseType.GetProperty("Instance", bflags).GetValue(result, null) as WorkflowApplication; // workaround to get a request message object rpc = result.AsyncState.GetType().GetField("Rpc", bflags).GetValue(result.AsyncState); FieldInfo fi = Type.GetType("System.ServiceModel.Dispatcher.ProxyRpc, System.ServiceModel, ...").GetField("Request", bflags); Message request = fi.GetValue(rpc) as Message; #region completed wfa.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { if (e.CompletionState == ActivityInstanceState.Faulted) { // create fault message; } else if (e.CompletionState == ActivityInstanceState.Canceled) { // create fault message; } else if (e.CompletionState == ActivityInstanceState.Closed) { if (e.Outputs != null && e.Outputs.Count > 0 && e.Outputs.ContainsKey(OutArgumentName) && e.Outputs[OutArgumentName] != null) { message = e.Outputs[OutArgumentName] as Message; } else { // create fault message; } } else { return; } wfc.Set(); }; #endregion wfa.EndRun(result); wfc.WaitOne(); return message; }如您所见,上述方法使用反射从异步结果中获取状态。如果能够公开访问工作流实例和请求消息,那将是很好的。无论如何,我很幸运地使用反射获得了它们。一旦我们有了对当前工作流应用程序实例的引用,我们就可以调用其
EndRun
方法来等待工作流完成。要在此方法中获取工作流完成状态,我们需要使用手动同步对象,该对象允许我们以适当的方式创建响应消息。好的,这就是对异步处理的支持。应该注意的是,环境事务没有通过 WorkflowApplication
宿主,与 WorkflowInvoker
相反,后者支持。因此,WorkflowChannel
的 Send
和 Request
方法是使用 WorkflowInvoker 实现的。其包装器在 WfHelper
实用程序中实现。
再提一点,本文中的解决方案没有针对 xaml 资源及其 clr 类型的缓存功能。
本主题到此为止。
现在是时候演示 WorkflowChannel
的用法了。
用法与测试
在本段中,我将详细描述 WorkflowChannel
的用法和测试。该解决方案包含四个项目,如下图所示
该解决方案的核心是 WorkflowChannel
项目,其实现基于 WCF 的 NullTransport 通道。其他项目仅用于测试目的。对于您的应用程序使用,只需要 WorkflowChannel.dll
程序集。上述解决方案包含多个项目,用于从不同托管域和存储库消费 WorkflowChannel
,以演示 WorkflowChannel
的功能。
我根据存储库的类型将测试用例分为两部分。简单部分用于存储库文件夹,其中所有 xaml 资源都位于项目文件夹中(例如:App_Data),因此这些项目已准备好进行测试。另一方面,当存储库存储在数据库中时,我们需要执行额外的步骤,例如设置数据库、使用重新托管的工作流设计器等,如您稍后将在高级测试 B 部分中看到的那样。
如果您只对简单测试感兴趣,而无需在 SQL Server 中创建存储库,请按照测试 A 部分操作。在我们的测试之前,请查看并提供 WorkflowChannel
使用和测试的以下先决条件
- 对 WF4 和 WCF4 + 路由服务有一些了解和经验
- Visual Studio 2010 (sp1)
- 下载 适用于 Windows 的 DebugView 以获取跟踪输出。
- 下载本文的源代码解决方案
- 创建一个私有事务队列
Test
并授予 Everyone 完全控制权限(仅适用于Test.TxQueue
活动) - 启动
setup.bat
文件(位于 Repository 文件夹中)以在 SQL Server 上安装 Repository(仅适用于 Test B 部分) - 将位于 3rdParty 解决方案文件夹中的
WorkflowChannelTesters
文件夹复制到桌面。有几个 .exe 程序供我们的测试人员使用。请注意,这些程序集可以在我之前的文章中找到。我们将在本文中使用它们进行测试。以下屏幕截图显示了四个快捷方式测试人员。
- 启动 StartWebServer40.bat 文件以启动 ASP.Net 开发服务器来托管我们的路由服务。cmd 程序将要求您输入此解决方案下载到的目录。之后,任务栏上应显示以下图标。
- 通过调用此地址 https://:33975/workflow.svc 检查路由服务访问
测试 A - 使用文件系统存储库的示例
示例 A1 - ConsoleApplication1
这是一个简单的测试,用于通过 WorkflowChannel
直接或通过路由服务调用活动。作为活动的示例,我们将调用一个公共天气服务 http://ws.cdyne.com/WeatherWS/Weather.asmx
。以下屏幕截图显示了此活动的图表。
请注意,上述活动的输入/输出消息是 Soap11 版本。当然,活动可以根据需要调解消息,例如,请参见路由器服务中的 xaml 活动。从设计的角度来看,上述活动代表了消息的前置和后置调解器。它看起来像实际 Web 服务调用的包装器。活动是修改(处理)消息或与其他服务创建一些轻量级组合的好地方。对于我们的简单测试,输入/输出活动契约与 Web 服务调用相同,但它也可以完全不同。
好的,为了进行此测试,拥有正确的配置非常重要,例如 WorkflowChannel
绑定的扩展、存储库文件夹和我们两个测试客户端的自定义绑定。
因此,ClientApplication1 测试器内置了两个测试,第一个将使用客户端名称“inprocess”(Soap11),第二个测试将调用名为“router”(Soap12)的客户端。上述配置文件显示了这些客户端的不同自定义绑定。
好的,让我们启动 ConsoleApplication1 程序。您应该得到与下图所示相同的结果。
示例 A2 - WcfClient1
此示例已内置在 Windows 窗体中,具有更多用户选项,例如更改请求消息、发送消息或请求/回复消息到特定端点。在此示例中,我将演示调用活动 Test.AddHeader
,该活动将内部设置消息头并使用自定义 WorkflowChannel
绑定将消息发送到另一个活动 Test.Gaga
。以下屏幕截图显示了此场景。
正如您在上述活动中看到的,有一些非常有用的自定义活动。自定义 BindingScope
是围绕 Send 活动的包装活动,用于自定义绑定并添加消息头。更多详细信息可以在我的 WF4 中的动态 Send 活动 文章中找到。另一个有趣的自定义活动是 CreateMessage。此活动可用于根据契约优先元数据创建消息。有关更多详细信息,请参阅我的其他文章 WF4 用于消息调解的自定义活动。
好的,启动 WcfClient1 测试器,选择地址 net.wf://folder/abc?xaml=Test.AddHeader
并按 Fire 按钮。结果应与下图所示相同。
一旦测试通过,尝试更改 Fire 或 Invoke 消息的地址。请记住,此测试仅适用于存储库 - 文件夹。
例如,选择地址 https://:33975/workflow.svc/Folder?xaml=Test.ZipCodeByPass 并按下 Invoke 按钮,请求消息将通过路由服务发送到 Test.ZipCodeByPass
活动。该活动存储在本地文件夹存储库 (App_Data) 中。
要跟踪来自通道或活动的输出消息,我们可以使用 适用于 Windows 的 DebugView 程序。有关更多故障排除详细信息,应启用标准 WCF 和 ETW 跟踪。
示例 A3 - WcfService1 - 路由服务
在此示例中,我将演示在路由服务过程中“托管”活动。以下屏幕截图显示了此项目。
这个项目有趣的地方在于它没有代码。没有单个 .cs
或 .svc
文件。该项目是基于配置和 xaml 文件声明性驱动的。对于我们的测试,已选择上述活动。此活动实际上是一个旁路活动,其中输入消息传递给输出。没有调解。
测试非常简单。客户端(例如 WcfClient1)将向路由器服务发送请求消息,消息将通过 WorkflowChannel
转发到 Test.ByPass 活动。在活动内部,消息被分配给输出参数并发送回通道。此消息返回给客户端。请注意,这是一个 RequestReply 模式场景。
好的,让我们进行一些测试。请选择 https://:33975/workflow.svc/Folder?xaml=Test.ByPass
并按下 Invoke 按钮。您应该在响应文本面板中收到一个请求消息,如下图所示。您可以例如更改请求消息中的邮政编码并再次调用消息。
现在,我们可以在活动中进行一些业务。让我们在活动中调用一些 Web 服务,该服务将响应发送回客户端。我已经准备好这种测试用例。请选择地址 https://:33975/workflow.svc/Folder?xaml=Test.ZipCodeByPass
并按下 Invoke 按钮。您应该会看到来自天气服务的响应消息。
正如我前面提到的,没有代码文件。路由服务是通过 web.config 文件中的节配置的。请浏览此文件查看内容。基本上,您会看到以下内容
- 通过相对地址
~/workflow.svc
激活服务 - 基于消息交换模式和存储库存储(OneWay、RequestReply、文件夹、数据库)的服务端点
- 两个客户端,一个用于文件夹,另一个用于数据库存储库
- 入站和出站消息版本 Soap12(http 和
wfTransport
)的自定义绑定 - 路由表,用于将消息从特定入站端点简单转发到出站端点。
测试 B - 使用数据库存储库的高级示例
在此高级测试中,我将向您展示一个更接近生产原型的场景。此测试需要一个运行时存储库,其中预加载了我们的测试 xaml 活动。此外,您将获得一个小工具来创建您的活动并将其存储在存储库中。
首先,让我们来玩一下重新托管的工作流设计器。
示例 B1 - 工作流设计器
解决方案包包含了一些程序集,这些程序集在我的之前文章(参见参考文献)中提供,用于测试目的。其中之一是我的工作流设计器程序,它是一个在 WinForm 中重新托管的工作流设计器。在此示例中,我将演示通过 WorkflowChannel
调用活动。请点击 DesignerTester 快捷方式启动此程序。
使用 WorkflowChannel
非常简单。它将需要为 Send 活动和 AddressUri
设置自定义绑定,如下图所示。请注意,Send 活动的消息是非类型化消息,是在自定义活动 CreateMessage 中创建的。
按下 Run 按钮,我们可以在自托管控制台程序中运行此活动。结果应与下图所示相同。
那里发生了什么?有两个活动。第一个(主)将为通过 WorfklowChannel
调用的子活动生成请求消息。这个自定义通道将从存储库(数据库)中提取子活动,并在宿主进程(控制台程序)中处理此活动。业务通过调用公共 Web 服务(天气)封装到子活动中。此子活动的响应返回给主活动并显示在控制台上。非常酷。我们以透明的方式使用与调用服务相同的范例。除此之外,我们正在从容器(存储库)中提取活动,而无需托管它,它不是工作流服务。这是一个纯粹的 xaml 活动。
示例 B2 - 发送消息到 MSMQ
在此测试场景中,我们有一个客户端 (WcfClient1)、路由服务 (WcfService1)、运行时存储库、MSMQ 和管理工具。下图显示了它们的组成。
在 WcfClient1 上选择地址 https://:33975/workflow.svc/Fire?xaml=Test.TxQueue
,然后按 Fire 按钮。消息将发送到路由服务,并根据路由表转发到带有自定义 wfTransport
绑定的出站端点。此自定义 WorkflowChannel
将从运行时存储库中提取 xaml 资源 Test.TxQueue,将其序列化为 clr 类型,并通过 WorkflowApplication 主机对象执行。这是一个运行时进程。
如果我们要即时更改 xaml 资源,而无需重新部署、故障等,例如,我们想在活动中调整(调解)消息;我们需要一些工具支持。
在此测试中,我正在使用我的前一篇文章 WF4 的企业变量 中的一个小工具,因此我建议您查看该文章以获取有关如何使用此工具的更多详细信息。请单击 EnterpriseVariables 快捷方式启动此工具。
选择名为 Test.TxQueuue 的变量(活动),然后单击 Editor 选项卡。现在,我们拥有重新托管的工作流设计器的功能来编辑此活动。
单击 Variables 选项卡,我们回到数据网格,我们可以在数据库中签入这些更改。
就这样。我希望上面的测试用例向您展示了 WorfklowChannel
的有趣功能。
ETW 跟踪
WorfklowChannel
为 ETW 跟踪向 WorkflowInstanceExtensionManager
添加了一个扩展。自定义跟踪可以在配置文件中配置,并通过其在绑定部分中的属性选择。下图是事件查看器的屏幕截图。请注意,必须启用 ETW 日志跟踪。
结论
总之,本文描述了一个自定义 WCF 通道,用于以透明的方式(如服务)调用工作流活动。这个自定义通道使我们能够将业务模型封装到存储在容器(存储库)中的小型可重用活动中。这些活动可以在运行时从容器中提取以执行。基本上,WorfklowChannel
可以在业务层中使用,以在同一进程中解耦层。其中一个优点是对于路由服务,这个自定义通道可以用于出站端点,用于消息调解和轻量级服务组合。此外,wcf 范式被许多 MS 技术使用,为我们提供了用于解耦业务层的可重用模式。
参考文献
[3] WCF4 的路由管理器
[4] WF4 的企业变量
[5] 可管理服务
[6] 可管理服务的契约模型
[7] WF4 用于消息调解的自定义活动
[8] WF4 中的动态 Send 活动