松耦合、高內聚是我們進行設計的永恆的目標,如何實現這樣的目標呢?我們有很多實現的方式和方法,不管這些方式和方法在表現形式上有什麼不同,他們的思想都可以表示為:根據穩定性進行關注點的分離或者分解,交互雙方依賴於一個穩定的契約,而降低對對方非穩定性因素的依賴。從抽象和穩定性的關系來講,抽象的程度和穩定程度成正相關關系。由此才有了我們面向抽象編程的說法,所以“只有依賴於不變,才能應萬變”。
然後,對於面向對象的思想來講,我們的功能通過一個個具體的對象來承載。對象是具體的,不是抽象的;創建對象是必然的;對象的創建從某種程度上即使對面向抽象的違背。所以模塊之間的耦合度在很大程度上是由於對象創建的方式決定的,而在對象創建過程實現解耦是實現我們“松耦合、高內聚”目標的一個重要途徑。對於這一點,我們可以看看我們常用的設計模式中有多少是用於解決如何合理進行對象創建的就可以知道了。Enterprise Library推出的新的Application Block:Unity Application Block為我們提供了一個很好的、可擴展的框架,幫助我們合理、有效的創建對象,並解決創建對象中的依賴。而通過WCF一個簡單的擴展對象,就可以很容易地實現和Unity的集成。
1、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 的網站。
2、實現基於Unity的IntanceProvider
在本系列的第三部分對Dispachter的介紹,和第四部分對WCF可擴展點的介紹中,我提到了一個重要的對象InstanceProvider, 該對象用於service instance的創建。既然Unity的根本目的是創建對象,我們就可以自定義InstanceProvider,讓Unity來幫助創建service instance,很容易地實現了和Unity的集成。
下面是我們的自定義InstanceProvider:UnityInstanceProvider
namespace Artech.WCFExtensions
{
public class UnityInstanceProvider: IInstanceProvider
{
private Type _contractType;
private string _containerName;
public UnityInstanceProvider(Type contractType, string containerName)
{
if (contractType == null)
{
throw new ArgumentNullException("contractType");
}
this._containerName = containerName;
this._contractType = contractType;
}
#region IInstanceProvider Members
public object GetInstance(InstanceContext instanceContext, Message message)
{
UnityConfigurationSection unitySection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
if (unitySection == null)
{
throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture, Resources.MissUnityConfiguration));
}
IUnityContainer container = new UnityContainer();
UnityContainerElement containerElement;
if (string.IsNullOrEmpty(this._containerName))
{
containerElement = unitySection.Containers.Default;
}
else
{
containerElement = unitySection.Containers[this._containerName];
}
containerElement.Configure(container);
UnityTypeElement[] unityTypeElements = Array.CreateInstance(typeof(UnityTypeElement), containerElement.Types.Count) as UnityTypeElement[];
containerElement.Types.CopyTo(unityTypeElements, 0);
if (unityTypeElements.Where(element => element.Type == this._contractType).Count() == 0)
{
container.RegisterType(this._contractType, instanceContext.Host.Description.ServiceType);
}
return container.Resolve(this._contractType);
}
public object GetInstance(InstanceContext instanceContext)
{
return this.GetInstance(instanceContext, null);
}
public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
IDisposable disposable = instance as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
#endregion
}
}
我們來簡單討論一下上面的邏輯。首先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。
private Type _contractType;
private string _containerName;
GetInstance方式提供了service instance創建的實現。具體是這樣做的:
UnityConfigurationSection unitySection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
在配置文件中找到unity的配置,如何沒有找到拋出ConfigurationErrorsException異常。
if (unitySection == null)
{
throw new ConfigurationErrorsException(string.Format(CultureInfo.CurrentCulture, Resources.MissUnityConfiguration));
}
IUnityContainer container = new UnityContainer();
UnityContainerElement containerElement;
if (string.IsNullOrEmpty(this._containerName))
{
containerElement = unitySection.Containers.Default;
}
else
{
containerElement = unitySection.Containers[this._containerName];
}
containerElement.Configure(container);
然後創建UnityContainer對象,然後通過配置的信息對我們創建的UnityContainer進行配置。如何我們沒有制定container name,使用默認的配置節,否則使用container name制定的配置節。
UnityTypeElement[] unityTypeElements = Array.CreateInstance(typeof(UnityTypeElement), containerElement.Types.Count) as UnityTypeElement[];
containerElement.Types.CopyTo(unityTypeElements, 0);
if (unityTypeElements.Where(element => element.Type == this._contractType).Count() == 0)
{
container.RegisterType(this._contractType, instanceContext.Host.Description.ServiceType);
}
因為很有可能在unity的配置中,並沒有ServiceContract type對應的配置項,在這種情況下,我將自動注冊ServiceContract 和ServiceType的匹配關系,ServiceType通過instanceContext.Host.Description.ServiceType獲得。
最後通過UnityContainer的Resolve創建service instance。
return container.Resolve(this._contractType);
3、創建UnityInstanceProvider對應的Behavior
InstanceProvider可以通過ContractBehavior來指定,也可以通過EndpointBehavior來指定,我們首先創建ContractBehavior:UnityBehaviorAttribute。由於ContractBehavior通過Custom Attribute的形式指定,所以UnityBehaviorAttribute是一個attribute.
namespace Artech.WCFExtensions
{
public class UnityBehaviorAttribute:Attribute, IContractBehavior
{
public string ContainerName
{ get; set; }
#region IContractBehavior Members
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InstanceProvider = new UnityInstanceProvider(contractDescription.ContractType, this.ContainerName);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{}
#endregion
}
}
需要做的僅僅是在ApplyDispatchBehavior中,將當前的DispatchRuntime的InstanceProvider 指定成我們的UnityInstanceProvider。
我們再定義一下EndpointBehavior:UnityBehavior。
namespace Artech.WCFExtensions
{
public class UnityBehavior: IEndpointBehavior
{
private string _containerName;
public UnityBehavior(string containerName)
{
this._containerName = containerName;
}
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(endpoint.Contract.ContractType, this._containerName);
}
public void Validate(ServiceEndpoint endpoint)
{}
#endregion
}
}
在ApplyDispatchBehavior,通過endpointDispatcher.DispatchRuntime獲得當前的DispatchRuntime對象,並將InstanceProvider我們的UnityInstanceProvider。
最後為了UnityBehavior定義BehaviorExtensionElement:
namespace Artech.WCFExtensions
{
public class UnityBehaviorElement: BehaviorExtensionElement
{
[ConfigurationProperty("containerName", IsRequired = false, DefaultValue = "")]
public string ContainerName
{
get
{
return this["containerName"] as string;
}
set
{
this["containerName"] = value;
}
}
public override Type BehaviorType
{
get
{
return typeof(UnityBehavior);
}
}
protected override object CreateBehavior()
{
return new UnityBehavior(this.ContainerName);
}
}
}
添加一個ContainerName的配置項,在配置文件中指定。
4、應用我們的UnityInstanceProvider
我們現在將我們上面所做的所有工作應用到具體的WCF調用場景中。為此我們創建了一個MessageService的例子(根據Message的Key的Culture返回具體的message的內容),這個例子在本系列第五部分中介紹通過WCF extension實現Localization中介紹過。
這是我們經典的四層結構:
I、Artech.Messages.Contract:
namespace Artech.Messages.Contract
{
IMessage上應用了我們的ContractBehavor:UnityBehavior,同時指定container name為:wcfservice。
[ServiceContract]
[UnityBehavior(ContainerName ="wcfservice")]
public interface IMessage
{
[OperationContract]
string GetMessage(string key);
}
}
在
II、Artech.Messages.Service
namespace Artech.Messages.Service
{
public class MessageService:IMessage
{
[Dependency]
public IMessageManager MessageManager
{ get; set; }
[InjectionMethod]
public void Initialize()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
}
#region IMessage Members
public string GetMessage(string key)
{
return this.MessageManager.GetMessage(key, Thread.CurrentThread.CurrentUICulture);
}
#endregion
}
public interface IMessageManager
{
string GetMessage(string key, CultureInfo culture, params object[] parameters);
}
public class MessageManager : IMessageManager
{
public ResourceManager ResourceManager
{ get; set; }
public MessageManager()
{
this.ResourceManager =new ResourceManager("Artech.Messages.Service.Properties.Resources", typeof(Resources).Assembly);
}
#region IMessageManager Members
public string GetMessage(string key, CultureInfo culture, params object[] parameters)
{
string message = ResourceManager.GetString(key, culture);
return string.Format(culture, message, parameters);
}
#endregion
}
}
對於MessageService,需要著重介紹一下。因為使用到了一些Unity的Attribute。首先是MessageManager屬性,它是一個Interface,上面標注了[Dependency],表明這是一個依賴屬性。仔細看代碼,此MessageManager並沒有進行賦值的地方,而且此屬性直接用在了GetMessage()方法上。實際上,對MessageManager進行初始化就是Unity container為我們實現的,在創建MessageService對象後,Unity container會來本container的范圍了找到IMessageManager 找到與之復配的concrete type。
[Dependency]
public IMessageManager MessageManager
{ get; set; }
對於我們創建出來的MessageService的對象,我們希望能夠自動調用一些初始化的方法來進行一些初始化的工作,我們可以通過InjectionMethodAttribute來實現。在Initialize,我希望指定當前的culture為簡體中文(我當前機器默認為en-US)
[InjectionMethod]public void Initialize()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
}
而MessageManager 實現了IMessageManager ,提供了GetMessage的真正實現。Message存儲在Resource溫家中:
Default:
zh-CN:
III、Artech.Messages.Hosting
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<system.serviceModel>
<services>
<service name="Artech.Messages.Service.MessageService">
<endpoint behaviorConfiguration="" binding="basicHttpBinding"
contract="Artech.Messages.Contract.IMessage" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/messageservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
<unity>
<containers>
<container name="wcfservice">
<types>
<type type="Artech.Messages.Service.IMessageManager,Artech.Messages.Service" mapTo="Artech.Messages.Service.MessageManager,Artech.Messages.Service" />
</types>
</container>
</containers>
</unity>
</configuration>
在unity 配置節中,定義了名為wcfservice的container,該名稱就是在ServiceContract在UnitBehavior中指定的參數。在該container中定了了IMessageManager和具體的type(MessageManager)之間的匹配。這解決了MessageService中MessageManager屬性的實例化的問題。
IV、Artech.Messages.Client
namespace Artech.Messages.Client
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<IMessage> channelFactory = new ChannelFactory<IMessage>("messageservice"))
{
IMessage messageProxy = channelFactory.CreateChannel();
Console.WriteLine(messageProxy.GetMessage("HelloWorld"));
}
Console.Read();
}
}
}
messageservice是配置的endpoint的名稱。config文件就不列出來了。最後我們運行程序看最終的結果:
V、使用EndpointBehavior
上面我們是通過ContractBeahvior。現在我們將ServiceContract的UnityBehaviorAttribute去掉,在config中運用我們的EndpointBehavior:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</configSections>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="UnityBehavior">
<UnityBehaviorExtension containerName="wcfservice" />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="UnityBehaviorExtension" type="Artech.WCFExtensions.UnityBehaviorElement, Artech.WCFExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="Artech.Messages.Service.MessageService">
<endpoint behaviorConfiguration="UnityBehavior" binding="basicHttpBinding"
contract="Artech.Messages.Contract.IMessage" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/messageservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
<unity>
<containers>
<container name="wcfservice">
<types>
<type type="Artech.Messages.Service.IMessageManager,Artech.Messages.Service" mapTo="Artech.Messages.Service.MessageManager,Artech.Messages.Service" />
</types>
</container>
</containers>
</unity>
</configuration>
我們一樣可以得到上面的結果。
本文配套源碼