专栏首页大内老A通过自定义ServiceHost实现对WCF的扩展[原理篇]

通过自定义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 后,不能更改此值”。

当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的扩展[实例篇]

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 难道调用ThreadPool.QueueUserWorkItem()的时候,真是必须调用Thread.Sleep(N)吗?

    开门见山,下面的例子中通过调用ThreadPool.QueueUserWorkItem(WaitCallback callBack, object state)...

    蒋金楠
  • 通过自定义ServiceHost实现对WCF的扩展[实例篇]

    在《原理篇》中我们谈到了通过自定义ServiceHost对WCF进行扩展的本质,以及在IIS/WAS寄宿情况下ServiceHostFactory的作用。接下来...

    蒋金楠
  • 解决T4模板的程序集引用的五种方案

    在众多.NET应用下的代码生成方案中,比如CodeDOM,BuildProvider, 我觉得T4是最好的一种。关于T4的基本概念和模板结果,可以参考我的文章《...

    蒋金楠
  • 周期表的清理逻辑设计

    今天做了下周期表清理的设计,从实践的效果来看还不错。现在线上环境有差不多50多个周期表要统一管理,随着这个管理的规模扩大,对于生命周期的管理也迫在眉睫。

    jeanron100
  • 我在爱奇艺实习的八个月

    记得去年暑假伊始,在那个炎热的午后,我带着懵懂来到上海面试,如今掐指一算,我已经在公司工作了八个多月。在这段时间里,我在工作上认识了很多前辈,生活中也结交了不少...

    好好学java
  • 论文实践学习 - Faster R-CNN 测试

    Faster R-CNN 的 caffe-fast-rcnn 版本没有更新,导致新版的 cudnn 不能使用,Makefile.config 添加 cudnn:...

    AIHGF
  • 【Linux】15 张 Vim 速查表奉上,帮你提高 N 倍效率!

    去年上半年开始全面使用linux进行开发和娱乐了,现在已经回不去windows了。话归正传,在linux上一直使用vim,慢慢熟悉了它的命令,才终于领悟了什么是...

    zenRRan
  • 响铃:从TCL、阿里、腾讯的合作,看传统厂商与互联网企业的压力与焦急

    如果说几年前小米乐视之间的互撕是互联网电视大战的“第一阶段”,那TCL在前不久推出定位年轻人市场的互联网电视品牌“雷鸟”,并作为子公司独立运营应该算是互联网电视...

    曾响铃
  • 丑,掩盖不了体感服酷炫吊天的隐藏内涵

    VRPinea
  • 【DB笔试面试401】​在非归档方式下操作的数据库禁用了()

    Oracle数据库可以设置为归档模式或非归档模式。当数据库运行在归档模式下时,数据库会将所有的事务记录在联机日志(Online Redo Log)中。当联机日志...

    小麦苗DBA宝典

扫码关注云+社区

领取腾讯云代金券