zoukankan      html  css  js  c++  java
  • 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执行的核心代码。

  • 相关阅读:
    sql两个字段相加减,第三个字段没有值的原因.
    CF 447B(DZY Loves Strings-贪心)
    Appium AndroidKeyCode
    初探Java多线程
    模拟实现Spring IoC功能
    Cordova 5 架构学习 Weinre远程调试技术
    快学Scala习题解答—第三章 数组相关操作
    org.hibernate.AssertionFailure: null id in com.you.model.User entry (don&#39;t flush the Session after a
    Swift3.0语言教程替换子字符串
    DHCP欺骗(DHCP Sproofing)
  • 原文地址:https://www.cnblogs.com/shaoqi/p/7384482.html
Copyright © 2011-2022 走看看