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


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

在《原理篇》中我們談到了通過自定義ServiceHost對WCF進行擴展的本質,以及在IIS/WAS寄宿情況下ServiceHostFactory的作用。接下來通過一個具體的例子來演示如何通過WCF擴展實現以Unity為代表的IoC框架的集成,以及應用該擴展的ServiceHost和ServiceHostFactory如何定義。[源代碼從這裏下載]

目錄
一、IoC/DI簡介
步驟一、自定義InstanceProvider:UnityInstanceProvider
步驟二、創建服務行為:UnityServiceBehaviorAttribute
步驟三、自定義ServiceHost:UnityServiceHost
步驟四、自定義ServiceHostFactory:UnityServiceHostFactory
步驟五、創建實例程序應用自定義ServiceHost

所謂控製反轉(IoC: Inversion Of Control)就是應用本身不負責依賴對象的創建和維護,而交給一個外部容器來負責。這樣控製權就由應用轉移到了外部IoC容器,控製權就實現了所謂的反轉。比如,在類型A中需要使用類型B的實例,而B實例的創建並不由A來負責,而是通過外部容器來創建。

有時我們又將IoC成為依賴注入(DI: Dependency Injection)。所謂依賴注入,就是由外部容器在運行時動態地將依賴的對象注入到組件之中。具體的依賴注入方式又包括如下三種典型的形式。

  • 構造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前會自定義創建相應參數對象;
  • 屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性;
  • 方法注入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。

在開源社區,具有很有流行的IoC框架,比如Castle Windsor、Unity、Spring.NET、StructureMap、Ninject等。現在我們就以Unity為例,介紹通過WCF的擴展如何實現基於IoC的服務實例的創建。

要實現WCF和Unity之間的集成,最終體現在如何通過Unity容器來創建服務實例。而從前麵介紹的關於服務端運行時框架的介紹,我們知道最終服務實例的提供落在了一個特殊的組件之上,即InstanceProvider。所以,本實例的核心就是要。我們將之命名為UnityInstanceProvider。

下麵的代碼給出了實現IInstanceProvider接口的UnityInstanceProvider的定義。在構造函數中指定連個參數:實現了IUnityContainer接口的Unity容器對象和服務契約類型。那麼在真正實現對服務實例創建的GetInstance方法上,直接調用IUnityContainer的Resolve方法傳入給定的服務契約類型來創建具體的人服務實例。而在ReleaseInstance方法中則直接調用IUnityContainer的Teardown方法進行服務實例的釋放。注意,在這之前需要確保如下兩個基於Unity的程序集被引用:Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll。

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Channels;
   4: using System.ServiceModel.Dispatcher;
   5: using Microsoft.Practices.Unity;
   6: namespace Artech.WcfExtensions.IoC
   7: {
   8:     public class UnityInstanceProvider: IInstanceProvider
   9:     {
  10:         public IUnityContainer UnityContainer { get; private set; }
  11:         public Type ContractType { get; private set; }
  12:  
  13:         public UnityInstanceProvider(IUnityContainer unityContainer, Type contractType)
  14:         {
  15:             this.UnityContainer = unityContainer;
  16:             this.ContractType = contractType;
  17:         }
  18:  
  19:         public object GetInstance(InstanceContext instanceContext, Message message)
  20:         {
  21:             return this.UnityContainer.Resolve(this.ContractType);
  22:         }
  23:  
  24:         public object GetInstance(InstanceContext instanceContext)
  25:         {
  26:             return this.GetInstance(instanceContext, null);
  27:         }
  28:  
  29:         public void ReleaseInstance(InstanceContext instanceContext, object instance)
  30:         {
  31:             this.UnityContainer.Teardown(instance);
  32:         }
  33:     }
  34: }

和前麵一個關於客戶端語言文化上下文自動傳播的實例一樣,我們自定義的組件最終通過相應的行為應用到WCF的運行時中。為此,我們針對上麵自定義的InstanceProvider定義了一個實現IServiceBehavior接口的服務行為UnityServiceBehaviorAttribute。為了能讓該服務行為以聲明的方式直接應用到服務類型上,我們將其定義成特性。

UnityServiceBehaviorAttribute的所有定義體現在如下所示的代碼片斷中。構造函數中具有一個字符串類型的參數containerName表示配置的Unity容器的名稱。真正的容器名稱在構造函數中被獲取,為了避免UnityConainter的頻繁創建,我們定義了一個靜態的以容器名稱為鍵值的字典保存已經被創建的Unity容器。

我們最終的目的是根據給定名稱的Unity容器,這樣的功能定義在ApplyDispatchBehavior方法中。創建UnityInstanceProvider還需要服務契約的類型,而得到服務契約類型采用了這樣的邏輯:得到表示服務描述的ServiceDescription對象,然後根據前麵得到的契約名稱和命名空間找到對應的表示契約描述的ContractDescription對象,而該對象的ContractType屬性表示服務契約的類型。此外,如果基於契約類型的注冊不存在,ApplyDispatchBehavior方法還進行了服務契約類型和服務類型之間的類型注冊。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Collections.ObjectModel;
   4: using System.Configuration;
   5: using System.Linq;
   6: using System.ServiceModel;
   7: using System.ServiceModel.Channels;
   8: using System.ServiceModel.Description;
   9: using System.ServiceModel.Dispatcher;
  10: using Microsoft.Practices.Unity;
  11: using Microsoft.Practices.Unity.Configuration;
  12:  
  13: namespace Artech.WcfExtensions.IoC
  14: {
  15:     public class UnityServiceBehaviorAttribute: Attribute, IServiceBehavior
  16:     {
  17:         static Dictionary<string, IUnityContainer> containers = new Dictionary<string, IUnityContainer>();
  18:         public IUnityContainer UnityContainer { get; private set;}
  19:  
  20:         public UnityServiceBehaviorAttribute()
  21:             : this(string.Empty)
  22:         { }
  23:  
  24:         public UnityServiceBehaviorAttribute(string containerName)
  25:         {
  26:             containerName = containerName ?? string.Empty;
  27:             if (containers.ContainsKey(containerName))
  28:             {
  29:                 this.UnityContainer = containers[containerName];
  30:             }
  31:             else
  32:             {                
  33:                 lock (typeof(UnityServiceBehaviorAttribute))
  34:                 {
  35:                     IUnityContainer container = new UnityContainer();
  36:                     UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
  37:                     if (containerName == string.Empty)
  38:                     {
  39:                         configuration.Configure(container);
  40:                     }
  41:                     else
  42:                     {
  43:                         configuration.Configure(container, containerName);
  44:                     }
  45:                     containers[containerName] = container;
  46:                     this.UnityContainer = container;
  47:                 }
  48:             }
  49:         }
  50:         public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {}
  51:         public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
  52:         {
  53:             foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
  54:             {
  55:                 foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
  56:                 {
  57:                     Type contractType = (from endpoint in serviceHostBase.Description.Endpoints
  58:                                          where endpoint.Contract.Name == endpointDispatcher.ContractName && endpoint.Contract.Namespace == endpointDispatcher.ContractNamespace
  59:                                          select endpoint.Contract.ContractType).FirstOrDefault();
  60:                     if (null == contractType)
  61:                     {
  62:                         continue;
  63:                     }
  64:                     if (!this.UnityContainer.Registrations.Any(registration => registration.RegisteredType == contractType))
  65:                     {
  66:                         this.UnityContainer.RegisterType(contractType, serviceHostBase.Description.ServiceType);
  67:                     }
  68:                     endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(this.UnityContainer, contractType);
  69:                 }
  70:             }
  71:         }
  72:         public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {}
  73:     }
  74: }

接下來,我們需要自定義一個ServiceHost來應用上麵定義的服務行為UnityServiceBehaviorAttribute。我們將這個自定義的ServiceHost命名為UnityServiceHost,下麵的代碼片斷給出了整個UnityServiceHost的定義。

   1: using System;
   2: using System.ServiceModel;
   3: namespace Artech.WcfExtensions.IoC
   4: {
   5:     public class UnityServiceHost : ServiceHost
   6:     {
   7:         private string containerName;
   8:         public UnityServiceHost(Type serviceType, string containerName, params Uri[] baseAddresses)
   9:             : base(serviceType, baseAddresses)
  10:         {
  11:             this.containerName = containerName;
  12:         }
  13:         protected override void OnOpening()
  14:         {
  15:             base.OnOpening();
  16:             if (this.Description.Behaviors.Find<UnityServiceBehaviorAttribute>() == null)
  17:             {
  18:                 this.Description.Behaviors.Add(new UnityServiceBehaviorAttribute(containerName));
  19:             }
  20:         }
  21:     }
  22: }

UnityServiceHost直接繼承自ServiceHost。在構造函數中,除了指定服務類型和可選的基地址數組之外,我們還指定了。在重寫的OnOpening方法中,服務行為UnityServiceBehaviorAttribute被創建出來並被添加到了服務行為列表之中。

自定義的ServiceHost在進行IIS或者WAS寄宿的時候需要通過相應的ServiceHostFactory來創建,我們需要為自定義的UnityServiceHost創建對應的UnityServiceHostFactory。

UnityServiceHost具有三個參數:服務類型、Unity容器名稱和基地址數組。而通過上麵的介紹我們知道最終作為構造函數參數的來源是.svc文件%@ServiceHost%指令的Service屬性。為了上該屬性能夠同時包含用於創建自定義UnityServiceHost必須的服務類型和Unity容器名稱,我們希望該屬性具有如下的格式,及前半部分代表服務類型,後半部分代表Unity容器名稱,中間采用分隔符“:”將兩者隔開。

   1: <%@ ServiceHost Service="{服務類型}:{Unity容器名稱}" ... %>

基於這樣的%@ServiceHost%指令的Service屬性格式,我們就可以將自定義的UnityServiceHostFactory定義成如下的樣子:UnityServiceHostFactory直接繼承自ServiceHostFactory,重寫的共有CreateServiceHost方法中,將Unity容器名稱從constructorString參數中提取出來,並傳入隻包含服務類型名稱的字符串作為參數調用基類的CreateServiceHost方法。而在重寫的受保護CreateServiceHost方法中,則根據之前提取出來的Unity容器名稱創建UnityServiceHost對象。

   1: using System;
   2: using System.ServiceModel;
   3: using System.ServiceModel.Activation;
   4: namespace Artech.WcfExtensions.IoC
   5: {
   6:     public class UnityServiceHostFactory : ServiceHostFactory
   7:     {
   8:         public string ContianerName { get; private set; }
   9:  
  10:         protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  11:         {
  12:             return new UnityServiceHost(serviceType, this.ContianerName, baseAddresses);
  13:         }
  14:         public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
  15:         {
  16:             var split = constructorString.Split(':');
  17:             constructorString = split[0];
  18:             if (split.Length > 1)
  19:             {
  20:                 this.ContianerName = split[1].Trim();
  21:             }            
  22:             return base.CreateServiceHost(constructorString, baseAddresses);
  23:         }
  24:     }
  25: }

最後我們創建一個實例程序來演示如何以IIS寄宿方式使用上麵我們自定義ServiceHost。我們依然沿用之前演示的資源服務的例子。在前麵演示的例子(《通過“四大行為”對WCF的擴展[實例篇]》)中,我們直接通過獲取定義在資源文件(.resx)的方式提供服務的實現。現在我們從可擴展性的角度對服務進行重新設計以實現對不同資源存儲方法的支持。也就是說,我可以將資源信息定義在資源文件中,也可能定義在數據庫中,或者說訪問另一個服務來提供你所需要的資源。

為了讓我們的資源服務具有這樣的可擴展性,我們將基於不同資源存儲方法的功能定義在一個接口中,並將其命名為IResourceProvider。如下麵的代碼所示,IResourceProvider接口僅僅具有一個GetString方法。

   1: public interface IResourceProvider
   2: {
   3:     string GetString(string key);
   4: }

然後我們將基於.resx文件的資源提供的功能定義成一個實現了IResourceProvider接口的ResxFileProvider。我們直接將定義在ResourceService的GetString方法中的代碼移到了ResxFileProvider的GetString方法中。

   1: public class ResxFileProvider : IResourceProvider
   2: {
   3:     public string GetString(string key)
   4:     {
   5:         return Resources.ResourceManager.GetString(key);
   6:     }
   7: }

接下來,為了創建於具體資源存儲無關的資源服務,我們需要讓ResourceService僅僅依賴我們定義的IResourceProvider,下麵是ResourceService的定義。

   1: public class ResourceService : IResourceService
   2: {
   3:     public IResourceProvider Provider { get; private set; }
   4:  
   5:     public ResourceService(IResourceProvider provider)
   6:     {
   7:         this.Provider = provider;
   8:     }
   9:     public string GetString(string key)
  10:     {
  11:         return this.Provider.GetString(key);
  12:     }
  13: }

為了演示IIS服務寄宿方式,我們在IIS管理器創建一個Web應用(比如將其命名為WcfServices)並將其物理路徑映射成定義ResourceService項目的根目錄。同時更改項目的編譯輸出目錄,從默認的\bin\debug切換成\bin(因為Web應用自動加載的是\bin目錄下的程序集)。然後為該項目添加一個Web.config,並進行如下的配置。通過這個配置文件,我們定義了一個名稱為defaultContainer的Unity容器,並在該容器中定義了。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
   5:   </configSections>
   6:   <unity>
   7:     <containers>
   8:       <container name="defaultContainer">
   9:         <register type="Artech.WcfServices.Servicies.IResourceProvider, Artech.WcfServices.Servicies" 
  10:                   mapTo="Artech.WcfServices.Servicies.ResxFileProvider, Artech.WcfServices.Servicies">
  11:         </register>
  12:       </container>    
  13:     </containers>
  14:   </unity>
  15:   <system.serviceModel>  
  16:     <services>
  17:       <service name="Artech.WcfServices.Servicies.ResourceService">       
  18:         <endpoint binding="ws2007HttpBinding" contract="Artech.WcfServices.Contracts.IResourceService"/>
  19:       </service>
  20:     </services>
  21:   </system.serviceModel>
  22: </configuration>

接下來我們需要在項目的根目錄中添加一個文件名為ResourceService.svc的文件,其<%@ServiceHost%>指令定義如下。Service屬性以“:”作為分隔符將代表服務類型和Unity容器名稱分開,而Factory屬性指定的正是用於創建自定義UnityServiceHost的UnityServiceHostFactory的類型。

   1: <%@ ServiceHost Service="Artech.WcfServices.Servicies.ResourceService: defaultContainer" 
   2: Factory="Artech.WcfExtensions.IoC.UnityServiceHostFactory, Artech.WcfExtensions.Lib"%>

現在你隻需要將客戶端配置中終結點地址替換成ResourceService.svc的地址(https://localhost/wcfservices/ResourceService.svc),你就可以直接運行客戶端程序並得到與之前一樣的輸出結果。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <client>
   5:       <endpoint name ="resourceservice"
   6:                 address = "https://localhost/wcfservices/ResourceService.svc"
   7:                 binding ="ws2007HttpBinding"
   8:                 contract ="Artech.WcfServices.Contracts.IResourceService"/>
   9:     </client>
  10:   </system.serviceModel>
  11: </configuration>

輸出結果:

   1: Happy New Year!
   2: Merry Christmas!
   3:  
   4: 新年快樂!
   5: 聖誕快樂!

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


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

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

  上一篇:go  通過自定義ServiceHost實現對WCF的擴展[原理篇]
  下一篇:go  一個簡單的小程序演示Unity的三種依賴注入方式