WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成
Posted on 2010-04-30 12:58 Happy Coding 阅读(227) 评论(0) 编辑 收藏 举报在上一篇文章中,我们通过自定义InstanceProvider实现了WCF和微软Enterprise Library Unity ApplicationBlock的集成, 今天我们已相同的方式实现WCF与Enterprise Library的另一个ApplicationBlock的集成:Policy Injection Application Block (PIAB)。
PIAB,通过Method Interception的机制实现了AOP(Aspect OrientedPrograming)。按照PIAB的编程方式,我们将非业务逻辑,比如Caching、Authorization、TransactionEnlist、Auditing、ExceptionHandling扽等等,定义在一个个的CallHandler,这些CallHandler通过Attribute或者Configuration的方式应用到目标方法上。关于PIAB的详细介绍,我们参考我的PIAB系列(http://www.cnblogs.com/artech/archive/2008/01/29/1057379.html)。
由于PIAB特殊的实现机制(PIAB实现原理),我们需要通过PIAB的PolicyInjector来创建新的对象或者包装现有的目标对象。只有调用这种能够方式的对象,应用在上面的CallHandler才能被执行。所以WCF和PIAB的核心问题就是如何通过PIAB PolicyInjector来创建新的ServiceInstance,或者包装已经生成的service instance。在上面一篇文章中,我们通过UnityContainer重新定义了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 isRemotingPolicyInjector)
{
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方式,比如ILInjection。所以我们定义了一个字段_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 isRemotingPolicyInjector)
{
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 voidAddBindingParameters(ContractDescription contractDescription,ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{}
public voidApplyClientBehavior(ContractDescription contractDescription,ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{}
public voidApplyDispatchBehavior(ContractDescription contractDescription,ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
Type serviceContractType = contractDescription.ContractType;
dispatchRuntime.InstanceProvider = newPolicyInjectionInstanceProvider(serviceContractType,this.PolicyInjectorName);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{}
#endregion
}
}
我们在ApplyDispatchBehavior,通过contractDescription.ContractType获得service contracttype,然后创建我们的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 = newPolicyInjectionInstanceProvider(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的结果: