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执行的核心代码。