[转载]使用IntelliTrace调试跟踪MVC框架Action调用 – lipan – 博客园.
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事件和调用信息”,如下 图所示。
配置好后点确定,然后停止调试,在HomeController的Index方法处加以断点,启动调试,程序请求Index页面命中断点,这时你会发现围绕断点处多了几个箭头符号,这就是IntelliTrace调试要用到的。
好了,现在我们就要展示IntelliTrace调试的神奇之处了。由于我们想知道Index方法到底被谁调用了,我们怎么操作?见证奇迹的时刻就要到了!我 们要让程序倒着执行,是不是就很容易知道它的调用者?看到有个向上的双箭头,我叫他“单步回退”,单击一次,程序定位到了某个类的Execute方法中, 它调用了名为_executor委托,然后我们分析代码发现这个委托在其下方的GetExecutor函数中被实现,我们重点关注 GetExecutor,我将其贴在下面。
01 |
private static ActionExecutor GetExecutor(MethodInfo methodInfo) { |
03 |
ParameterExpression controllerParameter = Expression.Parameter( typeof (ControllerBase), "controller" ); |
04 |
ParameterExpression parametersParameter = Expression.Parameter( typeof ( object []), "parameters" ); |
07 |
List<Expression> parameters = new List<Expression>(); |
08 |
ParameterInfo[] paramInfos = methodInfo.GetParameters(); |
09 |
for ( int i = 0; i < paramInfos.Length; i++) { |
10 |
ParameterInfo paramInfo = paramInfos[i]; |
11 |
BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); |
12 |
UnaryExpression valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); |
15 |
parameters.Add(valueCast); |
19 |
UnaryExpression instanceCast = (!methodInfo.IsStatic) ? Expression.Convert(controllerParameter, methodInfo.ReflectedType) : null ; |
20 |
MethodCallExpression methodCall = methodCall = Expression.Call(instanceCast, methodInfo, parameters); |
24 |
if (methodCall.Type == typeof ( void )) { |
25 |
Expression<VoidActionExecutor> lambda = Expression.Lambda<VoidActionExecutor>(methodCall, controllerParameter, parametersParameter); |
26 |
VoidActionExecutor voidExecutor = lambda.Compile(); |
27 |
return WrapVoidAction(voidExecutor); |
31 |
UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof ( object )); |
32 |
Expression<ActionExecutor> lambda = Expression.Lambda<ActionExecutor>(castMethodCall, controllerParameter, parametersParameter); |
33 |
return lambda.Compile(); |
研究过表达式树的同鞋一定知道,在这个方法中构建了一个调用Action方法的表达式树,并通过lambda.Compile()返回调用过程的委托。lambda表达式树调用方法的效率如何,老赵早已在这篇文章[点击学习]分析过,它的效率跟静态调用相差无几的。所以担心“反射”降低性能的朋友大可以放心了。
后记
本文虽然主题不够明确,讲了调试又讲MVC(我就喜欢跟着思维走,呵呵),也反映了些问题。假如我们通过一般的调试手段去分析这个问题,那么我 们得先把MVC源码给分析一边吧,然后呢,我们好容易找到了入口点,Controller的 Execute方法,加了断点了,然后就开始淌水了,趟了好深好深的一趟水,然后执行Action了,然后我们要再次调试,回忆刚才从哪里跳到 Action,终于找到了。好辛苦!而用IntelliTrace只是轻轻点击下鼠标,就完成了任务。另外,选MVC框架源码的作用,说明项目足够复杂 时,这样的调试功能的价值才能体现出来。另外,不是也有个收获么,知道了MVC框架怎么通过地址栏参数映射到方法了。