public class Order { public int Id { set; get; } public string Name { set; get; } public int Count { set; get; } public double Price { set; get; } public string Desc { set; get; } } public interface IOrderProcessor { void Submit(Order order); } public class OrderProcessor : IOrderProcessor { public void Submit(Order order) { Console.WriteLine("提交訂單"); } } public class OrderProcessorDecorator : IOrderProcessor { public IOrderProcessor OrderProcessor { get; set; } public OrderProcessorDecorator(IOrderProcessor orderprocessor) { OrderProcessor = orderprocessor; } public void Submit(Order order) { PreProceed(order); OrderProcessor.Submit(order); PostProceed(order); } public void PreProceed(Order order) { Console.WriteLine("提交訂單前,進行訂單數據校驗...."); if (order.Price < 0) { Console.WriteLine("訂單總價有誤,請重新核對訂單。"); } } public void PostProceed(Order order) { Console.WriteLine("提交帶單後,進行訂單日志記錄......"); Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "提交訂單,訂單名稱:" + order.Name + ",訂單價格:" + order.Price); } }
調用代碼:
static void Main(string[] args) { Order order = new Order() { Id = 1, Name = "lee", Count = 10, Price = 100.00, Desc = "訂單測試" }; IOrderProcessor orderprocessor = new OrderProcessorDecorator(new OrderProcessor()); orderprocessor.Submit(order); Console.ReadLine(); }
得到結果: 上面我們模擬訂單提交的例子,在提交一個訂單前,我們需要做很多的准備工作,比如數據有效性校驗等;訂單提交完成之後,我們還需要做日志記錄等。上面的代碼很簡單,沒有任何復雜的邏輯,從上面的代碼可以看出,我們通過靜態植入的方式手動在執行方法前和執行方法後讓它做一些我們需要的功能。AOP的實現原理應該也是如此,只不過它幫助我們做了方法攔截,幫我們省去了大量重復代碼,我們要做的僅僅是寫好攔截前和攔截後需要處理的邏輯。 2、動態代理 了解了靜態攔截的例子,你是否對AOP有一個初步的認識了呢。下面我們就來到底AOP該如何使用。按照園子裡面很多牛人的說法,AOP的實現方式大致可以分為兩類:動態代理和IL 編織兩種方式。博主也不打算照本宣科,分別拿Demo來說話吧。下面就以兩種方式各選一個代表框架來說明。 動態代理方式,博主就以微軟企業庫(MS Enterprise Library)裡面的PIAB(Policy Injection Application Block)框架來作說明。 首先需要下載以下幾個dll,然後添加它們的引用。 然後定義對應的Handler
public class User { public string Name { set; get; } public string PassWord { set; get; } } #region 1、定義特性方便使用 public class LogHandlerAttribute : HandlerAttribute { public string LogInfo { set; get; } public int Order { get; set; } public override ICallHandler CreateHandler(IUnityContainer container) { return new LogHandler() { Order = this.Order, LogInfo = this.LogInfo }; } } #endregion #region 2、注冊對需要的Handler攔截請求 public class LogHandler : ICallHandler { public int Order { get; set; } public string LogInfo { set; get; } //這個方法就是攔截的方法,可以規定在執行方法之前和之後的攔截 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { Console.WriteLine("LogInfo內容" + LogInfo); //0.解析參數 var arrInputs = input.Inputs; if (arrInputs.Count > 0) { var oUserTest1 = arrInputs[0] as User; } //1.執行方法之前的攔截 Console.WriteLine("方法執行前攔截到了"); //2.執行方法 var messagereturn = getNext()(input, getNext); //3.執行方法之後的攔截 Console.WriteLine("方法執行後攔截到了"); return messagereturn; } } #endregion #region 3、用戶定義接口和實現 public interface IUserOperation { void Test(User oUser); void Test2(User oUser, User oUser2); } //這裡必須要繼承這個類MarshalByRefObject,否則報錯 public class UserOperation : MarshalByRefObject, IUserOperation { private static UserOperation oUserOpertion = null; public UserOperation() { //oUserOpertion = PolicyInjection.Create<UserOperation>(); } //定義單例模式將PolicyInjection.Create<UserOperation>()產生的這個對象傳出去,這樣就避免了在調用處寫這些東西 public static UserOperation GetInstance() { if (oUserOpertion == null) oUserOpertion = PolicyInjection.Create<UserOperation>(); return oUserOpertion; } //調用屬性也會攔截 public string Name { set; get; } //[LogHandler],在方法上面加這個特性,只對此方法攔截 [LogHandler(LogInfo = "Test的日志為aaaaa")] public void Test(User oUser) { Console.WriteLine("Test方法執行了"); } [LogHandler(LogInfo = "Test2的日志為bbbbb")] public void Test2(User oUser, User oUser2) { Console.WriteLine("Test2方法執行了"); } } #endregion
最後我們來看調用的代碼:
static void Main(string[] args) { try { var oUserTest1 = new User() { Name = "test2222", PassWord = "yxj" }; var oUserTest2 = new User() { Name = "test3333", PassWord = "yxj" }; var oUser = UserOperation.GetInstance(); oUser.Test(oUserTest1); oUser.Test2(oUserTest1,oUserTest2); } catch (Exception ex) { //throw; } }
得到結果如下: 我們來看執行Test()方法和Test2()方法時候的順序。 由於Test()和Test2()方法上面加了LogHander特性,這個特性裡面定義了AOP的Handler,在執行Test和Test2方法之前和之後都會進入Invoke()方法裡面。其實這就是AOP的意義所在,將切面的通用功能在統一的地方處理,在主要邏輯裡面直接用過特性使用即可。 3、IL編織 靜態織入的方式博主打算使用PostSharp來說明,一來這個使用起來簡單,二來項目中用過這種方式。 Postsharp從2.0版本就開始收費了。為了說明AOP的功能,博主下載了一個免費版本的安裝包,使用PostSharp與其它框架不太一樣的是一定要下載安裝包安裝,只引用類庫是不行的,因為上文說過,AOP框架需要為編譯器或運行時添加擴展。使用步驟如下: (1)下載Postsharp安裝包,安裝。 (2)在需要使用AOP的項目中添加PostSharp.dll 這個dll的引用。 (3)定義攔截的方法:
[Serializable] public class TestAop : PostSharp.Aspects.OnMethodBoundaryAspect { //發生異常時進入此方法 public override void OnException(MethodExecutionArgs args) { base.OnException(args); } //執行方法前執行此方法 public override void OnEntry(MethodExecutionArgs args) { base.OnEntry(args); } //執行方法後執行此方法 public override void OnExit(MethodExecutionArgs args) { base.OnExit(args); } }
注意這裡的TestAop這個類必須要是可序列化的,所以要加上[Serializable]特性 (4)在需要攔截功能的地方使用。 在類上面加特性攔截,此類下面的所有的方法都會具有攔截功能。
[TestAop]public class Impc_TM_PLANT : Ifc_TM_PLANT { /// <summary> /// 獲取或設置服務接口。 /// </summary> private Ic_TM_PLANTService service { get; set; } public IList<DTO_TM_PLANT> Find() { DTO_TM_PLANT otest = null; otest.NAME_C = "test";//異常,會進入OnException方法 return service.FindAll(); } }
方法上面加特性攔截,只會攔截此方法。
[TestAop] public IList<DTO_TM_PLANT> Find() { DTO_TM_PLANT otest = null; otest.NAME_C = "test"; return service.FindAll(); }
有沒有感覺很簡單,很強大,其實這一簡單應用,解決我們常見的日志、異常、權限驗證等功能簡直太小菜一碟了。當然Postsharp可能還有許多更加高級的功能,有興趣可以深究下。 4、MVC裡面的Filter public class AOPFilterAttribute : ActionFilterAttribute, IExceptionFilter
{ public void OnException(ExceptionContext filterContext) { throw new System.NotImplementedException(); } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); } }
在controller裡面使用該特性:
[AOPFilter] public JsonResult GetEditModel(string strType) { var lstRes = new List<List<DragElementProp>>(); var lstResPage = new List<PageProperty>(); //.........todo return Json(new { lstDataAttr = lstRes, PageAttr = lstResPage, lstJsConnections = lstJsPlumbLines }, JsonRequestBehavior.AllowGet); }
調試可知,在執行GetEditModel(string strType)方法之前,會先執行OnActionExecuting()方法,GetEditModel(string strType)之後,又會執行OnActionExecuted()方法。這在我們MVC裡面權限驗證、錯誤頁導向、日志記錄等常用功能都可以方便解決。