asp.net mvc中“Action”的创建

原创|其它|编辑:郝浩|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) {
    List methodsMatchingName = 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”和“没有明确标注为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 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 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 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表达式。其实并没有那么复杂,他要先判断传入的ControllerDescriptorActionDescriptor列表是否为空,是则把原来列表中相同的部分删掉一个,然后返回(为什么会有相同的两个元素在节点中呢?请看下面的小注)。如果为空,则获取所有合法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

文章转载自:博客园

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP