控制器的执行


上一节说道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执行的核心代码。

MVC 源码系列之控制器执行(二)的更多相关文章

  1. MVC 源码系列之控制器执行(一)

    控制器的执行 之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现. public interface IController { void Execute( ...

  2. MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance

    GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...

  3. MVC 源码系列之控制器激活(一)

    Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...

  4. 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. MVC 源码系列之路由(一)

    路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...

  7. MVC 源码系列之路由(二)

    MVCParseData和Match方法的实现 ### ParseData 那么首先要了解一下ParseData. //namespace Route public string Url { get ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  9. 精尽Spring MVC源码分析 - 文章导读

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. Error response from daemon: Container ************** is not running

    $ sudo docker run -d centos:last 8022826ebd567e2b1818e90ce33c3b68ea9aeac0286001154eb05fc2283e0238$ s ...

  2. VMware虚拟机NAT模式无法上外网

    VMware虚拟机NAT模式无法上外网排错思路 1,确保三种模式只有一种在连接 2,确保ip配置正确 配置的子网跟DHCP必须是同一网段 3,确保网关配置正确 网关不管怎么配,一定不要配192.168 ...

  3. 02python程序设计基础——字符串

    字符串方法 format 1.替换字段名 在最简单的情况下,只需向 format 提供要设置其格式的未命名参数,并在格式字符串中使用未命名字段.此时,将按顺序将字段和参数配对.你还可给参数指定名称,这 ...

  4. Linux之bash的变量

    1. 变量的显示,echo echo $变量   或    echo ${变量} eg. echo $HOME   或   echo ${HOME} 2. 变量的设置 变量的设置规则: (1)变量与变 ...

  5. GUI学习之十八——QDateTimeEdit学习总结

    在前面两章我们总结了QSpinBox和QDoubleSpinBox的用法,今天来总结一下QDateTimeEdit控件的基本用法 一.描述 1.QDateTimeEdit是一个用来编辑日期和时间的单行 ...

  6. mysql 在查字符串字段中 条件参数传为数字0查到与实际数据不匹配问题

    比如: CREATE TABLE `e` ( `id` int(11) DEFAULT NULL, `status` varchar(255) DEFAULT NULL, `b` varchar(25 ...

  7. Cocos Creator更改底层,点击节点获取路径

    Mac : 右键creator->显示包内容->Contents->Resources->engine->bin->cocos2d-js-for-preview.j ...

  8. XTemplate模板学习和使用总结

    XTemplate模板学习和使用总结 前言   XTemplate是我接触的第一个模板语言,用在公司的一个NodeJS项目中,跟它打交道经常是因为需要使用它的语法向模板中注入数据.因为是刚入门前端不久 ...

  9. varnish流程图

    varnish流程图

  10. 使用VisualStudio 开发Arduino

    Arduino IDE界面简洁,整体功能还算完善,相比其他编译器明显的不足就是不能进行硬件调试,再就是没有代码提示功能,文件关系不清晰.头文件打开不方便. VisualStudio作为时下最为流行的W ...