Enterprise Library深入解析与灵活应用(7):再谈PIAB与Unity之间的集成

在EnteLib中,PIAB(Policy Injection Application Block)和Unity的定位是轻量级的AOP框架和IoC容器(Container)。通过PIAB,我们可以将一些业务无关的crosscutting concern定义于相应的CallHandler中,通过Attribute声明或者配置应用到承载业务逻辑的目标方法上。而通过Unity提供的IoC容器(或者DI容器),即UnityContainer,很好地实现了依赖的动态注入,从而实现了组件之间、模块之间或者服务之间的松耦合。

Unity完全建立在ObjectBuilder2之上,顾名思义,这是一个用于创建对象的基础组件。ObjectBuilder2提供了一种具有高可扩展性的、基于策略(Strategy Based)的对象创建框架,它不仅仅是Unity的基础组件,也是整个EnterLib和Software Factory的基石。而PIAB通过方法调用劫持(Method Call Interception)的机制实现了策略注入(Policy Injection)。PIAB提供了不同的方法劫持机制,最为典型的就是基于TransparentProxy(可以参考我的PIAB系列文章)和代码生成(比如动态生成一个继承自目标类型的子类,通过Override掉相应的Virtual方法实现策略注入;或者动态生成一个实现了目标接口的类型,实现相应的方法实现策略注入)。PIAB需要通过特殊的机制创建可被劫持(Interceptable)对象,而UnityContainer本质上是一个创建对象的容器,如果能够使UnityContainer按照PIAB的要求创建可被劫持(Interceptable)对象,那么就能实现两者之间的集成。(Source Code从这里下载)

一、Unity 1.2和EnterLib 4.1如何实现两者的集成

我在本系列的第一篇文章就谈过PIAB和Unity之间的集成问题,当时我们是采用了一个自定以UnityContainerExtension实现的,当时针对的版本是Unity 1.1和EnterLib 3.1。到了Unity 1.2和EnterLib 4.1,Unity已经被广泛地使用到了整个EnterLib内部,微软甚至通过Unity对PIAB进行了彻底的改造。所以,最新的Unity和PIAB中,已经提供了两者的原生集成。

Unity和PIAB两者之间的集成是通过一个特殊的UnityContainerExtension——Microsoft.Practices.Unity.InterceptionExtension.Interception实现的。为了演示Interception的使用,我们创建一个简单的例子。该例子中定义了一服务SyncTimeProvisionService用于实现同步时间的提供,SyncTimeProvisionService实现了接口ISyncTimeProvision。SyncTimeProvisionService本身并不提供具体实现,而是通过另一个组件SyncTimeProvider实现具体的同步时间的返回,SyncTimeProvider实现接口ISyncTimeProvider。

你可以将SyncTimeProvisionService和SyncTimeProvider看成是一个应用中具有依赖关系的两个模块,为了实现两个模块之间的解耦,采用基于接口的依赖是推荐的设计模式。所以,SyncTimeProvisionService并不之间依赖于SyncTimeProvider,而是依赖于SyncTimeProvider的接口ISyncTimeProvider。我们通过Constructor注入实现依赖注入。为了让读者对Unity和PIAB集成的效果具有一个直观的印象,我在SyncTimeProvider 上应用了一个CachingCallHandlerAttribute,如果该CallHandler生效,方法执行的结果将会被缓存,在缓存过期之前,得到的时间将是一样的。相应的代码如下所示:

using System;
namespace Artech.InterceptableUnity
{
    
    public interface ISyncTimeServiceProvision
    {
        DateTime GetCurrentTime();
    }

    public class SyncTimeServiceProvisionService : ISyncTimeServiceProvision
    {
        public ISyncTimeProvider SyncTimeProvider
        { get; private set; }

        public SyncTimeServiceProvisionService([Dependency]ISyncTimeServiceProvider syncTimeServiceProvider)
        {
            this.SyncTimeServiceProvider = syncTimeServiceProvider;
        }

        #region ISyncTimeServiceProvision Members

        public DateTime GetCurrentTime()
        {
            return this.SyncTimeProvider.GetCurrentTime();
        }

        #endregion
    }

    public interface ISyncTimeProvider
    {
        DateTime GetCurrentTime();
    }

    [CachingCallHandler]
    public class SyncTimeProvider : ISyncTimeProvider
    {
        #region ISyncTimeServiceProvider Members

        public DateTime GetCurrentTime()
        {
            return DateTime.Now;
        }

        #endregion
    }
}

那么我们就可以通过下面的方式,利用UnityContainer采用基于接口(ISyncTimeServiceProvision)的方式创建SyncTimeServiceProvisionService ,并调用GetCurrentTime方法

using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();
            container.RegisterType<ISyncTimeServiceProvision, SyncTimeServiceProvisionService>();
            container.RegisterType<ISyncTimeProvider, SyncTimeProvider>();

            container.AddNewExtension<Interception>();
            container.Configure<Interception>().SetDefaultInterceptorFor(typeof(ISyncTimeServiceProvider), new TransparentProxyInterceptor());
            var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
                Thread.Sleep(1000);
            }

        }
    }
}

通过下面的输出,我们看出输出的时间都是相同的,从而证实了CachingCallHandlerAttribute的有效性,进而正式了UnityContainer和PIAB的集成:

image.png

二、通过自定义UnityContainerExtension的方式实现Unity与PIAB的集成

通过Microsoft.Practices.Unity.InterceptionExtension.Interception对Unity和PIAB两者之间的集成,需要我们借助Interception为每一个需要被劫持(Interception)的类型注册相应的Interceptor(实现接口Microsoft.Practices.Unity.InterceptionExtension.IInterceptor),如下面的代码片断所示。

container.Configure<Interception>().SetDefaultInterceptorFor(typeof(ISyncTimeServiceProvider), new TransparentProxyInterceptor()); 

但是在每多情况下,我们不可能预先确定需要注册哪些对象,或者这样的类型很多,手工注册的方式将不具有可操作性。比如,在一个N-Layer的应用中,上层的对象通过UnityContainer创建下层对象,并且通过PIAB的方式将不同的Crosscutting Concern应用于相应的层次,我们不可能对每一个应用了PAIB CallHandler相关的类型进行Interceptor的注册。

为此,我对Interception进行了扩展,实现了Interceptor的动态注册。Unity采用两种不同的InterceptionStrategy:InstanceInterceptionStrategy和TypeInterceptionStrategy,它们分别采用基于InstanceInterceptor和TypeInterceptor实现方法调用劫持。我继承了InstanceInterceptionStrategy和TypeInterceptionStrategy,将Inteceptor的动态注册定义在PreBuildUp方法中。继承自Interception,在Initialize方法中将两个扩展的InstanceInterceptionStrategy和TypeInterceptionStrategy——ExtendedInstanceInterceptionStrategy和ExtendedTypeInterceptionStrategy添加到UnityContainer的BuildStrategy列表中。在这个扩展的Inteception——ExtendedInterception中,被用于动态注册的Interceptor定义在ExtendedInterception中,默认为TransparentProxyInteceptor。下面是ExtendedInterception、ExtendedInstanceInterceptionStrategy和ExtendedTypeInterceptionStrategy的定义:

ExtendedInterception:

using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.Practices.Unity.ObjectBuilder;
namespace Artech.InterceptableUnity
{
    public class ExtendedInterception : Interception
    {
        public IInterceptor Interceptor
        { get; internal set; }

        public ExtendedInterception()
        {
            this.Interceptor = new TransparentProxyInterceptor();
        }

        protected override void Initialize()
        {
            this.Context.Strategies.Add(new ExtendedInstanceInterceptionStrategy(this), UnityBuildStage.Setup);
            this.Context.Strategies.Add(new ExtendedTypeInterceptionStrategy(this), UnityBuildStage.PreCreation);
            this.Context.Container.RegisterInstance<InjectionPolicy>(typeof(AttributeDrivenPolicy).AssemblyQualifiedName, new AttributeDrivenPolicy());
        }
    }
}

ExtendedInstanceInterceptionStrategy:

using System;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace Artech.InterceptableUnity
{
    public class ExtendedInstanceInterceptionStrategy : InstanceInterceptionStrategy
    {
        public ExtendedInterception Interception
        { get; private set; }

        public ExtendedInstanceInterceptionStrategy(ExtendedInterception interception)
        {
            if (null == interception)
            {
                throw new ArgumentNullException("interception");
            }

            this.Interception = interception;
        }

        private static IInstanceInterceptionPolicy FindInterceptorPolicy(IBuilderContext context)
        {
            Type buildKey = BuildKey.GetType(context.BuildKey);
            Type type = BuildKey.GetType(context.OriginalBuildKey);
            IInstanceInterceptionPolicy policy = context.Policies.Get<IInstanceInterceptionPolicy>(context.BuildKey, false) ?? context.Policies.Get<IInstanceInterceptionPolicy>(buildKey, false);
            if (policy != null)
            {
                return policy;
            }
            policy = context.Policies.Get<IInstanceInterceptionPolicy>(context.OriginalBuildKey, false) ?? context.Policies.Get<IInstanceInterceptionPolicy>(type, false);
            return policy;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            if (BuildKey.GetType(context.BuildKey) == typeof(IUnityContainer))
            {
                return;
            }

            IInstanceInterceptionPolicy policy = FindInterceptorPolicy(context);
            if (null != policy)
            {
                if (policy.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)))
                {
                    this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), policy.Interceptor);
                }
            }
            else
            {
                if (this.Interception.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)) && this.Interception.Interceptor is IInstanceInterceptor)
                {
                    this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), (IInstanceInterceptor)this.Interception.Interceptor);
                }
            }
            base.PreBuildUp(context);
        }
    }
}
 

ExtendedTypeInterceptionStrategy:

using System;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity.InterceptionExtension;

namespace Artech.InterceptableUnity
{
    public class ExtendedTypeInterceptionStrategy : TypeInterceptionStrategy
    {

        public ExtendedInterception Interception
        { get; private set; }

        public ExtendedTypeInterceptionStrategy(ExtendedInterception interception)
        {
            if (null == interception)
            {
                throw new ArgumentNullException("interception");
            }

            this.Interception = interception;
        }

        private static ITypeInterceptionPolicy GetInterceptionPolicy(IBuilderContext context)
        {
            ITypeInterceptionPolicy policy = context.Policies.Get<ITypeInterceptionPolicy>(context.BuildKey, false);
            if (policy == null)
            {
                policy = context.Policies.Get<ITypeInterceptionPolicy>(BuildKey.GetType(context.BuildKey), false);
            }
            return policy;
        }

        public override void PreBuildUp(IBuilderContext context)
        {
            var policy = GetInterceptionPolicy(context);
            if (null != policy)
            {
                if (policy.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)))
                {
                    this.Interception.SetInterceptorFor(BuildKey.GetType(context.BuildKey), policy.Interceptor);
                }
            }
            else
            {
                if (this.Interception.Interceptor.CanIntercept(BuildKey.GetType(context.BuildKey)) && this.Interception.Interceptor is ITypeInterceptor)
                {
                    this.Interception.SetDefaultInterceptorFor(BuildKey.GetType(context.BuildKey), (ITypeInterceptor)this.Interception.Interceptor);
                }
            }
            base.PreBuildUp(context);
        }
    }
}

那么使用的时候,动态注册Interceptor的操作将不再需要,如下面代码片断所示:

using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();
            container.RegisterType<ISyncTimeServiceProvision, SyncTimeServiceProvisionService>();
            container.RegisterType<ISyncTimeServiceProvider, SyncTimeServiceProvider>();

            ExtendedInterception interception = new ExtendedInterception();
            interception.Interceptor = new TransparentProxyInterceptor();
            container.AddExtension(interception);
            var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
                Thread.Sleep(1000);
            }
        }
 }

三、通过配置的方式应用ExtendedInterception

为了通过配置的方式应用ExtendedInterception,我们需要为之定义相应的配置类型,一个继承自Microsoft.Practices.Unity.Configuration.UnityContainerExtensionConfigurationElement得类型。为此,我定义了下面一个ExtendedInterceptionElement类型,配置属性为默认的Inteceptor的类型。

using System;
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Artech.InterceptableUnity
{
    public class ExtendedInterceptionElement : UnityContainerExtensionConfigurationElement
    {
        [ConfigurationProperty("interceptor", IsRequired = false, DefaultValue = "")]
        public string Interceptor
        {
            get
            {
                return (string)this["interceptor"];
            }
        }

        public override void Configure(IUnityContainer container)
        {
            base.Configure(container);
            ExtendedInterception interception = new ExtendedInterception();
            if (!string.IsNullOrEmpty(this.Interceptor))
            {
                var type = System.Type.GetType(this.Interceptor);
                if (null == type)
                {
                    throw new ConfigurationErrorsException(string.Format("The {0} is not a valid Interceptor.", this.Interceptor));
                }

                if (!typeof(IInterceptor).IsAssignableFrom(type))
                {
                    throw new ConfigurationErrorsException(string.Format("The {0} is not a valid Interceptor.", this.Interceptor));
                }
                interception.Interceptor = (IInterceptor)Activator.CreateInstance(type);
            }

            container.AddExtension(interception);
        }
    }
}

那么对于上面的例子,我么可以将Type Mapping和ExtendedInterception扩展定义在如下一个配置文件中:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration, Version=1.2.0.0,                 Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <unity>

    <typeAliases>
      <typeAlias alias="ISyncTimeServiceProvision" type="Artech.InterceptableUnity.ISyncTimeServiceProvision,Artech.InterceptableUnity" />
      <typeAlias alias="SyncTimeServiceProvisionService" type="Artech.InterceptableUnity.SyncTimeServiceProvisionService,Artech.InterceptableUnity" />
      <typeAlias alias="ISyncTimeProvider" type="Artech.InterceptableUnity.ISyncTimeProvider,Artech.InterceptableUnity" />
      <typeAlias alias="SyncTimeProvider" type="Artech.InterceptableUnity.SyncTimeProvider,Artech.InterceptableUnity" />
    </typeAliases>
    
    <containers>
      <container>
        <types>
          <type type="ISyncTimeServiceProvision" mapTo="SyncTimeServiceProvisionService" />
          <type type="ISyncTimeProvider" mapTo="SyncTimeProvider" />
        </types>
          <extensionConfig>
          <add name="extendedInterception" type="Artech.InterceptableUnity.ExtendedInterceptionElement,Artech.InterceptableUnity" interceptor="Microsoft.Practices.Unity.InterceptionExtension.TransparentProxyInterceptor,Microsoft.Practices.Unity.Interception, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
        </extensionConfig>
      </container>
    </containers>
  </unity>
</configuration>

那么我们的代码将会变得异常简洁:

using System;
using System.Threading;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;
namespace Artech.InterceptableUnity
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();
            UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection("unity") ;
            configuration.Containers.Default.Configure(container);
            var syncTimeServiceProvision = container.Resolve<ISyncTimeServiceProvision>();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(syncTimeServiceProvision.GetCurrentTime());
                Thread.Sleep(1000);
            }
        }
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

WCF技术剖析之四:基于IIS的WCF服务寄宿(Hosting)实现揭秘

通过《再谈IIS与ASP.NET管道》的介绍,相信读者已经对IIS和ASP.NET的请求处理管道有了一个大致的了解,在此基础上去理解基于IIS服务寄宿的实现机制...

23490
来自专栏AhDung

【手记】WebBrowser响应页面中的blank开新窗口及window.close关闭本窗体

上述窗体是指WebBrowser所在的Form,基本上,上述目的就是让该窗体表现得像个正常浏览器而已。

10830
来自专栏大内老A

难道.NET Core到R2连中文编码都不支持吗?

今天写了一个简单的.NET Core RC2控制台程序,发现中文显示一直是乱码。查看操作系统设置,没有问题;查看源文件编码,也没有问题;甚至查看了Console...

20760
来自专栏木宛城主

ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇

在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号。那么在本篇文章中,我将继续ASP.NET Identity 之...

46960
来自专栏魏琼东

AgileEAS.NET SOA 中间件平台.Net Socket通信框架-简单例子-实现简单的服务端客户端消息应答

一、AgileEAS.NET SOA中间件Socket/Tcp框架介绍      在文章AgileEAS.NET SOA 中间件平台Socket/Tcp通信框架...

29950
来自专栏恰童鞋骚年

自己动手写一个简单的MVC框架(第二版)

  在ASP.NET MVC中,最核心的当属“路由系统”,而路由系统的核心则源于一个强大的System.Web.Routing.dll组件。

12220
来自专栏恰童鞋骚年

ASP.Net请求处理机制初步探索之旅 - Part 3 管道

开篇:上一篇我们了解了一个ASP.Net页面请求的核心处理入口,它经历了三个重要的入口,分别是:ISAPIRuntime.ProcessRequest()、Ht...

17820
来自专栏.NET开发者社区

C#/.NET RestSharp网络组件实现上传文件到远程服务器【可跨域传文件】

以前给大家分享了一个C#/.NET的网络组件–RestSharp,具体请参考:推荐一个.NET(C#)的HTTP辅助类组件–restsharp 今天再给大家示...

446100
来自专栏微服务

ASP.NET Core 依赖注入

一、什么是依赖注入(Denpendency Injection) 这也是个老身常谈的问题,到底依赖注入是什么? 为什么要用它? 初学者特别容易对控制反转IOC(...

33380
来自专栏博客园

持久化方式

HttpContext抽象提供了一个简单的IDictionary<Object,Object>类型的字典集合,叫做Items。在每个请求中,这个集合从HttpR...

13020

扫码关注云+社区

领取腾讯云代金券