[MVC] asp.net mvc中“Action”的创建

转载: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
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏