Enterprise Library Policy Injection Application Block

(原文)

在过去的半年里,定期或者不定期地写点东西已经成为了我的一种习惯。可是最近两个月来一直忙于工作的事情一直足够的时间留给自己,虽然给自己列了很长一串写作计划,可是心有余而力不足。这一段工作主要是帮助公司开发一套分布式的开发框架,对一些技术和设计方法有了一些新的认识。这两天的工作主要是如何把Enterprise Library V3.1的PIAB(Policy Injection Application Block)引入到我们自己的框架中,为次对PIAB进行了一些研究,借此机会与大家一起分享。

  1. Part I:   PIAB Overview
  2. Part II:  PIAB设计和实现原理
  3. Part III:PIAB扩展-如何创建Custom CallHandler

一、Business Logic 和 Infrastructure Logic的分离

对于任何一个企业级应用的开发人员来说,他们编写的代码不仅仅是处理单纯的业务逻辑,同时还需要处理很多的非业务方面的逻辑,比如:Caching、Transaction Enlist、Authorization、Auditing、Exception Handling、Logging、Validation甚至是Performance Counter。我习惯把这些非业务逻辑成为Infrastructure Logic。由于这些Infrastructure Logic通常根据具体的需求注入的具体的业务逻辑的执行中,所以又被更多地成为Crosscutting Concern。

如果按照传统的OO的编程方式,我们通常将这些Business Concern和Crosscutting Concern糅合在一起,从而导致了某种意义的紧耦合。这种紧耦合在一个大型的企业级应用可能是会给应用的可扩展性带来致命的影响。所以必须以某种方式实现Business Concern和Crosscutting Concern的分离,这通常是AOP实现的目标。

举个简单的例子,我们现在有如下一个简单的订单处理操作,可能具体的业务逻辑很简单。但是此操作面临的非业务逻辑可能有很多。比如:· 在处理之前进行Authorization,确定当前的用户是否具有此操作的权限。
· Auditing,记录处理的用户、处理时间等等。
· Transaction Enlist,将所有Data Access操作纳入同一个Transaction,保证数据的一致性。
· 进行Exception Handling。
· 如果出现异常,记录日志。

上面的这些可以体现在线面的Code中:

public void ProcessOrder(Order order)

        {

            Authorize();

            Audit();

            using (TransactionScope scope = new TransactionScope())

            {

                try

                {

OrderDataAccess();
                }

                catch (Exception ex)

                {

                    HandleExceptoin(ex);

                    Log();

                }

            }
        }

可以看到上面这么多行代码中,和业务相关的就一行而已。虽然对这些非业务逻辑的实现通常通过调用一个封装好的方法或者组件完成,你往往只需要Copy Paste就可以了,但是将如此众多的Infrastructure Logic和Business Logic按照这样的方式组合在一起有很多的隐患。

  • 首先,将一些公共的Source code进行大规模的复制不能保证其同一性和正确性。我个人觉得,对于一个程序原来说,如果你频繁地使用到Ctrl + C和Ctrl + V,你应该想到你的代码需要重构。
  • 其次,Infrastructure Logic和Business Logic这种耦合性导致对Infrastructure Logic的变动将获导致Source Code的改变。
  • 此外,这样的编程方式增加了程序员的压力,可能对于一个相对Junior的程序员来说,可能根本不知道这个Infrastructure Logic的具体作用何在,而实际上对于最终的程序员来讲,这些应该是对他们透明的。

所以我们需要寻求某种方式将这些Infrastructure Logic注入到某个具体的操作中,而不需要将所有的逻辑实现在具体的方法定义中。比如:你可以通过配置文件进行配置,你也可以通过添加Attribute一声明的方式来实现。而这一切都可以通过Enterprise Library的PIAB来实现。

二、PIAB(Policy Injection Application Block)Overview

PIAB在Enterprise Library 3.0中被引入(注意:不要把PIAB和DIAB混淆,DIAB-Dependence Injection Application Block将在Enterprise Library 4.0中被引入,个人觉得这将又是一个具有重大意义的AB,它的使用将会极大的减轻模块或组件的依赖关系,在Software Factory中已经有了广泛的应用)。PIAB提供了一种简单而有效的方式是你能够将你所需的非业务逻辑的Crosscutting concern注入到的某个方法的Invocation stack中。比如可以在方法的调用前注入Auditing的执行,在成功调用后执行Transaction commit的调用,在出现异常时进行Exception Handling的处理。

对于PIAB来说,Policy的意思是:“将对应的处理操作注入到对应的方法调用”。这实际上包含两方面的内容:要注入怎样的处理操作和如何与具体的方法匹配。前者封装在一个称作CallHandler的对象中,而匹配关系通过另一个称作MatchingRule的对象来表现。所以可以说Policy= CallHandler+MatchingRule。

PIAB给我们提供了很多系统自定义CallHandler,在一般情况下它能满足我们绝大部分的需求,比如:

  • Enterprise Library Security Application Block的Authorization来实现。
  • CachingCallHandler:将方法返回值作为Value、参数列表作为Key存入Cache,如果下次出现相同参数列表的调用,则直接将Cache中的值返回,从而免去了再次执行方法对性能的影响。像一般的Cache处理一样,你也可以设置Cache的过期时间。
  • ExceptionCallHandler:通过调用Enterprise Library Exception Handing Application Block实现对一异常处理的支持。
  • LogCallHandler:通过调用Enterprise Library Logging Application Block进行日志的处理。
  • ValidationCallHandler:进行参数的验证等功能,该CallHandler依赖于Enterprise Library Validation Application Block。
  • PerformanceCounterCallHandler。

至于MatchingRule,他实际上是定义了一种如何将CallHander和对应的Method进行匹配的规则。同样的,一系列的MatchingRule被定义出来,比如:基于Assembly的MatchingRule将CallHandler匹配到某个Assembly的所有对象上面;基于Custom Attribute的MatchingRule通过声明在某个元素上的某个Attribute进行匹配。此外还有基于NameSpace、Method Signature、等等的MatchingRule。

如何现有的CallHandler和MatchingRule不能满足你具体的要求,你还可以定义Custom CallHandler或者 Custom MatchingRule。在后续的部分将会介绍一个完整的定义Custom CallHandler的例子。

三、Get Started

经过上面对PIAB的介绍,我想大家对PIAB使用的目标又一个简单的认识,为了加深大家的映像,在本节中,我们做一个简单的Walkthrough,做一个简单的使用PIAB的例子。在这个例子通过PIAB实现两个主要的功能:Caching和Logging。为了简单起见,我们使用一个Console Application来实现这样一个例子。我们假设的一个场景时处理一个订单并将处理后的订单返回。

Step I: 创建一个Console Application,并添加下面连个Dll Reference.

  • Microsoft.Practices.EnterpriseLibrary.PolicyInjection
  • Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers

Step II: 编写Order Processing的代码:

public class OrderProcessor:MarshalByRefObject

    {

public Order Process(Order order)

        {

Console.WriteLine("OrderProcessor.Process() is invocated!");

return order;

        }

    }

public class Order

{    }

至于为什么要继承MarshalByRefObject将会在后面对PIAB的实现原理中介绍。将对OrderProcessor的调用写在Main()。

static void Main(string[] args)

{         

OrderProcessor processor = PolicyInjection.Create<OrderProcessor>();

Order order = new Order();

       processor.Process(order);

       processor.Process(order);          
}  

Step III:创建app.config,并通过Enterprise Library Configuration Console进行配置:

1、 添加一个Logging Application Block
Enterprise Library Policy Injection Application Block _第1张图片

2、 按照相同的方式添加一个Policy Injection Application Block,然后再PIAB节点添加一个Policy。在该Policy下的Match Rules节点下创建一个Method Name Matching Rule (该MatchingRule根据Method name进行CallHandler的匹配)。对该MatchingRule进行如下的配置:

Enterprise Library Policy Injection Application Block _第2张图片
那么该Policy的CallHandler将会自动应用到Method name为Process的方法上。

3、 在Policy下的Handlers节点添加两个CallHandler:Caching Handler和Logging Handler。保持Caching Handler的默认配置,按如下进行Loging Handler的配置:

Enterprise Library Policy Injection Application Block _第3张图片

通过上面的配置,我们将会得到如下的Configuration:

Configuration

我们现在来运行以下程序:

Enterprise Library Policy Injection Application Block _第4张图片

从输出的结果可以看出,虽然我们在Main()两次调用了Process方法,但是真正执行的数次只有一次。这是Caching所致,第一次调用PIAB将返回值存入Cache,该Cache Entry的Key为参数Order对象。第二次调用由于传入的参数相同,所有直接将上次的结果返回。

上面我看到了Caching的效果,现在我们在看看Logging。默认情况下,Logging entry被写入Event Log。下图就是我们写入的Log。
Enterprise Library Policy Injection Application Block _第5张图片

· [原创]Enterprise Library Policy Injection Application Block 之一: PIAB Overview
· [原创]Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
· [原创]Enterprise Library Policy Injection Application Block 之三: PIAB的扩展—创建自定义CallHandler(提供Source Code下载)
· [原创]Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

 

前面一篇文章中,我对Enterprise Library中的PIAB (Policy Injection Application Block)作了简单的介绍。在这篇文章主要谈谈我个人对PIAB设计和实现原理的一些理解。在介绍过程中,我尽量采用由浅入深出的方式,同时结合例子、Source Code。希望通过本篇文章让大家对PIAB有一个全面、深刻的认识。

一、MBR、ObjRef、RealProxy、TransparentProxy

在真正进入PIAB之前,我们现来谈论一些与之相关的、必要的背景知识。MBR、ObjRef、RealProxy和TransparentProxy,对于这些名词,我想熟悉或者接触过.NET Remoting的人肯定不会不陌生。由于PIAB的实现机制依赖于Remoting的这种Marshaling,如果对此不了解的读者将对后面的介绍很难理解,所以很有必要先做以下简单的介绍。

我们知道,CLR通过AppDomain实现不同Application之间的隔离,在通常情况下,不同AppDomain不同共享内存。在一个AppDomain中创建的对象不能直接被运行在另一个AppDomain的程序使用。跨AppDomain对象的共享依赖于一个重要的过程:Marshaling。我们有两种不同的Marshaling方式:Marshaling by Value和Marshaling by Reference。前者采用的是Serialization/Deserialization的方式,而后者则是采用传递Reference的方式来实现,其实现原来如下:

Remoting Infrastructure先生成对象的ObjRef Instance,ObjRef(System.Runtime.Remoting.ObjRef)代表远程对象的一个Reference,存储着跨AppDomain远程调用所需的所有的信息,比如URI、类型的层次结构、以及实现的Interface等等。ObjRef是可以序列化的,也就是说它可以按照by Value的方式进行Marshaling。所以可以这么说Marshaling by Reference依赖对对ObjRef的Marshaling by Value。

当ObjRef产地到另一个AppDomain的实现,将根据ObjRef的数据生成两个Proxy对象:RealProxy和TransparentProxy。RealProxy根据ObjRef创建,通过RealProxy创建TransparentProxy。当进行远程调用的时候,Client直接和TransparentProxy打交道,对TransparentProxy的调用将会Forward到RealProxy,RealProxy通过Remoting Infrastructure的Communicate和Activation机制将Invocate传递给被激活的Remote Object。

MBR通常在一个跨AppDomain的环境下实现远程调用,但是这种机制在用一个AppDomian依然有效,而且可以免去跨AppDomain的性能损失。PIAB就是充分运用了这种在同一个AppDomain下的MBR。

二、Method Interception & Custom RealProxy

在第一部分我们知道,PIAB的实现是通过将Policy应用到对应的Method上,在真正执行目标对象具体的方法的之前,PIAB将整个方法的调用拦截,然后逐个调用应在该Method上的Policy包含的所有CallHandler(在前一章我们说过Policy = Matching Rule + Call Handler),最后再调用真正目标对象的方法。我们把这种机制成为Method Injection。

如何才有实现这样的Method Injection呢?这就要需要使用到我们在上面一节介绍的MBR了。通过上面的介绍,我们知道我们调用一个MBR Object的过程是这样的:

Client Code==〉Transparent Proxy==〉Real Proxy==〉Target Object

在上面的Invocate Chain中,由于真正的目标对象的方法在最后一部才被调用,我们完全有可以在中途将调用”劫持”,使之先调用我们的CallHandler。而这种Inject的突破口在于RealProxy。在FCL(Framework Class Library)中RealProxy(System.Runtime.Remoting.Proxies.RealProxy)是个特殊的Abstract Class,你可以继承RealProxy定义你自己的Custom RealProxy,将你需要注入的操作写在Invoke方法中。PIAB采用的就是这样一种解决方案。

我们先不忙介绍PIAB的具体的实现原理,因为相对比较复杂。为了使读者能够更加容易地理解PIAB的实现,我写了一个简单的例子。我们它能够大体体现PIAB的实现机制。

这是一个简单的Console Application,我首先定义了一个Custom RealProxy:

public class MyRealProxy<T>:RealProxy

    {

private T _target;

public MyRealProxy(T target)

           : base(typeof(T))

       {

this._target = target;

       }

public override IMessage Invoke(IMessage msg)

        {

//Invoke injected pre-operation.

Console.WriteLine("The injected pre-operation is invoked");

//Invoke the real target instance.

IMethodCallMessage callMessage = (IMethodCallMessage)msg;

object returnValue = callMessage.MethodBase.Invoke(this._target, callMessage.Args);

//Invoke the injected post-operation.

Console.WriteLine("The injected post-peration is executed");

//Return

return new ReturnMessage(returnValue, new object[0], 0, null, callMessage);

        }
    }

这是一个Generic的RealProxy。在Invoke方法中,两个Console.Write()代表PIAB注入的CallHandler的调用(对于CallHandler的操作可以是在调用Target Object之前调用,也可以在之后调用,我们不妨称这两种类型的操作为Pre-operation和Post-op

eration)。而对Target object的调用实际上是通过Reflection的方式调用的(callMessage.MethodBase.Invoke)。MyRealProxy通过传入Target Instance来创建。

我们在创建一个Factory Class,用于创建TransparentProxy。在PIAB中,这样一个Class由Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjection来充当。

public static class PolicyInjectionFactory

{

public static T Create<T>()

        {

            T instance = Activator.CreateInstance<T>();

MyRealProxy<T> realProxy = new MyRealProxy<T>(instance);

            T transparentProxy = (T)realProxy.GetTransparentProxy();

return transparentProxy;

        }
}

先通过Reflection的方式来创建Target Instance。通过该Instance创建我们在上面定义的Custom RealProxy。最后通过RealProxy返回一个TransparentProxy。

有了上面两个Class,我们的编写如下的Code来验证我们自己的Method Injection:

public class Foo:MarshalByRefObject

{

public void DoSomeThing()

        {

Console.WriteLine("The method of target object is invoked!");

        }
}

public class Program

{

public static void Main()

        {

Foo foo = PolicyInjectionFactory.Create<Foo>();

            foo.DoSomeThing();

        }
}

我们来看看程序运行后的结果:

Enterprise Library Policy Injection Application Block _第6张图片
可以看到正式我们需要的结果。从这个例子中我们可以看到,我们的Client code中包含的仅仅是Business Logic相关的code, 而另外一些业务无关的Code则是通过Custom RealProxy的形式注入到Invocation Stack中。这充分体现了Business Concern和Crosscutting Concern的分离。

三、Call Handler Pipeline

我想有了上面的理论和例子作基础,大家对于PIAB真正的实现不难理解了。我们先来介绍一下PI一个重要的概念:CallHandler Pipeline。我们知道Policy被运用Method方面,一个Policy有多个CallHandler。所有一个Method往往关联着一连串的CallHandler。这种现在很常见,就上我们第一部分给出的例子一样,一个简单的ProcessOrder方面需要执行许多额外的业务无关的逻辑:Authorization、Auditing、Transaction Enlist、Exception Handling和Logging。

在PIAB中,这些基于某个Method的所有CallHandler逐个串联在一起,组成一个CallHandler Pipeline 。具体情况如下图:

Enterprise Library Policy Injection Application Block _第7张图片
我们从Class Diagram的角度来认识CallHandler Pipeline (在PIAB中通过Microsoft.Practices.EnterpriseLibrary.PolicyInjection.HandlerPipeline来表示)。
Enterprise Library Policy Injection Application Block _第8张图片

public delegate IMethodReturnInvokeHandlerDelegate(IMethodInvocation input, GetNextHandlerDelegategetNext);

public interface IMethodInvocation

{

// Methods

IMethodReturnCreateExceptionMethodReturn(Exception ex);

IMethodReturnCreateMethodReturn(object returnValue, params object[] outputs);

// Properties

IParameterCollectionArguments { get; }

IParameterCollectionInputs { get; }

IDictionaryInvocationContext { get; }

MethodBaseMethodBase { get; }

objectTarget { get; }
}
public delegate InvokeHandlerDelegate GetNextHandlerDelegate();

public interface ICallHandler

{

// Methods

IMethodReturnInvoke(IMethodInvocation input, GetNextHandlerDelegate getNext);
}

IMethodInvocation代表对一个方法的调用,它封装了一个Method Invocation的相关信息。比如:Parameter List、Invocation Context和Target Object. InvokeHandlerDelegate代表的是对CallHandler的调用,由于所有的CallHandler被串成一个CallHandler Pipeline ,在调用了当前CallHandler之后,需要调用Pipeline中后一个CallHandler,对后一个CallHandler调用通过一个Delegate,GetNextHandlerDelegate来表示,而该Delegate的返回值是InvokeHandlerDelegate。结合上面的CallHandler Pipeline 的链状结构,对这些应该不难理解。

我们最后来看看HandlerPipeline的定义,它的所有的CallHandler通过一个List来表示。

public class HandlerPipeline

{

// Fields

private List<ICallHandler> handlers;

// Methods

public HandlerPipeline();

public HandlerPipeline(IEnumerable<ICallHandler> handlers);

public IMethodReturnInvoke(IMethodInvocation input, InvokeHandlerDelegate target);
}

HandlerPipeline通过Invoke开始对其CallHandler PipeLine的调用:

public IMethodReturn Invoke(IMethodInvocation input, InvokeHandlerDelegate target)

{

     if (this.handlers.Count == 0)

     {

         return target(input, null);

     }

     int handlerIndex = 0;

     return this.handlers[0].Invoke(input, delegate {

         handlerIndex++;

         if (handlerIndex < this.handlers.Count)

         {

             ICallHandler local1 = this.handlers[handlerIndex];

             return new InvokeHandlerDelegate(local1.Invoke);

         }

         return target;

     });

}

逻辑并不复杂,按照CallHandler List的先后顺序逐个调用,最后调用Target Object。方法中的第二个参数代表target代表对Target Object的调用。

四、PIAB中的Custom RealProxy:InterceptingRealProxy

我们一直再强调,PIAB实际上是通过自定义RealProxy来实现的。而且在第二节我们也实验了这种做法的可行性。我们现在就来看看PIAB的Custom RealProxy:Microsoft.Practices.EnterpriseLibrary.PolicyInjection.RemotingInterception.InterceptingRealProxy。

public class InterceptingRealProxy : RealProxy, IRemotingTypeInfo
{

     // Fields

     private Dictionary<MethodBase, HandlerPipeline> memberHandlers;

     private readonly object target;

     private string typeName;

     // Methods  

     public InterceptingRealProxy(object target, Type classToProxy, PolicySet policies);

     private void AddHandlersForInterface(Type targetType, Type itf);

     private void AddHandlersForType(Type classToProxy, PolicySet policies);

     public bool CanCastTo(Type fromType, object o);

     public override IMessage Invoke(IMessage msg);

     // Properties

     public object Target { get; }

     public string TypeName { get; set; }

}

上面是它所有的成员定义,其中memberHandlers是一个以MethodBase为Key的Dictionary,表示应用在Target Object上面的所有的CallHandler Pipeline。通过这个很容易获得某个具体的方法调用的Pipeline。而target值得是真正的Target Object。

我们着重来看看Invoke的定义:

public override IMessage Invoke(IMessage msg)

{

     HandlerPipeline pipeline;

     IMethodCallMessage callMessage = (IMethodCallMessage) msg;

     if (this.memberHandlers.ContainsKey(callMessage.MethodBase))

     {

         pipeline = this.memberHandlers[callMessage.MethodBase];

     }

     else

     {

         pipeline = new HandlerPipeline();

     }

     RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, this.target);

     return ((RemotingMethodReturn) pipeline.Invoke(invocation, delegate (IMethodInvocation input, GetNextHandlerDelegate getNext) {

         try

         {

             object returnValue = callMessage.MethodBase.Invoke(this.target, invocation.Arguments);

             return input.CreateMethodReturn(returnValue, invocation.Arguments);

         }

         catch (TargetInvocationException exception)

         {

             return input.CreateExceptionMethodReturn(exception.InnerException);

         }

     })).ToMethodReturnMessage();

}

上面的一段代码不长,多看几遍应该不难理解。总的来说上面的Code现根据msg解析出MethodBase,再获取对应的CallHandler Pipeline,最后调用Pipeline。

五、Policy Injection Transparent Proxy Factory

介绍到这里,细心的读者可以意识到了,我们实际上还还有两件事情没有解决:CallHandler Pipeline的初始化和Transparent Proxy的创建。这两件事情都由PolicyInject.Create和方法来完成。

需要指出的,应用PIAB的Class需要具有两个条件中至少一个:

· Class继承System.MarshalByRefObject。

· Class实现某个Interface。

PolicyInjectiond提供了两种类型的Create方式,一种需要制定Interface,另一种不需要:

public static TInterface Create<TObject, TInterface>(params object[] args);

public static TObject Create<TObject>(params object[] args);

其实还有两个重载,在这里就不需要多做介绍了。在具体的实现中,最终又是调用一个PolicyInjector对象来实现的。

public static TObject Create<TObject>(params object[] args)

{

return DefaultPolicyInjector.Create<TObject>(args);
}

public static TInterface Create<TObject, TInterface>(params object[] args)

{

returnDefaultPolicyInjector.Create<TObject, TInterface>(args);
}

PolicyInjector是一个Abstract Class。其中Policies属性代表应用在该对象上的所有Policy(Policy = CallHandler + Matching Rule)

[CustomFactory(typeof(PolicyInjectorCustomFactory))]

public abstract class PolicyInjector

{

// Fields

private PolicySetpolicies;

// Methods

public PolicyInjector();

public PolicyInjector(PolicySet policies);

public TInterface Create<TObject, TInterface>(params object[] args);

public TObject Create<TObject>(params object[] args);

public objectCreate(Type typeToCreate, params object[] args);

publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args);

… … … … …

// Properties

public PolicySetPolicies { get; set; }
}

在PolicyInjection Class中定义了一个叫做DefaultPolicyInjector的属性,其定义如下:

private static PolicyInjectorDefaultPolicyInjector

{

get

    {

if (defaultPolicyInjector == null)

        {

lock (singletonLock)

            {

if (defaultPolicyInjector == null)

                {

defaultPolicyInjector = GetInjectorFromConfig(ConfigurationSourceFactory.Create());

                }

            }

        }

return defaultPolicyInjector;

    }
}

由于上面这个方法具体调用的Stack trace太深了,不可能很详细地指出其具体的实现。简单地说,该属性会返回一个默认的PolicyInjectorRemotingPolicyInjector并对其进行初始化。初始化的内容就包括初始化所有的Policy(这就是我们在本节开始提出的CallHandler Pipeline的初始化)。由于我们可以有两种方式将Policy映射到目标成员:Attribute和Configuration。所有具体的做法是先通过分析Configuration找出所有通过configuration方式添加的Policy,然后通过Reflection找出所有通过使用Attribute方式添加的Policy。所以,如果你通过两种方式将相同的Policy应用到同一个对象上,该对象上将会有两个一样CallHandler,个人觉得这是一个值得商榷的做法,我不太赞成这样的行为。

我们接着看PolicyInjector接着是如何工作的:

public TInterface Create<TObject, TInterface>(params object[] args)

{

return (TInterface) this.Create(typeof(TObject), typeof(TInterface), args);
}

public TObject Create<TObject>(params object[] args)

{

return (TObject) this.Create(typeof(TObject), typeof(TObject), args);
}

上面连个方法由于调用到同一个Create Overload:

publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args)

{

PolicySet policiesFor = this.policies.GetPoliciesFor(typeToCreate);

this.EnsureTypeIsInterceptable(typeToReturn, policiesFor);

return this.DoCreate(typeToCreate, typeToReturn, policiesFor, args);
}

首先找出对应Type的Policy,然后判断该类型是否支持Method Interception。RemotingPolicyInjector对该方法是这样实现的:要么继承MarshalByRefObject的Class,要么是个Interface。所以我们才有本节开始提出的两个条件。

public override boolTypeSupportsInterception(Type t)

{

if (!typeof(MarshalByRefObject).IsAssignableFrom(t))

    {

return t.IsInterface;

    }

return true;
}

上面连个方法由于调用到同一个Create Overload:

publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args)

{

PolicySet policiesFor = this.policies.GetPoliciesFor(typeToCreate);

this.EnsureTypeIsInterceptable(typeToReturn, policiesFor);

return this.DoCreate(typeToCreate, typeToReturn, policiesFor, args);
}

首先找出对应Type的Policy,然后判断该类型是否支持Method Interception。RemotingPolicyInjector对该方法是这样实现的:要么继承MarshalByRefObject的Class,要么是个Interface。所以我们才有本节开始提出的两个条件。

protected override object DoWrap(object instance, Type typeToReturn, PolicySet policiesForThisType)

{

     if (PolicyInjector.PolicyRequiresInterception(policiesForThisType))

     {

         InterceptingRealProxy proxy = new InterceptingRealProxy(this.UnwrapTarget(instance), typeToReturn, policiesForThisType);

         return proxy.GetTransparentProxy();

     }

     return instance;

}

和我们给出的例子差不多,创建RealPoxy,根据该RealProxy返回Transparent Proxy。

五、Policy Injection Design

最后给出整个PIAB实现的所使用的Class,基本上所有的Class都在上面的内容中介绍过了:

Enterprise Library Policy Injection Application Block _第9张图片
在本系列的第三部分,我将介绍如何创建Custom Handler,以及如何将他采用不同的方式应用到你的Application中。

· [原创]Enterprise Library Policy Injection Application Block 之一: PIAB Overview
· [原创]Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
· [原创]Enterprise Library Policy Injection Application Block 之三: PIAB的扩展—创建自定义CallHandler(提供Source Code下载)
· [原创]Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

0

0

(请您对文章做出评价)

posted @ 2008-01-31 09:45 Artech 阅读(5006) 评论(38) 编辑 收藏 网摘 所属分类: G. Enterprise Library

发表评论

回复 引用 查看

#1楼2008-01-31 09:57 | Justin

Enterprise Library Policy Injection Application Block _第10张图片Enterprise Library Policy Injection Application Block _第11张图片
刚看到这两个介绍Remoting架构的图,大家随便看看

 

本系列的第一部分对PIAB使用场景进行了简单的介绍,作中阐述了通过PI(Policy Injection)的方式实现了Business Logic和Non-Business Infrastructure Logic的分离,从而实现了AOP(Aspect Oriented Programming)。在第二部分中详细介绍PIAB的实现机制:通过自定义RealProxy的方式实现了Method Injection。通过这几天接收到的网友的留言,觉得很多人对在具体的项目开发中如何使用PIAB还有很多困惑,对PIAB的价值还不是很了解。为此,在本系列的第三篇文章中,我将以Walk through的方式定义一个Custom CallHandler,并通过两种不同的方式:Attribute和Configuration将其运用到所以得Method上。你可以这里从下载Source Code.

场景描述:本Demo模拟一个简单的场景:订单处理,我们将订单处理之前对订单的验证通过PI的方式提供。我们假设需要进行如何的验证:

  • Order Date必须早于当前日期。
  • 必须有具体的Product。
  • 供应商必须是制定的几家合法供应商(可选)。
  • Order的总价必须是所有Product价格之和(可选)。

其中前两个验证规则为必须的,后两个未可选项,可以通过Attribute和Configuration进行相应的配置。

Step I 创建Solution

Enterprise Library Policy Injection Application Block _第12张图片
如上图,整个Solution由两个Project组成,一个Class Library和一个Console Application。所有与Custom CallHandler的Class都定义在Artech.CustomCallHandler.ClassLibrary中,而Artech.CustomCallHandler.ConsoleApp重在演示对Custom CallHandler的使用。在Artech.CustomCallHandler.ClassLibrary中,添加如下3个Dll Reference,你可以在安装Enterprise Library V3 .1的目录中找到。

  • Microsoft.Practices.EnterpriseLibrary.Common
  • Microsoft.Practices.EnterpriseLibrary.PolicyInjection
  • Microsoft.Practices.ObjectBuilder

Step II定义辅助类:Order、OrderItem、OrderValidationException

namespace Artech.CustomCallHandlers

{

public class Order

    {

public Order()

        {

this.Items = new List<OrderItem>();

        }

public Guid OrderNo

        { get; set; }

public DateTime OrderDate

        { get; set; }

public string Supplier

        { get; set; }

public IList<OrderItem> Items

        { get; set; }

public double TotalPrice

        { get; set; }

    }
}

namespace Artech.CustomCallHandlers

{

public class OrderItem

    {

public Guid ProductID

        { get; set; }

public string ProductName

        { get; set; }

public double UnitPrice

        { get; set; }

public int Quantity

        { get; set; }

    }
}

namespace Artech.CustomCallHandlers

{

public class OrderValidationException:ApplicationException

    {

public OrderValidationException(string message)

            : base(message)

        { }

    }

}

注:本Demo通过VS2008创建,上面广泛使用了C# 3.0的一个新特性:Automatically Implemented Property

Step III 定义Custom CallHandler:  OrderValidationCallHandler

namespace Artech.CustomCallHandlers

{

public class OrderValidationCallHandler:ICallHandler

    {

private static IList<string> _legalSuppliers;

public static IList<string> LegalSuppliers

       {

get

           {

if (_legalSuppliers == null)

               {

                   _legalSuppliers = new List<string>();

                   _legalSuppliers.Add("Company AAA");

                   _legalSuppliers.Add("Company BBB");

                   _legalSuppliers.Add("Company CCC");

               }

return _legalSuppliers;

           }

       }

       #region Public Properties

public bool ValidateTotalPrice

       { get; set; }

public bool ValidateSupplier

       { get; set; }

       #endregion

       #region ICallHandler Members

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

        {

if (input.Inputs.Count == 0)

            {

return getNext()(input, getNext);

            }

Order order = input.Inputs[0] as Order;

if (order == null)

            {

return getNext()(input, getNext);

            }

if (order.OrderDate > DateTime.Today)

            {

return input.CreateExceptionMethodReturn(new OrderValidationException("The order date is later than the current date!"));

            }

if(order.Items.Count == 0)

            {

return input.CreateExceptionMethodReturn(new OrderValidationException("There are not any items for the order!"));

            }

if (this.ValidateSupplier)

            {

if (!LegalSuppliers.Contains<string>(order.Supplier))

                {

return input.CreateExceptionMethodReturn(new OrderValidationException("The supplier is inllegal!"));

                }

            }

if(this.ValidateTotalPrice)

            {

double totalPrice = 0;

foreach (OrderItem item in order.Items)

                {

                    totalPrice += item.Quantity * item.UnitPrice;

                }

if (totalPrice != order.TotalPrice)

                {

return input.CreateExceptionMethodReturn(new OrderValidationException("The sum of the order item is not equal to the order total price!"));

                }

            }

return getNext()(input, getNext);

        }

        #endregion

    }

}

OrderValidationCallHandler实现了Interface:Microsoft.Practices.EnterpriseLibrary.PolicyInjection. ICallHandler。ICallHandler仅仅有一个方法成员:

namespace Microsoft.Practices.EnterpriseLibrary.PolicyInjection

{

public interface ICallHandler

    {

IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext);

    }

}

参数input代表对方法的调用,你可以通过他们获得方法调用的参数、Context、MethodBase和Target Object。上本系列的第二部分已经详细介绍了,运用到某一个Method上的Policy可能包含一个或多个CallHandler,这些Handler在初始化的时候串成一个Pipeline。在一般的情况下在完成某一个Handler的操作之后会调用后一个Handler或者是Target Object(如何改Handler是最后一个Handler)。但是在一些特殊的场合,比如:验证错误;在执行当前Handler的操作时抛出Exception;对于某些特殊的输入有固定的返回值,那么就没有必要再将接力棒向后传递了。在这个时候我们可能直接抛出Exception或者返回一个特设的返回值。这个时候可以调用CreateExceptionMethodReturn和CreateMethodReturn来实现。

namespace Microsoft.Practices.EnterpriseLibrary.PolicyInjection

{

public interface IMethodInvocation

    {

IParameterCollection Arguments { get; }

        IParameterCollection Inputs { get; }

IDictionary InvocationContext { get; }

MethodBase MethodBase { get; }

object Target { get; }

IMethodReturn CreateExceptionMethodReturn(Exception ex);

IMethodReturn CreateMethodReturn(object returnValue, params object[] outputs);

    }

}

而第二个参数getNext是一个Delegate,代表对CallHandler Pipeline后面CallHandler或者是Target Object的调用,这也在第二部分有提到。

我们在回到Invoke的具体定义。我们假设我们具体调用的Method的第一个参数必须是我们定义的Order对象:先验证方法的调用是否含有输入参数(如何没有直接调用后面的CallHandler或者Target Object);返回获得第一个输入参数并验证其类型(如果不是Order类型直接调用后面的CallHandler或者Target Object)

if (input.Inputs.Count == 0)

{

return getNext()(input, getNext);

}

Order order = input.Inputs[0] as Order;

if (order == null)

{

return getNext()(input, getNext);

}

然后我们再验证Order对象是否满足我们在上面提出的验证规则,先看看必须的验证规则:对Order Date和Order Item Count的验证。

if (order.OrderDate > DateTime.Today)

{

return input.CreateExceptionMethodReturn(new OrderValidationException("The order date is later than the current date!"));

}

if(order.Items.Count == 0)

{

return input.CreateExceptionMethodReturn(new OrderValidationException("There are not any items for the order!"));

}

以及对可选的规则的验证:Total Price和Supplier。是否需要对其进行验证由两个Property来决定: ValidateSupplier和ValidateTotalPrice。

if (this.ValidateSupplier)

{

if (!LegalSuppliers.Contains<string>(order.Supplier))

    {

return input.CreateExceptionMethodReturn(new OrderValidationException("The supplier is inlegal!"));

    }

}

if(this.ValidateTotalPrice)

{

double totalPrice = 0;

foreach (OrderItem item in order.Items)

    {

        totalPrice += item.Quantity * item.UnitPrice;

    }

if (totalPrice != order.TotalPrice)

    {

return input.CreateExceptionMethodReturn(new OrderValidationException("The sum of product unit price * quantity is not equal to the order total price!"));

    }

}

最有将接力棒向后传递:

return getNext()(input, getNext);

到此为止,我们的OrderValidationCallHandler就定义完毕。但这仅仅完成了一半而已。因为我们最终需要通过Attribute或者Configuration的方式将我们的CallHandler运用到具体的Method上。我们先来看看使用Attribute的清况。我们需要在定一个Custom Attribute: OrderValidationCallHandlerAttribute.

namespace Artech.CustomCallHandlers

{

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]

public class OrderValidationCallHandlerAttribute : HandlerAttribute

    {

        #region Public Properties

public bool ValidateTotalPrice

        { get; set; }

public bool ValidateSupplier

        { get; set; }

        #endregion

public override ICallHandler CreateHandler()

        {

return new OrderValidationCallHandler() { ValidateSupplier = this.ValidateSupplier, ValidateTotalPrice = this.ValidateTotalPrice };

        }

    }

}

这是一个派生Microsoft.Practices.EnterpriseLibrary.PolicyInjection. HandlerAttribute自得特殊的Custom Attribute。HandlerAttribute是一个Abstract Class,继承自该Class通过其Orverride的CreateHandler来创建所需要的CallHandler,在这里当然是创建我们定义的OrderValidationCallHandler。由于对Total Price和Supplier的验证时可选的,所以我们定义了两个对应的Property来供Developer进行自由地配置,这两个Property用于初始化CallHandler。
注:本Demo通过VS2008创建,上面广泛使用了C# 3.0的一个新特性:Object Initializer

Step IV
通过Attribute运用OrderValidationCallHandler

我想到此为止,我们已经迫不及待地想将我们定义的OrderValidationCallHandler应用到我们程序中了。我们就通过一个Console Application来演示如何通过Attibute的方式来运用OrderValidationCallHandler到我们所需的Method 上。

现在定义以一个处理Order的Class: OrderProcessor。

public class OrderProcessor : MarshalByRefObject

{

        [OrderValidationCallHandlerAttribute]

public void ProcessOrder(Order order)

        {

Console.WriteLine("The order has been processed!");

        }

public static Order CreateOrder(DateTime orderDate, string supplier)

        {

Order order = new Order() { OrderNo = Guid.NewGuid(), OrderDate = orderDate, Supplier = supplier, TotalPrice = 10000 };

            order.Items.Add(new OrderItem() { ProductID = Guid.NewGuid(), UnitPrice = 6000, Quantity = 1, ProductName = "PC" });

            order.Items.Add(new OrderItem() { ProductID = Guid.NewGuid(), UnitPrice = 5000, Quantity = 2, ProductName = "Print"});

return order;

        }

}

CreateOrder用于创建Order对象。而我们将我们的OrderValidationCallHandlerAttribute运用到ProcessOrder Method上。现在我们就可以在Main方法上调用这个方法了:

class Program

    {

static void Main(string[] args)

        {

OrderProcessor orderProcessor = PolicyInjection.Create<OrderProcessor>();

Order order;

try

            {

                order = OrderProcessor.CreateOrder(DateTime.Today.AddDays(1), " Company AAA");

Console.WriteLine("Proceed to process an order with an invalid order date!");

                orderProcessor.ProcessOrder(order);

            }

catch (OrderValidationException ex)

            {

Console.WriteLine("Error: {0}"n",ex.Message);

            }

try

            {

                order = OrderProcessor.CreateOrder(DateTime.Today.AddDays(-1), " Company DDD");

Console.WriteLine("Proceed to process an order with an illegal supplier!");

                orderProcessor.ProcessOrder(order);

            }

catch (OrderValidationException ex)

            {

Console.WriteLine("Error: {0}"n", ex.Message);

            }

try

            {

                order = OrderProcessor.CreateOrder(DateTime.Today.AddDays(-1), " Company AAA");

Console.WriteLine("Proceed to process an order with incorrect total price!");

                orderProcessor.ProcessOrder(order);

            }

catch (OrderValidationException ex)

            {

Console.WriteLine("Error: {0}"n", ex.Message);

            }

        }

    }

Enterprise Library Policy Injection Application Block _第13张图片
我们看出,Order Date 的验证正常执行,而对于Total Price和Supplier的验证却没有起作用。因为这两个是可选的(默认为不进行验证),我们可以通过修改运用在ProcessOrder Method的OrderValidationCallHandlerAttribute来进行有效的配置。比如:

[OrderValidationCallHandlerAttribute(ValidateSupplier = true, ValidateTotalPrice = true)]

public void ProcessOrder(Order order)

{

Console.WriteLine("The order has been processed!");

}

这样将会出现如下的结果:

Enterprise Library Policy Injection Application Block _第14张图片
Step V
定义HandlerData和CallHandlerAssembler

在上面我们实现了通过Attribute的方式使用CallHandler的方式,我们现在来看看另一种运用CallHandler的方式:Configuration。为此我们需要定义两个额外的Class: HandlerData和CallHandlerAssembler。前者用于定义Configuration相关的Property,而后者则通过Configuration创建并初始化相应的CallHandler。

下面是HandlerData的定义:

namespace Artech.CustomCallHandlers

{

    [Assembler(typeof(OrderValidationCallHandlerAssembler))]

public class OrderValidationCallHandlerData:CallHandlerData

    {

        [ConfigurationProperty("validateSupplier", DefaultValue = false)]

public bool ValidateSupplier

        {

get

            {

return (bool)base["validateSupplier"];

            }

set

            {

base["validateSupplier"] = value;

            }

        }

        [ConfigurationProperty("validateTotalPrice", DefaultValue = false)]

public bool ValidateTotalPrice

        {

get

            {

return (bool)base["validateTotalPrice"];

            }

set

            {

base["validateTotalPrice"] = value;

            }

        }

    }

}

这和ConfigurationProperty相同,唯一的区别是在Class上运用了一个Assembler Attribute,并制定了一个CallHandlerAssembler type:OrderValidationCallHandlerAssembler。OrderValidationCallHandlerAssembler定义如下:

namespace Artech.CustomCallHandlers

{

public class OrderValidationCallHandlerAssembler : IAssembler<ICallHandler, CallHandlerData>

    {

        #region IAssembler<ICallHandler,OrderValidationCallHandlerData> Members

public ICallHandler Assemble(Microsoft.Practices.ObjectBuilder.IBuilderContext context, CallHandlerData objectConfiguration, Microsoft.Practices.EnterpriseLibrary.Common.Configuration.IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)

        {

OrderValidationCallHandlerData handlerData = objectConfiguration as OrderValidationCallHandlerData;

return new OrderValidationCallHandler(){ ValidateSupplier = handlerData.ValidateSupplier, ValidateTotalPrice = handlerData.ValidateTotalPrice};

        }

        #endregion

    }

}

OrderValidationCallHandlerAssembler派生自IAssembler<ICallHandler, CallHandlerData>,实现了Assemble方法。该方法用于收集的Configuration来创建所需的CallHandler。

到此为止,任务尚未结束,我们还需要将我们定义的CallHandler和HandlerData之间建立一个Mapping关系。这主要通过在CallHandler Class上运用ConfigurationElementType Attribute来实现。为此我们将此Attribute加在OrderValidationCallHandler上面:

namespace Artech.CustomCallHandlers

{

   [ConfigurationElementType(typeof(OrderValidationCallHandlerData))]

public class OrderValidationCallHandler:ICallHandler

    {

。。。。。。

    }

}

Step VI 通过Configuration来使用CallHandler

现在我们就可以采用Configuration的方式来讲我们定义的OrderValidationCallHandler运用到我们所需的Method上。我们先去掉OrderProcessor. ProcessOrder上的OrderValidationCallHandlerAttribute。然后添加一个Application Configuration 文件,并进行如下配置:

<configuration>

            <configSections>

                        <sectionname="policyInjection"type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration.PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

            </configSections>

            <policyInjection>

                        <policies>

                                    <addname="Policy">

                                                <matchingRules>

                                                            <addtype="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules.MemberNameMatchingRule, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="Member Name Matching Rule">

                                                                        <matches>

                                                                                    <addmatch="ProcessOrder"ignoreCase="false" />

                                                                        </matches>

                                                            </add>

                                                </matchingRules>

                                                <handlers>

                                                            <addtype="Artech.CustomCallHandlers.OrderValidationCallHandler, Artech.CustomCallHandlers"name="OrderValidationCallHandler"validateSupplier="true"validateTotalPrice="true"/>

                                                </handlers>

                                    </add>

                        </policies>

            </policyInjection>

</configuration>

在policyInjection Configuration Section中添加了一个Policy(Policy=Matching Rule + Call Handler), 对于Matching Rule,我们采用的是基于Method Name的Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules.MemberNameMatchingRule。而我们定义的OrderValidationCallHandler则加在<handlers> element下,两个属性validateSupplier和validateTotalPrice直接置于其中。

我们再次运行我们的程序,我们的输出结果和上面的没有任何区别:

Enterprise Library Policy Injection Application Block _第15张图片

· [原创]Enterprise Library Policy Injection Application Block 之一: PIAB Overview
· [原创]Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
· [原创]Enterprise Library Policy Injection Application Block 之三: PIAB的扩展—创建自定义CallHandler(提供Source Code下载)
· [原创]Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

一、为什么CallHandler需要进行排序

PIAB为我们提供了一个很好地实现AOP的方式。AOP旨在实现Business Logic和Non-Business Infrastructure Logic的分离。通过PIAB,我们将这些业务无关的逻辑定义在一个个的CallHandler中,然后通过Attribute或者Configuration的方式,将我们所需的CallHandler运用到相应的目标对象中。从这个意义上讲,PIAB具有很好的Flexibility和Extensibility。但是,就我看来PIAB也具有一些不足之处,其最大的局限性在于:不能控制运用到某个Method的多个方法的执行顺序。而让CallHandler按照我们希望的顺序进行调用是非常有必要的。

举个例子,假设我们将以下3个CallHandler运用到某个方法中:

  • ValidationHandler:用于参数参数的验证,比如是否为null, string的Length是否超出长度等等。
  • TransactionEnlistHandler: 用于将操作自动纳入到一个Transaction中,从而保证数据的一致性。
  • AuditLoggingHandler:当时操作成功执行后进行Audit Log。

很显然,正常的执行顺序应该是这样的:在最开始调用ValidationHandler进行参数的验证;Audit Log需要和目标方法一起纳入同一个Transaction中,所以TransactionEnlistHandler的调用紧随其后,最后才是AuditLoggingHandler。

Microsoft提供的原生的PIAB是无法实现的,好在Enterprise Library是开源的,我们可以修改PIAB的Source Code来使其实现我们的目标。而仅仅是一个很小的改动。接下来我们就来讨论一下如何来实现可被排序的CallHandler Pipeline。

二、如何创建Sequential CallHandler Pipeline

如果要了解我们这个Sequential CallHandler Pipeline的实现,需要对PIAB的是实现机制有一定的了解。在本系列的第二部分里,我对PIAB的实现机制进行了详细的阐述,在这里我仅仅简单介绍一个PIAB是如何实现AOP的。

PIAB对AOP的实现原理可以用一个词来概括:Method Interception。具体的做法做法是:通过PIAB Factory创建基于Target Type的Real Proxy,然后通过这个Real Proxy创建Transparent Proxy,并通过该Transparent Proxy调用Target Instance。在创建Real Proxy中,将运用到该Type的所有CallHandler缓存起来。当进行调用的时候,Transparent Proxy调用Real Proxy的Invoke方法。在该方法中,在将运用到当前Method的CallHandler构成一个Handler Pipeline。在真正调用Target Instance之前,按照Pipeline的先后顺序依次调用每个CallHandler。

而我们实现的切入点就是:CallHandler Pipeline创建之后,再根据我们希望的顺序将所有的CallHander重新排序

三、Sequential CallHandler Pipeline的实现

实现一个Sequential CallHandler Pipeline的一个前提就是,如何确定一个CallHandler在Pipeline的位置。为此,我们需要我们的Custom CallHandler有一个额外的属性:Ordinal,表明在Pipeline的序号,序号小的在前,大的在后。如何没有该属性,比如PIAB提供的所有CallHandler,我们将其放在最后。

我们仅仅需要修改两个PIAB Class: Microsoft.Practices.EnterpriseLibrary.PolicyInjection. HandlerPipeline和Microsoft.Practices.EnterpriseLibrary.PolicyInjection. RemotingInterception. InterceptingRealProxy。

对于HandlerPipeline,添加了一个新的Property:Handlers,用于在InterceptingRealProxy中能够获得组成Pipeline的所有CallHandler以利于排序。

public class HandlerPipeline

    {

private List<ICallHandler> handlers;

///<summary>

/// This property is added to get the CallHandler(s).

///</summary>

public List<ICallHandler> Handlers

        {

get { return handlers; }

set { handlers = value; }

        }
      。。。。。。
}

在才中,添加一个新的方法:ResortHandlers,将所有CallHandler按照Ordinal的大小进行重新排序(通过Reflection得到Ordinal的值)。

public HandlerPipeline ResortHandlers(HandlerPipeline pipeline)

        {

HandlerPipeline sequentialPipeline = new HandlerPipeline();

IDictionary<ICallHandler,int> handlerOrdinalPairList = new Dictionary<ICallHandler,int>();

ICallHandler[] handlers = Array.CreateInstance(typeof(ICallHandler), pipeline.Handlers.Count) as ICallHandler[];

int[] ordinals = Array.CreateInstance(typeof(int), pipeline.Handlers.Count) as int[];

for (int i = 0; i < pipeline.Handlers.Count; i++ )

            {

ICallHandler handler = pipeline.Handlers[i];

                handlers[i] = handler;

Type handlerType = handler.GetType();

MemberInfo[] memberInfos = handlerType.GetMember("Ordinal");

if (memberInfos.Length == 0)

                {

                    ordinals[i] = int.MaxValue;

continue;

                }

PropertyInfo propertyInfo = memberInfos[0] as PropertyInfo;

if (propertyInfo == null)

                {

                    ordinals[i] = int.MaxValue;

continue;

                }

int ordinal = (int)propertyInfo.GetValue(handler, null);

                ordinals[i] = ordinal;

            }

            ICallHandler swapHandler;

int swapOrdinal;

for (int i = 0; i < pipeline.Handlers.Count - 1; i++)

            {

for (int j = i + 1; j < pipeline.Handlers.Count; j++)

                {

if (ordinals[i] > ordinals[j])

                    {

                        swapOrdinal = ordinals[i];

                        ordinals[i] = ordinals[j];

                        ordinals[j] = swapOrdinal;

                        swapHandler = handlers[i];

                        handlers[i] = handlers[j];

                        handlers[j] = swapHandler;

                    }

                }

            }

return new HandlerPipeline(handlers);
        }

注:采用Reflection的方式获得Ordinal并不是一种很好的方式,最好是定义一个Abstract CallHandler BaseClass,并将Ordinal Property定义在这个BaseClass中。

该方法将在Ordinal的Invoke中调用:

public override IMessage Invoke(IMessage msg)

        {

IMethodCallMessage callMessage = (IMethodCallMessage)msg;

HandlerPipeline pipeline;

if (memberHandlers.ContainsKey(callMessage.MethodBase))

            {

                pipeline = memberHandlers[callMessage.MethodBase];

//Added by Jiang Jin Nan

                pipeline = ResortHandlers(pipeline);

            }

else

            {

                pipeline = new HandlerPipeline();

            }

RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, target);

IMethodReturn result =

                pipeline.Invoke(

                    invocation,

delegate(IMethodInvocation input, GetNextHandlerDelegate getNext)

                    {

try

                        {

object returnValue = callMessage.MethodBase.Invoke(target, invocation.Arguments);

return input.CreateMethodReturn(returnValue, invocation.Arguments);

                        }

catch (TargetInvocationException ex)

                        {

// The outer exception will always be a reflection exception; we want the inner, which is

// the underlying exception.

return input.CreateExceptionMethodReturn(ex.InnerException);

                        }

                    });

return ((RemotingMethodReturn)result).ToMethodReturnMessage();
        }

这就是所有需要的改动,为了验证是否有效,我们照例写一个测试程序。

四、如何使用Sequential CallHandler的PIAB

为了验证我们上所做的能否实现我们的目标:让运用到某个Method上的CallHandler按照我们希望的顺序来执行,我们创建了两个Custom CallHandler: CustomHandlerA 和CustomHandlerB:

namespace Artech.SequentialCallHandlers

{

public class CustomHandlerA: ICallHandler

    {

public int Ordinal

        { get; set; }

public CustomHandlerA()

        {

this.Ordinal = int.MaxValue;

        }

        #region ICallHandler Members

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

        {

Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerA is invoked!");

return getNext()(input, getNext);

        }

        #endregion

    }

}

namespace Artech.SequentialCallHandlers

{

public class CustomHandlerB:ICallHandler

    {

public int Ordinal

        { get; set; }

public CustomHandlerB()

        {

this.Ordinal = int.MaxValue;

        }

        #region ICallHandler Members

public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)

        {

Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerB is invoked!");

return getNext()(input, getNext);

        }

        #endregion

    }

}

下面是两个对应的HandlerAttribute:

namespace Artech.SequentialCallHandlers

{

public class ACustomHandlerAttribute:HandlerAttribute

    {

public int Ordinal

       { get; set; }

public override ICallHandler CreateHandler()

        {

return new CustomHandlerA() { Ordinal = this.Ordinal };

        }

    }

}

namespace Artech.SequentialCallHandlers

{

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]

public class BCustomHandlerAttribute:HandlerAttribute

    {

public int Ordinal

        { get; set; }

public override ICallHandler CreateHandler()

        {

return new CustomHandlerB() { Ordinal = this.Ordinal };

        }

    }

}

注:如何定义Custom CallHandler,在本系列的第三部分有详细的介绍。

然后,我们将这连个Attribute运用到同一个方法中:

class PolicyInjectionType:MarshalByRefObject

    {

        [BCustomHandlerAttribute(Ordinal = 1)]

        [ACustomHandlerAttribute(Ordinal = 2)]

public void DoSomething()

        {

Console.WriteLine("The target object is invoked!");

        }

}

我们在一个Console Application的Main()种调用这个DoSomething()方法:

class Program

    {

static void Main(string[] args)

        {

PolicyInjectionType proxy = PolicyInjection.Create<PolicyInjectionType>();

            proxy.DoSomething();

        }

}

由于CustomHandlerA的Ordinal为2,CustomHandlerB的Ordinal为1,所以他们正确的执行顺序为:CustomHandlerB-〉CustomHandlerA。输出的结果证实了这一点:

Enterprise Library Policy Injection Application Block _第16张图片
我们来改变一下他们的顺序:

class PolicyInjectionType:MarshalByRefObject

    {

        [BCustomHandlerAttribute(Ordinal = 2)]

        [ACustomHandlerAttribute(Ordinal = 1)]

public void DoSomething()

        {

Console.WriteLine("The target object is invoked!");

        }

}

这样的话,两个CallHandler的顺序将变成:CustomHandlerA-〉CustomHandlerB。我们再来看看输出的结果:

Enterprise Library Policy Injection Application Block _第17张图片
这正是我们所希望的。
· [原创]Enterprise Library Policy Injection Application Block 之一: PIAB Overview
· [原创]Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
· [原创]Enterprise Library Policy Injection Application Block 之三: PIAB的扩展—创建自定义CallHandler(提供Source Code下载)
· [原创]Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

你可能感兴趣的:(application)