本系列的第一部分對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直接置於其中。
我們再次運行我們的程序,我們的輸出結果和上面的沒有任何區別:
本文配套源碼