使用IntelliTrace调试跟踪MVC框架Action调用

  IntelliTrace调试跟普通断点加单步跟踪模式的区别在于,它支持对历史过程的模拟重新调试。当我们在普通调试下想了解应用程序曾经的执行情况,一般情况下我们会停止调试,重新加断点启动调试。而有了IntelliTrace之后,我们可以用其独有的历史调试功能“回到过去”,这样一次调试就可以有效定位问题。现在我要用这个功能,在开源MVC框架中寻找控制器的Action方法是如何被调用的。

  大家都知道,MVC通过URL路由截获地址栏参数获取Controller和Action的值,并通过这两个两个字符串型的去定位控制器和控制器的方法,再由这个方法返回视图。可问题在于,只知道字符串的类名和方法名是没有办法直接实现类并调用方法的。于是“很自然的”就想到了反射。由于反射的性能代价太大,很多人就开始抱怨,微软的新特性都是以牺牲性能为代价的,C#是性能低下的语言。然而事实是什么样呢,话说没有调查就没有发言权,我们先展开调查。

将MVC开源文件引入项目

  1. 下载MVC框架源码: ASP.NET MVC 2 源码 ASP.NET MVC 3 源码 ,本文用的是MVC2。

  2. 在VS2010新建一个MVC项目,删除引用“System.Web.Mvc”。将源码包解压,将src下SystemWebMvc目录拷贝至项目文件夹,在解决方案中添加项目,再添加对这个开源项目的引用。

  点击下载配置好的项目

使用IntelliTrace调试

  第一步,由于IntelliTrace调试默认是未启用的,首先你要开启它。安F5进入调试状态,看到右边的“IntelliTrace”窗口,单击打开IntelliTrace设置按钮。勾选“启用IntelliTrace”,单选组合点选“IntelliTrace事件和调用信息”,如下图所示。

  图1

  配置好后点确定,然后停止调试,在HomeController的Index方法处加以断点,启动调试,程序请求Index页面命中断点,这时你会发现围绕断点处多了几个箭头符号,这就是IntelliTrace调试要用到的。

  好了,现在我们就要展示IntelliTrace调试的神奇之处了。由于我们想知道Index方法到底被谁调用了,我们怎么操作?见证奇迹的时刻就要到了!我们要让程序倒着执行,是不是就很容易知道它的调用者?看到有个向上的双箭头,我叫他“单步回退”,单击一次,程序定位到了某个类的Execute方法中,它调用了名为_executor委托,然后我们分析代码发现这个委托在其下方的GetExecutor函数中被实现,我们重点关注GetExecutor,我将其贴在下面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private static ActionExecutor GetExecutor(MethodInfo methodInfo) {
    // Parameters to executor
    ParameterExpression controllerParameter = Expression.Parameter(typeof(ControllerBase), "controller");
    ParameterExpression parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
 
    // Build parameter list
    List<Expression> parameters = new List<Expression>();
    ParameterInfo[] paramInfos = methodInfo.GetParameters();
    for (int i = 0; i < paramInfos.Length; i++) {
        ParameterInfo paramInfo = paramInfos[i];
        BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
        UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType);
 
        // valueCast is "(Ti) parameters[i]"
        parameters.Add(valueCast);
    }
 
    // Call method
    UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(controllerParameter, methodInfo.ReflectedType) : null;
    MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters);
 
    // methodCall is "((TController) controller) method((T0) parameters[0], (T1) parameters[1], ...)"
    // Create function
    if (methodCall.Type == typeof(void)) {
        Expression<VoidActionExecutor> lambda = Expression.Lambda<VoidActionExecutor>(methodCall, controllerParameter, parametersParameter);
        VoidActionExecutor voidExecutor = lambda.Compile();
        return WrapVoidAction(voidExecutor);
    }
    else {
        // must coerce methodCall to match ActionExecutor signature
        UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
        Expression<ActionExecutor> lambda = Expression.Lambda<ActionExecutor>(castMethodCall, controllerParameter, parametersParameter);
        return lambda.Compile();
    }
}

  研究过表达式树的同鞋一定知道,在这个方法中构建了一个调用Action方法的表达式树,并通过lambda.Compile()返回调用过程的委托。lambda表达式树调用方法的效率如何,老赵早已在这篇文章[点击学习]分析过,它的效率跟静态调用相差无几的。所以担心“反射”降低性能的朋友大可以放心了。

后记

  本文虽然主题不够明确,讲了调试又讲MVC(我就喜欢跟着思维走,呵呵),也反映了些问题。假如我们通过一般的调试手段去分析这个问题,那么我们得先把MVC源码给分析一边吧,然后呢,我们好容易找到了入口点,Controller的 Execute方法,加了断点了,然后就开始淌水了,趟了好深好深的一趟水,然后执行Action了,然后我们要再次调试,回忆刚才从哪里跳到Action,终于找到了。好辛苦!而用IntelliTrace只是轻轻点击下鼠标,就完成了任务。另外,选MVC框架源码的作用,说明项目足够复杂时,这样的调试功能的价值才能体现出来。另外,不是也有个收获么,知道了MVC框架怎么通过地址栏参数映射到方法了。

作者:李盼(Lipan)
出处:[Lipan]http://www.cnblogs.com/lipan/
版权声明:本文的版权归作者与博客园共有。转载时须注明本文的详细链接,否则作者将保留追究其法律责任。
posted @   lipan  阅读(4121)  评论(6编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示