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