C# 实现AOP的几种常见方式

发表于:2017-11-29 10:07

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:梦在旅途    来源:博客园

  这里顺便分享微软官方如果利用RealProxy类实现AOP的,详见地址:https://msdn.microsoft.com/zh-cn/library/dn574804.aspx
  第四种:反射+ 通过定义统一的出入口,并运用一些特性实现AOP的效果,比如:常见的MVC、WEB API中的过滤器特性 ,我这里根据MVC的思路,实现了类似的MVC过滤器的AOP效果,只是中间用到了反射,可能性能不佳,但效果还是成功实现了各种拦截,正如MVC一样,既支持过滤器特性,也支持Controller中的Action执行前,执行后,错误等方法实现拦截
  实现思路如下:
  A.过滤器及Controller特定方法拦截实现原理:
  1.获取程序集中所有继承自Controller的类型;
  2.根据Controller的名称找到第1步中的对应的Controller的类型:FindControllerType
  3.根据找到的Controller类型及Action的名称找到对应的方法:FindAction
  4.创建Controller类型的实例;
  5.根据Action方法找到定义在方法上的所有过滤器特性(包含:执行前、执行后、错误)
  6.执行Controller中的OnActionExecuting方法,随后执行执行前的过滤器特性列表,如:ActionExecutingFilter
  7.执行Action方法,获得结果;
  8.执行Controller中的OnActionExecuted方法,随后执行执行后的过滤器特性列表,如:ActionExecutedFilter
  9.通过try catch在catch中执行Controller中的OnActionError方法,随后执行错误过滤器特性列表,如:ActionErrorFilter
  10.最后返回结果;
  B.实现执行路由配置效果原理:
  1.增加可设置路由模板列表方法:AddExecRouteTemplate,在方法中验证controller、action,并获取模板中的占位符数组,最后保存到类全局对象中routeTemplates;
  2.增加根据执行路由执行对应的Controller中的Action方法的效果: Run,在该方法中主要遍历所有路由模板,然后与实行执行的请求路由信息通过正则匹配,若匹配OK,并能正确找到Controller及Action,则说明正确,并最终统一调用:Process方法,执行A中的所有步骤最终返回结果。
  需要说明该模拟MVC方案并没有实现Action方法参数的的绑定功能,因为ModelBinding本身就是比较复杂的机制,所以这里只是为了搞清楚AOP的实现原理,故不作这方面的研究,大家如果有空可以实现,最终实现MVC不仅是ASP.NET MVC,还可以是 Console MVC,甚至是Winform MVC等。
  以下是实现的全部代码,代码中我已进行了一些基本的优化,可以直接使用:
public abstract class Controller
{
public virtual void OnActionExecuting(MethodInfo action)
{
}
public virtual void OnActionExecuted(MethodInfo action)
{
}
public virtual void OnActionError(MethodInfo action, Exception ex)
{
}
}
public abstract class FilterAttribute : Attribute
{
public abstract string FilterType { get; }
public abstract void Execute(Controller ctrller, object extData);
}
public class ActionExecutingFilter : FilterAttribute
{
public override string FilterType => "BEFORE";
public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutingFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
}
}
public class ActionExecutedFilter : FilterAttribute
{
public override string FilterType => "AFTER";
public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionExecutedFilter中拦截发出的消息!-{DateTime.Now.ToString()}");
}
}
public class ActionErrorFilter : FilterAttribute
{
public override string FilterType => "EXCEPTION";
public override void Execute(Controller ctrller, object extData)
{
Console.WriteLine($"我是在{ctrller.GetType().Name}.ActionErrorFilter中拦截发出的消息!-{DateTime.Now.ToString()}-Error Msg:{(extData as Exception).Message}");
}
}
public class AppContext
{
private static readonly Type ControllerType = typeof(Controller);
private static readonly Dictionary<string, Type> matchedControllerTypes = new Dictionary<string, Type>();
private static readonly Dictionary<string, MethodInfo> matchedControllerActions = new Dictionary<string, MethodInfo>();
private Dictionary<string,string[]> routeTemplates = new Dictionary<string, string[]>();
public void AddExecRouteTemplate(string execRouteTemplate)
{
if (!Regex.IsMatch(execRouteTemplate, "{controller}", RegexOptions.IgnoreCase))
{
throw new ArgumentException("执行路由模板不正确,缺少{controller}");
}
if (!Regex.IsMatch(execRouteTemplate, "{action}", RegexOptions.IgnoreCase))
{
throw new ArgumentException("执行路由模板不正确,缺少{action}");
}
string[] keys = Regex.Matches(execRouteTemplate, @"(?<={)\w+(?=})", RegexOptions.IgnoreCase).Cast<Match>().Select(c => c.Value.ToLower()).ToArray();
routeTemplates.Add(execRouteTemplate,keys);
}
public object Run(string execRoute)
{
//{controller}/{action}/{id}
string ctrller = null;
string actionName = null;
ArrayList args = null;
Type controllerType = null;
bool findResult = false;
foreach (var r in routeTemplates)
{
string[] keys = r.Value;
string execRoutePattern = Regex.Replace(r.Key, @"{(?<key>\w+)}", (m) => string.Format(@"(?<{0}>.[^/\\]+)", m.Groups["key"].Value.ToLower()), RegexOptions.IgnoreCase);
args = new ArrayList();
if (Regex.IsMatch(execRoute, execRoutePattern))
{
var match = Regex.Match(execRoute, execRoutePattern);
for (int i = 0; i < keys.Length; i++)
{
if ("controller".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
ctrller = match.Groups["controller"].Value;
}
else if ("action".Equals(keys[i], StringComparison.OrdinalIgnoreCase))
{
actionName = match.Groups["action"].Value;
}
else
{
args.Add(match.Groups[keys[i]].Value);
}
}
if ((controllerType = FindControllerType(ctrller)) != null && FindAction(controllerType, actionName, args.ToArray()) != null)
{
findResult = true;
break;
}
}
}
if (findResult)
{
return Process(ctrller, actionName, args.ToArray());
}
else
{
throw new Exception($"在已配置的路由模板列表中未找到与该执行路由相匹配的路由信息:{execRoute}");
}
}
public object Process(string ctrller, string actionName, params object[] args)
{
Type matchedControllerType = FindControllerType(ctrller);
if (matchedControllerType == null)
{
throw new ArgumentException($"未找到类型为{ctrller}的Controller类型");
}
object execResult = null;
if (matchedControllerType != null)
{
var matchedController = (Controller)Activator.CreateInstance(matchedControllerType);
MethodInfo action = FindAction(matchedControllerType, actionName, args);
if (action == null)
{
throw new ArgumentException($"在{matchedControllerType.FullName}中未找到与方法名:{actionName}及参数个数:{args.Count()}相匹配的方法");
}
var filters = action.GetCustomAttributes<FilterAttribute>(true);
List<FilterAttribute> execBeforeFilters = new List<FilterAttribute>();
List<FilterAttribute> execAfterFilters = new List<FilterAttribute>();
List<FilterAttribute> exceptionFilters = new List<FilterAttribute>();
if (filters != null && filters.Count() > 0)
{
execBeforeFilters = filters.Where(f => f.FilterType == "BEFORE").ToList();
execAfterFilters = filters.Where(f => f.FilterType == "AFTER").ToList();
exceptionFilters = filters.Where(f => f.FilterType == "EXCEPTION").ToList();
}
try
{
matchedController.OnActionExecuting(action);
if (execBeforeFilters != null && execBeforeFilters.Count > 0)
{
execBeforeFilters.ForEach(f => f.Execute(matchedController, null));
}
var mParams = action.GetParameters();
object[] newArgs = new object[args.Length];
for (int i = 0; i < mParams.Length; i++)
{
newArgs[i] = Convert.ChangeType(args[i], mParams[i].ParameterType);
}
execResult = action.Invoke(matchedController, newArgs);
matchedController.OnActionExecuted(action);
if (execBeforeFilters != null && execBeforeFilters.Count > 0)
{
execAfterFilters.ForEach(f => f.Execute(matchedController, null));
}
}
catch (Exception ex)
{
matchedController.OnActionError(action, ex);
if (exceptionFilters != null && exceptionFilters.Count > 0)
{
exceptionFilters.ForEach(f => f.Execute(matchedController, ex));
}
}
}
return execResult;
}
private Type FindControllerType(string ctrller)
{
Type matchedControllerType = null;
if (!matchedControllerTypes.ContainsKey(ctrller))
{
var assy = Assembly.GetAssembly(typeof(Controller));
foreach (var m in assy.GetModules(false))
{
foreach (var t in m.GetTypes())
{
if (ControllerType.IsAssignableFrom(t) && !t.IsAbstract)
{
if (t.Name.Equals(ctrller, StringComparison.OrdinalIgnoreCase) || t.Name.Equals($"{ctrller}Controller", StringComparison.OrdinalIgnoreCase))
{
matchedControllerType = t;
matchedControllerTypes[ctrller] = matchedControllerType;
break;
}
}
}
}
}
else
{
matchedControllerType = matchedControllerTypes[ctrller];
}
return matchedControllerType;
}
private MethodInfo FindAction(Type matchedControllerType, string actionName, object[] args)
{
string ctrlerWithActionKey = $"{matchedControllerType.FullName}.{actionName}";
MethodInfo action = null;
if (!matchedControllerActions.ContainsKey(ctrlerWithActionKey))
{
if (args == null) args = new object[0];
foreach (var m in matchedControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
if (m.Name.Equals(actionName, StringComparison.OrdinalIgnoreCase) && m.GetParameters().Length == args.Length)
{
action = m;
matchedControllerActions[ctrlerWithActionKey] = action;
break;
}
}
}
else
{
action = matchedControllerActions[ctrlerWithActionKey];
}
return action;
}
}
  使用前,先定义一个继承自Controller的类,如:TestController,并重写相应的方法,或在指定的方法上加上所需的过滤器特性,如下代码所示:
public class TestController : Controller
{
public override void OnActionExecuting(MethodInfo action)
{
Console.WriteLine($"{action.Name}执行前,OnActionExecuting---{DateTime.Now.ToString()}");
}
public override void OnActionExecuted(MethodInfo action)
{
Console.WriteLine($"{action.Name}执行后,OnActionExecuted--{DateTime.Now.ToString()}");
}
public override void OnActionError(MethodInfo action, Exception ex)
{
Console.WriteLine($"{action.Name}执行,OnActionError--{DateTime.Now.ToString()}:{ex.Message}");
}
[ActionExecutingFilter]
[ActionExecutedFilter]
public string HelloWorld(string name)
{
return ($"Hello World!->{name}");
}
[ActionExecutingFilter]
[ActionExecutedFilter]
[ActionErrorFilter]
public string TestError(string name)
{
throw new Exception("这是测试抛出的错误信息!");
}
[ActionExecutingFilter]
[ActionExecutedFilter]
public int Add(int a, int b)
{
return a + b;
}
}
  最后前端实际调用就非常简单了,代码如下:
class MVCProgram
{
static void Main(string[] args)
{
try
{
var appContext = new AppContext();
object rs = appContext.Process("Test", "HelloWorld", "梦在旅途");
Console.WriteLine($"Process执行的结果1:{rs}");
Console.WriteLine("=".PadRight(50, '='));
appContext.AddExecRouteTemplate("{controller}/{action}/{name}");
appContext.AddExecRouteTemplate("{action}/{controller}/{name}");
object result1 = appContext.Run("HelloWorld/Test/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果1:{result1}");
Console.WriteLine("=".PadRight(50, '='));
object result2 = appContext.Run("Test/HelloWorld/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果2:{result2}");
Console.WriteLine("=".PadRight(50, '='));
appContext.AddExecRouteTemplate("{action}/{controller}/{a}/{b}");
object result3 = appContext.Run("Add/Test/500/20");
Console.WriteLine($"执行的结果3:{result3}");
object result4 = appContext.Run("Test/TestError/梦在旅途-zuowenjun.cn");
Console.WriteLine($"执行的结果4:{result4}");
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"发生错误:{ex.Message}");
Console.ResetColor();
}
Console.ReadKey();
}
}
  可以看到,与ASP.NET MVC有点类似,只是ASP.NET MVC是通过URL访问,而这里是通过AppContext.Run 执行路由URL 或Process方法,直接指定Controller、Action、参数来执行。
  通过以上调用代码可以看出路由配置还是比较灵活的,当然参数配置除外。如果大家有更好的想法也可以在下方评论交流,谢谢!
  MVC代码执行效果如下:
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号