在这之前,我写过深入介绍MS EnterLib PIAB的文章(参阅《MS Enterprise Library Policy Injection Application Block 深入解析[总结篇]》),也写过WCF与PIAB的集成(参阅:《WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成》)、WCF与Unity的集成(参阅《WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成》)以及Unity与PIAB的集成(参阅《Enterprise Library深入解析与灵活应用(1):通过Unity Extension实现和Policy Injection Application Block的集成》、《Enterprise Library深入解析与灵活应用(7):再谈PIAB与Unity之间的集成》)。由于部分实现时基于EnterLib、Unity前一个版本,在新的版本中(EnterLib V4.1与Unity 1.2)中,MS通过Unity对PIAB进行了重新设计与实现,所以我们很有必要重拾着这个话题,谈谈对于新的EnterLib和Unity,如何将PIAB和Unity集成到WCF之中。(Source Code从这里下载)
一、设计原理简述
在EnterLib中,PIAB与Unity的定位分别是轻量级的IoC Container(或者DI Container)与AOP框架。PIAB采用Method Call Interception的机制实现了策略的动态注入,其本身依赖于Interceptable对象的创建;UnityContainer建立在ObjectBuilder2之上,本质上是一个用于对象创建的容器。所以,我们可以通过UnityContainer按照PIAB的要求创建Interceptable对象,就能实现Unity与PIAB之间的集成(参阅《Enterprise Library深入解析与灵活应用(7):再谈PIAB与Unity之间的集成》)。
Unity与WCF之间的集成,本质上就是让WCF使用UnityContainer进行服务实例的创建。而WCF框架内部,服务实例的创建同时一个特殊的对象——InstanceProvider。所以我们可以通过自定义InstanceProvider,并借助UnityContainer进行服务实例的提供,那么就能实现Unity与WCF两者之间的集成。所以,创建基于UnityContainer的InstanceProvider是关键。
二、创建基于UnityContainer的InstanceProvider:UnityInstanceProvider
在WCF框架内部,InstanceProvider用户进行服务实例的提供。所有的InstanceProvider实现了接口System.ServiceModel.Dispatcher.IInstanceProvider,下面是IInstanceProvider的定义。服务实例提供实现在GetInstance中,而ReleaseInstance用于实现对服务实例的释放和资源回收。
1: public interface IInstanceProvider
2: {
4: object GetInstance(InstanceContext instanceContext);
5: object GetInstance(InstanceContext instanceContext, Message message);
6: void ReleaseInstance(InstanceContext instanceContext, object instance);
7: }
我们现在的目的就是创建一个基于Unity的InstanceProvider,借助UnityContainer提供对GetInstance方法的实现,我姑且把这个自定义的InstanceProvider称为UnityInstanceProvider。在正式介绍UnityInstanceProvider的具体实现之前,我先介绍一个辅助类型的定义:UnityTypeMapping。
我们知道,UnityContainer采用动态注册接口或者抽象类于具体类型的匹配关系,使得我们可以利用UnityContaner实现基于接口或者抽象类的方式创建我们希望的具体类的对象。UnityTypeMapping用以描述类型的匹配,其定义如下。Type和Mapto分别表示相应的类型(接口或者抽象类)与被匹配的类型(具体类),Name则表示该Mapping Entry的名称(唯一标识)。
1: public class UnityTypeMapping
2: {
3: public Type Type
4: { get; set; }
5:
6: public Type MapTo
7: { get; set; }
8:
9: public string Name
0: { get; set; }
1: }
Unity可以采用编程和配置的方式实现类型的匹配,在真正的系统开发中,后者是首选。为了实现类型匹配配置(UnityTypeElementCollection)到我们定义的UnityTypeMapping列表(IList<UnityTypeMapping>)之间的转化,我定义了下面一个扩展方法(Extension Method):Copy。
1: public static class Extension
2: {
3: public static IList<UnityTypeMapping> Copy(this UnityTypeElementCollection unityTypeElements)
4: {
5: IList<UnityTypeMapping> mappings = new List<UnityTypeMapping>();
6: foreach (UnityTypeElement type in unityTypeElements)
7: {
8: mappings.Add(new UnityTypeMapping { Type = type.Type, MapTo = type.MapTo, Name = type.Name });
9: }
0:
1: return mappings;
2: }
3: }
下面列出了UnityInstanceProvider的具体定义。属性ContractType与Container分别代表服务契约与用于创建服务实例的UnityContainer对象,字段_registeredTypeMapping表示当前UnityContainer相关的类型匹配集合。出于性能的考虑,为了避免UnityContainer的频繁创建和类型匹配关系的频繁解析,我通过两个静态属性|字段来保存它们(Containers和registeredTypeMappings,Key为Container的名称)。在构造函数中接受两个输入参数:contractType与containerName,分别表示服务契约类型与相应UnityContainer的名称。根据containerName判断相应的UnityContainer是否已经创建,如果是,则直接从上述的两个静态变量中提取相应的UnityContainer和类型匹配列表。否则,重新创建UnityContainer,加载相应的配置信息对其进行配置。需要特别指出的是,在对创建的UnityContainer进行初始化的时候,添加了一个特殊的UnityContainerExtension:ExtendedIntercepiton,该UnityContainerExtension用户实现Unity与PIAB的集成,在《Enterprise Library深入解析与灵活应用(7):再谈PIAB与Unity之间的集成》中对ExtendedIntercepiton的实现原理具有详细的介绍。
在GetInstance方法中,我们通过UnityContainer根据服务契约(接口)类新进行具体服务实例的创建。在创建之前,我们需要判断服务契约类型与服务类型之间的类型匹配是否已经注册到UnityContainer中,如果没有,则进行注册,并将类型匹配添加到当前类型匹配列表(_registeredTypeMappings)和全局类型匹配列表(registeredTypeMappings)中。
1: public class UnityInstanceProvider : IInstanceProvider
2: {
3: private static object syncHelper = new object();
4: private static IDictionary<string, IList<UnityTypeMapping>> registeredTypeMappings;
5: public static IDictionary<string, IUnityContainer> Containers
6: { get; private set; }
7:
8: private IList<UnityTypeMapping> _registeredTypeMappings;
9: public Type ContractType
10: { get; private set; }
11: public IUnityContainer Container
12: { get; private set; }
13:
14: static UnityInstanceProvider()
15: {
16: registeredTypeMappings = new Dictionary<string, IList<UnityTypeMapping>>();
17: Containers = new Dictionary<string, IUnityContainer>();
18: }
19:
20: public UnityInstanceProvider(Type contractType, string containerName)
21: {
22: if (contractType == null)
23: {
24: throw new ArgumentNullException("contractType");
25: }
26:
27: this.ContractType = contractType;
28:
29: string key = containerName ?? string.Empty;
30: if (Containers.ContainsKey(key))
31: {
32: this.Container = Containers[key];
33: this._registeredTypeMappings = registeredTypeMappings[key];
34: return;
35: }
36:
37: UnityContainerElement containerElement = this.GetUnitySettings(containerName);
38: IUnityContainer container = new UnityContainer();
39: if (null != containerElement)
40: {
41: containerElement.Configure(container);
42: }
43: container.AddNewExtension<ExtendedInterception>();
44: PolicyInjectionSettings section = (PolicyInjectionSettings)ConfigurationSourceFactory.Create().GetSection("policyInjection");
45: if (section != null)
46: {
47: section.ConfigureContainer(this.Container, ConfigurationSourceFactory.Create());
48: }
49: lock (syncHelper)
50: {
51: if (!Containers.ContainsKey(key))
52: {
53: Containers[key] = container;
54: registeredTypeMappings[key] = containerElement.Types.Copy();
55: }
56: }
57:
58: this.Container = container;
59: this._registeredTypeMappings = registeredTypeMappings[key];
60: }
61:
62: #region IInstanceProvider Members
63:
64: public object GetInstance(InstanceContext instanceContext, Message message)
65: {
66: string contractServiceTypeMappingName = string.Empty;
67: var contractServiceTypeMappings = from mapping in this._registeredTypeMappings
68: where mapping.Type == this.ContractType &&
69: mapping.MapTo == instanceContext.Host.Description.ServiceType
70: select mapping;
71: if (contractServiceTypeMappings.Count() == 0)
72: {
73: contractServiceTypeMappingName = Guid.NewGuid().ToString();
74: this.Container.RegisterType(this.ContractType, instanceContext.Host.Description.ServiceType, contractServiceTypeMappingName);
75: this._registeredTypeMappings.Add(new UnityTypeMapping { Type = this.ContractType, MapTo = instanceContext.Host.Description.ServiceType, Name = contractServiceTypeMappingName });
76: }
77: else
78: {
79: contractServiceTypeMappingName = contractServiceTypeMappings.ToArray<UnityTypeMapping>()[0].Name;
80: }
81:
82: return this.Container.Resolve(this.ContractType, contractServiceTypeMappingName);
83: }
84:
85: private UnityContainerElement GetUnitySettings(string containerName)
86: {
87: UnityConfigurationSection unitySection = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;
88: if (unitySection == null)
89: {
90: return null;
91: }
92:
93: if (string.IsNullOrEmpty(containerName))
94: {
95: return unitySection.Containers.Default;
96: }
97: else
98: {
99: return unitySection.Containers[containerName];
100: }
101: }
102:
103: public object GetInstance(InstanceContext instanceContext)
104: {
105: return this.GetInstance(instanceContext, null);
106: }
107:
108: public void ReleaseInstance(InstanceContext instanceContext, object instance)
109: {
110: IDisposable disposable = instance as IDisposable;
111: if (disposable != null)
112: {
113: disposable.Dispose();
114: }
115: }
116:
117: #endregion
118: }
三、为UnityInstanceProvider创建Behavor对象
自定义行为(Behavior)是进行WCF扩张最为典型和常用的方式。按照作用域的不同,WCF的行为可以分为以下四类:契约行为(Contract Behavior)、服务行为(Service Contract)、终结点行为(Endpoint Behavior)和操作行为(Operation Behavior)。为了将上面自定义的UnityInstanceProvider应用到WCF服务端的分发系统,定义了如下一个行为类型:UnityIntegrationBehaviorAttribute。我们可以看出,UnityIntegrationBehaviorAttribute同时实现了IServiceBehavior、IContractBehavior和IEndpointBehavior,所以既是一个服务行为,也是一个契约行为,同时还是一个终结点行为。同时UnityIntegrationBehaviorAttribute继承了Attribbute,所以同时可以以Attribute的形式应用到服务契约(作为契约行为)类型和服务(作为服务类型)。你同样可以通过配置的方式以服务行为和终结点行为的方式应用该UnityIntegrationBehaviorAttribute。如果想采用配置的方式,你还需要定义相关的BehaviorExtensionElement,由于篇幅的问题,我就不就对BehaviorExtensionElement的问题作介绍了。
1: using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace Artech.UnityIntegration
{
public class UnityIntegrationBehaviorAttribute : Attribute, IServiceBehavior, IContractBehavior,IEndpointBehavior
{
public string ContainerName
{ get; set; }
#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
#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(endpoint.Contract.ContractType, this.ContainerName);
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
#endregion
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(GetContractType(serviceHostBase, endpointDispatcher), this.ContainerName);
}
}
}
private Type GetContractType(ServiceHostBase serviceHostBase, EndpointDispatcher endpointDispatcher)
{
var endpoint = serviceHostBase.Description.Endpoints.Where(item => item.Contract.Name == endpointDispatcher.ContractName &&
item.Contract.Namespace == endpointDispatcher.ContractNamespace).ToArray<ServiceEndpoint>()[0];
return endpoint.Contract.ContractType;
}
public void Validate(ServiceDescription serviceDescription,ServiceHostBase serviceHostBase)
{
}
#endregion
}
}
四、将UnityIntegrationBehavior应用到WCF应用中
为了演示UnityIntegrationBehavior的效果,我们创建了一个简单的WCF实例应用。我们采用《Enterprise Library深入解析与灵活应用(7):再谈PIAB与Unity之间的集成》中同步时间提供的例子,通过一个服务得到同步的当前时间。下面是服务契约的定义:
1: using System;
2: using System.ServiceModel;
3: namespace Artech.UnityIntegrationDemo.Contracts
4: {
5: [ServiceContract(Namespace="urn:artech.com")]
6: public interface ISyncTimeProvision
7: {
8: [OperationContract]
9: DateTime GetCurrentTime();
0: }
1: }
实现了该服务契约的SyncTimeProvisionService本是并不具体实现不同时间的提供,而是通过另一个组件SyncTimeProvider。你可以将SyncTimeProvider看成是同一个应用的另一个模块,将此例子看成是一个典型的跨模块调用。为了实现真正的模块化,达到模块之间的松耦合,我们借助Unity,采用“属性注入(Propetry Setter Injection)”的方式,通过接口的方式(ISyncTimeProvider)调用另一个模块。为了证实PIAB的效果,我在SyncTimeProvider上面应用了CachingCallHandlerAttribute,如果该CallHandler生效的话,方法返回的结果将会被缓存,在缓存过期之前,你将得到相同的时间。而我们定义的UnityIntegrationBehaviorAttribute以服务行为的方式直接应用到服务类型(SyncTimeProvisionService)上。
1: using System;
2: using Artech.UnityIntegration;
3: using Artech.UnityIntegrationDemo.Contracts;
4: using Microsoft.Practices.Unity;
5:
6: namespace Artech.UnityIntegrationDemo.Services
7: {
8: [UnityIntegrationBehavior(ContainerName="myContainer")]
9: public class SyncTimeProvisionService : ISyncTimeProvision
10: {
12: [Dependency]
13: public ISyncTimeProvider SyncTimeProvider
14: { get; set; }
15:
16: #region ISyncTimeProvision Members
17:
18: public DateTime GetCurrentTime()
19: {
20: return this.SyncTimeProvider.GetCurrentTime();
21: }
22:
23: #endregion
24: }
25:
26: public interface ISyncTimeProvider
27: {
28: DateTime GetCurrentTime();
29: }
30:
31: [CachingCallHandler]
32: public class SyncTimeProvider : ISyncTimeProvider
33: {
34: #region ISyncTimeProvider Members
35:
36: public DateTime GetCurrentTime()
37: {
38: return DateTime.Now;
39: }
40:
41: #endregion
42: }
43: }
在服务寄宿的配置中,提供了WCF服务和Unity的相关设置:
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.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
5: </configSections>
6: <system.serviceModel>
7: <services>
8: <service name="Artech.UnityIntegrationDemo.Services.SyncTimeProvisionService">
9: <endpoint address="http://127.0.0.1:3721/synctimeprovisionservice"
10: binding="ws2007HttpBinding" bindingConfiguration="" contract="Artech.UnityIntegrationDemo.Contracts.ISyncTimeProvision" />
11: </service>
12: </services>
13: </system.serviceModel>
14: <unity>
15: <typeAliases>
16: <typeAlias alias="ISyncTimeProvider" type="Artech.UnityIntegrationDemo.Services.ISyncTimeProvider, Artech.UnityIntegrationDemo.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
17: <typeAlias alias="SyncTimeProvider" type="Artech.UnityIntegrationDemo.Services.SyncTimeProvider, Artech.UnityIntegrationDemo.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
18: </typeAliases>
19: <containers>
20: <container name="myContainer">
21: <types>
22: <type type="ISyncTimeProvider" mapTo="SyncTimeProvider" />
23: </types>
24: </container>
25: </containers>
26: </unity>
27: </configuration>
当服务成功寄宿,在Console Appliation下,执行下面一段服务端调用程序,你将得到下面的输出。从输出结果中,我们可以清晰地看到,返回的5个返回的时间均是相同的,由此我们可以看出应用才SyncTimeProvider上面的CachingCallHandlerAttribute生效了。进而证明了PIAB和Unity、Unity和WCF的有效集成:
1: using System;
2: using System.ServiceModel;
3: using System.Threading;
4: using Artech.UnityIntegrationDemo.Contracts;
5:
6: namespace Artech.UnityIntegrationDemo.Client
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: using (ChannelFactory<ISyncTimeProvision> channelFactory = new ChannelFactory<ISyncTimeProvision>("synctimeprovisionservice"))
13: {
14: ISyncTimeProvision proxy = channelFactory.CreateChannel();
15: using (proxy as IDisposable)
16: {
17: try
18: {
19: for (int i = 0; i < 5; i++)
20: {
21: Console.WriteLine("The current time is {0}", proxy.GetCurrentTime());
22: Thread.Sleep(1000);
23: }
24: }
25: catch (CommunicationException)
26: {
27: (proxy as ICommunicationObject).Abort();
28: throw;
29: }
30: catch (TimeoutException)
31: {
32: (proxy as ICommunicationObject).Abort();
33: throw;
34: }
35: }
36: }
37:
38: Console.Read();
39: }
40: }
41: }
42:
执行结果:
注:部分内容节选自《WCF技术剖析(卷1)》第十章: WCF实例研究(WCF in Practice)