MVC 源码系列之控制器执行(二)

控制器的执行


上一节说道Controller中的ActionInvoker.InvokeAction

public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }

    Contract.Assert(controllerContext.RouteData != null);
    if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
    {
        throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
    }

    ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
    ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

    if (actionDescriptor != null)
    {
        FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

        try
        {
            AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);

            if (authenticationContext.Result != null)
            {
                // An authentication filter signaled that we should short-circuit the request. Let all
                // authentication filters contribute to an action result (to combine authentication
                // challenges). Then, run this action result.
                AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                    controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                    authenticationContext.Result);
                InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
            }
            else
            {
                AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                if (authorizationContext.Result != null)
                {
                    // An authorization filter signaled that we should short-circuit the request. Let all
                    // authentication filters contribute to an action result (to combine authentication
                    // challenges). Then, run this action result.
                    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                        authorizationContext.Result);
                    InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
                }
                else
                {
                    if (controllerContext.Controller.ValidateRequest)
                    {
                        ValidateRequest(controllerContext);
                    }

                    IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                    ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);

                    // The action succeeded. Let all authentication filters contribute to an action result (to
                    // combine authentication challenges; some authentication filters need to do negotiation
                    // even on a successful result). Then, run this action result.
                    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                        postActionContext.Result);
                    InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters,
                        challengeContext.Result ?? postActionContext.Result);
                }
            }
        }
        catch (ThreadAbortException)
        {
            // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
            // the filters don't see this as an error.
            throw;
        }
        catch (Exception ex)
        {
            // something blew up, so execute the exception filters
            ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
            if (!exceptionContext.ExceptionHandled)
            {
                throw;
            }
            InvokeActionResult(controllerContext, exceptionContext.Result);
        }

        return true;
    }

    // notify controller that no method matched
    return false;
}

接着来说一下首先说一下

 ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
 ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
 
 protected virtual ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
{
    // Frequently called, so ensure delegate is static
    Type controllerType = controllerContext.Controller.GetType();
    ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(
        controllerType: controllerType,
        creator: (Type innerType) => new ReflectedControllerDescriptor(innerType),
        state: controllerType);
    return controllerDescriptor;
}
 

获得了两个Descriptor,首先看一下ControllerDescriptor是如何获得的。看这个方法,首先获得Controller的Type,然后从DescriptorCache(缓存里面)获取Descriptor。如果没有的话 就使用ReflectedControllerDescriptor为默认的Descriptor。然后返回。ActionDescriptor用了,controllerDescriptor为参数,调用了FindAction

protected virtual ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
    Contract.Assert(controllerContext != null);
    Contract.Assert(controllerContext.RouteData != null);
    Contract.Assert(controllerDescriptor != null);

    if (controllerContext.RouteData.HasDirectRouteMatch())
    {
        List<DirectRouteCandidate> candidates = GetDirectRouteCandidates(controllerContext);

        DirectRouteCandidate bestCandidate = DirectRouteCandidate.SelectBestCandidate(candidates, controllerContext);
        if (bestCandidate == null)
        {
            return null;
        }
        else
        {
            // We need to stash the RouteData of the matched route into the context, so it can be
            // used for binding.
            controllerContext.RouteData = bestCandidate.RouteData;
            controllerContext.RequestContext.RouteData = bestCandidate.RouteData;

            // We need to remove any optional parameters that haven't gotten a value (See MvcHandler)
            bestCandidate.RouteData.Values.RemoveFromDictionary((entry) => entry.Value == UrlParameter.Optional);

            return bestCandidate.ActionDescriptor;
        }
    }
    else
    {
        ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
        return actionDescriptor;
    }
}

好家伙,还是有点多的。还是一点一点来看。首先入口参数检查,然后一个判断HasDirectRouteMatch(是否有直路由匹配)

public const string DirectRouteMatches = "HasDirectRouteMatch";

public static bool HasDirectRouteMatch(this RouteData routeData)
{
    if (routeData == null)
    {
        throw Error.ArgumentNull("routeData");
    }

    return routeData.Values.ContainsKey(RouteDataTokenKeys.DirectRouteMatches);
}

代码就是简单的看一下routeData是否包含HasDirectRouteMatch这个字段的key。这个key是在什么时候加的呢?到时候分析

如果返回为false,直接调用ReflectedControllerDescriptor的FindeAction如果为true,GetDirectRouteCandidates通过这个方法获得‘直接候选人’,然后SelectBestCandidate选出‘最佳候选人’。如果不为空,就将bestCandidate的ActionDescriptor返回。是不是对这个‘候选人’有点蒙?我们仔细来看。首先看GetDirectRouteCandidates

private static List<DirectRouteCandidate> GetDirectRouteCandidates(ControllerContext controllerContext)
{
    Debug.Assert(controllerContext != null);
    Debug.Assert(controllerContext.RouteData != null);

    List<DirectRouteCandidate> candiates = new List<DirectRouteCandidate>();

    RouteData routeData = controllerContext.RouteData;
    foreach (var directRoute in routeData.GetDirectRouteMatches())
    {
        if (directRoute == null)
        {
            continue;
        }

        ControllerDescriptor controllerDescriptor = directRoute.GetTargetControllerDescriptor();
        if (controllerDescriptor == null)
        {
            throw new InvalidOperationException(MvcResources.DirectRoute_MissingControllerDescriptor);
        }

        ActionDescriptor[] actionDescriptors = directRoute.GetTargetActionDescriptors();
        if (actionDescriptors == null || actionDescriptors.Length == 0)
        {
            throw new InvalidOperationException(MvcResources.DirectRoute_MissingActionDescriptors);
        }

        foreach (var actionDescriptor in actionDescriptors)
        {
            if (actionDescriptor != null)
            {
                candiates.Add(new DirectRouteCandidate()
                {
                    ActionDescriptor = actionDescriptor,
                    ActionNameSelectors = actionDescriptor.GetNameSelectors(),
                    ActionSelectors = actionDescriptor.GetSelectors(),
                    Order = directRoute.GetOrder(),
                    Precedence = directRoute.GetPrecedence(),
                    RouteData = directRoute,
                });
            }
        }
    }

    return candiates;
}

首先也是入口检查,然后创建一个新的DirectRouteCandidate的数组,获得路由信息。GetDirectRouteMatches。在方法最里面也是通过MS_DirectRouteMatches这个可以去RouteData中去找。对应的RouteData,如果有返回。将返回的RouteData,GetTargetControllerDescriptor获得controllerDescriptor,GetTargetActionDescriptors获得actionDescriptors。(代码里面controllerDescriptor这个斌没有给最后的candiates候选人赋值,只是做了个判断。不懂)。GetTargetActionDescriptors方法中也是使用了一个Key:MS_DirectRouteActions去RouteData中寻找。如果都不为空的话就将actionDescript放到一开始的集合中去。返回给调用方法。(这种情况下和RouteData接触的比较多,在RouteData部分会细说)

还有第二种情况调用前一步获得的ControllerDescriptor的FindeAction,前一步获得的是ReflectedControllerDescriptor,看看方法实现。

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);
}
    
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName)
{
    if (controllerContext == null)
    {
        throw Error.ArgumentNull("controllerContext");
    }

    if (actionName == null)
    {
        throw Error.ArgumentNull("actionName");
    }

    List<MethodInfo> finalMethods = FindActionMethods(controllerContext, actionName);

    switch (finalMethods.Count)
    {
        case 0:
            return null;

        case 1:
            return finalMethods[0];

        default:
            throw CreateAmbiguousActionMatchException(finalMethods, actionName);
    }
}

protected List<MethodInfo> FindActionMethods(ControllerContext controllerContext, string actionName)
{
    List<MethodInfo> matches = new List<MethodInfo>();

    // Performance sensitive, so avoid foreach
    for (int i = 0; i < AliasedMethods.Length; i++)
    {
        MethodInfo method = AliasedMethods[i];
        if (IsMatchingAliasedMethod(method, controllerContext, actionName))
        {
            matches.Add(method);
        }
    }
    matches.AddRange(NonAliasedMethods[actionName]);
    RunSelectionFilters(controllerContext, matches);
    return matches;
}

别看代码多,其实简单。第一个方法:入口检查。如果通过FindActionMethod方法找到合适的MethodInfo就new一个ReflectedActionDescriptor。FindActionMethod的方法里面包装了另一个FindActionMethods。最后一个方法就是具体实现了。看看代码,首先初始化了一个MethodInfo的数组。然后看到注释,说避免性能影响,就不用foreach了。循环了AliasedMethods(别名方法)这个属性。然后对这个属性进行循环。判断,如果为true,添加到matchs中。然后运行方法的过滤方法。成功之后返回数组。

那个AliasedMethods和NonAliasedMethods到底是在什么时候初始化的呢?

其实你看源码就会发现,刚刚这几个FindAction都是ActionMethodSelectorBase抽象类里面,他的子类有ActionMethodSelector和AsyncActionMethodSelector分别是同步和异步的Action选择器。在ReflectedControllerDescriptor里面就是使用了ActionMethodSelector,在ReflectedControllerDescriptor初始化的时候也初始化了ActionMethodSelector。


public ActionMethodSelector(Type controllerType)            
{
    Initialize(controllerType);
}
//父级的初始化
protected void Initialize(Type controllerType)
{
    ControllerType = controllerType;

    // If controller type has a RouteAttribute, then standard routes can't reach it.             
    _hasRouteAttributeOnController = controllerType.GetCustomAttributes(typeof(IDirectRouteFactory), inherit: false).Any()
        || controllerType.GetCustomAttributes(typeof(IRouteInfoProvider), inherit: false).Any();

    PopulateLookupTables();
}

private void PopulateLookupTables()
{
    MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
    MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethodNoDirectRoute);

    if (_hasRouteAttributeOnController)
    {
        // Short circuit these tables when there's a direct route attribute on the controller, none of these
        // will be reachable by-name.
        AliasedMethods = _emptyMethodInfo;
        NonAliasedMethods = _emptyMethodInfoLookup;
    }
    else
    {
        AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
        NonAliasedMethods = actionMethods.Except(AliasedMethods).ToLookup(GetCanonicalMethodName, StringComparer.OrdinalIgnoreCase);
    }

    DirectRouteMethods = Array.FindAll(allMethods, IsValidActionMethodWithDirectRoute);
    StandardRouteMethods = actionMethods;
}

子类初始化调用了父类的方法,看看父类中的Initialize方法。首先就是一个赋值,然后判断controller是否有RouteAttribute,如果有特性路由的话。标准路由就达不到这个Controll。用一个_hasRouteAttributeOnController属性去表示。然后到了第三个也是最重要的方法。PopulateLookupTables:首先获得controllerType中所有公开的,出去构造函数,实例成员的方法获取到。然后过滤条件IsValidActionMethodNoDirectRoute。这时有个判断条件就是_hasRouteAttributeOnController之前的获得的值。如果Controller有RouteAttribute的话,就直接将空的两个值赋给两个变量。如果没有RouteAttribute的话,还是通过IsMethodDecoratedWithAliasingAttribute去过滤。获得由别名的方法,剩下的就是没有别名的方法。到这了就清楚了,只是将由别名和没别的分成了两个数组。我们具体看看过滤条件,就能知道他到底是按照上面去过滤分离的。

private bool IsValidActionMethodNoDirectRoute(MethodInfo methodInfo)
{
    return IsValidActionMethod(methodInfo) && !HasDirectRoutes(methodInfo);
}

protected override bool IsValidActionMethod(MethodInfo methodInfo)
{
    return !(methodInfo.IsSpecialName ||
             methodInfo.GetBaseDefinition().DeclaringType.IsAssignableFrom(typeof(Controller)));
}   

private bool HasDirectRoutes(MethodInfo method)
{
    // Inherited actions should not inherit the Route attributes.
    // Only check the attribute on declared actions.
    bool isDeclaredAction = method.DeclaringType == ControllerType;
    return isDeclaredAction && (method.GetCustomAttributes(typeof(IDirectRouteFactory), inherit: false).Any()
        || method.GetCustomAttributes(typeof(IRouteInfoProvider), inherit: false).Any());
}

IsValidActionMethodNoDirectRoute(是有效的action方法没有直接路由?)第一个过滤条件,这个过滤条件是根据两个过滤条件去判断的。看第一个条件IsValidActionMethod是否是个正确的ActionMethod。第一个判断是MethodInfo是不是有特殊的名字(一般会用在枚举或者属性方法上面),然后第二个GetBaseDefinition方法是派生类重写基类方法时获得基类的方法。然
DeclaringType获得声明的类型,IsAssignableFrom看父类是不是Controller。这个判断条件就是说,不是属性方法并且是继承Controller类的就是有效的ActionMethod。

第二个判断条件HasDirectRoutes,方法的声明类是不是Controller,然后判断方法上面是否有RouteAttribute的属性。 第一个判断在一般情况下为true,如果有RouteAttribute的话就返回这个返回式就返回false。

总的来说就是 方法要在Controller下面声明 且不是属性方法没有RouteAttribute的就可以被过滤成功。

private static bool IsMethodDecoratedWithAliasingAttribute(MethodInfo methodInfo)
{
    return methodInfo.IsDefined(typeof(ActionNameSelectorAttribute), true /* inherit */);
}

第二个过滤对于AliasedMethods和NOAliasedMethods的过滤就较为简单,只要判断是否有ActionNameSelectorAttribute属性就行了。就是给Aciton的名字取了个别名。

然后说完了如何在ReflectedControllerDescriptor里面初始化的方法数据的时候。然后回到上的第三个FindeAction方法。

protected List<MethodInfo> FindActionMethods(ControllerContext controllerContext, string actionName)
{
    List<MethodInfo> matches = new List<MethodInfo>();

    // Performance sensitive, so avoid foreach
    for (int i = 0; i < AliasedMethods.Length; i++)
    {
        MethodInfo method = AliasedMethods[i];
        if (IsMatchingAliasedMethod(method, controllerContext, actionName))
        {
            matches.Add(method);
        }
    }
    matches.AddRange(NonAliasedMethods[actionName]);
    RunSelectionFilters(controllerContext, matches);
    return matches;
}

protected static bool IsMatchingAliasedMethod(MethodInfo method, ControllerContext controllerContext, string actionName)
{
    // return if aliased method is opting in to this request
    // to opt in, all attributes defined on the method must return true
    ReadOnlyCollection<ActionNameSelectorAttribute> attributes = ReflectedAttributeCache.GetActionNameSelectorAttributes(method);
    // Caching count is faster for ReadOnlyCollection
    int attributeCount = attributes.Count;
    // Performance sensitive, so avoid foreach
    for (int i = 0; i < attributeCount; i++)
    {
        if (!attributes[i].IsValidName(controllerContext, actionName, method))
        {
            return false;
        }
    }
    return true;
}

public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
    return String.Equals(actionName, Name, StringComparison.OrdinalIgnoreCase);
}

首先循环有别名的方法。IsMatchingAliasedMethod如果,集具体方法就是获得ActionNameSelectorAttribute,如果没有在方法上面赋值这个属性的话,就返回false。主要是比对名字。忽略大小写哦。(奇怪的是ActionNameAttribute是不允许多个附加到方法上面,为什么这里是获得一个集合呢?)。

然后获得了与actionname相同的名字的有ActionName的action和没有actionname的action。运行action上面的过滤器。

protected static void RunSelectionFilters(ControllerContext controllerContext, List<MethodInfo> methodInfos)
{
    // Filter depending on the selection attribute.
    // Methods with valid selection attributes override all others.
    // Methods with one or more invalid selection attributes are removed.

    bool hasValidSelectionAttributes = false;
    // loop backwards for fastest removal
    for (int i = methodInfos.Count - 1; i >= 0; i--)
    {
        MethodInfo methodInfo = methodInfos[i];
        ReadOnlyCollection<ActionMethodSelectorAttribute> attrs = ReflectedAttributeCache.GetActionMethodSelectorAttributesCollection(methodInfo);
        if (attrs.Count == 0)
        {
            // case 1: this method does not have a MethodSelectionAttribute

            if (hasValidSelectionAttributes)
            {
                // if there is already method with a valid selection attribute, remove method without one
                methodInfos.RemoveAt(i);
            }
        }
        else if (IsValidMethodSelector(attrs, controllerContext, methodInfo))
        {
            // case 2: this method has MethodSelectionAttributes that are all valid

            // if a matching action method had a selection attribute, consider it more specific than a matching action method
            // without a selection attribute
            if (!hasValidSelectionAttributes)
            {
                // when the first selection attribute is discovered, remove any items later in the list without selection attributes
                if (i + 1 < methodInfos.Count)
                {
                    methodInfos.RemoveFrom(i + 1);
                }
                hasValidSelectionAttributes = true;
            }
        }
        else
        {
            // case 3: this method has a method selection attribute but it is not valid

            // remove the method since it is opting out of this request
            methodInfos.RemoveAt(i);
        }
    }
}

代码略多。慢慢来看,首先定义了一个属性hasValidSelectionAttributes 默认值为false。循环MethodInfo,然后获取ActionMethodSelectorAttribute,这几个属性就是[Post],规定请求方法的属性。如果没有的话就不做操作。如果是有ActionMethodSelectorAttribute,就循环所有的属性,然后IsValidForRequest检查请求是否符合。如果不符合的话就从数组中去掉。如果符合就,将之后的所有的都从表中删除,优先级最高。返回之将MethInfo放回到ReflectedActionDescriptor参数,然后实例化。

整个FindeAction就说完了,接下去说Action执行的核心代码。

posted @ 2017-08-17 21:29  终生义务教育制  阅读(234)  评论(0编辑  收藏  举报