In the previous asp.net mvc source code analysis-Controllerl TempData data storage, we talked about ActionInvoker. invokeAction (ControllerContext, actionName) was skipped at that time. Now let's take a look at the definition of the ActionInvoker attribute:
Public IActionInvoker ActionInvoker {
Get {
If (_ actionInvoker = null ){
_ ActionInvoker = CreateActionInvoker ();
}
Return _ actionInvoker;
}
Set {
_ ActionInvoker = value;
}
}
Protected virtual IActionInvoker CreateActionInvoker (){
Return new ControllerActionInvoker ();
}
Like the TempDataProvider attribute definition, you must get used to this code.
The definition of ControllerActionInvoker is also very simple, but this class is not simple.
Let's take a look at your InvokeAction definition:
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(actionName)) { 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 { AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor); if (authContext.Result != null) { // the auth filter signaled that we should let it short-circuit the request InvokeActionResult(controllerContext, authContext.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext); } IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, 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; }
The content in this method cannot be completed at one time. Let's take a look at ControllerDescriptor controllerDescriptor = GetControllerDescriptor (controllerContext). It is obvious that ControllerDescriptor is a packaging class of the Controller instance. Protected virtual ControllerDescriptor GetControllerDescriptor (ControllerContext controllerContext ){
Type controllerType = controllerContext. Controller. GetType ();
ControllerDescriptor controllerDescriptor = DescriptorCache. GetDescriptor (controllerType, () => new ReflectedControllerDescriptor (controllerType ));
Return controllerDescriptor;
}
From this method, we can know that the actual returned is a ReflectedControllerDescriptor instance, which is a subclass of ControllerDescriptor and DescriptorCache. getDescriptor (...) it seems to have been obtained from the cache. Let's verify it. Let's take a look at the GetDescriptor method of ControllerDescriptorCache:
Internal sealed class ControllerDescriptorCache: ReaderWriterCache <Type, ControllerDescriptor> {
Public ControllerDescriptor GetDescriptor (Type controllerType, Func <ControllerDescriptor> creator ){
Return FetchOrCreateItem (controllerType, creator );
}
}
The FetchOrCreateItem method is very simple. The ControllerDescriptor is obtained from the cache. If no ControllerDescriptor exists, it is created and added to the cache and then returned. The cache implementation method is actually a Dictionary <TKey, TValue>.
Now let's see if the ReflectedControllerDescriptor is special:
_ ControllerType = controllerType;
_ Selector = new ActionMethodSelector (_ controllerType );
Why does ActionMethodSelector exist? Its constructor is as follows:
Public ActionMethodSelector (Type controllerType ){
ControllerType = controllerType;
PopulateLookupTables ();
}
Private void PopulateLookupTables (){
MethodInfo [] allMethods = ControllerType. GetMethods (BindingFlags. InvokeMethod | BindingFlags. Instance | BindingFlags. Public );
MethodInfo [] actionMethods = Array. FindAll (allMethods, IsValidActionMethod );
AliasedMethods = Array. FindAll (actionMethods, IsMethodDecoratedWithAliasingAttribute );
NonAliasedMethods = actionMethods. Methods T (AliasedMethods). ToLookup (method => method. Name, StringComparer. OrdinalIgnoreCase );
}
This method is very simple. Find all the instances and common methods of ControllerType. Then, when filtering the call is not an Action, these Action methods are divided into two parts: one part has an alias, and the other part has no alias.
Now we have the ControllerDescriptor instance. Next we should look at the code of ActionDescriptor actionDescriptor = FindAction (controllerContext, controllerDescriptor, actionName). We can also confirm that ActionDescriptor is actually a packaging class of Action.
Protected virtual ActionDescriptor FindAction (ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName) is actually called
The ControllerDescriptor class FindAction method. Let's take a look at your ReflectedControllerDescriptor's FindAction method. This method is very simple. The Group has to have two lines of code:
MethodInfo matched = _ selector. FindActionMethod (controllerContext, actionName );
Return new ReflectedActionDescriptor (matched, actionName, this );
_ Selector. FindActionMethod (controllerContext, actionName); find the MethodInfo corresponding to the Action.
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName) { List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext, actionName); methodsMatchingName.AddRange(NonAliasedMethods[actionName]); List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methodsMatchingName); switch (finalMethods.Count) { case 0: return null; case 1: return finalMethods[0]; default: throw CreateAmbiguousMatchException(finalMethods, actionName); } }
Cycle each MethodInfo to find their custom ActionMethodSelectorAttribute. If any, only the features that pass verification are returned. The Code such as ReflectedAttributeCache. GetActionMethodSelectorAttributes (methodInfo) is determined by the cache,
Private static ReadOnlyCollection <TAttribute> GetAttributes <TMemberInfo, TAttribute> (ConcurrentDictionary <TMemberInfo, ReadOnlyCollection <TAttribute> lookup, TMemberInfo memberInfo)
Where TAttribute: Attribute
Where TMemberInfo: MemberInfo {
Return lookup. GetOrAdd (memberInfo, mi => new ReadOnlyCollection <TAttribute> (TAttribute []) memberInfo. GetCustomAttributes (typeof (TAttribute), inherit: true )));
}
The ReflectedAttributeCache class has several cache dictionaries:
ConcurrentDictionary <MethodInfo, ReadOnlyCollection <ActionMethodSelectorAttribute>
ConcurrentDictionary <MethodInfo, ReadOnlyCollection <ActionNameSelectorAttribute>
ConcurrentDictionary <MethodInfo, ReadOnlyCollection <FilterAttribute>
ConcurrentDictionary <Type, ReadOnlyCollection <FilterAttribute>
The default ActionMethodSelectorAttribute class includes the following:
AcceptVerbsAttribute
HttpDeleteAttribute
HttpGetAttribute
HttpPostAttribute
HttpPutAttribute
NonActionAttribute
AcceptVerbsAttribute
The rest is to directly instance a ReflectedActionDescriptor object. This is nothing special, but there is a verification method in it.
If (validateMethod ){
String failedMessage = VerifyActionMethodIsCallable (methodInfo );
If (failedMessage! = Null ){
Throw new ArgumentException (failedMessage, "methodInfo ");
}
}
It is used to verify whether the method can be executed. In the following cases, it is not passed. (1) The method is a static method. (2) The instance type of the method is not ControllerBase (3) whether to include a generic parameter such as public ActionResult Index <T> () is invalid, but public ActionResult Index (List <string> aa) is legal (4) the parameter cannot contain Ref and out.
This article is very scattered. We need to note that Microsoft caches well in mvc. When the previous one gets ControllerTyper, it has a cache, read all controllertypes of the current Assembly at a time. Here we mention a DescriptorCache cache that caches the ControllerType-> ReflectedControllerDescriptor called each time, while the ReflectedControllerDescriptor instance reads all the Action methods of the Controller at a time; there is also a ReflectedAttributeCache, which caches all features of MethodInfo (ActionMethodSelectorAttribute, ActionNameSelectorAttribute, and FilterAttribute) each call. Of course, the FilterAttribute feature can also be in the class.