程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Enterprise Library Policy Injection Application Block之三

Enterprise Library Policy Injection Application Block之三

編輯:關於.NET

本系列的第一部分對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上。

場景描述:本Demo模擬一個簡單的場景:訂單處理,我們將訂單處理之前對訂單的驗證通過PI的方式提供。我們假設需要進行如何的驗證:

Order Date必須早於當前日期。

必須有具體的Product。

供應商必須是制定的幾家合法供應商(可選)。

Order的總價必須是所有Product價格之和(可選)。

其中前兩個驗證規則為必須的,後兩個未可選項,可以通過Attribute和Configuration進行相應的配置。

Step I 創建Solution

如上圖,整個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 staticIList<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);
      }
    }
  }

我們看出,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!");
}

這樣將會出現如下的結果:

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直接置於其中。

我們再次運行我們的程序,我們的輸出結果和上面的沒有任何區別:

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved