ASP. net mvc: Introduction to Filter and Action execution

Source: Internet
Author: User

A controller object is instantiated Based on the controller name. Return to the BeginProcessRequest method of MVCHandler. We can see that after obtaining the controller object, we first determine whether it is an IAsyncController. If yes, a delegate is created for asynchronous execution. Generally, we all inherit from the Controller class. This is not an IAsyncController, so we will directly Execute the Execute method of the Controller. The Execute method is defined in the Controller's base class ControllerBase. This method removes some security checks and initializes the ControllerContext (including the ControllerBase and Request information). The core is to call the ExecuteCore method, this is an abstract method in ControllerBase, which is implemented in the Controller class:
Copy codeThe Code is as follows:
Protected override void ExecuteCore (){
PossiblyLoadTempData ();
Try {
String actionName = RouteData. GetRequiredString ("action ");
If (! ActionInvoker. InvokeAction (ControllerContext, actionName )){
HandleUnknownAction (actionName );
}
}
Finally {
PossiblySaveTempData ();
}}

This method is relatively simple. The first step is to load temporary data, which only appears when it is a child action. We will not discuss it for the moment. The next step is to get the action name and then InvokeAction. Here the ActionInvoker is a ControllerActionInvoker type object. Let's look at its InvokeAction method,
Copy codeThe Code is as follows:
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 shocould 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;
}
// Policy controller that no method matched
Return false ;}

This is a very core method, and a lot of work is done here. ASP. net mvc contains several types ending with a Descriptor. First, the ControllerDescriptor is obtained. This is relatively simple. The actual returned type is the ReflectedControllerDescriptor object. The second step is to call the FindAction method of ReflectedControllerDescriptor to obtain ActionDescriptor. The most important attribute of ActionDescriptor is MethodInfo, which is the method of action corresponding to the current Action name. The FindAction method actually calls the FindActionMethod of ActionMethodSelector to obtain MethodInfo. As you can imagine, this method will reflect the names of all methods of the controller and then match the action name. In fact, ASP. NET also supports some additional features, mainly: 1. rename the action name through the ActionNameAttribute; 2. supports ActionMethodSelectorAttribute to filter action methods, such as [HttpPost. The following describes the implementation of ActionMethodSelector in four steps. The first step is to call the following method in the constructor to reflect all action methods in the controller:
Copy codeThe Code is as follows:
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 );
} The FindActionMethod method is as follows:
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 );
}}

This method is very clear. Find the method that matches the name after renaming, and then all the methods determine whether the conditions of ActionMethodSelectorAttribute are met, and finally return the matched MethodInfo or throw an exception, or return null. The implementation of the three steps is not difficult and will not be analyzed.
The third step is to obtain the Filter. FilterInfo filterInfo = GetFilters (controllerContext, actionDescriptor); actually called:
FilterProviders. Providers. GetFilters (controllerContext, actionDescriptor); the Code style here is not the same as before, especially like to use a variety of Delegate, it is a bit difficult to read the code, it is estimated that not the same person wrote. The following analysis shows the actual code executed. First, let's take a look at the constructors of FilterProvider:
Copy codeThe Code is as follows:
Static FilterProviders (){
Providers = new FilterProviderCollection ();
Providers. Add (GlobalFilters. Filters );
Providers. Add (new FilterAttributeFilterProvider ());
Providers. Add (new ControllerInstanceFilterProvider ());
}

Recall that ASP. NET adds filter to Action in the following ways:
1. Register global filter in Application_Start
2. Add filter to the Action method or Controller through the attribute
3. The Controller class also implements several interfaces, such as IActionFilter. Add filter by rewriting several related methods of the Controller class.
These three methods correspond to three filterproviders. The implementation of these three providers is not very difficult and will not be analyzed. So far, the preparation is complete, and then the Filter and Action will be executed. There are four filters for ASP. NET:


Filter Type Interface Description
Authorization IAuthorizationFilter Runs first
Action IActionFilter Runs before and after the action method
Result IResultFilter Runs before and after the result is executed
Exception IExceptionFilter Runs if another filter or action method throws an exception
The following describes the implementation of its source code, namely, InvokeAuthorizationFilters:
Copy codeThe Code is as follows:
Protected virtual AuthorizationContext InvokeAuthorizationFilters (ControllerContext controllerContext, IList <IAuthorizationFilter> filters, ActionDescriptor actionDescriptor ){
AuthorizationContext context = new AuthorizationContext (controllerContext, actionDescriptor );
Foreach (IAuthorizationFilter filter in filters ){
Filter. OnAuthorization (context );
If (context. Result! = Null ){
Break;
}
}
Return context ;}

Note that when implementing the IAuthorizationFilter interface, it indicates that the verification fails. You need to set the Result of the context parameter to ActionResult in the OnAuthorization method, indicating the page to be displayed after the verification fails. Next, if the verification fails, the Result of context will be executed. If the verification succeeds, GetParameterValues will be executed to obtain the parameter of Action. In this method, Model Binding will be performed, which is also ASP. an important feature of. NET. Next we will execute InvokeActionMethodWithFilters and InvokeActionResultWithFilters respectively. The structures of these two methods are similar, but they are the execution of Action method and IActionFilter, and the execution of ActionResult and IResultFilter. Taking InvokeActionMethodWithFilters as an example:
Copy codeThe Code is as follows:
Protected virtual ActionExecutedContext InvokeActionMethodWithFilters (ControllerContext controllerContext, IList <IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary <string, object> parameters ){
ActionExecutingContext preContext = new ActionExecutingContext (controllerContext, actionDescriptor, parameters );
Func <ActionExecutedContext> continuation = () =>
New ActionExecutedContext (controllerContext, actionDescriptor, false/* canceled */, null/* exception */){
Result = InvokeActionMethod (controllerContext, actionDescriptor, parameters)
};
// Need to reverse the filter list because the continuations are built up backward
Func <ActionExecutedContext> thunk = filters. Reverse (). Aggregate (continuation,
(Next, filter) => () => InvokeActionMethodFilter (filter, preContext, next ));
Return thunk ();
}

This code is a little functional and difficult to understand if you are not familiar with this style. In functional programming language, Here Aggregate is actually foldr,
Foldr: :( a-> B)-> B-> [a]-> B
Foldr accepts a function as the first parameter. The function has two parameters: a and B. The return type is B. The second parameter is type B, the third parameter is an array of type a. The foldr function is to call the returned values of a and the first parameter function (f) in the array as the two parameters of f in sequence, start value is used when f is called for the first time. For C #, the object-oriented representation is implemented as an extension method of IEnummerable. Because C # cannot directly pass functions as function parameters, therefore, the delegate is passed in. Let's take a look at the following example:
Copy codeThe Code is as follows:
Static void AggTest ()
{
Int [] data = {1, 2, 3, 4 };
Var res = data. Aggregate ("String", (str, val) => str + val. ToString ());
Console. WriteLine (res );
}

The final output result is String1234. return to the implementation of InvokeActionMethodWithFilters. Here the corresponding type a is IActionFilter, type B is Func <ActionExecutedContext>, and the initial value is continuation. Suppose we have three filters, [f1, f2, f3]. Let's look at what thunk is,
First time: next = continue, filter = f1, return value () => InvokeActionMethodFilter (f1, preContext, continue)
Second: next = () => InvokeActionMethodFilter (f1, preContext, continue), filter = f2
Return Value: () => InvokeActionMethodFilter (f2, preContext, () => InvokeActionMethodFilter (f1, preContext, continue )),
Eventually: thunk = () => InvokeActionMethodFilter (f3, preContext, () => InvokeActionMethodFilter (f2, preContext, () => InvokeActionMethodFilter (f1, preContext, continue )));
Until return thunk (), all the real code is not executed. The key is to build the thunk delegate and expand the thunk to the above, we should be clear about the actual call sequence. I have spent a lot of ink here to introduce how to construct a call chain through the Aggregate method. Here is an article dedicated to this. You can also refer to it. Imagine that if the filter function is to traverse and call the Executing method of f, then call the Action method, and then call the Executed method of f in turn, then it can be fully implemented through iteration, the key is ASP. net mvc has some special features for processing exceptions in the filter. Let's take a look at the implementation of InvokeActionMethodFilter:
Copy codeThe Code is as follows:
Internal static ActionExecutedContext InvokeActionMethodFilter (IActionFilter filter, ActionExecutingContext preContext, Func <ActionExecutedContext> continuation ){
Filter. OnActionExecuting (preContext );
If (preContext. Result! = Null ){
Return new ActionExecutedContext (preContext, preContext. ActionDescriptor, true/* canceled */, null/* exception */){
Result = preContext. Result
};
}
Bool wasError = false;
ActionExecutedContext postContext = null;
Try {
PostContext = continuation ();
}
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.
PostContext = new ActionExecutedContext (preContext, preContext. ActionDescriptor, false/* canceled */, null/* exception */);
Filter. OnActionExecuted (postContext );
Throw;
}
Catch (Exception ex ){
WasError = true;
PostContext = new ActionExecutedContext (preContext, preContext. ActionDescriptor, false/* canceled */, ex );
Filter. OnActionExecuted (postContext );
If (! PostContext. ExceptionHandled ){
Throw;
}
}
If (! WasError ){
Filter. OnActionExecuted (postContext );
}
Return postContext;
}

The Code is a bit long. The first step is to trigger the OnActionExecuting method of the filter, which is the core of the method. The following focuses on postContext = continuation (); finally, the OnActionExecuted method. Combined with the preceding expansion, we can know that the actual call order will be:
Copy codeThe Code is as follows:
F3.Executing-> f2.Executing-> f1.Exectuing-> InvokeActionMethod-> f1.Executed-> f2-> Executed-> f3.Executed.

In the source code, note // need to reverse the filter list because the continuations are built up backward. The execution sequence is correct after the filter is sorted in reverse order.
Another type of filter is triggered when an exception occurs. In the InvokeAction method, you can see that the code that triggers it is placed in a catch Block. The IExceptionFilter trigger process is relatively simple and I will not explain it much. The only thing to note is that when the ExceptionHandled attribute is set to true, no exception will be thrown. This attribute is available under various context, and they have the same effect. For example, you can set the OnActionExecuted method to true without throwing an exception. These are relatively simple and do not analyze the source code. This article describes in detail the execution sequence after an exception occurs in the filter process.
Finally, let's talk about the execution of the Action Method. We have already obtained methodInfo, and obtained the parameter through data binding. It should be ready to call the Action Method. The processing of asp.net mvc is still complicated. ReflectedActionDescriptor will call the Execute method of ActionMethodDispatcher. The method is as follows:
Copy codeThe Code is as follows:
Public object Execute (ControllerBase controller, object [] parameters ){
Return _ executor (controller, parameters );
}

Here _ executor is
Delegate object ActionExecutor (ControllerBase controller, object [] parameters); _ exectuor is assigned a value through a method that uses Expression to spell out the method body and parameters. The code is in (ActionMethodDispatcher. cs ):
Static ActionExecutor GetExecutor (MethodInfo methodInfo) is not attached here, which is complicated. What makes me puzzled here is that since MethodInfo and parameters both have them, we can use reflection directly. Why is it so complicated? I changed the Execute method above:
Copy codeThe Code is as follows:
Public object Execute (ControllerBase controller, object [] parameters ){
Return MethodInfo. Invoke (controller, parameters );
// Return _ executor (controller, parameters );
}

The running results are exactly the same. I believe that the implementation of the mvc source code must be considered. This requires further research.
A function call diagram is provided for your reference only. The image is large. You can click to view the source image.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.