通过自定义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 条评论
登录 后参与评论

相关文章

来自专栏安恒网络空间安全讲武堂

二进制学习系列-栈溢出之libc_init

这是一道ctf wiki上面的一道中级ROP,思路很明确,但是还是有些小坑,比如说write函数上面,还有pwntools函数上面等等…

3523
来自专栏Micro_awake web

javascript(一):javascript基本介绍及基本语法

什么是javascript? javascript是一种直译型脚本语言,是一种动态类型、弱类型、基于原型的语言。(所谓“脚本语言”:指的是它不具有开发操作系统的...

2028
来自专栏小樱的经验随笔

如何在命令长度受限的情况下成功get到webshell(函数参数受限突破、mysql的骚操作)

还记得上篇文章记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门),我们讲到了一些PHP的一些如何巧妙地绕过数字和字母受限的技巧,今天...

1492
来自专栏大内老A

WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]

WCF客户端和服务端的框架体系相互协作,使得开发人员可以按照我们熟悉的方式进行异常的处理:在服务操作执行过程中抛出异常(FaultException),在调用服...

2019
来自专栏ImportSource

并发编程-加锁机制

本文翻译自《Java Concurrency ?In ?Practice》,定期放送 ,让你利用碎片时间悄悄的看了一本书! 我们的文章是系列的。所以先请允许...

3208
来自专栏安恒网络空间安全讲武堂

堆学习入门

借助hitcon training的题目对三种堆的利用方法进行了一个系统的学习,刚入坑的堆小白们可以一起学习一下。题目链接:https://github.com...

1302
来自专栏Golang语言社区

Go性能优化小结

做过C/C++的同学可能知道,小对象在堆上频繁地申请释放,会造成内存碎片(有的叫空洞),导致分配大的对象时无法申请到连续的内存空间,一般建议是采用内存池。Go ...

2783
来自专栏nnngu

02 Java类的加载机制

1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Cla...

2967
来自专栏崔庆才的专栏

你还在用 os.path?快来感受一下 pathlib 给你带来的便捷吧!

pathlib 是Python内置库,Python 文档给它的定义是 Object-oriented filesystem paths(面向对象的文件系统路径)...

1664
来自专栏Golang语言社区

设计包导出接口的随想

简介:本文讨论在设计一个包的导出接口时遇到的问题以及所采取的解决思路和方法,并提供了模拟代码作为例子。 假设有一个包gameword有个导出结构Player,包...

3446

扫码关注云+社区

领取腾讯云代金券