从 WCF 触发工作流事件






4.87/5 (18投票s)
2007 年 4 月 3 日
15分钟阅读

86842

659
本文描述了一种使用 WCF 范例来触发工作流事件活动的松耦合连接方式。
目录
特点
- Microsoft Windows Communication Foundation (WCF) 和 Windows Workflow Foundation (WF) 技术
- 无需自定义活动即可接收事件消息
- 基于 WCF 和
Workflow
技术之间的接口契约映射 - 事务支持
- 声明式编程
- 无需构建本地服务
- 关联事件的 Null
Workflow
本地服务 - Null WCF 服务
- 操作调用者中进行预/后处理的能力
- 与
Workflow
本地服务 (ExternalDataExchangeService
) 并行工作
引言
Microsoft WF 技术使得通过紧耦合的本地服务层接收外部 (主机) 事件成为可能。该层基于委托/事件模式简化了 workflow
与主机应用程序之间的连接。所有这些神奇的连接都隐藏在 ExternalDataExchangeService
类中,该类拦截本地服务并生成事件消息到特定的 Workflow
队列。
为了让事件驱动的 Activity 接收事件消息,必须使用应用程序特定的事件处理程序声明 ExternalDataExchange
接口契约。
事件委托具有标准的 .NET 签名,如下一行所示
public void EventHandler(object sender, ExternalDataEventArgs eventArgs);
其中 eventArgs
代表 Workflow
事件兴趣,例如 InstanceId、Identity、应用程序特定数据等。
接口契约元数据用于根据接口类型、事件名称和属性集合创建唯一的 workflow
队列,以作为关联模式的可选附加项。请注意,workflow
队列消息传递是主机应用程序与 workflow
通信的唯一方式。
从连接的角度来看,Workflow
代表一个事件接收器,而 WCF 客户端是事件源 (订阅者兴趣)。下图显示了这种逻辑模式
在事件源端,我们可以创建多种模式 (如 Remoting、WSE、WCF 等) 来生成事件消息并将其发送到本地或远程 workflow
队列。
本文描述了如何使用 WCF 范例来实现这种连接,该范例基于在两端分别定义的接口契约。您将看到事件源如何与事件接收器元数据在逻辑上 (声明式地) 连接。
下图显示了 WCF 事件源与 WF 事件接收器之间松耦合连接的表示
注意,Workflow
端无需本地服务层即可生成事件消息。对于此 WCF/WF 连接,我们只需要接口契约的元数据,当然还需要一个通用的操作装饰器,以使所有神奇的工作得以实现。
可以使用 WorkflowFireEventAttribute
将服务操作契约显式映射到特定的 WF 接口契约。在此属性背后是生成事件消息并将其发送到 workflow
队列的连接代码。
使用 WCF 连接将事件消息传递到 Workflow
事件接收器 (如 HandleExternalEventActivity
) 可以构建一个具有 WCF 范例能力的应用程序模型,该模型基于地址、绑定和契约。例如:使用 netMsmgBinding
绑定以异步断开连接的方式传递事件消息,或者使用 webHttpBinding
绑定以启用从由 AJAX/Json (Orcas CTP 版本) 驱动的浏览器传递事件消息的连接。
让我们详细了解一下 WCF/WF 连接如何用于将事件消息分派到 workflow
队列。我将假设您熟悉这些技术。此外,我前面提到过,Workflow
端不使用任何自定义活动进行此连接,因此它是一个标准的 (顺序或状态机) 工作流定义,使用了 HandleExternalEventActivity
。因此,我将专注于连接概念和实现。
概念与设计实现
松耦合 WCF/WF 连接用于发送 workflow
事件消息的概念,基于服务操作契约上的拦截器 (WorkflowFireEventAttribute
),它将映射到特定的 ExternalDataExchange
接口契约 (参见上图)。
WCF 服务与 WorkflowRuntime
托管在同一个应用程序域中。基本上,我们需要创建一个派生自我们的 WCF 接口契约的 Null
服务。以下是一个简单的 Null
服务和接口契约的示例
// WCF
[ServiceContract]
public interface IFireEventTest
{
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
public class ServiceTest1 : IFireEventTest
{
[WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
public void Approve(object sender, ExternalDataEventArgs e) { }
}
// Workflow EventType
[ExternalDataExchange]
public interface IWorkflowSimpleEvents
{
event EventHandler<ExternalDataEventArgs> Approve;
}
在启动应用程序域期间,WorkflowFireEventAttribute
将 WorkflowFireEventOperationInvoker
插入到 OperationInvoker
中,以便在调用服务方法之前进行拦截。以下代码片段显示了一个方法,它将分派 workflow
事件消息
object IOperationInvoker.Invoke(object instance, object[] inputs,
out object[] outputs)
{
object retVal = null;
outputs = new object[0];
// validation for mandatory inputs
if (inputs.Length < 2 ||
!typeof(ExternalDataEventArgs).IsInstanceOfType(inputs[1]))
throw new InvalidOperationException(string.Format(" ... ");
// mandatory inputs
object sender = inputs[0];
ExternalDataEventArgs eventArgs = inputs[1] as ExternalDataEventArgs;
if (Transaction.Current != null &&
Transaction.Current.TransactionInformation.Status ==
TransactionStatus.Active)
{
WorkflowInstance workflowInstance = null;
eventArgs.WaitForIdle = false;
#region action - transactional delivery to the workflow queue
using(TransactionScope ts =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
// pre-processing
if (this.Option == WorkflowFireEventOption.After)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs,
out outputs);
}
// fire event
workflowInstance =
WorkflowHelper.DispatchEvent(this.EventType,this.EventName,
sender,eventArgs,true);
// storing a workflow into the database
workflowInstance.Unload();
// post-process
if (this.Option == WorkflowFireEventOption.Before)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs,
out outputs);
}
// done
ts.Complete();
}
try
{
// workaround to detect WorkflowStatus
workflowInstance.Start();
}
catch (Exception ex)
{
// WorkflowStatus != Created (therefore we can go ahead and use it)
Trace.WriteLine(ex.Message);
}
#endregion
}
else
{
#region action - non-transactional delivery to the workflow queue
// pre-processing
if (this.Option == WorkflowFireEventOption.After)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
// dispatching event
WorkflowHelper.DispatchEvent(this.EventType, this.EventName,
sender, eventArgs);
// post-processing
if (this.Option == WorkflowFireEventOption.Before)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
#endregion
}
return retVal;
}
基本上,该方法根据选项和事务上下文分为三部分:预处理、分派和后处理调用。该选项可用于应用程序特定的逻辑,例如;创建 workflow
,填充 eventArg
属性等。默认选项是 None
。调用中的其他扩展使用强制参数 (如 inputs[0]
和 inputs[1]
) 或输出之后的其他方法参数。这些扩展用于应用程序特定功能。
在事务上下文中触发事件
以事务方式将事件消息传递到 workflow
实例与其他由事务上下文驱动的资源略有不同。目前,我们没有用于工作流实例的事务性内存资源,因此 ACID 事务必须通过 SqlWorkflowPersistenceService
间接完成。该概念基于将事件消息同步发送到 workflow
实例 (WaitForIdle = false
),并在事务范围中进行 workflow
的反序列化,然后在事务提交后,workflow
被返回内存 (重新序列化)。为此,我们必须创建一个新的事务上下文以避免死锁。注意,预/后处理在新事务上下文中运行,它们的逻辑可以参与 ACID 事务。
我们继续进行 DispatchEvent
调用。此方法负责根据 eventArgs.InstanceId
值创建和启动 workflow
实例,并调用辅助方法 SendEventMessage
来生成 workflow
事件消息并将其发送到实例队列。以下是 DispatchEvent
方法的代码片段
public static WorkflowInstance DispatchEvent(Type interfaceType,
string eventName, object sender, ExternalDataEventArgs eventArgs)
{
// input arguments validation
if (interfaceType == null)
throw new ArgumentNullException("interfaceType");
if (string.IsNullOrEmpty(eventName))
throw new ArgumentException("eventName");
if (eventArgs == null)
throw new ArgumentNullException("eventArgs");
// Find the custom WCF Extension on the Service Host by it's type
WFServiceHostExtension extension =
OperationContext.Current.Host.Extensions.Find<WFServiceHostExtension>();
// Get a reference to the WorkflowRuntime
WorkflowRuntime workflowRuntime = extension.WorkflowRuntime;
// get instance of the workflow
WorkflowInstance instance = workflowRuntime.GetWorkflow(eventArgs.InstanceId);
if (!bTransactional)
{
try
{
// workaround to detect WorkflowStatus
instance.Start();
}
catch (Exception ex)
{
// WorkflowStatus != Created (therefore we can go ahead and use it)
Trace.WriteLine(ex.Message);
}
}
//WorkflowMessageEventHandler2 handler2 =
// new WorkflowMessageEventHandler2(interfaceType, eventName, instance);
//handler2.EventHandler(sender, eventArgs);
SendEventMessage(interfaceType, eventName, sender, eventArgs, instance);
return instance;
}
注意,如果 System.Workflow.Activities 命名空间中有公共的 WorkflowMessageEventHandler
类,那将非常方便,这将大大简化此实现。我为此创建了一个——请参见 helpers.cs 文件,但它在事务范围内不起作用 (我将解决此问题并更新它)。目前,我们可以使用反射调用内部类上的公共方法,如下面的代码片段所示
private static void SendEventMessage(Type interfaceType, string eventName,
object sender, ExternalDataEventArgs eventArgs, WorkflowInstance instance)
{
// get the type class
Type type =
Type.GetType("System.Workflow.Activities.WorkflowMessageEventHandler, ...");
// create instance
object[] args1 =
new object[3] {eventType, eventType.GetEvent(eventName), instance.WorkflowRuntime};
object handler = Activator.CreateInstance(type, bindingAttr1, null, args1, cultureInfo);
// invoke method
object args2 = new object[2] { sender, eventArgs };
type.InvokeMember("EventHandler", bindingAttr2, null, handler, args2);
}
WorkflowFireEventAttribute
这是一个连接类,用于在操作契约中插入自定义调用者,以以松耦合的方式将事件消息从 WCF 分派到 WF 范例。
WorkflowFireEventAttribute
类可以通过以下属性进行初始化
EventType
- 带有ExternalDataExchange
属性的Workflow
接口的类型 (必需属性)EventName
-Workflow
事件的名称 (默认事件名称是操作契约的名称)WorkflowFireEventOption
- 操作调用者在预处理或后处理服务方法时的选项- None - 不调用服务方法 (默认选项)
- Before - 在调用服务方法之前触发
workflow
事件 - After - 在调用服务方法之后触发
workflow
事件
以上属性的使用方法如下面的示例所示,其中 IFireEventTest
接口被映射到两个具有相同事件名称的 Workflow
接口
[ServiceBehavior]
public class ServiceTest1 : IFireEventTest
{
[WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
public void Approve(object sender, ExternalDataEventArgs e) { }
[WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e) { }
[WorkflowFireEvent(EventName = "Reject", EventType =
typeof(IMyWorkflowEvents))]
public void MyReject(object sender, MyEventArgs e) { }
}
或者,如下面的示例所示,我们可以如何在操作方法中处理事件消息的分派或预处理触发 workflow
事件
[ServiceBehavior]
public class ServiceTest2 : IFireEventTest
{
public void Approve(object sender, ExternalDataEventArgs e,
string workflowType)
{
// for test purpose
MyObject mo = e.WorkItem as MyObject;
Console.WriteLine("Operation Approve - {0}", mo.Name);
// deliver message to the workflow instance
WorkflowHelper.DispatchEvent(typeof(IWorkflowSimpleEvents),
"Approve", sender, e);
}
[WorkflowFireEvent(Option = WorkflowFireEventOption.After,
EventName = "Reject", EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e)
{
// for test purpose
Console.WriteLine("Operation Reject - {0}", e.TaskName);
}
}
如我之前提到的,WCF 连接允许在事件源 (客户端) 和服务之间使用任何绑定。以下代码片段显示了一个通过 config 文件配置服务终结点的示例
<service name="ServiceHost.ServiceTest1">
<endpoint
address="net.tcp://:11111/Workflow"
binding="netTcpBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
<service name="ServiceHost.ServiceTest3">
<endpoint
address="net.msmq:///private/rk.workflow_test"
binding="netMsmqBinding"
bindingConfiguration="PoisonBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
关联事件
在使用由相同接口契约驱动的多个 workflow
事件接收器时,必须使用特定的参数值将事件关联起来,以便在同一个 workflow
实例中创建唯一的 workflow
队列名称。本文描述的解决方案支持此功能,并且对 workflow
事件接收器 HandleExternalEventActivity
(HEEA) 是完全透明的。此功能也在 WorkflowMessageEventHandler2
类中实现。
以下代码片段显示了一个基于参数 taskName
关联的简单事件 Reject
[ExternalDataExchange]
[CorrelationParameter("taskName")]
public interface IWorkflowEvents
{
[CorrelationInitializer]
void Initializer(string taskName);
[CorrelationAlias("taskName", "TaskName")]
event EventHandler<MyEventArgs> Reject;
}
关联事件需要本地服务层来执行一个带有 CorrelationInitializer
属性的方法。此方法必须在 HEEA 之前调用,在特定的逻辑关联标记范围内,以将关联值告知本地服务。
public class MyLocalService : IWorkflowEvents
{
public void Initializer(string taskName)
{
Console.WriteLine("Initializer - {0}", taskName);
}
public event EventHandler<MyEventArgs> Reject;
}
注意,在我们松耦合的连接中,关联值可以由事件源发送,也可以在 WCF 服务的操作方法中进行预处理。
附加 WorkflowRuntime
可以通过其扩展将 WorkflowRuntime
附加到 WCF 服务主机。对于这种情况,我创建了一个 WSServiceHostExtension
类,它有两个构造函数。第一个将存储现有的 WorkflowRuntime
引用 (例如:由 Orcas WorkflowServices
创建),第二个将从 config 文件创建 WorkflowRuntime
。
以下代码片段显示了一个基于 config 文件创建 WorkflowRuntime
并将其附加到 WCF 主机的示例
using (System.ServiceModel.ServiceHost host =
new System.ServiceModel.ServiceHost(typeof(ServiceTest3)))
{
// Service extension for WF
WFServiceHostExtension extension =
new WFServiceHostExtension("WorkflowRuntimeConfig");
// Add the Extension to the ServiceHost collection
host.Extensions.Add(extension);
host.Open();
Console.WriteLine("Press any key to stop server...");
Console.ReadLine();
host.Close();
}
workflow
运行时配置示例
注意,SqlWorkflowPersistenceService
必须包含在 config 文件中。
测试
FireWorkflowEvent
解决方案分为两个文件夹:WorkflowEventService
- 托管实现和 WorkflowFireEventAttribute
。此程序集必须包含在托管 WCF 和 WF 的应用程序域中。另一个文件夹仅用于测试目的,以演示 WorkflowFireEventAttribute
的用法。该文件夹包含几个项目,例如:用于生成事件的 ConsoleApplications
,用于 WCF 和 WF 的 InterfaceContracts
,用于在控制台上简单记录 WCF 消息的 Logger
,用于托管 WCF 服务的 ServiceHost
项目,以及 WorkflowRuntime
和最后一个项目——3 个工作流的 WorkflowLibrary
(状态机、顺序和 xoml 激活)。
让我们开始测试。我假设您的环境中已安装 netfx 3.0 和 SqlExpress,并包含了 workflow
相关内容。
测试 1. 可断开事务性事件
此测试演示了通过事务性 MSMQ 在事件源和事件接收器 (WCF - Workflow
) 之间的连接。以事务性 (可断开) 方式端到端传递事件消息,可以构建健壮的事件驱动架构。
以下是集成 WCF 和 WF 模型的主要步骤
步骤 1. 工作流
在此步骤中,我们需要为 ExternalDataExchange
创建一个接口契约,其中包含 EventHandler
方法。在我们的示例测试中,有一个简单的事件 Approve
,以及一个 workflow
ExternalDataEventArgs
。一旦我们有了这个契约,就可以分配事件接收器,如 HandleExternalEventActivity
(HEEA)。请注意,无需创建本地服务实现。
下图显示了这些步骤——接口契约、HEEA 属性和测试 workflow
这就是 Workflow
的全部内容。接下来的步骤仅涉及 WCF。
步骤 2. WCF 服务
首先,我们需要创建一个 Service
(Interface) 契约来消费 Workflow
事件。以下代码片段显示了一个简单的契约,用于消费两个事件——Reject
和 Approve
。在此测试中,只有 Approve
事件会被触发。
[ServiceContract]
public interface IFireEventTest
{
[OperationContract(IsOneWay = true)]
void Reject(object sender, MyEventArgs e);
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
现在,我们可以创建一个服务并装饰一个特定的事件以集成 Workflow
。在此测试中,我们要求操作调用者进行事件后处理 (事件在 Before
触发,请参阅选项),并与 IWorkflowSimpleEvents
契约进行连接。该操作设置为事务性处理,自动提交。
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ServiceTest3 : IFireEventTest
{
[WorkflowFireEvent(Option = WorkflowFireEventOption.Before,
EventType = typeof(IWorkflowSimpleEvents))]
[OperationBehavior(TransactionAutoComplete =
true, TransactionScopeRequired = true)]
public void Approve(object sender, ExternalDataEventArgs e)
{
/*
if(MessageBox.Show("Abort ...", "Approve", MessageBoxButtons.YesNo)==
DialogResult.Yes)
{
throw new Exception("Transaction has been aborted manually");
}
*/
}
[WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e) { }
}
在应用程序域中托管此服务之前,我们必须创建一个服务配置节。以下代码片段显示了 ServiceHost.exe.config 文件的 endpoint
和 netMsmqBinding
部分。
<services>
<service name="ServiceHost.ServiceTest3">
<endpoint
address="net.msmq:///private/rk.workflow_test"
binding="netMsmqBinding"
bindingConfiguration="PoisonBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
</services>
<bindings>
<netMsmqBinding>
<binding name="PoisonBinding"
receiveErrorHandling="Drop"
receiveRetryCount="0"
exactlyOnce="true"
useSourceJournal="true"
useActiveDirectory="false">
<security mode="None" />
</binding>
</netMsmqBinding>
</bindings>
现在,我们可以在应用程序域中托管上述服务——参见 ServiceHost
项目,program.cs 文件。之后,我们的 Workflow
就通过 WCF 服务暴露出来,以接收 Approve
事件的消息。
步骤 3. WCF 客户端
此步骤展示了一个客户端如何以事务方式生成 Approve
事件的示例。为了测试目的,workflow2.xoml 已预先创建并持久化,然后才触发事件。其余工作很简单,例如使用 FireWorkflowEvent
配置创建通道,以及创建带有应用程序特定事件兴趣的事件参数并发送 Approve
消息。请注意,消息将在事务范围提交时发送到 MSMQ 队列。
using (TransactionScope ts =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
// unload workflow (for test purpose)
using(XmlReader xmlReader=
XmlReader.Create(@"..\..\..\WorkflowLibrary\Workflow2.xoml"))
{
wi = wr.CreateWorkflow(xmlReader);
}
wi.Unload();
using (ChannelFactory<IFireEventTest> factory =
new ChannelFactory<IFireEventTest>("FireWorkflowEvent"))
{
IFireEventTest channel = factory.CreateChannel();
// eventArgs
ExternalDataEventArgs eventArgs =
new ExternalDataEventArgs(wi.InstanceId);
eventArgs.WaitForIdle = false;
eventArgs.WorkItem = MyObject.Create(sender, ii);
// fire workflow event
channel.Approve(sender, eventArgs);
// close this channel
factory.Close();
}
ts.Complete();
}
现在,我们准备进行实际测试。
步骤 4. 运行测试
启动 ConsoleApplication2
程序。以下屏幕截图显示了创建和卸载 Workflow2
的过程。使用包含的 Logger
,控制台屏幕将显示发送到 WCF 服务 (并转发到 Workflow
) 的事件消息信封。
在启动 ServiceHost
程序之前,可以在 private rk.workflow_test 队列中检查消息。我们没有设置超时传递的绑定,也没有设置服务接收消息的机制,因此我们可以在任何时间启动服务。
下图显示了 ServiceHost
程序的控制台屏幕截图。我们可以看到此主机中有多少 endpoints
,接收了事件消息,以及该消息如何通过 Workflow2
。根据工作流定义,在收到此消息后,workflow
将完成。
通过按 ConsoleApplication2
屏幕上的任意键,可以重复测试另一个 workflow
实例。
还有一件事。在步骤 2 中,我们注释掉了触发事件的后处理。让我们取消注释并再次运行服务主机。每次 Approve
事件调用都将被以下对话框中断,位于事务范围之内
根据您的选择,我们可以模拟 Abort
场景。注意,消息已从 MSMQ 队列中删除,但如果您使用的是 Vista,则可以重新配置 netMsmqBinding
以使用垃圾邮件队列。
本次测试到此结束,让我们看看 WorkflowFireEventAttribute
的其他示例。
测试 2. 触发关联事件
此测试演示了一个关联的 Reject
事件,其中关联参数是 taskName
。每个 workflow
关联作用域都有责任在等待 Reject
事件之前初始化此参数。为此,workflow
调用 CallExternalMethodActivity
,并将关联参数的 initialize
值传递给主机应用程序,因此必须创建本地服务并在 WorkflowRuntime
中注册。
要测试此场景,必须在 ConsoleApplication1
程序之前启动 ServiceHost
程序。请注意,此测试在事件消息中使用派生自 ExternalDataEventArgs
的应用程序特定 MyEventArgs
,因此我们必须将其添加到两个 config 文件 (service 和 client) 的 <system.runtime.serialization>
部分。
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="System.Workflow.Activities.ExternalDataEventArgs,
System.Workflow.Activities,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
</add>
<add type="InterfaceContract.MyEventArgs, InterfaceContract">
<knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
让我们再做一个 StateMachine
Workflow
的测试。
测试 3. 触发简单状态机事件
下图显示了一个简单的状态机示例,其中一个状态在 Timeout
限制内等待 Approve
事件。传入的 Approve
事件将重新启动一个状态并重新触发超时限制。
接口契约非常简单,只需要一个 Null
(无主体) WCF 服务。
要测试此场景,必须在 ConsoleApplication
程序之前启动 ServiceHost
程序。
注意,测试 ServiceHost
程序配置为运行所有测试客户端控制台程序,因此它可以无限循环测试。
附录
通用 WCF 服务
Generic
服务旨在托管一个通用的服务,用于具有典型事件签名 (如 sender
和 ExternalDataEventArgs
参数) 的简单事件。应用程序特定的对象可以通过 ExternalDataEventArgs
对象的 WorkItem
属性传递给 Workflow
。
以下代码片段显示了用于通过 WCF 公开 Workflow
的通用服务的完整实现
[ServiceContract]
public interface IFireWorkflowEvent
{
[OperationContract(Action = "*", IsOneWay = true)]
void FireWorkflowEvent(Message message);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WorkflowEventService : IFireWorkflowEvent
{
public void FireWorkflowEvent(Message message)
{
WorkflowHelper.FireEvent(message);
}
}
如您所见,没有操作拦截器和 WorkflowFireEventAttribute
用于映射接口契约。此解决方案的概念和限制基于将 soap 消息隐式映射到由事件名称和接口类型定义的 Workflow
接口契约。操作契约的名称可以轻松地映射到 workflow
事件名称。第二个映射要求,例如事件类型,并不直接。我决定 (目前) 使用 WCF 接口契约的命名空间。
好的,现在我们可以看看 WorkflowHelper.FireEvent
如何将传入的消息映射到 workflow
public static void FireEvent(Message message)
{
System.Xml.XmlDictionaryReader reader = message.GetReaderAtBodyContents();
// event contract
Type interfaceType = Type.GetType(reader.NamespaceURI);
string eventName = reader.Name;
// body of the event message
reader.ReadStartElement();
string sender = reader.ReadElementContentAsString("sender",
reader.NamespaceURI);
DataContractSerializer xs =
new DataContractSerializer(typeof(ExternalDataEventArgs),
"e", reader.NamespaceURI);
ExternalDataEventArgs eventArgs =
(ExternalDataEventArgs)xs.ReadObject(reader);
while (reader.NodeType != XmlNodeType.EndElement) { reader.Read(); }
reader.ReadEndElement();
// deliver message to the workflow queue
DispatchEvent(interfaceType, eventName, sender, eventArgs);
}
FireEvent
方法将消息体反序列化,以获取前两个事件处理程序参数,如 sender
和 e
。如上所述,事件契约是从方法名称和命名空间获得的。有了这些参数,就会调用 DispatchEvent
将事件消息分派到 Workflow
队列,方式与之前描述的相同。以下代码片段显示了一个用于此通用 WCF 服务的 WCF 接口契约示例
[ServiceContract(Namespace="InterfaceContract.IWorkflowSimpleEvents,
InterfaceContract")]
public interface IFireEventTest2
{
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
它是如何工作的?
好了,在 WorkflowEventService
托管到带有 WorkflowRuntime
的应用程序域后,我们就可以通过终结点接收事件消息了。以下配置片段显示了这种通用配置。当然,我们可以创建多种绑定方式
<service name="RKiss.WorkflowEventService.WorkflowEventService">
<endpoint
address="net.tcp://:11110/Workflow"
binding="netTcpBinding"
contract="RKiss.WorkflowEventService.IFireWorkflowEvent"/>
</service>
因此,客户端 (例如 ConsoleApplication
) 将根据 WCF 接口契约 (例如 IFireEventTest2
) 将事件消息发送到上述终结点,然后转发到通用服务 (Action = ""*"
) 操作 FireWorkflowEvent
。一旦我们收到了方法中的消息,我们就将其作为事件消息处理,否则将抛出异常。
状态机中的关联事件
当同一状态中的多个事件驱动活动 (HEEA) 在等待同一事件时,关联功能可以提供帮助。下图显示了如何在 StateInitializationActivity
中初始化关联参数。我没有包含此测试,因此请尝试修改 Workflow1
以适应这种情况,并使用 ConsoleApplication1
程序进行测试。
结论
本文描述了用于触发 workflow
事件的 WCF/WF 集成。该概念基于 WCF 和 WF 接口契约之间的松耦合连接。这是一种直接且透明的集成,无需修改 workflow
并为非关联事件构建自定义本地服务。