PostSharp是一個用於在.NET平台上實現AOP(Aspect-Oriented Programming,面向方面編程)的框架,現通過簡單的示例代碼來演示如何使用postsharp。
1、新建一個控制台應用程序。.net框架是4.6.1版本的。
2、創建一個類CoreBusiness,表示用於完成項目的核心業務。
class CoreBusiness { [Log(ActionName = "Work_1")] public void Work_1() { Console.WriteLine("執行Work_1核心業務"); } }
3、在Program中調用CoreBusiness的對象來完成業務功能。
class Program { static CoreBusiness cb = new CoreBusiness(); static void Main(string[] args) { cb.Work_1(); Console.Read(); } }
4、假設現在項目已經開發完成了。但是現在要求給項目添加日志,記錄每個核心業務的執行情況。按照以前的老辦法(不用篩選器的情況下),需要定義一個日志操作類:
class LoggingHelper { public static void Writelog(String message) { Console.WriteLine(message); } }
然后在需要記錄日志的地方,實例化LoggingHelper的對象,然后寫入日志。這樣一來,就必須對原來已經開發的項目進行修改,這就違反了開閉原則了。而且添加日志並不是業務的需求變動,不應該去修改業務項目。
5、現在通過AOP面向方面的編程思想來解決這個日志的問題。要實現AOP,有很多框架,比如:Encase ,NKalore,PostSharp,AspectDNG,SetPoint等等。現在通過PostSharp來演示一下。
6、要使用PostSharp,首先必須要安裝它,在NuGet中收索PostSharp,安裝的是6.0.27版本的,這個版本只有45天的試用期
7、定義一個LogAttribute類,繼承OnMethodBoundaryAspect,這個Aspect提供了進入、退出函數等連接點方法。另外,Aspect上必須設置“[Serializable] ”,這與PostSharp內部對Aspect的生命周期管理有關
[Serializable] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class LogAttribute : OnMethodBoundaryAspect { public string ActionName { get; set; } public override void OnEntry(MethodExecutionArgs eventArgs) { LoggingHelper.Writelog(ActionName + "開始執行"); } public override void OnExit(MethodExecutionArgs eventArgs) { LoggingHelper.Writelog(ActionName + "成功完成"); } }
8、然后Log特性應用到Work_1函數,這個也可以應用到類上面,如果要應用到類,在定義LogAttribute的時候,AttributeUsage屬性的值就需要添加一個AttributeTargets.class
整個程序唯一修改的也就這里了。然后運行程序
這樣就完成了日志的添加功能。
9、現在有這樣一個需求,有個帶參數的函數,希望在記錄日志的時候,將這個函數的參數及參數值也記錄下來。如下圖,在記錄日志的時候,需要知道調用Work_2時,具體傳入的參數值。
要實現這個功能,需要修改LogAttribute的OnEntry函數:
public override void OnEntry(MethodExecutionArgs eventArgs) { Arguments arguments = eventArgs.Arguments; StringBuilder sb = new StringBuilder(); ParameterInfo[] parameters = eventArgs.Method.GetParameters(); for (int i = 0; arguments != null && i < arguments.Count; i++) {//進入的參數的值 sb.Append( parameters[i].Name + "=" + arguments[i] + ";"); } LoggingHelper.Writelog(ActionName + "開始執行"); LoggingHelper.Writelog(ActionName + "的參數:" + sb.ToString()); }
運行后:
10、現在有一個有返回值的函數,要求在日志中記錄這個函數的返回結果:
public override void OnExit(MethodExecutionArgs eventArgs) { string name = eventArgs.Method.Name;//用這種方式也能獲取特性修飾的函數的名稱 string value = eventArgs.ReturnValue.ToString(); LoggingHelper.Writelog(name + "的返回值:"+value); LoggingHelper.Writelog(ActionName + "成功完成"); }
運行后:
如果返回結果不是字符串,而是一個類對象,那么eventArgs.ReturnValue就是那個返回的對象,至於怎么將這個對象序列化,這里就不用說了。
比如我定義一個類:
class Modelsf { public string name { set; get; } public string IdNo { set; get; } }
添加一個核心業務函數:
[Log(ActionName = "Work_4")] public Modelsf Work_4(Modelsf m) { m.name = m.name + "FFFF"; return m; }
運行程序后的結果:
所以,對於參數和返回值不是基本類型的函數,如果要記錄參數和返回值的詳細信息,還需要特殊處理。
PostSharp與ActionFilterAttribute的比較
通過上面的示例可以看出postsharp與篩選器很類似。但是篩選器似乎只能用於web項目,不能用在控制台和winform項目,因為這兩種項目中無法添加System.Web.Mvc的引用。至於其他項目比如webservice,wcf,webapi能不能用,這個沒有
去求證。但是postsharp是可以用在這些項目的。因此postsharp比篩選器的應用范圍廣。
在web項目中自定義一個action篩選器,繼承ActionFilterAttribute。然后在OnActionExecuted和OnActionExecuting這兩個函數中添加日志記錄。這點和postsharp是一樣的。
public class LoggingHelper { public static void Writelog(String message) { Debug.WriteLine(message); } } public class MyActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName + "結束執行"); } public override void OnActionExecuting(ActionExecutingContext filterContext) { LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName+ "開始執行"); } }
這個MyActionFilter可以直接放到控制器上,不需要像postsharp那樣需要專門設置AttributeTargets.class。
對於有參數和返回值的函數,在篩選器中記錄日志:
public class MyActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { if (filterContext.Result.GetType().Name == "ContentResult") {//只有當函數返回的結果是字符串的時候,這種處理方式才能看到返回的內容。如果是其他類型,比如Dictionary<string, string>,都 //沒法獲得返回的具體內容 ContentResult res = (ContentResult)filterContext.Result; LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName + "的返回結果:" + res.Content); } LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName + "結束執行"); } public override void OnActionExecuting(ActionExecutingContext filterContext) { StringBuilder sb = new StringBuilder(); foreach (var key in filterContext.ActionParameters.Keys) { sb.Append(key + "=" + filterContext.ActionParameters[key] + ";"); } LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName + "的參數:"+sb.ToString()); LoggingHelper.Writelog(filterContext.ActionDescriptor.ActionName+ "開始執行"); } }
上面的web項目中沒法獲取返回值為string以外的函數的返回值,這是因為OnActionExecuted的參數沒法獲取返回值。下面在webapi里面試試。因為webapi和web項目中的ActionFilterAttribute是處於不同命名空間的,所以其OnActionExecuting和OnActionExecuted
函數的參數類型也就不同。
還是老規矩,在webapi中定義篩選器,代碼如下,在webapi中ActionFilterAttribute位於System.Web.Http.Filters空間,而web項目中的ActionFilterAttribute位於System.Web.Mvc。而且復寫的兩個函數的參數類型也是不同的。
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Web.Http.Controllers; //using System.Web.Mvc; using System.Web.Http.Filters; namespace WebApi.Loger { public class LoggingHelper { public static void Writelog(String message) { Debug.WriteLine(message); } } public class MyActionFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { StringBuilder sb = new StringBuilder(); foreach (var key in actionContext.ActionArguments.Keys) { sb.Append(key + "=" + actionContext.ActionArguments[key] + ";"); } LoggingHelper.Writelog(actionContext.ActionDescriptor.ActionName + "的參數:" + sb.ToString()); LoggingHelper.Writelog(actionContext.ActionDescriptor.ActionName + "開始執行"); } public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { string result = actionExecutedContext.Response.Content.ReadAsStringAsync().Result; var d = JsonConvert.DeserializeObject<Dictionary<string, string>>(result); LoggingHelper.Writelog(actionExecutedContext.ActionContext.ActionDescriptor.ActionName + "執行結果"+result); LoggingHelper.Writelog(actionExecutedContext.ActionContext.ActionDescriptor.ActionName + "結束執行"); } } public class User { public string Name { set; get; } public string Idno { set; get; } } }
在webapi的value控制器中添加3個函數:
public HttpResponseMessage GetInfo(string name,string idno) { JavaScriptSerializer serializer = new JavaScriptSerializer(); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(serializer.Serialize(name + "###" + idno), Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } public HttpResponseMessage GetInfos(string name,string idno) { JavaScriptSerializer serializer = new JavaScriptSerializer(); string str = serializer.Serialize(new Dictionary<string, string>() { { name, idno } ,{ "abc","23223"} }); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(str, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; } public HttpResponseMessage GetUser(string name,string idno) { User u = new User() { Name = name, Idno = idno }; JavaScriptSerializer serializer = new JavaScriptSerializer(); string str = serializer.Serialize(u); HttpResponseMessage result = new HttpResponseMessage { Content = new StringContent(str, Encoding.GetEncoding("UTF-8"), "application/json") }; return result; }
因為webapi默認返回的數據到了客戶端是xml格式的,所以通過代碼中的處理,將其返回的數據先序列化放到HttpResponseMessage,然后在篩選器中再反序列化。
注意,下圖中的反序列化的語句,這個要根據webapi返回的內容的類型來具體指定反序列化的類型。但是result肯定是一個json字符串。
用這種方式,可以獲取函數返回的字符串或者其他類對象的數據。當然可能還有其他的返回數據的方式,這里主要是證明在篩選器中,可以獲得函數的返回值這個事實。