WF与WCF集成
随着 Windows Workflow Foundation (WF) 的问世,Microsoft 逐步将各种工作流功能引入了 .NET 开发人员平台。这些功能使开发人员能够构建用于满足各种应用需求的工作流,从简单的顺序工作流到需要复杂的人员交互的复杂状态机工作流。
与此同时,业务能力越来越多地通过封装的服务端点展现出来,这样就可以重用和组合业务功能和业务流程,使面向服务的体系架构更加完善。Windows Communication Foundation (WCF) 提供了统一的开发人员 API、稳健的托管运行时和灵活的配置驱动解决方案来帮助进行部署,进而帮助开发人员通过各种功能来轻松开发互联系统。
开支报告示例
本文的代码示例基于为员工报销申请提交与审批标准业务流程建立模型的“开支报告”工作流示例。我们在原示例基础上进行了更新,用以说明如何利用 WCF 和 .NET 3.0 Framework 来更有效地托管这一业务环节。
在发行的第一版“开支报告”示例中,使用 .NET 远程调用来提供客户端应用程序与包含工作流运行时实例的宿主应用程序之间的通信。
而经我们重构的开支报告示例在实现时使用 WCF 来执行客户端和服务端之间的通信。此外,该解决方案还实现了逻辑上的结构化,从而将其中的各种问题分离出来。
了解消息在业务流程上下文中的使用方式非常重要,只有这样您才能将它们融入设计之中。在“开支报告”的生命周期中,存在几个交互点。我们来简单分析一下:
-
这一流程涉及到三方:客户端、管理人员和开支报告宿主系统。
-
这一流程从客户端提交新的报销申请开始。
-
我们使用一种规则策略来确定报销申请是否可以自动审批。
-
如果报销申请不能自动审批,则需要管理人员审批该报告。管理人员要么需要自行检查是否有待审批的新报告,要么需要在有待审批的新报告时获得通知。
-
如果管理人员在灵活的延迟时间内未进行审批,该流程会自动拒绝此报销申请。
-
报销申请被复审完毕后,客户端和管理人员必须被及时告知结果。
通过 WF,我们可以利用该框架所提供的标准活动来为这一流程建模。我们可以使用 DelayActivity 来管理在一段时间后触发的事件,可以使用规则引擎和 PolicyActivity 来管理一套灵活的规则,通过询问这些规则可以获得结果。
由于这是一个面向人员的流程,因此我们必须与最终用户进行交互,并将该交互交回到工作流中。WF 提供了“本地服务”、HandleExternalEventActivity 和 CallExternalMethodActivity,从而为实现宿主和工作流之间的通信提供了全面的编程模型。
由于这对于构建交互式工作流而言是一个重要的概念,因此让我们快来了解一下它是如何被设计到 WF 中的。
为了在 WF 中建立用户交互的模型,我们必须设计用于提供许多事件和方法的约定。工作流和宿主进程都应理解这一约定。所构建的约定/接口必须以 [ExternalDataExchange()] 属性来标记,这一属性会将其标识为专用于进行工作流数据交换。在我们所列举的示例中,工作流使用 IExpenseLocalService 接口。
然后我们订阅一个与工作流运行时实现该接口的类(称为本地服务)。工作流活动可以注册到事件或者使用接口类型上定义的方法,并将绑定到我们已经注册的本地服务。这采用的是一种称为“控制反转”的模式,这种模式消除了工作流与具体类型本地服务之间的紧耦合。在我们所列举的示例中,ExpenseLocalService 类实现了 IExpenseLocalService 约定。
工作流在第一次运行时,可以获得一个用于执行操作的初始数据包。当工作流到达需要外部交互的点后,我们可以引发一个能够在工作流中绑定到 HandleExternalEventActivity 的事件。此活动将接口类型和事件作为参数,当该事件被引发后,工作流会被唤醒,使操作继续下去。
如果工作流必须对本地服务进行回调,可以使用 CallExternalMethodActivity 并以接口和方法名为参数来实现。
通过这些活动,可以在宿主进程中与运行中的工作流实现双向通信,而通过在 WF 中使用“控制反转”模式,避开了工作流和本地服务之间的紧耦合。
然而,一旦跨出宿主进程这一范围,我们就必须允许交互由其他系统甚至是人来驱动。为实现这一级别的交互,我们可以通过服务来分配交互操作,而这些服务进而再由其他服务或用户驱动的应用程序来调用。而 WCF 就是能够灵活构建这一消息传递功能的框架。
在这个与 WCF 集成的方案中,主要的优点在于:
-
可以将服务实现与消息传递管道代码相分离。
-
大大减少了与系统连接有关的代码量和复杂程度。
-
增加了部署的灵活性。
-
可以利用从主机到客户端的直接回调,使信息更新的速度更快、开销更低。
集成工作检查表
为了实现 WF 与 WCF 的集成,必须有一个将为使用者提供众多接口点的服务接口,使用者可以在这些接口点启动运行中的工作流或与之交互。应围绕业务流程与外部实体(例如这一流程所涉及的人员)进行交互的这些接口点来建立服务模型。
为此,我们必须:
-
定义服务约定。
-
实现通过事件创建新工作流(或与现有工作流进行交互)的服务操作。
-
将工作流运行时实例托管到服务宿主中。
除了简单地托管工作流之外,我们还可以利用 WCF 双工信道将事件从工作流交回给作为使用方的客户端。就“开支报告”而言,这是非常有益的,因为该解决方案依靠客户端进行服务轮询来实现定期的数据更新。但这些客户端也可以直接从服务处获得通知。
为此,我们必须:
-
定义一个回调约定。
-
使用将支持双工信道的绑定。
定义服务约定
Windows Communication Foundation (WCF) 要求声明一份正式的约定来抽象地定义服务功能和数据交换。这份约定通过声明接口以代码的形式定义。
在设计业务服务时,通常会使用“请求/响应”协作模式。使用这种模式时,在所提供的约定中必须考虑三方面因素,即:
-
所发布的操作。这些操作是服务发布给其使用者的功能,是接口上的方法。
-
为每个请求和响应封装结构化数据的消息。这些消息是每个方法的参数和返回类型。在 WCF 术语中,它们通常是消息约定,而在更简单的情况中,它们是数据约定。
-
可通过服务进行交换的核心业务实体的数据定义。这些定义构成消息的一部分。在 WCF 术语中,它们就是数据约定。
服务约定使用基于属性的标记来定义,它首先定义提供操作的约定,然后再定义通过网络发布的具体操作。
每个服务约定都以 [ServiceContract] 属性明确标记。此属性可通过下列参数进行声明:
-
Name。控制在 WSDL <portType> 元素中声明的约定名称
-
Namespace。控制在 WSDL <portType> 元素中声明的约定名称
-
SessionMode。指定约定是否需要支持会话的绑定
-
CallbackContract。指定用于客户端回调的约定
-
ProtectionLevel。指定约定是否需要支持 ProtectionLevel 属性的绑定,该属性用于声明加密和数字签名需求
声明操作
在约定定义之后,服务再由许多发布的操作构成。操作通过 [OperationContract] 属性标记被明确地加入约定之中。与 ServiceContract 一样,OperationContract 也有许多用于控制与端点的绑定方式的参数。这些参数包括:
-
Action。控制用于唯一标识此操作的名称。当某个端点接收到消息时,调度程序会使用该控件和动作来确定所要调用的方法。
-
IsOneWay。指示该操作将接受一个“请求”消息,但不会产生任何响应。这不同于简单地返回一个还将生成“结果”消息的 void 返回类型。
-
ProtectionLevel。指定该操作的加密和签名需求。
以下是代码形式服务约定的示例。
[ServiceContract] public interface IExpenseService { [OperationContract] GetExpenseReportsResponse GetExpenseReports(); [OperationContract] GetExpenseReportResponse GetExpenseReport(GetExpenseReportRequest getExpenseReportRequest); }
声明消息和数据实体
在构建消息的模型时,您可能希望采用类的形式,来为每个将发送的消息定义负载或正文。这与在以 ASP.NET 构建 Web 服务时使用 WS Contract First (WSCF) 等工具来构建消息模型的方式十分相似。
在默认情况下,WCF 使用称为 DataContractSerializer 的序列化引擎来实现数据的序列化和反序列化(即与 XML 的来回转换)。我们通过添加对 System.Runtime.Serialization 命名空间的引用来利用 DataContractSerializer,然后以 [DataContract] 属性标记类,以 [DataMember] 标记要发布的成员。
[DataContract] public class GetExpenseReportsResponse { private List<ExpenseReport> reports; [DataMember] public List<ExpenseReport> Reports { get { return reports; } set { reports = value; } } }
消息中所使用的数据实体代表您业务领域中的实体。就像消息约定一样,我们可以通过 DataContractSerializer 和标记属性来明确加入所分配的成员;或者,如果我们只想构建数据模型,可以使用公共字段的方法,并将类标记为可序列化类。
在本示例中,我们使用了数据约定的方法来标记消息传递。在实际的工作中,您常常会需要面对更为复杂的架构,需要在架构中使用属性,以及需要使用 SOAP 标头。WCF 支持定义以 [MessageContract] 属性(可描述整个 SOAP 封装,而不仅仅是正文)标记的类,从而解决了这些尖锐的问题。
有关数据约定和消息约定的详细信息,请参阅本文结尾处更多信息部分所列的相应 MSDN 库文章。
托管工作流运行时
服务通常会支持将创建新服务类型实例并在整个会话生命周期内维护该服务类型实例的并行行为。要在这种情况下使用工作流,必须创建工作流运行时的一个实例,并在服务宿主实例的生命周期内(而不是基于每次调用)对其进行维护。
对此,我们建议使用一个会在创建服务宿主后被激活的扩展类。此扩展类会创建和维护工作流运行时的全局实例,使每个单独的服务实例都能够访问它。
为在 ServiceHost 上实现扩展,应创建一个实现 IExtension<ServiceHostBase> 的类。在此解决方案中,可以在 WcfExtensions 代码项目下的 WfWcfExtension 类中找到这样一个示例。
我们需要实现的两个方法是:Attach(当扩展附加到其父对象上时调用此方法)和 Detach(当卸载父对象时调用此方法)。
如下所示的 Attach 方法会创建一个新的 WorkflowRuntime 实例,并就所需服务将其进行实例化。我们将此存储在名为 workflowRuntime 的本地私有字段中。
void IExtension<ServiceHostBase>.Attach(ServiceHostBase owner) { workflowRuntime = new WorkflowRuntime(workflowServicesConfig); ExternalDataExchangeService exSvc = new ExternalDataExchangeService(); workflowRuntime.AddService(exSvc); workflowRuntime.StartRuntime(); }
如您所见,工作流运行时的初始化还涉及在启动运行时之前将服务实例添加到其中。在构建解决方案时,通常建议您在启动运行时之前添加所有的服务。但如果耦合是一个重要因素,您会发现采用后期绑定的方法更为明智。
在本示例中,作为 ExpenseService 类中 SetUpWorkflowEnvironment 方法的一部分,我们在 WorkflowRuntime 启动后将 ExpenseLocalService 实例添加到 ExternalDataExchangeService 中。
以下所示的 Detach 方法通过调用 StopRuntime 来关闭运行时。
void IExtension<ServiceHostBase>.Detach(ServiceHostBase owner) { workflowRuntime.StopRuntime(); }
由于 WorkflowRuntime 在服务宿主启动过程中创建并初始化,因此在服务调用执行前,任何现有的工作流都可以继续工作。当服务宿主终止时,工作流运行时会随之彻底关闭。
注意 在托管工作流和构建长期工作流模型时,建议您使用工作流持久性服务(如 SqlWorkflowPersistenceService),这是一条基本的准则。这将提供一个实现状态持久性的机制,即使重新启动应用程序或进程也不会受到影响。
创建服务操作
要创建包含服务行为的类,必须实现一个或多个用于定义服务约定的接口。
public class ExpenseService : IExpenseService, IExpenseServiceClient, IExpenseServiceManager
为了与工作流相集成,我们的服务方法将不包含业务逻辑,而是包含一些代码,用以控制事件或将事件交给将封装业务流程的运行中工作流。
对于处理工作流的操作,我们将启动新的工作流,或者与现有的运行中工作流进行交互。
要创建新的工作流实例,需要使用 WorkflowRuntime 来实例化所需工作流类型的一个新实例。我们已在 ServiceHost 扩展类中创建了一个这样的实例。为了获取对该实例的引用,我们必须使用 OperationContext 来找到自定义的扩展。
WfWcfExtension extension = OperationContext.Current.Host.Extensions.Find<WfWcfExtension>(); workflowRuntime = extension.WorkflowRuntime;
OperationContext 是供我们访问服务方法扩展上下文的类。正如您在之前的代码中所看到的,它提供一个名为 Current 的单例,来为我们展示当前服务方法的上下文。我们调用 Host 属性来将实例返回给所属的 ServiceHost,然后根据其类型找到扩展。
在获得扩展实例的引用后,可以通过公共属性返回 WorkflowRuntime,并用它来创建 SequentialWorkflow 的新实例。
Guid workflowInstanceId = submitExpenseReportRequest.Report.ExpenseReportId; Assembly asm = Assembly.Load("ExpenseWorkflows"); Type workflowType = asm.GetType("ExpenseWorkflows.SequentialWorkflow"); WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow(workflowType, null, workflowInstanceId); workflowInstance.Start(); expenseLocalService.RaiseExpenseReportSubmittedEvent( workflowInstanceId, submitExpenseReportRequest.Report);
在上面的代码中,我们基于预定义的类型创建了新的工作流实例。虽然这也可以通过直接的类型实例化来实现,但上述的方式告诉我们,其实可以在运行时基于动态规则(而不是通过强类型化绑定)来灵活地创建工作流。
最后一行代码会引发由工作流中第一个 HandleExternalEventActivity 处理的事件,标志着工作流的开始。我们通过 ExpenseLocalService 类的实例来引发这一事件。在本示例中,ExpenseLocalService 会启动新的工作流或将事件交给现有工作流,从而与工作流进行交互。我们将此类用作封装业务流程的机制。在内部,我们使用 WF 来实现这一机制。
我们将要应对的另一类情况是如何回调到现有工作流中并引发事件。我们必须将事件交给将促使现有工作流接收事件并继续进行处理的工作流引擎。
在“开支报告”流程中引发事件的一个示例就是在需要管理人员进行审批之时发生的。工作流将对 RequestManagerApproval 调用外部方法,用以向管理人员发出警报,告诉他们必须批准或拒绝新的开支报告。
工作流中包含在一个可能事件发生前一直处于阻塞状态的 ListenActivity。在本例中,我们会接收到一个事件,指示管理人员已经审核该报告,或者已经超过 DelayActivity 的时间限制。
Guid workflowInstanceId = submitReviewedExpenseReportRequest.Report.ExpenseReportId; ExpenseReportReviewedEventArgs e = new ExpenseReportReviewedEventArgs(workflowInstanceId, report, review); if (ExpenseReportReviewed != null) { ExpenseReportReviewed(null, e); }
管理人员使用 ManagerApplication 审核报告时,会对宿主进行服务回调,来调用引发 ExpenseReportReviewed 事件的 SubmitReviewedExpenseReport 方法。
引发交给工作流中 HandleExternalEventActivity 的事件时,必须知道所处理工作流实例的 GUID,使事件能够得以路由。
每个事件都以 EventArgs 引发,使我们能够通过事件模型将数据传递回工作流。在本例中,我们可以传递详细的当前报表状态信息和审核活动上下文数据信息。
在工作流中,事件通过 HandleExternalEventActivity 的属性自动绑定到工作流。
首先指定必须以 [ExternalDataExchange] 属性标记的接口类型,然后指定该接口上 HandleExternalEventActivity 将订阅的事件。
事件参数必须从 ExternalDataEventArgs 类派生而来。这至少意味着每个事件都会包含上下文(例如工作流的 InstanceId)。然后,工作流运行时负责将事件路由给正确的工作流实例,以使工作流继续进行。如果使用持久性服务,运行时还会在整个执行期间管理工作流所有运行状态的水化和再水化。
托管服务
要托管 WCF 服务,必须在 ServiceHost 容器内运行。
为了了解如何使用 WCF 实现托管,首先让我们来了解一下几种可用的备选方案:
-
对于标准的 Windows 进程,可以手动创建并打开 ServiceHost 实例。
-
在通过 Microsoft Internet Information Services (IIS) 6.0 托管 Web 端点(Web 服务)时,使用 System.ServiceModel 命名空间下提供的自定义 HttpHandler。
-
在 IIS 7 下进行托管时,可以使用 Windows Activation Service (WAS) 来托管端点。
构建 Web 服务时,您通常会选择使用 Internet Information Services 进行托管。而在构建将用作后台程序的单个实例端口时,您通常会选择通过 Windows 服务进行托管。
在本示例中,我们在一个 Windows 控制台应用程序中托管主要的服务实例,托管方式与 Windows 服务的托管方式类似。
为了部署服务,我们必须创建 ServiceHost 类的一个实例,并针对要发布的每个服务类型开放其端点。ServiceHost 将许多参数作为其构造函数的一部分;但主要参数是 Type 参数或实现 ServiceContract 的类的实例。
-
如果要采用 PerCall 或 PerSession 实例化,请使用 Type。
-
如果采用 Single 实例化,请使用单个实例。
宿主建立起来后,会分析所有可用的配置(在下面的配置部署部分做详细介绍)并将其与任何明确添加的配置合并,来确定可用的端点并针对发布开放那些端点。宿主接收到从客户端发来的调用后,会在新的后台工作线程上处理请求,并按消息中 SOAP 约定名称和动作的指示,将其路由给相应的服务操作。
using (ServiceHost serviceHost = new ServiceHost(new ExpenseService())) { WfWcfExtension wfWcfExtension = new WfWcfExtension("WorkflowRuntimeConfig"); serviceHost.Extensions.Add(wfWcfExtension); serviceHost.Open(); // 在此处阻止该进程,例如 Console.ReadLine(); serviceHost.Close(); }
配置 ServiceHost 时,必须首先为连接开放端点。为此,您可以在调用 .Open() 之前与宿主对象进行交互,如前面代码所示。建议您利用作用域在使用前对 ServiceHost 进行处置,并建议您在该作用域结束时明确地调用 Close() 来彻底关闭所有活动的连接和端点。
配置部署
WCF 提供了一种机制,允许通过 XML 配置来配置端点,从而将部署问题从实现工作中分离出来。这使得管理员无需重新部署代码即可修改服务策略。
每个服务在一个或多个端点上发布。一个端点就是一个可寻址的连接点,客户端可对其使用服务。在 WCF 中,每个端点都以三个属性声明,这三个属性就是人们所熟知的 WCF 的 ABC。
它们是“地址”(A)、“绑定”(B) 和“约定”(C)。
地址:此端点的唯一可寻址位置。通常情况下,地址是为您提供绝对地址的 URI,服务在该地址处侦听请求,例如:http://myhost/myservice 或 net.tcp://myhost:400/myservice
绑定:用于规定服务与其使用者之间通信协议的策略。绑定指定了几个方面的要素,例如所使用的传输类型、消息的编码方式以及数据的序列化方式。WCF 附带了许多用于支持最常见情况的现成绑定。
约定:通过接口以代码形式定义的、要发布的操作和数据。
要配置服务,我们必须对声明服务的配置进行声明,并为服务配置任意数量的端点。由于服务可能正在实现任意多个约定,因此这还将影响到需要发布的端点数。
以下是一个配置示例。
<services> <service name="ExpenseServices.ExpenseService"> <endpoint address="http://localhost:8081/ExpenseService/Manager" binding="wsHttpBinding" contract="ExpenseContracts.IExpenseServiceManager" /> <endpoint address="http://localhost:8081/ExpenseService/Client" binding="wsDualHttpBinding" contract="ExpenseContracts.IExpenseServiceClient" /> </service> </services>
在此配置示例中,我们为 ExpenseServices.ExpenseService 类型的服务声明配置。这样,运行时就可以在我们根据此类型实例化新的 ServiceHost 时找到该配置。
使用服务
通过 ChannelFactory 类,可以利用 WCF 来使用服务。ChannelFactory 通过工厂模式为我们提供连接到配置中指定端点的服务约定代理实例。我们可以使用运行时信息(例如用于消息加密的安全凭据和证书)配置工厂,或者动态地确定端点信息。
private IExpenseServiceManager CreateChannelExpenseServiceManager() { ChannelFactory<IExpenseServiceManager> factory = new ChannelFactory<IExpenseServiceManager>("ExpenseServiceManager"); IExpenseServiceManager proxy = factory.CreateChannel(); return proxy; }
如您所见,我们首先创建了一个工厂实例,该实例对服务约定使用泛型参数,以构造出将仅返回所需约定实例的更精确的工厂。我们还指定了用于确定端点所用配置的参数。在本例中,我们使用名为 ExpenseServiceManager 的端点配置,它就是我们应用程序配置文件中的配置。
<system.serviceModel> <client> <endpoint name="ExpenseServiceManager" address="http://localhost:8081/ExpenseService/Manager" binding="wsHttpBinding" contract="ExpenseContracts.IExpenseServiceManager" /> </client> </system.serviceModel>
您会发现端点定义与在宿主配置中声明的定义完全相符。通常,只有在因网络配置而造成客户端与服务器的地址不同时,或者在实现自定义行为时,才会出现配置的差异。
如果安装了 Windows SDK,您会发现一个用于自动创建代理类和端点配置的工具,您可以将其集成到您的解决方案中。目标服务必须通过 WSDL 或 WS-MetadataExchange 发布其元数据的说明,才能利用此工具。
配置双工信道
到现在为止,我们一直在假设我们的通信流采用的是请求/响应协作模式,消息由使用者发出并由服务做出应答。WCF 支持许多备选的消息流,例如单向(“即发即弃”模式)或双向双工通信。如果处理每一方都可以启动会话的消息流,则需要使用双工或双向信道。对于那些连接更为紧密且支持沿任一方向发送数据的系统而言,双工信道非常有效。例如,如果要提供始于事件处理过程的回调,双工信道将非常有用。
实现客户端回调
在 WCF 中,客户端回调通过一个称为 CallbackContracts 的概念来实现。对于所发布的约定,我们可以指定另一个约定来定义客户端将发布的、可由服务上运行的代码回调的操作。
要声明 CallbackContract,应在发出回调的服务约定中指定接口类型。
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))]
您还需要使用支持双工信道的绑定,如 netTcpBinding 或 wsDualHttpBinding。基于 TCP 的双工通信通过在整个消息交换过程中所建立和维护的双向连接来实现。而基于 HTTP 的双工通信则通过对客户端侦听程序的回调来实现。由于客户端可能不知道其返回路径,或者您可能希望通过配置对其进行强定义,因此我们可以使用自定义绑定配置来声明一个替代的 clientBaseAddress。
<endpoint binding="wsDualHttpBinding" bindingConfiguration="AlternativeClientCallback"/> <bindings> <wsDualHttpBinding> <binding name="AlternativeClientCallback" clientBaseAddress="http://localhost:8082/ExpenseService/ClientCallback"/> </wsDualHttpBinding> </bindings>
在客户端实现回调
实现回调约定的方式与实现服务约定的方式是完全相同的。我们必须提供所定义接口的实现。
class CallbackHandler : IExpenseServiceClientCallback { public void ExpenseReportReviewed( ExpenseReportReviewedRequest expenseReportReviewedRequest) { // 在此实现客户端逻辑以对回调作出响应。 } }
为了使宿主回调到 CallbackHandler 类的实例,建立的客户端信道必须能够知晓连接的双工特性。
如之前所述,首先使用支持双工信道的绑定。其次,在初始化与服务端点的连接时,使用名为 DuplexChannelFactory 的 ChannelFactory 子类版本,它将创建与服务的双工连接。
private IExpenseServiceClient CreateChannelExpenseServiceClient() { InstanceContext context = new InstanceContext(new CallbackHandler()); DuplexChannelFactory<IExpenseServiceClient> factory = new DuplexChannelFactory<IExpenseServiceClient>(context, "ExpenseServiceClient"); IExpenseServiceClient proxy = factory.CreateChannel(); return proxy; }
使用 DuplexChannelFactory 的主要不同之处在于,要先初始化 CallbackHandler 类的实例,并将其传递给工厂构造函数,以便初始化回调所使用的上下文。
实现宿主的回调
从宿主的角度来看,我们可以通过在 IExpenseServiceClient 约定中定义的回调信道来获取对面向客户端的回调的一个引用。
[ServiceContract(CallbackContract = typeof(IExpenseServiceClientCallback))] public interface IExpenseServiceClient : IExpenseService
CallbackContract 属性声明用于定义宿主回调约定的接口。
为了进行回调,我们通过调用 OperationContext.Current.GetCallbackChannel 来获取回调约定的引用,如下所示。
IExpenseServiceClientCallback callback = OperationContext.Current.GetCallbackChannel <IExpenseServiceClientCallback>(); callback.ExpenseReportReviewed(new ExpenseReportReviewedRequest(e.Report));
在获得对回调信道的引用后,我们即可正常地进行调用。
结束语
Windows Workflow Foundation 提供了一个用于定义工作流的通用框架,以及一个用于托管运行中工作流并与之进行交互的、稳健的运行时引擎。
Windows Communication Foundation 提供了用于构建互联系统的通用框架,并为开发人员提供了一致的 API 和丰富的功能集,来定义通信方式。
您可以将这两种框架结合起来,为在所处环境中构建和部署分布式业务流程提供灵活而全面的应用程序平台。WF 允许您为业务逻辑和流程建模并进行封装,而 WCF 又为您提供了具有多种系统分布方式选择的消息传递基础结构。
以下是您在设计服务时需要牢记的几条原则:
-
应通过使用持久性服务来支持长时间运行的工作流。
-
服务操作可以通过运行工作流、通过引发事件进行交互。应将工作流设计为在需要介入时引发事件,在与外部(例如,外部服务或人员)进行交互时响应事件。
-
工作流将异步执行服务调用,因此考虑自服务返回的数据及当时数据所处的状态,可有针对性地进行设计。如果要使用同步方法,可以利用 ManualWorkflowSchedulerService 类对工作流的执行进行手动调度。