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执行的核心代码。
MVC 源码系列之控制器执行(二)的更多相关文章
- MVC 源码系列之控制器执行(一)
控制器的执行 之前说了Controller的激活,现在看一个激活Controller之后控制器内部的主要实现. public interface IController { void Execute( ...
- MVC 源码系列之控制器激活(二)之GetControllerType和GetcontrollerInstance
GetControllerType和GetcontrollerInstance GetControllerType protected internal virtual Type GetControl ...
- MVC 源码系列之控制器激活(一)
Controller的激活 上篇说到Route的使用,GetRoute的方法里面获得RouteData.然后通过一些判断,将最后的RouteData中的RouteHandler添加到context.R ...
- 精尽Spring MVC源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring MVC源码分析 - HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- MVC 源码系列之路由(一)
路由系统 注释:这部分的源码是通过Refector查看UrlRoutingModule的源码编写,这部分的代码没有写到MVC中,却是MVC的入口. 简单的说一下激活路由之前的一些操作.一开始是由MVC ...
- MVC 源码系列之路由(二)
MVCParseData和Match方法的实现 ### ParseData 那么首先要了解一下ParseData. //namespace Route public string Url { get ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- 精尽Spring MVC源码分析 - 文章导读
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
随机推荐
- editplus的使用技巧
数据库sql语句中的 in 后面需要 ('xx','bb')这样的结果,多的话修改起来就比较麻烦,这时候使用editplus 的替换功能就可以实现 ,顶部菜单的 搜索 - > 替换 或者 ctr ...
- Tomcat报java.io.IOException: Broken pipe错误
Tomcat报java.io.IOException: Broken pipe错误,如下图: 解决方案:我的原因是因为网络策略导致出现该问题,即网络端口未启用或被限制.
- fpga延时程序编写
//工匠小建 延时计数 100微妙计数 50M*0.00001-1 (个人理解:1s中50M次动作.那么100us多少次动作.做完这些动作就是延时)parameter delay_100us=16'd ...
- 025-Cinder服务-->安装并配置一个本地存储节点(ISCSI)
一:Cinder提供块级别的存储服务,块存储提供一个基础设施为了管理卷,以及和OpenStack计算服务交互,为实例提供卷.此服务也会激活管理卷的快照和卷类型的功能,块存储服务通常包含下列组件:cin ...
- PAT Advanced 1006 Sign In and Sign Out (25 分)
At the beginning of every day, the first person who signs in the computer room will unlock the door, ...
- Xor Sum 2 AtCoder - 4142 (异或前缀和性质+ 双指针)
Problem Statement There is an integer sequence A of length N. Find the number of the pairs of intege ...
- centos7安装mxnet
pip install mxnet-cu90 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com 安装sklearn时总报错 ...
- java 通过反射获取数组
1.创建数组.设置数组元素.访问数组 一维数组: 多维数组: public Class<?> getComponentType() 返回表示数组组件类型的 Class.如果此类不表示数组类 ...
- 配置本地yum仓库
前言 我们知道yum工具是基于rpm的,其一个重要的特性就是可以自动解决依赖问题,但是yum的本质依旧是把后缀名.rpm的包下载到本地,然后按次序安装之.但是每次执行yum install x ...
- MySQL简版(一)
第一章 数据库的基本概念 1.1 数据库的英文单词 Database,简称DB. 1.2 什么是数据库? 用于存储和管理数据的仓库. 1.3 数据库的特点 持久化存储数据的.其实数据库就是一个文件系统 ...