转载:http://www.cnblogs.com/niuchenglei/archive/2009/12/29/1635482.html
在上一篇《ControllerActionInvoker——Action的导火索》中,我介绍了ControllerAcionInvoker类,那么接下来就到了Action的创建了,继续我们的ASP.NET mvc源代码之旅。
内容概览Top
本篇主要探讨“Action”的创建过程,为什么要加引号呢?因为我们创建的不是真正的Action,方法是没法创建的,它是指 ActionDescriptor对象,是对Action方法描述的一个对象,在mvc中,方法的调用是利用反射来实现的。下面我们就具体讨论一下这个过 程。
为什么要创建Action?Top
在一个请求到达时,必然最终会由一个Action去执行,那么这个Action是怎么执行的呢?答案是利用反射得到 Action的描述,然后再调用Action的。为什么要这么大费周折呢?因为在Action上还有好多Filter,我们要在执行的时候考虑到AOP的 影响,并把二者无缝的结合起来。所以在执行Action上,我们要得到一个ActionDescriptor对象,这个对象用以描述Action方法的一 些特性。
ControllerDescriptor与ActionDescriptorTop
ControllerDescriptor是描述Controller的类,ActionDescriptor是描述Action的类,而Action是 Controller的方法,那么在ControllerDescriptor和ActionControllerDescriptor两者之间就必然存 在着某种关联,下面我们看看到底他们是一种什么关系:
在ControllerActionInvoker类中,我们发现了两个类直接的一次协作,代码是这样的:
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName); ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
我们看到,ActionDescriptor是调用ControllerDescriptor的FindAction方法得到的。我们猜想它们可能是一对 多关系,一个ControllerDescriptor对应多个ActionDescriptor,下面就一步一步来验证我们的猜想,首先我们先从 ReflectedControllerDescriptor类入手,因为这个类是ControllerDescriptor类的惟一继承者。
ReflectedControllerDescriptor类有几个比较重要的字段:
private ActionDescriptor[] _canonicalActionsCache; private readonly Type _controllerType; private readonly ActionMethodSelector _selector;
从上面我们看到,ReflectedControllerDescriptor类有一个 ActionDescriptor[]类型的字段,这就证明了我们的猜想是正确的。还有一个ActionMethodSelector类型的字段,这个类 我们暂且不去管它。接着,我发现ReflectedControllerDescriptor类的构造函数接受一个Type类型的参数,这个Type就是 Controller的类型,然后new ActionMethodSelector(Type)一个ActionMethodSelector类型的对象,把它赋值给_selector字段。然 后,我们回到FindAction方法上,ControllerDescriptor就是通过这个方法得到ActionDescriptor对象的。该方 法代码如下:
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(actionName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ●MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName); if (matched == null) { return null; } ●return new ReflectedActionDescriptor(matched, actionName, this); }
从上面我们看到,首先利用ActionMethodSelector获得MethodInfo对象,然后把它作为参数new ReflectedActionDescriptor对象并返回。
总结一下,ControllerDescriptor通过ActionMethodSelector得到ActionDescriptor对 象,ReflectedActionDescriptor对象的构造操作只需要一个MethodInfo对象。具体的 ActionMethodSelector类的机制下面介绍。
ActionMethodSelector是什么?Top
从上一节中我们了解到,ActonMethodSelector是一个工具,是被ControllerDescriptor利用来获取 ActionDescriptor对象的工具。那么我们就有必要来了解一下这个工具了。从上一节中,我们得知FindActionMethod是一个突破 口,但是这次我们要先从构造函数入手,因为这个类在构造函数里面完成了一些初始化的操作,而这些初始化的操作是非常重要的。下面是它的构造函数:
//构造函数 public ActionMethodSelector(Type controllerType) { ControllerType = controllerType; ●PopulateLookupTables(); } //构造函数调用的方法,用来获取属于某个Controller的所有Action方法的MethodInfo对象 private void PopulateLookupTables() { ●MethodInfo[] allMethods = ControllerType.GetMethods( BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public); ●MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod); ●AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute); ●NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup( method => method.Name, StringComparer.OrdinalIgnoreCase); } //判断MethodInfo是否为合法的Action private static bool IsValidActionMethod(MethodInfo methodInfo) { return !(methodInfo.IsSpecialName || methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller))); } //判断MethodInfo是否为被ActionNameSelectorAttribute所修饰 private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo) { return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */); }
在上面的代码中,初始化的操作做了一些工作,这些工作是获取一个Controller的合法的所有Action,并存放在ActionMethodSelector的两个字段中,下面是这两个字段的定义:
public MethodInfo[] AliasedMethods public ILookup<string, MethodInfo> NonAliasedMethods
这两个属性是存放Action方法对应MethodInfo的,AliasedMethods存放那些使用 ActionNameSelectorAttribute属性标注的Action,也就是我们告诉mvc这是一个Action。 NonAliasedMethods用来存放我们没有明确指出这是一个Action,但的确它是一个Action的Action方法。
ActionMethodSelector的初始化操作已经完成了,下面我们从FindActionMethod方法入手,继续探究是如何获取一个Action的MethodInfo的。下面是该方法的代码:
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) { List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName); methodsMatchingName.AddRange(NonAliasedMethods[actionName]); List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName); switch (finalMethods.Count) { //匹配到0个 case 0: return null; //匹配到1个 case 1: return finalMethods[0]; //匹配到多个,抛出异常 default: throw CreateAmbiguousMatchException(finalMethods, actionName); } }
在上面的一段代码中,还涉及到了GetMatchAliasedMethods、RunSelectionFilters方法,这两个方法的代码就不再做详细分析了,相信大家看看mvc的源代码就很容易明白了。步骤是这样的:
- 该类在初始化操作中已经把“明确标注为Action”和“没有明确标注为Action”的所有Action填充到了自己的两个字段中。即我们通常使用的[ActionName("name")]特性。
- 建立一个最终查询的列表,并把“明确标注为Action”的所有Action都加入其中。
- 从“没有明确标注为Action”(即不使用ActionName进行标注)的Action列表中,即该类的NonAliasedMethods属性中找到名为action参数的一个MethodInfo,并把它加入到最终查询列表。
- 从最终查询列表中查找名为action参数的MethodInfo,如果匹配0个表示没有该Action,匹配1个就正确,匹配多个就抛出异常。
好了,到此我好了,到此我们便获取到了一个Action的MethodInfo,即反射信息。下一步就是利用这个MethodInfo构建一个描述Action的ReflectedActionDescriptor对象了,这点我们在上一节已经说过了,的确很简单。
获取全部合法的ActionTop
在ControllerControllerDescriptor类中,我们发现有一个 GetCanonicalActions的抽象方法。我们转到ReflectedActionDescriptor类,看看这个方法的实现。它的返回值是 ActionDescriptor[]类型的,它返回一个Controller所有的合法的Action。下面我们具体分析一下,它与 FindActionMethod有什么不同。下面是该方法的代码:
public override ActionDescriptor[] GetCanonicalActions() { ●ActionDescriptor[] actions = LazilyFetchCanonicalActionsCollection(); // need to clone array so that user modifications aren't accidentally stored return (ActionDescriptor[])actions.Clone(); } private ActionDescriptor[] LazilyFetchCanonicalActionsCollection() { ●return DescriptorUtil.LazilyFetchOrCreateDescriptors<MethodInfo, ActionDescriptor>( /* cacheLocation */ ref _canonicalActionsCache , /* initializer */ GetAllActionMethodsFromSelector , /* converter */ methodInfo => ReflectedActionDescriptor.TryCreateDescriptor(methodInfo, methodInfo.Name, this)); }
在上面的代码中,我们看到GetCanonicalActions方法需要一个DescriptorUtil类来辅助得到 ActionDescriptor[]类型列表。这个类的LazilyFetchOrCreateDescriptors方法需要三个参数,他们分别是 /*cacheLocation*/就是ControllerDescriptor存放ActionDescriptor的列表, /*initializer*/获取所有Action的一个委托,/*converter*/一个把MethodInfo对象包装成 ActionDescriptor对象的委托。第一个参数值为ControllerDescriptor类的一个字段,第二个参数值为一个方法的委托,下 面为这个委托:
private MethodInfo[] GetAllActionMethodsFromSelector() { List<MethodInfo> allValidMethods = new List<MethodInfo>(); ●allValidMethods.AddRange(_selector.AliasedMethods); ●allValidMethods.AddRange(_selector.NonAliasedMethods.SelectMany(g => g)); return allValidMethods.ToArray(); }
我们看到这个方法就是要把Controller的所有的Action都返回。第三个参数是一个lambda形式的表 达式,它直接调用ReflectedActionDescriptor的方法TryCreateDescriptor来包装MethodInfo对象成一 个ActionDescriptor对象。接下来我们看看DescriptorUtil类是如何设计的,这是一个静态类,只有一个静态的泛型方法:
public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>( ref TDescriptor[] cacheLocation, Func<TReflection[]> initializer, Func<TReflection, TDescriptor> converter) { // did we already calculate this once? ●TDescriptor[] existingCache = Interlocked.CompareExchange(ref cacheLocation, null, null); if (existingCache != null) { return existingCache; } ●TReflection[] memberInfos = initializer(); ●TDescriptor[] descriptors = memberInfos.Select(converter).Where( descriptor => descriptor != null).ToArray(); ●TDescriptor[] updatedCache = Interlocked.CompareExchange(ref cacheLocation,descriptors,null); return updatedCache ?? descriptors; }
这段代码看起来很吓人,因为它不但使用了泛型方法,还使用了linq查询,已经lambda表达式。其实并没有那么 复杂,他要先判断传入的ControllerDescriptor的ActionDescriptor列表是否为空,是则把原来列表中相同的部分删掉一 个,然后返回(为什么会有相同的两个元素在节点中呢?请看下面的小注)。如果为空,则获取所有合法Action,然后把两个相同中的一个删除,然后返回 (怎么又出现两个元素相同的现象呢?请看下面的小注)。看,并不是那么的难理解,泛型只是对类型抽象了一下而已,而lambda表达式只不过是传递一个方 法,不要畏惧这些小把戏,有了这些小把戏编程才更有趣。
为什么会出现有两个相同元素相同的情况?还记得ActionMethodSelector的两个属性 吗,AliasedMethods和NonAliasedMethods,我们使用[ActionName("actionname")]标注的 Action是“非常合法”的Action,这些Action将被划入AliasedMethods行列,我们没有使用这一特性标注的Action是“合 法”的Action,这些Action将被划入NonAliasedMethods行列。如果我们有如下代码的两个Action时会出现什么情况呢?很明 显,在获取Controller的全部Action时这两个就都获取到了,就会有两个Action相同。
public ActionResult Index(){……} [ActionName("Index")] public ActionResult Default(){……}
总结Top
在这一篇中,我们围绕ActionDescriptor讨论了一些,讨论了ActionDescriptor的创 建,与ControllerDescriptor的关系,以及怎么去获取相应的Action等,其实这个过程十分的简单,概括一句话就是 ControllerDescriptor利用ActionMethod获取到ActionDescriptor,而且这个过程在mvc中也很微小,但是 的确它是一个不可或缺的部分,而且是相当重要的一部分。谢谢大家的阅读,也许你对mvc源代码不是太感兴趣或对这个过程不太清楚,但我还是要告诉你,学习 mvc源代码有很多用处,只有坚持一下,一切就都会明白的。希望朋友们多批评指正,相互学习。
作者:Creason New(Creason's Blog)
出处: http://www.cnblogs.com/niuchenglei
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。