通过自定义ServiceHost实现对WCF的扩展[原理篇]

简介:

除了采用自定义特性声明(服务行为、契约行为和操作行为)或者配置的方式(服务行为和终结点行为)应用自定义的行为之外,我们还可以通过自定义ServiceHost来应用这些自定义的行为。自定义ServiceHost是对WCF的服务端进行扩展的一种常用的方式。

在创建ServiceHost的时候,WCF会加载服务相关的配置并将其作为服务的描述信息附加到ServiceHost对象上,我们也可以在开启ServiceHost之前对其服务描述信息进行相应的修改。ServiceHost在开启之前具有的服务描述信息将会决定在开启之后创建的服务端运行时框架。所以如果我们通过自定义ServiceHost对象并根据具体应用场景的具体需求对其服务描述进行定制,同样可以起到对WCF服务端进行扩展的目的。

目录
一、自定义ServiceHost的本质:对服务描述进行定制
二、ServiceHost开启后对Description的定制无效
三、通过自定义ServiceHost对分发运行时进行定制是无效的
四、 自定义ServiceHost的创建者:ServiceHostFactory

一、自定义ServiceHost的本质:对服务描述进行定制

通过前面对WCF服务端运行时框架的介绍,我们知道了在初始化ServiceHost时创建的服务描述是构建服务端运行时框架的基础。服务描述通过类型ServiceDescription表示,被创建的服务描述可以通过ServiceHost的只读属性Description得到。下面的代码片断表示该属性在ServiceHost的基类ServiceHostBase中的定义。

   1: public abstract class ServiceHostBase : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
   2: {
   3:     //其他成员
   4:     public ServiceDescription Description { get; }
   5: }

在服务众多描述信息中,以前面介绍的四种行为表示的行为信息作为重要的组成部分。顾名思义,这里的行为信息最终决定了WCF服务端框架进行消息分发、实例激活、操作执行、异常处理、元数据发布、事务管理、并发控制、流量限制、传输安全、存取控制等方面的行为。我们通过自定义ServiceHost首先对WCF的扩展,其本质在于对服务的行为描述进行相应的定制。

以上面一篇(《通过“四大行为”对WCF的扩展[实例篇]》)关于实现语言文化信息自动传播的扩展为例,代表客户端线程CurrentUICulture和CurrentCulture的语言文化代码在客户端的发送和服务端接收与对当前线程语言文化上下文的设置都是通过自定义行为CulturePropagationBehaviorAttribute实现的。而该CulturePropagationBehaviorAttribute特性最终作为契约行为被应用到了契约接口上。如果没有这个特性,对于服务端来说我们也可以通过自定义ServiceHost的方式直接将CulturePropagationBehaviorAttribute行为添加到服务描述信息中。

通过自定义ServiceHost以实现对服务描述的定义很简单,我们只需要重写ServiceHost的虚方法OnOpening方法,并对Description属性进行相应的修改即可。在下面的代码片断中,我们创建了一个继承自ServiceHost的CulturePropagationServiceHost类型,并在重写的OnOpening方法中将创建的CulturePropagationBehaviorAttribute对象作为服务行为添加到服务行为列表中。此外,还定义相应的构造函数。

   1: public class CulturePropagationServiceHost: ServiceHost
   2: {
   3:     public CulturePropagationServiceHost(Type serviceType, params Uri[] baseAddresses)
   4:         : base(serviceType, baseAddresses)
   5:     { }
   6:  
   7:     protected override void OnOpening()
   8:     {
   9:         base.OnOpening();
  10:         CulturePropagationBehaviorAttribute behavior = this.Description.Behaviors.Find<CulturePropagationBehaviorAttribute>();
  11:         if(null == behavior)
  12:         {
  13:             this.Description.Behaviors.Add(new CulturePropagationBehaviorAttribute());
  14:         }
  15:     }
  16: }

那么我们在进行自我寄宿的情况下,就可以直接创建CulturePropagationServiceHost来寄宿相应的服务。无须再进行基于CulturePropagationBehaviorAttribute行为的设置,被寄宿的服务就具有“语言文化识别”的能力。

   1: using (CulturePropagationServiceHost host = new CulturePropagationServiceHost(typeof(ResourceService)))
   2: {
   3:     host.Open();
   4:     Console.Read();
   5: }

注:我们说的基于自定义ServiceHost的扩展,实际上只需要让我们定义的类继承自ServiceHostBase即可。但是在绝大部分情况下,我们可以直接使用定义在ServiceHost类型中的功能,所以我们一般会通过继承自ServiceHost来定义我们的自己的ServiceHost。

二、ServiceHost开启后对Description的定制无效

由于基于服务描述的服务端运行时框架式在ServiceHost开启过程中被构建出来的,这就意味着只要在ServiceHost开启之前对服务描述的定义才是有效的。这也是为什么我们需要将对服务描述的定制操作定义在重写的OnOpening方法中的原因。

比如在下面的代码片断中,我对CulturePropagationServiceHost进行了重新定义,将原本定义在OnOpening方法中应用CulturePropagationBehaviorAttribute行为的代码转移到了重写的OnOpened方法中。其实上这样的定义是无意义的,根本起不到任何作用。

   1: public class CulturePropagationServiceHost : ServiceHost
   2: {
   3:     //其他成员
   4:     protected override void OnOpened()
   5:     {
   6:         base.OnOpened();
   7:         CulturePropagationBehaviorAttribute behavior = this.Description.Behaviors.Find<CulturePropagationBehaviorAttribute>();
   8:         if (null == behavior)
   9:         {
  10:             this.Description.Behaviors.Add(new CulturePropagationBehaviorAttribute());
  11:         }
  12:     }
  13: }

三、 通过自定义ServiceHost对分发运行时进行定制是无效的

由于CulturePropagationBehaviorAttribute针对服务端的意义在于将CultureReceiver对象添加到基于终结点的分发运行时(DispatchRuntime)所有操作(DispatchOperation)的CallContextInitializer列表中。而CultureReceiver的目的在于从请求消息中获取代表客户端语言文化上下文,并为但前线程的语言文化上下文进行相应的设置。有人也许会问这么一个问题:如果我们在自定义CulturePropagationServiceHost的时候,绕开对服务描述的设置,直接对分发运行时进行定制是否可以起到一样的作用。照理说,我们通过下面的方式来重新定义CulturePropagationServiceHost也是等效的。

   1: public class CulturePropagationServiceHost : ServiceHost
   2: {
   3:     //其他成员
   4:     protected override void OnOpened()
   5:     {
   6:         base.OnOpened();
   7:         foreach (ChannelDispatcher channelDispatcher in this.ChannelDispatchers)
   8:         {
   9:             foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
  10:             { 
  11:                 foreach(DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
  12:                 {
  13:                     operation.CallContextInitializers.Add(new CultureReceiver(new CultureMessageHeaderInfo()));
  14:                 }
  15:             }
  16:         }
  17:     }
  18: }

但是,这种在ServiceHost开启之后对分发运行时进行的更改是不合法的。如果你使用上面定义的CulturePropagationServiceHost进行服务寄宿的时候,当程序执行到为DispatchOperation添加CallContextInitializer的地方,会抛出如下图所示的InvalidOperationException异常,并且提示“打开ServiceHost 后,不能更改此值”。

image

当ServiceHost被开启的情况试图对创建的分发运行时进行改变,都会抛出如上图所示的异常。其背后的原因在于,在开启ServiceHost过程中针对没有具体的终结点创建相应的分发运行时并对其进行初始化之后,会将其标记为只读。WCF内部的做法是:基于初始化的DispatchRuntime对象创建一个ImmutableDispatchRuntime对象,原来的DispatchRuntime将不会被使用。ImmutableDispatchRuntime是一个定义在System.ServiceModel.Dispatcher命名空间下的内部类型。从名称上就可以看得出来,ImmutableDispatchRuntime对象是一个恒定不变的运行时。既然原来的DisaptchRuntime对象在ImmutableDispatchRuntime创建之后就不会被使用,我们针对它的任何修改已经变得没有意义,所以在设计DisaptchRuntime相关API的时候,针对它属性的修改都会加上ServiceHost是否被开启的检验。相同的设计同样应用在ClientRuntime上。

四、自定义ServiceHost的创建者:ServiceHostFactory

对于我们自定义的ServiceHost,我们可以在自我寄宿的时候直接使用。如果我们采用IIS或者WAS寄宿方式,我们需要为寄宿的服务创建一个.svc文件(在WCF 4.0中这个文件可以借助于相应的配置省掉)。如果读者阅读了《WCF技术剖析(卷1)》第7章《服务寄宿(Service Hosting)》,你应该知道在这种情况,用于寄宿服务的自定义ServiceHost是通过自定义的ServiceHostFactory来创建的。自定义ServiceHostFactory需要继承抽象类ServiceHostFactoryBase,下面的代码片断给出了ServiceHostFactoryBase的定义,而通过调用CreateServiceHost方法得到的类型为ServiceHostBase对象用于进行服务的寄宿工作。

   1: public abstract class ServiceHostFactoryBase
   2: {   
   3:     protected ServiceHostFactoryBase();
   4:     public abstract ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses);
   5: }

自定义ServiceHostFactory的类型通过定义在.svc 文件中“%ServiceHost%”指令(Directive)的Factory属性来表示。

   1: <%@ ServiceHost Service="Artech.WcfServices.Servicies.ResourceService" 
   2: Factory="Artech.WcfExtensions.CulturePropagation.CulturePropagationServiceHostFactory" %>

除此之外,从上面的代码片断中我们可以看到,CreateServiceHost方法中需要传入一个特殊的字符串类型的参数constructorString。从字面上的意思我们知道它代表创建ServiceHost时调用相应构造函数以字符串形式表示的参数列表。而这个constructorString参数来源于“%ServiceHost%”指令的Service属性值。也就是说,“%ServiceHost%”指令的Service属性严格来说并不是指寄宿服务的有效类型,而是传递给对应ServiceHostFactory的CreateServiceHost方法的第一个参数值。之所以在正常的情况下我们只需要指定寄宿服务的有效类型就可以了,原因在于默认使用的ServiceHost为System.ServiceModel.Activation.ServiceHostFactory,在它通过CreateServiceHost方法进行ServiceHost的创建时,只需要知道寄宿服务的类型就可以了。

注:由于ServiceHost既可以泛指用于进行服务寄宿的继承自ServiceHostBase的对象或者类型,又可以具体指System.ServiceModel.ServiceHost类型。同理,ServiceHostFactory既可以泛指继承于ServiceHostFactoryBase的对象或者类型,也可以具体指System.ServiceModel.Activation.ServiceHostFactory类型。读者应该更具上下文判断这两个词所指为何。

虽然说自定义ServiceHostFactory只需要继承ServiceHostFactoryBase即可,但是在绝大多数情况下我们会让我们自定义的ServiceHostFactory继承自System.ServiceModel.Activation.ServiceHostFactory,因为我们需要借助它提供的自动程序集加载机制。不知道读者有没有注意这样一个问题:对于“%ServiceHost%”指令的Service属性值,我们仅仅需要指定寄宿服务的全名(命名空间+类型名称)就可以了,而无须指定具体的程序集名称。如果定义服务类型的程序集没有被加载,服务类型是不能被正确解析的。实际上,当System.ServiceModel.Activation.ServiceHostFactory在调用CreateServiceHost方法的时候,如果指定的服务类型不能被解析,它会加载所有被引用的程序集。System.ServiceModel.Activation.ServiceHostFactory定义如下。

   1: public class ServiceHostFactory : ServiceHostFactoryBase
   2: {
   3:     public ServiceHostFactory();
   4:     public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses);
   5:     protected virtual ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses);
   6: }

在上面我们创建了自定义的CulturePropagationServiceHost,我们在为之定义相应的ServiceHostFactory类型的时候,只需要继承System.ServiceModel.Activation.ServiceHostFactory类型,并通过重写受保护的CreateServiceHost方法创建自定义CulturePropagationServiceHost即可。具体的实现如下所示。

   1: public class CulturePropagationServiceHostFactory : ServiceHostFactory
   2: {
   3:     protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
   4:     {
   5:         return new CulturePropagationServiceHost(serviceType, baseAddresses);
   6:     }
   7: } 

通过自定义ServiceHost实现对WCF的扩展[原理篇]
通过自定义ServiceHost实现对WCF的扩展[实例篇]


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
WCF基础教程(四)——数据契约实现传送自定义数据类型
WCF基础教程(四)——数据契约实现传送自定义数据类型
119 0
|
前端开发 .NET 开发框架
Wcf扩展
ASP.NET MVC和WCF真是微软两个很棒的框架,设计的很好,可扩展性非常强,到处都是横切、管道。 以前写过一篇MVC流程的文章,http://www.cnblogs.com/lovecindywang/archive/2010/12/02/1894740.html主要是使用了MVC的各种扩展。
799 0
跟着Artech学习WCF扩展(1) Binding进行通信
这个demo简单 就一个服务器段的一个客户端的 主要是注释 Server的 using System; using System.Collections.Generic; using System.
712 0
跟着Artech学习WCF扩展(2) 自定义Channel与执行的顺序
源代码下载地址:点我 原文地址:http://www.cnblogs.com/artech/archive/2008/07/09/1238626.html 这节不看源码 看着真费劲哈   服务器端是这样的顺序 MyBindingElement.
732 0
|
XML 网络架构 数据格式
跟着Artech学习WCF(5) Responsive Service 与自定义Soap Message Header
记得以前看.NET各类框架教程在介绍SOAP时经常提到Soap Header 以前一直认为这个玩意就是个理论 应该和具体的编码和应用无关 后来在看到一些关于SOAP安全的书可以在header里 进行加密解密信息的存储 用于安全方面的验证 但一直苦于这个玩意到底是神马东西,一直没见过代码,今天A...
697 0
|
前端开发
WCF更新服务引用报错的原因之一
WCF更新服务引用报错的原因之一
|
C# 数据安全/隐私保护
c#如何创建WCF服务到发布(SqlServer版已经验证)
c#如何创建WCF服务到发布(SqlServer版已经验证)
71 0