65.9K
CodeProject 正在变化。 阅读更多。
Home

运行时生成公开 .NET 或 COM 类型的 WCF 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (37投票s)

2008年4月24日

CPOL

8分钟阅读

viewsIcon

81799

downloadIcon

1135

运行时在 .NET 或 COM 类型周围生成一个 WCF 服务包装器以公开其接口。

引言

本文介绍了如何自动使用 Windows Communication Foundation (WCF) 服务公开 .NET 类型或 COM 类型的公共方法和属性。用户只需执行以下操作:为服务创建一个宿主进程,为其添加对下面描述的标准程序集的引用,将 <system.serviceModel> 部分(定义用户选择的服务参数)添加到 .config 文件中,然后仅用一行代码实际启动服务。该行代码指定您希望公开的类型以及包含该类型的程序集。

每次启动宿主进程时都会重新生成一个 WCF 服务包装器。如果公开类型的公共接口发生更改,则修改后的接口将自动公开给客户端,而无需修改代码。当宿主进程运行时,可以通过(例如,使用 MS Visual Studio)自动生成服务引用,从而使客户端能够访问公开的所有公共方法和属性。

背景

自动“即时”服务生成技术相当直接。WcfServiceHost 组件加载包含要公开的类型的程序集,并对其进行反射以获取类型的公共方法和属性。基于反射, [ServiceContract] 接口和服务类将作为 C# 代码字符串在内存中生成。服务类的每个方法都实现了相应的 [OperationContract],并实际调用要公开类型的相应方法。生成的 C# 代码通过 System.CodeDom.Compiler 构建到内存中的程序集中,并创建一个新的 System.ServiceModel.ServiceHost 对象(包含服务类)并打开它。

公开 COM 接口时,将使用其运行时可调用包装器 (RCW) 进行反射。

代码示例

随附代码示例中的项目如下表所示

文件夹 文件夹中的项目 项目描述
ServiceHostFolder WcfServiceHost 一个托管类库,负责实际创建 WCF 服务组件。它加载包含要公开类型的程序集,对其进行反射,即时生成 WCF 服务组件程序集,然后创建服务类型,构造其 System.ServiceModel. ServiceHost 对象,并打开它以与客户端进行通信。
ServiceHostContainerFolder ServiceHostContainer 一个可执行的控制台应用程序;作为 WcfServiceHost 组件的容器。它实例化 SvcHost 类型并调用其 CreateServiceHost() 方法来创建服务。该应用程序的 App.config 文件包含所有生成的 WCF 服务的 <system.serviceModel> 定义。
ComponentNetFolder ComponentNet 一个托管类库,实现要通过 WCF 服务公开的 ComponentNet.Class1 类型示例。
ComponentNetBase 这些托管类库包含 ComponentNet.Class1 的“父”类和“祖父”类。提供它们是为了说明在生成的代码中引用所有要公开类型的祖先的必要性。
ComponentNetBase2
ComponentComFolder ComponentCom 公开其自定义、与自动化兼容的接口 IClass2 的 COM 对象。
DummyComInterop 这只是一个占位符托管控制台应用程序。其唯一目的是自动生成和测试 ComponentCom COM 对象上的 RCW。实际上,底层使用的是 tlbimp.exe 实用程序。
ClientFolder ConsoleClient 一个客户端控制台应用程序。它持有所有生成 WCF 服务所需的服务引用,并提供一个非常简单的服务消费示例。

让我们来看看示例是如何工作的。我们的第一个任务是获取位于 ComponentNet.dll 程序集中的 ComponentNet.Class1 类型,并公开其公共方法和属性。ComponentNet.Class1 类型派生自位于 ComponentNetBase.dllComponentNetBase.BaseClass1 类型,而 ComponentNetBase.BaseClass1 类型又派生自位于 ComponentNetBase2.dllComponentNetBase2.BaseClass2 类型。

ComponentNetBase2.BaseClass2 <-- ComponentNetBase.BaseClass1 <-- ComponentNet.Class1

相应的服务由 ServiceHostContainer.exe 宿主。此程序集引用 WcfServiceHost 程序集,并仅用一行代码创建所需的服务。

new SvcHost().CreateServiceHost("ComponentNet.dll", "Class1", true);

SvcHost 类型的 CreateServiceHost() 方法有三个参数,即包含要公开的类型的程序集的路径、该类型的名称以及一个布尔参数,指示服务是否支持会话。为简单起见,所有服务模块都放在同一个目录下;因此,第一个参数实际上简化为仅程序集文件名。SvcHost.CreateServiceHost() 方法内部调用静态方法 WcfGenerator.GenerateServiceType()。后者加载 ComponentNet.dll,对其进行反射,并使用 GenerateInterfaceAndClass() 静态私有方法生成 C# 代码。此方法看起来相当丑陋,但它完成了工作:其输出的 C# 代码包含相应的 ServiceContract 和实现它的类。接下来,静态方法 WcfGenerator.GenerateServiceType() 将 C# 代码构建到内存中的服务程序集中。可选地,将方法的最后一个参数设置为 true,您可以指示将包括生成的 C# 代码在内的调试信息放入磁盘文件。顺便说一句,可以使用 System.Reflection.Emit 命名空间中的类直接以中间语言 (MSIL) 获取内存中的服务程序集,而无需事先生成 C# 代码。但我发现用一种更易读的语言生成代码更简单,而且也更容易更改和调试。最后,创建 System.ServiceModel.ServiceHost 类型的实例,并通过其 Open() 方法将其置于打开状态。

ServiceHost 实例是使用 App.config 文件创建的(该文件成为 ServiceHostContainer.exe.config 文件)。示例中仅实现了一个 wsHttpBinding,但可以轻松添加具有其他绑定配置的终结点。服务通过标准元数据交换 (mex) 终结点向客户端描述自己。

要运行附加的示例而不进行编译,首先需要将演示项目下载并解压缩到某个目录,然后运行其 RunServices.cmd 文件(该文件注册 COM 对象,启动 ServiceHostContainer.exe,然后在关闭它之后,取消注册 COM),然后运行 ConsoleClient.exe 客户端应用程序。请等待所有服务启动并在 ServiceHostContainer.exe 控制台中出现消息“Press any key to stop Services...”后,然后在客户端控制台中按任意键以允许 ConsoleClient.exe 继续执行。在其执行过程中,客户端应用程序会“休眠”一段时间,以测试生存期控制功能。

讨论

代码示例的局限性

为简化起见,本文的代码示例存在以下局限性:要公开的类型的实例应使用默认构造函数创建(在公开 COM 类型的情况下,这是创建 RCW 的强制要求),并且要公开的方法的参数和返回值不应是用户定义的类型。通过一些额外的生成代码可以解除这两种限制。例如,用户定义的参数应首先用 [DataContract][DataMember] 属性(表示对原始组件的额外要求)进行标记,或者使用更多生成的代码进行序列化。

PerSessionPerCall

服务可以使用 PerSessionPerCall 实例上下文模式创建(SvcHost.CreateServiceHost() 方法中的最后一个参数分别设置为 truefalse)。在 PerSession 情况下,会在一个客户端和一个要公开的类型的实例之间创建一个会话。该类型的构造函数在每个会话开始时调用一次。要公开的类型实例的状态在整个会话期间得以保留,并可供客户端访问。因此,如果应保留要公开的对象的状态,则 PerSession 行为是一个选项。在大多数情况下,可能存在此要求。即使状态保留不是问题,一旦创建,服务器端的对象就会在整个会话期间存在。在 PerCall 情况下,要公开的类型的实例会在每次客户端调用时创建(这可以通过在 ServiceHostContainer.exe 示例的输出中计算构造函数调用次数来观察)。因此,PerCall 方法适用于那些(罕见的)情况,即无状态的要公开类型很少被调用,因此无需一直将要公开的类型的“待机”实例保存在内存中。

在代码示例中,对于 PerCall 行为,会在生成的服务类名后自动添加 _PerCall 后缀。

要公开的类型实例的生存期

PerSession 模式下,代码示例中没有办法显式销毁要公开的类型实例。可以通过定义服务 .config 文件中 receiveTimeout 绑定参数的值来控制其生存期。通过尝试在 ConsoleCleint 项目中调整 receiveTimeoutSleep() 方法的参数值来测试这一点。如果睡眠时间超过接收超时时间,那么在客户端调用时,相应的服务对象实例将不再有效,客户端将抛出异常。

结论

本文介绍了一种用于自动生成 .NET 和 COM 类型公共方法和属性的 WCF 接口的技术。要公开的类型被反射,并且相应 [ServiceContract] 接口和服务类(实现该接口)被即时生成。服务类的每个方法都调用要公开类型的相应方法。每个服务对象都可以通过 PerSessionPerCall 行为创建。客户端代理使用服务元数据交换自动生成。代码示例说明了针对 .NET 和 COM 公开类型的技术。

谢谢

我非常感谢我尊敬的同事 Ronen Halili、Eran David、Barak Cohen 和 Alex Katz 就本文主题进行的有益讨论。

© . All rights reserved.