在《原理篇》中我们谈到了通过自定义ServiceHost对WCF进行扩展的本质,以及在IIS/WAS寄宿情况下ServiceHostFactory的作用。接下来通过一个具体的例子来演示如何通过WCF扩展实现以Unity为代表的IoC框架的集成,以及应用该扩展的ServiceHost和ServiceHostFactory如何定义。[源代码从这里下载]
目录
一、IoC/DI简介
步骤一、自定义InstanceProvider:UnityInstanceProvider
步骤二、创建服务行为:UnityServiceBehaviorAttribute
步骤三、自定义ServiceHost:UnityServiceHost
步骤四、自定义ServiceHostFactory:UnityServiceHostFactory
步骤五、创建实例程序应用自定义ServiceHost
一、IoC/DI简介
所谓控制反转(IoC: Inversion Of Control)就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转移到了外部IoC容器,控制权就实现了所谓的反转。比如,在类型A中需要使用类型B的实例,而B实例的创建并不由A来负责,而是通过外部容器来创建。
有时我们又将IoC成为依赖注入(DI: Dependency Injection)。所谓依赖注入,就是由外部容器在运行时动态地将依赖的对象注入到组件之中。具体的依赖注入方式又包括如下三种典型的形式。
- 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前会自定义创建相应参数对象;
- 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
- 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。
在开源社区,具有很有流行的IoC框架,比如Castle Windsor、Unity、Spring.NET、StructureMap、Ninject等。现在我们就以Unity为例,介绍通过WCF的扩展如何实现基于IoC的服务实例的创建。
步骤一、自定义InstanceProvider:UnityInstanceProvider
要实现WCF和Unity之间的集成,最终体现在如何通过Unity容器来创建服务实例。而从前面介绍的关于服务端运行时框架的介绍,我们知道最终服务实例的提供落在了一个特殊的组件之上,即InstanceProvider。所以,本实例的核心就是要自定义一个采用Unity实现服务实例提供机制的自定义InstanceProvider。我们将之命名为UnityInstanceProvider。
下面的代码给出了实现IInstanceProvider接口的UnityInstanceProvider的定义。在构造函数中指定连个参数:实现了IUnityContainer接口的Unity容器对象和服务契约类型。那么在真正实现对服务实例创建的GetInstance方法上,直接调用IUnityContainer的Resolve方法传入给定的服务契约类型来创建具体的人服务实例。而在ReleaseInstance方法中则直接调用IUnityContainer的Teardown方法进行服务实例的释放。注意,在这之前需要确保如下两个基于Unity的程序集被引用:Microsoft.Practices.Unity.dll和Microsoft.Practices.Unity.Configuration.dll。
1: using System;
2: using System.ServiceModel;
3: using System.ServiceModel.Channels;
4: using System.ServiceModel.Dispatcher;
5: using Microsoft.Practices.Unity;
6: namespace Artech.WcfExtensions.IoC
7: {
8: public class UnityInstanceProvider: IInstanceProvider
9: {
10: public IUnityContainer UnityContainer { get; private set; }
11: public Type ContractType { get; private set; }
12:
13: public UnityInstanceProvider(IUnityContainer unityContainer, Type contractType)
14: {
15: this.UnityContainer = unityContainer;
16: this.ContractType = contractType;
17: }
18:
19: public object GetInstance(InstanceContext instanceContext, Message message)
20: {
21: return this.UnityContainer.Resolve(this.ContractType);
22: }
23:
24: public object GetInstance(InstanceContext instanceContext)
25: {
26: return this.GetInstance(instanceContext, null);
27: }
28:
29: public void ReleaseInstance(InstanceContext instanceContext, object instance)
30: {
31: this.UnityContainer.Teardown(instance);
32: }
33: }
34: }
步骤二、创建服务行为:UnityServiceBehaviorAttribute
和前面一个关于客户端语言文化上下文自动传播的实例一样,我们自定义的组件最终通过相应的行为应用到WCF的运行时中。为此,我们针对上面自定义的InstanceProvider定义了一个实现IServiceBehavior接口的服务行为UnityServiceBehaviorAttribute。为了能让该服务行为以声明的方式直接应用到服务类型上,我们将其定义成特性。
UnityServiceBehaviorAttribute的所有定义体现在如下所示的代码片断中。构造函数中具有一个字符串类型的参数containerName表示配置的Unity容器的名称。真正的容器名称在构造函数中被获取,为了避免UnityConainter的频繁创建,我们定义了一个静态的以容器名称为键值的字典保存已经被创建的Unity容器。
我们最终的目的是根据给定名称的Unity容器创建上面定义的UnityInstanceProvider,然后将其作为基于终结点的DispatchRuntime的InstanceProvider,这样的功能定义在ApplyDispatchBehavior方法中。创建UnityInstanceProvider还需要服务契约的类型,而得到服务契约类型采用了这样的逻辑:首先根据当前EndpointDispatcher得到契约名称和命名空间,然后通过ServiceHostBase得到表示服务描述的ServiceDescription对象,然后根据前面得到的契约名称和命名空间找到对应的表示契约描述的ContractDescription对象,而该对象的ContractType属性表示服务契约的类型。此外,如果基于契约类型的注册不存在,ApplyDispatchBehavior方法还进行了服务契约类型和服务类型之间的类型注册。
1: using System;
2: using System.Collections.Generic;
3: using System.Collections.ObjectModel;
4: using System.Configuration;
5: using System.Linq;
6: using System.ServiceModel;
7: using System.ServiceModel.Channels;
8: using System.ServiceModel.Description;
9: using System.ServiceModel.Dispatcher;
10: using Microsoft.Practices.Unity;
11: using Microsoft.Practices.Unity.Configuration;
12:
13: namespace Artech.WcfExtensions.IoC
14: {
15: public class UnityServiceBehaviorAttribute: Attribute, IServiceBehavior
16: {
17: static Dictionary<string, IUnityContainer> containers = new Dictionary<string, IUnityContainer>();
18: public IUnityContainer UnityContainer { get; private set;}
19:
20: public UnityServiceBehaviorAttribute()
21: : this(string.Empty)
22: { }
23:
24: public UnityServiceBehaviorAttribute(string containerName)
25: {
26: containerName = containerName ?? string.Empty;
27: if (containers.ContainsKey(containerName))
28: {
29: this.UnityContainer = containers[containerName];
30: }
31: else
32: {
33: lock (typeof(UnityServiceBehaviorAttribute))
34: {
35: IUnityContainer container = new UnityContainer();
36: UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName);
37: if (containerName == string.Empty)
38: {
39: configuration.Configure(container);
40: }
41: else
42: {
43: configuration.Configure(container, containerName);
44: }
45: containers[containerName] = container;
46: this.UnityContainer = container;
47: }
48: }
49: }
50: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {}
51: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
52: {
53: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
54: {
55: foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
56: {
57: Type contractType = (from endpoint in serviceHostBase.Description.Endpoints
58: where endpoint.Contract.Name == endpointDispatcher.ContractName && endpoint.Contract.Namespace == endpointDispatcher.ContractNamespace
59: select endpoint.Contract.ContractType).FirstOrDefault();
60: if (null == contractType)
61: {
62: continue;
63: }
64: if (!this.UnityContainer.Registrations.Any(registration => registration.RegisteredType == contractType))
65: {
66: this.UnityContainer.RegisterType(contractType, serviceHostBase.Description.ServiceType);
67: }
68: endpointDispatcher.DispatchRuntime.InstanceProvider = new UnityInstanceProvider(this.UnityContainer, contractType);
69: }
70: }
71: }
72: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {}
73: }