WCF後續之旅(8):通過WCF Extension 實現與MS Enterprise Library Policy Injection Application Block的集成
在上一篇文章中,我們通過自定義InstanceProvider實現了WCF和微軟Enterprise Library Unity Application Block的集成,今天我們已相同的方式實現WCF與Enterprise Library的另一個Application Block的集成:Policy Injection Application Block (PIAB)。
PIAB,通過Method Interception的機制實現了AOP(Aspect Oriented Programing)。按照PIAB的編程方式,我們將非業務邏輯,比如Caching、Authorization、Transaction Enlist、Auditing、ExceptionHandling扽等等, 定義在一個個的CallHandler,這些CallHandler通過Attribute或者Configuration的方式應用到目標方法上。
由於PIAB特殊的實現機制(PIAB實現原理),我們需要通過PIAB的PolicyInjector來創建新的對象或者包裝現有的目標對象。只有調用這種能夠方式的對象,應用在上面的CallHandler才能被執行。所以WCF和PIAB的核心問題就是如何通過PIAB PolicyInjector來創建新的Service Instance,或者包裝已經生成的service instance。在上面一篇文章中,我們通過Unity Container重新定義了InstanceProvider,我們今天的實現方案也是通過自定義InstanceProvider的方式來實現,不是我們需需要通過PolicyInjector來進行對象的創建。
1、創建基於PolicyInjection的InstanceProvider
下面是我們新的InstanceProvider(PolicyInjectionInstanceProvider )的定義
namespace Artech.WCFExtensions
{
public class PolicyInjectionInstanceProvider : IInstanceProvider
{
private Type _serviceContractType;
private string _policyInjectorName;
public PolicyInjectionInstanceProvider(Type serviceContractType, string policyInjectorName)
{
this._serviceContractType = serviceContractType;
this._policyInjectorName = policyInjectorName;
}
#region IInstanceProvider Members
public object GetInstance(InstanceContext instanceContext, Message message)
{
PolicyInjector policyInjector = null;
if (string.IsNullOrEmpty(this._policyInjectorName))
{
policyInjector = new PolicyInjectorFactory().Create();
}
else
{
policyInjector = new PolicyInjectorFactory().Create(this._policyInjectorName);
}
Type serviceType = instanceContext.Host.Description.ServiceType;
object serviceInstance = Activator.CreateInstance(serviceType);
if (!this._serviceContractType.IsInterface && !serviceType.IsMarshalByRef && policyInjector is RemotingPolicyInjector)
{
return serviceInstance;
}
return policyInjector.Wrap(serviceInstance, this._serviceContractType);
}
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
}
}
我們對PolicyInjectionInstanceProvider 的實現進行簡單的說明:在PIAB中真正用於創建對象的是PolicyInjector,雖然PIAB中僅僅定義了一種基於Remoting的RemotingPolicyInjector,但是我們可以根據我們的需要實現一些不同Injection方式,比如IL Injection。所以我們定義了一個字段_policyInjectorName在配置中定位我們需要的PolicyInjector。該字段如果為null或者empty,將使用默認的PolicyInjector。PolicyInjection的獲取通過下面的代碼實現:
PolicyInjector policyInjector = null;
if (string.IsNullOrEmpty(this._policyInjectorName))
{
policyInjector = new PolicyInjectorFactory().Create();
}
else
{
policyInjector = new PolicyInjectorFactory().Create(this._policyInjectorName);
}
能夠被RemotingPolicyInjector創建的對象不是滿足下面兩個條件中的一個:
Target type實現一個Interface。
Target Type直接或者間接集成System.MarshalByRefObject.
所以如果不能滿足這個條件,我們直接通過反射創建service instance:
Type serviceType = instanceContext.Host.Description.ServiceType;
object serviceInstance = Activator.CreateInstance(serviceType);
if (!this._serviceContractType.IsInterface && !serviceType.IsMarshalByRef && policyInjector is RemotingPolicyInjector)
{
return serviceInstance;
}
最後我們通過policyInjector 的Wrap方法對service instance進行封裝並返回:
return policyInjector.Wrap(serviceInstance, this._serviceContractType);
2、為PolicyInjectionInstanceProvider創建Behavior
我們可以通過ContractBehavior或者EndpointBehavior應用我們定義的PolicyInjectionInstanceProvider 。
I、ContractBehavior:PolicyInjectionBehaviorAttribute
namespace Artech.WCFExtensions
{
public class PolicyInjectionBehaviorAttribute:Attribute, IContractBehavior
{
public string PolicyInjectorName
{ 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)
{
Type serviceContractType = contractDescription.ContractType;
dispatchRuntime.InstanceProvider = new PolicyInjectionInstanceProvider(serviceContractType, this.PolicyInjectorName);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{}
#endregion
}
}
我們在ApplyDispatchBehavior,通過contractDescription.ContractType獲得service contract type,然後創建我們的PolicyInjectionInstanceProvider, 並將其指定成當前DispatchRuntime 的InstanceProvider 。PolicyInjector通過屬性PolicyInjectorName進行設置。
II、Endpoint Behavior & Behavior Extension: PolicyInjectionBehavior
namespace Artech.WCFExtensions
{
public class PolicyInjectionBehavior:IEndpointBehavior
{
private string _policyInjectorName;
public PolicyInjectionBehavior(string policyInjectorName)
{
this._policyInjectorName = policyInjectorName;
}
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Type serviceContractType = endpoint.Contract.ContractType;
endpointDispatcher.DispatchRuntime.InstanceProvider = new PolicyInjectionInstanceProvider(serviceContractType, this._policyInjectorName);
}
public void Validate(ServiceEndpoint endpoint)
{}
#endregion
}
}
當前DispatchRuntime的InstanceProvider 在ApplyDispatchBehavior方法中指定,PolicyInjectorName通過配置文件配置。該配置節通過下面的PolicyInjectionBehaviorElement定義:
namespace Artech.WCFExtensions
{
public class PolicyInjectionBehaviorElement:BehaviorExtensionElement
{
[ConfigurationProperty("policyInjectorName",IsRequired = false, DefaultValue = "")]
public string PolicyInjectorName
{
get
{
return this["policyInjectorName"] as string;
}
set
{
this["policyInjectorName"] = value;
}
}
public override Type BehaviorType
{
get { return typeof(PolicyInjectionBehavior); }
}
protected override object CreateBehavior()
{
return new PolicyInjectionBehavior(this.PolicyInjectorName);
}
}
}
3、應用我們的PolicyInjectionBehavior
現在模擬一個WCF的場景來應用我們創建的PolicyInjectionBehavior。為了直觀我們我們創建一個Timeservice,用於返回當前的系統之間,然後我們運用PIAB的CachingCallHandler。那麼我們可以通過返回值是否反映真正的當前時間來判斷Policy Injection是否起作用了。
我們依然采用我們的4層結構程序構架:
I、Artech.TimeService.Contract
namespace Artech.TimeService.Contract
{
[ServiceContract]
[PolicyInjectionBehavior]
public interface ITime
{
[OperationContract]
DateTime GetCurrentTime();
}
}
我們先試驗ContractBehavior,我們僅僅需要將PolicyInjectionBehaviorAttribute應用到ServiceContract上。
II、Artech.TimeService.Service
namespace Artech.TimeService.Service
{
public class TimeService:ITime
{
#region ITime Members
[CachingCallHandler]
public DateTime GetCurrentTime()
{
return DateTime.Now;
}
#endregion
}
}
我們在GetCurrentTime方法上應用了CachingCallHandlerAttribute,那麼在第一次執行該方法的時候,方法返回的結果會被緩存,緩存的Key將會是方法和參數值的列表。後續的執行,將會直接從Cache中獲取已經執行過的結果。
III、Artech.TimeService.Hosting
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Artech.TimeService.Service.TimeService">
<endpoint behaviorConfiguration="" binding="basicHttpBinding"
contract="Artech.TimeService.Contract.ITime" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/timeservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
namespace Artech.TimeService.Hosting
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(Artech.TimeService.Service.TimeService)))
{
host.Opened += delegate
{
Console.WriteLine("Time service has been started up!");
};
host.Open();
Console.Read();
}
}
}
}
IV、Artech.TimeService.Client
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://127.0.0.1/timeservice" binding="basicHttpBinding"
contract="Artech.TimeService.Contract.ITime" name="timeservice" />
</client>
</system.serviceModel>
</configuration>
namespace Artech.TimeService.Client
{
class Program
{
static void Main(string[] args)
{
using (ChannelFactory<ITime> channelFactory = new ChannelFactory<ITime>("timeservice"))
{
ITime proxy = channelFactory.CreateChannel();
for (int i = 0; i < 10; i++)
{
Console.WriteLine(proxy.GetCurrentTime());
Thread.Sleep(1000);
}
}
Console.Read();
}
}
}
下面是最終輸出的結果:
從返回的時間都是相同的,我們可以確認caching發揮了作用,如何我們將Contract上[PolicyInjectionBehavior]注釋掉。
namespace Artech.TimeService.Contract
{
[ServiceContract]
//[PolicyInjectionBehavior]
public interface ITime
{
[OperationContract]
DateTime GetCurrentTime();
}
}
我們將會得到這樣的結果:
上面我們演示了ContractBehavior的應用,我們接著來演示EndpointBehavior的應用。我們僅僅需要修改Hosting的cnonfiguration就可以了:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="PolicyInjectionBehavior">
<PolicyInjectionBehaviorExtension />
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="PolicyInjectionBehaviorExtension" type="Artech.WCFExtensions.PolicyInjectionBehaviorElement, Artech.WCFExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="Artech.TimeService.Service.TimeService">
<endpoint behaviorConfiguration="PolicyInjectionBehavior" binding="basicHttpBinding"
contract="Artech.TimeService.Contract.ITime" />
<host>
<baseAddresses>
<add baseAddress="http://127.0.0.1/timeservice" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
此時運行我們的程序一樣可以得到被返回值被Cache的結果:
本文配套源碼