原创|其它|编辑:郝浩|2009-12-30 10:40:49.000|阅读 537 次
概述:本篇主要探讨“Action”的创建过程,为什么要加引号呢?因为我们创建的不是真正的Action,方法是没法创建的,它是指ActionDescriptor对象,是对Action方法描述的一个对象,在mvc中,方法的调用是利用反射来实现的。下面我们就具体讨论一下这个过程。
# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>
本篇主要探讨“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 MethodInfo> NonAliasedMethods
这两个属性是存放Action方法对应MethodInfo的,AliasedMethods存放那些使用ActionNameSelectorAttribute属性标注的Action,也就是我们告诉mvc这是一个Action。NonAliasedMethods用来存放我们没有明确指出这是一个Action,但的确它是一个Action的Action方法。
ActionMethodSelector的初始化操作已经完成了,下面我们从FindActionMethod方法入手,继续探究是如何获取一个Action的MethodInfo的。下面是该方法的代码:
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) { ListmethodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName); methodsMatchingName.AddRange(NonAliasedMethods[actionName]); List 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的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 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() { ListallValidMethods = 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 TDescriptor>( ref TDescriptor[] cacheLocation, Func initializer, Func 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源代码有很多用处,只有坚持一下,一切就都会明白的。希望朋友们多批评指正,相互学习。
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com
文章转载自:博客园