閱讀320 返回首頁    go 阿裏雲 go 技術社區[雲棲]


通過自定義ServiceHost實現對WCF的擴展[原理篇]

除了采用自定義特性聲明(服務行為、契約行為和操作行為)或者配置的方式(服務行為和終結點行為)應用自定義的行為之外,我們還可以通過自定義ServiceHost來應用這些自定義的行為。自定義ServiceHost是對WCF的服務端進行擴展的一種常用的方式。

在創建ServiceHost的時候,WCF會加載服務相關的配置並將其作為服務的描述信息附加到ServiceHost對象上,我們也可以在開啟ServiceHost之前對其服務描述信息進行相應的修改。ServiceHost在開啟之前具有的服務描述信息將會決定在開啟之後創建的服務端運行時框架。所以如果我們通過自定義ServiceHost對象並根據具體應用場景的具體需求對其服務描述進行定製,同樣可以起到對WCF服務端進行擴展的目的。

目錄
一、自定義ServiceHost的本質:對服務描述進行定製
二、ServiceHost開啟後對Description的定製無效
三、通過自定義ServiceHost對分發運行時進行定製是無效的
四、 自定義ServiceHost的創建者:ServiceHostFactory

通過前麵對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類型,並在重寫的方法中將創建的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。

由於,這就意味著隻要在對服務描述的定義才是有效的。這也是為什麼我們需要將對服務描述的定製操作定義在重寫的OnOpening方法中的原因。

比如在下麵的代碼片斷中,我對CulturePropagationServiceHost進行了重新定義,將原本定義在OnOpening方法中應用CulturePropagationBehaviorAttribute行為的代碼轉移到了重寫的方法中。其實上這樣的定義是無意義的,根本起不到任何作用。

   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: }

由於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異常,並且提示“”。

image

當ServiceHost被開啟的情況試圖對創建的分發運行時進行改變,都會拋出如上圖所示的異常。其背後的原因在於,。WCF內部的做法是:基於初始化的DispatchRuntime對象創建一個對象,原來的DispatchRuntime將不會被使用。ImmutableDispatchRuntime是一個定義在System.ServiceModel.Dispatcher命名空間下的內部類型。從名稱上就可以看得出來,ImmutableDispatchRuntime對象是一個。既然原來的DisaptchRuntime對象在ImmutableDispatchRuntime創建之後就不會被使用,我們針對它的任何修改已經變得沒有意義,所以在設計DisaptchRuntime相關API的時候,針對它屬性的修改都會加上。相同的設計同樣應用在ClientRuntime上。

對於我們自定義的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方法中需要傳入一個特殊的字符串類型的參數。從字麵上的意思我們知道它代表創建ServiceHost時調用相應構造函數以字符串形式表示的參數列表。而這個。也就是說。之所以在正常的情況下我們隻需要指定寄宿服務的有效類型就可以了,原因在於默認使用的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
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 16:04:24

  上一篇:go  安裝OpenStack問題
  下一篇:go  通過自定義ServiceHost實現對WCF的擴展[實例篇]