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字符串。
用这种方式,可以获取函数返回的字符串或者其他类对象的数据。当然可能还有其他的返回数据的方式,这里主要是证明在筛选器中,可以获得函数的返回值这个事实。