WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成

时间:2021-12-26 20:24:04

松耦合、高内聚是我们进行设计的永恒的目标,如何实现这样的目标呢?我们有很多实现的方式和方法,不管这些方式和方法在表现形式上有什么不同,他们的思想都可以表示为:根据稳定性进行关注点的分离或者分解,交互双方依赖于一个稳定的契约,而降低对对方非稳定性因素的依赖。从抽象和稳定性的关系来讲,抽象的程度和稳定程度成正相关关系。由此才有了我们面向抽象编程的说法,所以“只有依赖于不变,才能应万变”。

然后,对于面向对象的思想来讲,我们的功能通过一个个具体的对象来承载。对象是具体的,不是抽象的;创建对象是必然的;对象的创建从某种程度上即使对面向抽象的违背。所以模块之间的耦合度在很大程度上是由于对象创建的方式决定的,而在对象创建过程实现解耦是实现我们“松耦合、高内聚”目标的一个重要途径。对于这一点,我们可以看看我们常用的设计模式中有多少是用于解决如何合理进行对象创建的就可以知道了。Enterprise Library推出的新的Application Block:Unity Application Block为我们提供了一个很好的、可扩展的框架,帮助我们合理、有效的创建对象,并解决创建对象中的依赖。而通过WCF一个简单的扩展对象,就可以很容易地实现和Unity的集成。

一、Unity Application Block

由于本篇文章的重点仍然是对WCF的扩展,因此我不会花太多的篇幅对Enterprise Library Unity作详细的介绍。这是比较官方的定义:"The Unity Application Block (Unity) is a lightweight, extensible dependency injection container with support for constructor, property, and method call injection”. Unity实际是建立在ObjectBuilder基础之上,而ObjectBuilder是整个Enterprise Library的基石(实际上还不止于此,MS P&P开发的很多的开源框架都依赖于ObjectBuilder,比如CAB、SCSF等),为我们提供了一个可扩展的、基于策略(strategy based)对象创建方式。借助于ObjectBuilder,Unity可以帮助我们基于Interface或者abstract class创建对象;可以帮助我们管理对象的生命周期;以及实现依赖注入(DI:Dependency Injection)。下面是3种主要的DI方式:

  • Constructor Injection:帮助我们选择我们需要的构造函数来创建对象。
  • Property (Setter) Injection:帮助我们在创建的对象上自动设置某些必要的属性值。
  • Method Injection:帮助我们在对象上调用我们指定的方法做一些初始化的工作。

如果读者想进一步了解Unity Application Block和Enterprise Library,可以访问微软P&P 的网站。

二、实现基于Unity的IntanceProvider

在本系列的第三部分对Dispachter的介绍,和第四部分对WCF可扩展点的介绍中,我提到了一个重要的对象InstanceProvider, 该对象用于service instance的创建。既然Unity的根本目的是创建对象,我们就可以自定义InstanceProvider,让Unity来帮助创建service instance,很容易地实现了和Unity的集成。

下面是我们的自定义InstanceProvider:UnityInstanceProvider

   1: namespace Artech.WCFExtensions
   2: {
   3:     public class UnityInstanceProvider: IInstanceProvider
   4:     {
   5:         private Type _contractType;
   6:         private string _containerName; 
   7:  
   8:         public UnityInstanceProvider(Type contractType, string containerName)
   9:         {
  10:             if (contractType == null)
  11:             {
  12:                 throw new ArgumentNullException("contractType");
  13:             } 
  14:  
  15:             this._containerName = containerName;
  16:             this._contractType = contractType;
  17:         } 
  18:  
  19:         #region IInstanceProvider Members 
  20:  
  21:         public object GetInstance(InstanceContext instanceContext, Message message)
  22:         {
  23:             UnityConfigurationSection unitySection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
  24:             if (unitySection == null)
  25:             {
  26:                 throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture, Resources.MissUnityConfiguration));
  27:             } 
  28:  
  29:             IUnityContainer container = new UnityContainer();
  30:             UnityContainerElement containerElement;
  31:             if (string.IsNullOrEmpty(this._containerName))
  32:             {
  33:                 containerElement = unitySection.Containers.Default;
  34:             }
  35:             else
  36:             {
  37:                 containerElement = unitySection.Containers[this._containerName];
  38:             }
  39:             containerElement.Configure(container);
  40:             UnityTypeElement[] unityTypeElements = Array.CreateInstance(typeof(UnityTypeElement), containerElement.Types.Count) as UnityTypeElement[];
  41:             containerElement.Types.CopyTo(unityTypeElements, 0); 
  42:  
  43:             if (unityTypeElements.Where(element => element.Type == this._contractType).Count() == 0)
  44:             {
  45:                 container.RegisterType(this._contractType, instanceContext.Host.Description.ServiceType);
  46:             } 
  47:  
  48:             return container.Resolve(this._contractType);
  49:         } 
  50:  
  51:         public object GetInstance(InstanceContext instanceContext)
  52:         {
  53:             return this.GetInstance(instanceContext, null);
  54:         } 
  55:  
  56:         public void ReleaseInstance(InstanceContext instanceContext, object instance)
  57:         {
  58:             IDisposable disposable = instance as IDisposable;
  59:             if (disposable != null)
  60:             {
  61:                 disposable.Dispose();
  62:             }
  63:         } 
  64:  
  65:         #endregion
  66:     }
  67: } 
  68:  

我们来简单讨论一下上面的逻辑。首先Unity常用的做法就根据Interface或者abstract class来进行对象的创建,从而实现对抽象的依赖,而Interface或者abstract class和concrete type之间的mapping关系可以通过配置或者代码注册。对于service instace来说,这个Interface就是ServiceContract。而Unity通过一个叫做UnityContainer对象创建具体的对象和进行生命周期的管理,Container是一个囊括了所有对象创建和生命周期管理所需资源的容器。这些资源包括:Interface或者abstract class和concrete type之间的mapping关系;Dependency Injection的定义;Extensions等等。UnityContainer之间可以嵌套从而形成一个树状结构,我们可以通过一个ID来定位我们需要的container。所以我们定义了两个field:_contractType和_containerName。

   1: private Type _contractType;
   2: private string _containerName; 

GetInstance方式提供了service instance创建的实现。具体是这样做的:

   1: UnityConfigurationSection unitySection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
   2: if (unitySection == null)
   3: {
   4:     throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture,   Resources.MissUnityConfiguration));
   5: } 

在配置文件中找到unity的配置,如何没有找到抛出ConfigurationErrorsException异常。

   1: IUnityContainer container = new UnityContainer();
   2: UnityContainerElement containerElement;
   3: if (string.IsNullOrEmpty(this._containerName))
   4: {
   5:       containerElement = unitySection.Containers.Default;
   6: }
   7: else
   8: {
   9:       containerElement = unitySection.Containers[this._containerName];
  10: }
  11: containerElement.Configure(container);
  12:  

然后创建UnityContainer对象,然后通过配置的信息对我们创建的UnityContainer进行配置。如何我们没有制定container name,使用默认的配置节,否则使用container name制定的配置节。

   1: UnityTypeElement[] unityTypeElements = Array.CreateInstance(typeof(UnityTypeElement), containerElement.Types.Count) as UnityTypeElement[];
   2: containerElement.Types.CopyTo(unityTypeElements, 0); 
   3:  
   4: if (unityTypeElements.Where(element => element.Type == this._contractType).Count() == 0)
   5: {
   6:      container.RegisterType(this._contractType, instanceContext.Host.Description.ServiceType);
   7: } 

因为很有可能在unity的配置中,并没有ServiceContract type对应的配置项,在这种情况下,我将自动注册ServiceContract 和ServiceType的匹配关系,ServiceType通过instanceContext.Host.Description.ServiceType获得。

最后通过UnityContainer的Resolve创建service instance。

   1: return container.Resolve(this._contractType);

三、创建UnityInstanceProvider对应的Behavior

InstanceProvider可以通过ContractBehavior来指定,也可以通过EndpointBehavior来指定,我们首先创建ContractBehavior:UnityBehaviorAttribute。由于ContractBehavior通过Custom Attribute的形式指定,所以UnityBehaviorAttribute是一个attribute.

   1: namespace Artech.WCFExtensions
   2: {
   3:     public class UnityBehaviorAttribute:Attribute, IContractBehavior
   4:     {
   5:         public string ContainerName
   6:         { get; set; } 
   7:  
   8:         #region IContractBehavior Members 
   9:  
  10:         public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  11:         {} 
  12:  
  13:         public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  14:         {} 
  15:  
  16:         public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
  17:         {
  18:            dispatchRuntime.InstanceProvider = new UnityInstanceProvider(contractDescription.ContractType, this.ContainerName);
  19:         } 
  20:  
  21:         public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
  22:         {} 
  23:  
  24:         #endregion
  25:     }
  26: } 
  27:  

需要做的仅仅是在ApplyDispatchBehavior中,将当前的DispatchRuntime的InstanceProvider 指定成我们的UnityInstanceProvider。

我们再定义一下EndpointBehavior:UnityBehavior。

   1: namespace Artech.WCFExtensions
   2: {
   3:    public class UnityBehavior: IEndpointBehavior
   4:     {
   5:        private string _containerName; 
   6:  
   7:        public UnityBehavior(string containerName)
   8:        { 
   9:         this._containerName = containerName;
  10:        } 
  11:  
  12:         #region IEndpointBehavior Members 
  13:  
  14:         public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  15:         {} 
  16:  
  17:         public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  18:         {} 
  19:  
  20:         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  21:         {
  22:            endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(endpoint.Contract.ContractType, this._containerName);
  23:         } 
  24:  
  25:         public void Validate(ServiceEndpoint endpoint)
  26:         {} 
  27:  
  28:         #endregion
  29:     }
  30: } 
  31:  

在ApplyDispatchBehavior,通过endpointDispatcher.DispatchRuntime获得当前的DispatchRuntime对象,并将InstanceProvider我们的UnityInstanceProvider。

最后为了UnityBehavior定义BehaviorExtensionElement:

   1: namespace Artech.WCFExtensions
   2: {
   3:     public class UnityBehaviorElement: BehaviorExtensionElement
   4:     {
   5:         [ConfigurationProperty("containerName", IsRequired = false, DefaultValue = "")]
   6:         public string ContainerName
   7:         {
   8:             get
   9:             {
  10:                 return this["containerName"] as string;
  11:             }
  12:             set
  13:             {
  14:                 this["containerName"] = value;
  15:             }
  16:         } 
  17:  
  18:         public override Type BehaviorType
  19:         {
  20:             get
  21:             {
  22:                 return typeof(UnityBehavior);
  23:             }
  24:         } 
  25:  
  26:         protected override object CreateBehavior()
  27:         {
  28:             return new UnityBehavior(this.ContainerName);
  29:         }
  30:     }
  31: } 
  32:  

添加一个ContainerName的配置项,在配置文件中指定。

四、应用我们的UnityInstanceProvider

我们现在将我们上面所做的所有工作应用到具体的WCF调用场景中。为此我们创建了一个MessageService的例子(根据Message的Key的Culture返回具体的message的内容),这个例子在本系列第五部分中介绍通过WCF extension实现Localization中介绍过。

这是我们经典的四层结构:

WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成

I、Artech.Messages.Contract:

   1: namespace Artech.Messages.Contract
   2: {
   3:     [ServiceContract]
   4:    [UnityBehavior(ContainerName ="wcfservice")]
   5:     public interface IMessage
   6:     {
   7:         [OperationContract]
   8:         string GetMessage(string key);
   9:     }
  10: } 

在IMessage上应用了我们的ContractBehavor:UnityBehavior,同时指定container name为:wcfservice。

II、Artech.Messages.Service

   1: namespace Artech.Messages.Service
   2: {
   3:     public class MessageService:IMessage
   4:     {
   5:        [Dependency]
   6:         public IMessageManager MessageManager
   7:         { get; set; } 
   8:  
   9:        [InjectionMethod]
  10:         public void Initialize()
  11:         {
  12:             Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
  13:             Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
  14:         } 
  15:  
  16:         #region IMessage Members 
  17:  
  18:         public string GetMessage(string key)
  19:         {
  20:             return this.MessageManager.GetMessage(key, Thread.CurrentThread.CurrentUICulture);
  21:         } 
  22:  
  23:         #endregion
  24:     } 
  25:  
  26:     public interface IMessageManager
  27:     {
  28:         string GetMessage(string key, CultureInfo culture, params object[] parameters);
  29:     } 
  30:  
  31:     public class MessageManager : IMessageManager
  32:     {
  33:         public ResourceManager ResourceManager
  34:         { get; set; } 
  35:  
  36:         public MessageManager()
  37:         {
  38:             this.ResourceManager =new  ResourceManager("Artech.Messages.Service.Properties.Resources", typeof(Resources).Assembly);
  39:         } 
  40:  
  41:         #region IMessageManager Members 
  42:  
  43:         public string GetMessage(string key, CultureInfo culture, params object[] parameters)
  44:         {
  45:             string message = ResourceManager.GetString(key, culture);
  46:             return string.Format(culture, message, parameters);
  47:         } 
  48:  
  49:         #endregion
  50:     }
  51: } 
  52:  

对于MessageService,需要着重介绍一下。因为使用到了一些Unity的Attribute。首先是MessageManager属性,它是一个Interface,上面标注了[Dependency],表明这是一个依赖属性。仔细看代码,此MessageManager并没有进行赋值的地方,而且此属性直接用在了GetMessage()方法上。实际上,对MessageManager进行初始化就是Unity container为我们实现的,在创建MessageService对象后,Unity container会来本container的范围了找到IMessageManager 找到与之复配的concrete type。

   1: [Dependency]
   2: public IMessageManager MessageManager
   3: { get; set; } 

对于我们创建出来的MessageService的对象,我们希望能够自动调用一些初始化的方法来进行一些初始化的工作,我们可以通过InjectionMethodAttribute来实现。在Initialize,我希望指定当前的culture为简体中文(我当前机器默认为en-US)

   1: [InjectionMethod]
   2: public void Initialize()
   3: {
   4:     Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
   5:     Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
   6: } 

而MessageManager 实现了IMessageManager ,提供了GetMessage的真正实现。Message存储在Resource温家中:

Default:

WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成

zh-CN:

WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成

III、Artech.Messages.Hosting

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
   5:   </configSections> 
   6:  
   7:   <system.serviceModel>
   8:        <services>
   9:       <service name="Artech.Messages.Service.MessageService">
  10:         <endpoint behaviorConfiguration="" binding="basicHttpBinding"
  11:             contract="Artech.Messages.Contract.IMessage" />
  12:         <host>
  13:           <baseAddresses>
  14:             <add baseAddress="http://127.0.0.1/messageservice" />
  15:           </baseAddresses>
  16:         </host>
  17:       </service>
  18:     </services>
  19:   </system.serviceModel> 
  20:  
  21:   <unity>
  22:     <containers>
  23:       <container name="wcfservice">
  24:         <types>
  25:               <type type="Artech.Messages.Service.IMessageManager,Artech.Messages.Service" mapTo="Artech.Messages.Service.MessageManager,Artech.Messages.Service" />
  26:         </types>
  27:       </container>
  28:     </containers>
  29:   </unity>
  30: </configuration> 
  31:  

在unity 配置节中,定义了名为wcfservice的container,该名称就是在ServiceContract在UnitBehavior中指定的参数。在该container中定了了IMessageManager和具体的type(MessageManager)之间的匹配。这解决了MessageService中MessageManager属性的实例化的问题。

IV、Artech.Messages.Client

   1: namespace Artech.Messages.Client
   2: {
   3:     class Program
   4:     {
   5:         static void Main(string[] args)
   6:         {
   7:             using (ChannelFactory<IMessage> channelFactory = new ChannelFactory<IMessage>("messageservice"))
   8:             {
   9:                 IMessage messageProxy = channelFactory.CreateChannel();
  10:                 Console.WriteLine(messageProxy.GetMessage("HelloWorld"));                
  11:             } 
  12:  
  13:             Console.Read();
  14:         }
  15:     }
  16: } 
  17:  

messageservice是配置的endpoint的名称。config文件就不列出来了。最后我们运行程序看最终的结果:

WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成

V、使用EndpointBehavior

上面我们是通过ContractBeahvior。现在我们将ServiceContract的UnityBehaviorAttribute去掉,在config中运用我们的EndpointBehavior:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
   5:   </configSections>
   6:   <system.serviceModel>
   7:     <behaviors>
   8:       <endpointBehaviors>
   9:        <behavior name="UnityBehavior">
  10:           <UnityBehaviorExtension containerName="wcfservice" />
  11:         </behavior>
  12:       </endpointBehaviors>
  13:     </behaviors>
  14:     <extensions>
  15:       <behaviorExtensions>
  16:        <add name="UnityBehaviorExtension" type="Artech.WCFExtensions.UnityBehaviorElement, Artech.WCFExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  17:       </behaviorExtensions>
  18:     </extensions>
  19:     <services>
  20:       <service name="Artech.Messages.Service.MessageService">
  21:         <endpoint behaviorConfiguration="UnityBehavior" binding="basicHttpBinding"
  22:             contract="Artech.Messages.Contract.IMessage" />
  23:         <host>
  24:           <baseAddresses>
  25:             <add baseAddress="http://127.0.0.1/messageservice" />
  26:           </baseAddresses>
  27:         </host>
  28:       </service>
  29:     </services>
  30:   </system.serviceModel>
  31:   <unity>
  32:     <containers>
  33:       <container name="wcfservice">
  34:         <types>
  35:           <type type="Artech.Messages.Service.IMessageManager,Artech.Messages.Service" mapTo="Artech.Messages.Service.MessageManager,Artech.Messages.Service" />
  36:         </types>
  37:       </container>
  38:     </containers>
  39:   </unity>
  40: </configuration>
  41:  

我们一样可以得到上面的结果。

 

WCF后续之旅:
WCF后续之旅(1): WCF是如何通过Binding进行通信的
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher
WCF后续之旅(4):WCF Extension Point 概览
WCF后续之旅(5): 通过WCF Extension实现Localization
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I]
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II]
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇]
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇]
WCF后续之旅(14):TCP端口共享
WCF后续之旅(15): 逻辑地址和物理地址
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter)
WCF后续之旅(17):通过tcpTracer进行消息的路由