asp.net

位置:IT落伍者 >> asp.net >> 浏览文章

ASP.NET MVC:Filter和Action的执行介绍


发布日期:2019年08月11日
 
ASP.NET MVC:Filter和Action的执行介绍

根据controller的名字正确的实例化了一个controller对象回到MVCHandler的BeginProcessRequest方法可以看到当得到controller对象之后首先判断它是不是IAsyncController如果是则会创建委托用来异步执行通常情况下我们都是继承自Controller类这不是一个IAsyncController于是会直接执行Controller的Execute方法Execute方法是在Controller的基类ControllerBase中定义的这个方法除去一些安全检查初始化了ControllerContext(包含了ControllerBase和Request的信息)核心是调用了ExecuteCore方法这在ControllerBase是个抽象方法在Controller类中有实现

复制代码 代码如下:

protected override void ExecuteCore() {

PossiblyLoadTempData();

try {

string actionName = RouteDataGetRequiredString(action);

if (!ActionInvokerInvokeAction(ControllerContext actionName)) {

HandleUnknownAction(actionName);

}

}

finally {

PossiblySaveTempData();

}}

这个方法比较简单首先是加载临时数据这仅在是child action的时候会出现暂不讨论接下来就是获取action的名字然后InvokeAction 这里的ActionInvoker是一个ControllerActionInvoker类型的对象我们来看它的InvokeAction方法

复制代码 代码如下:

public virtual bool InvokeAction(ControllerContext controllerContext string actionName) {

if (controllerContext == null) {

throw new ArgumentNullException(controllerContext);

}

if (StringIsNullOrEmpty(actionName)) {

throw new ArgumentException(MvcResourcesCommon_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 filterInfoAuthorizationFilters actionDescriptor);

if (authContextResult != null) {

// the auth filter signaled that we should let it shortcircuit the request

InvokeActionResult(controllerContext authContextResult);

}

else {

if (controllerContextControllerValidateRequest) {

ValidateRequest(controllerContext);

}

IDictionary<string object> parameters = GetParameterValues(controllerContext actionDescriptor);

ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext filterInfoActionFilters actionDescriptor parameters);

InvokeActionResultWithFilters(controllerContext filterInfoResultFilters postActionContextResult);

}

}

catch (ThreadAbortException) {

// This type of exception occurs as a result of ResponseRedirect() but we specialcase so that

// the filters dont see this as an error

throw;

}

catch (Exception ex) {

// something blew up so execute the exception filters

ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext filterInfoExceptionFilters ex);

if (!exceptionContextExceptionHandled) {

throw;

}

InvokeActionResult(controllerContext exceptionContextResult);

}

return true;

}

// notify controller that no method matched

return false;}

这是一个非常核心的方法有很多工作在这里面完成ASPNET MVC中有几个以Descriptor结尾的类型首先获得ControllerDescriptor这个比较简单实际返回的是ReflectedControllerDescriptor对象第二步实际上是调用了ReflectedControllerDescriptor的FindAction方法获得ActionDescriptorActionDescriptor最重要的属性是一个MethodInfo这就是当前action name对应的Action的方法FindAction方法内部实际上是调用了ActionMethodSelector的FindActionMethod来获得MethodInfo可以想象这个方法将会反射controller的所有方法的名字然后和action name匹配实际上ASPNET还支持一些额外的功能主要是 通过ActionNameAttribute属性重命名action的名字支持ActionMethodSelectorAttribute对action方法进行筛选比如[HttpPost]之类的下面简单看下ActionMethodSelector的实现大致分为首先是在构造函数中调用了如下方法反射controller中的所有action方法:

复制代码 代码如下:

private void PopulateLookupTables() {

MethodInfo[] allMethods = ControllerTypeGetMethods(BindingFlagsInvokeMethod | BindingFlagsInstance | BindingFlagsPublic);

MethodInfo[] actionMethods = ArrayFindAll(allMethods IsValidActionMethod);

AliasedMethods = ArrayFindAll(actionMethods IsMethodDecoratedWithAliasingAttribute);

NonAliasedMethods = actionMethodsExcept(AliasedMethods)ToLookup(method => methodName StringComparerOrdinalIgnoreCase);

}FindActionMethod方法如下:

public MethodInfo FindActionMethod(ControllerContext controllerContext string actionName) {

List<MethodInfo> methodsMatchingName = GetMatchingAliasedMethods(controllerContext actionName);

methodsMatchingNameAddRange(NonAliasedMethods[actionName]);

List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext methodsMatchingName);

switch (finalMethodsCount) {

case :

return null;

case :

return finalMethods[];

default:

throw CreateAmbiguousMatchException(finalMethods actionName);

} }

这个方法是很清晰的找到重命名之后符合的本身名字符合的然后所有的方法判断是否满足ActionMethodSelectorAttribute的条件最后或者返回匹配的MethodInfo或者抛出异常或者返回null三个步骤的实现并不困难不再分析下去

第三步是得到Filter FilterInfo filterInfo = GetFilters(controllerContext actionDescriptor);实际调用的是

FilterProvidersProvidersGetFilters(controllerContext actionDescriptor);这里的代码风格和之前的不太一样特别喜欢用各种委托读代码有点困难估计不是同一个人写的下面的分析都直接给出实际执行的代码首先看下FilterProvider的构造函数:

复制代码 代码如下:

static FilterProviders() {

Providers = new FilterProviderCollection();

ProvidersAdd(GlobalFiltersFilters);

ProvidersAdd(new FilterAttributeFilterProvider());

ProvidersAdd(new ControllerInstanceFilterProvider());

}

回忆下ASPNET给Action加上filter的方法一共有如下几种

在Application_Start注册全局filter

通过属性给Action方法或者Controller加上filter

Controller类本身也实现了IActionFilter等几个接口通过重写Controller类几个相关方法加上filter

这三种方式就对应了三个FilterProvider这三个Provider的实现都不是很困难不分析了到此为止准备工作都好了接下来就会执行Filter和ActionASPNET的Filter一共有

Filter TypeInterfaceDescriptionAuthorizationIAuthorizationFilterRuns firstActionIActionFilterRuns before and after the action methodResultIResultFilterRuns before and after the result is executedExceptionIExceptionFilterRuns if another filter or action method throws an exception下面看其源代码的实现首先就是InvokeAuthorizationFilters:

复制代码 代码如下:

protected virtual AuthorizationContext InvokeAuthorizationFilters(ControllerContext controllerContext IList<IAuthorizationFilter> filters ActionDescriptor actionDescriptor) {

AuthorizationContext context = new AuthorizationContext(controllerContext actionDescriptor);

foreach (IAuthorizationFilter filter in filters) {

filterOnAuthorization(context);

if (contextResult != null) {

break;

}

}

return context;}

注意到在实现IAuthorizationFilter接口的时候要表示验证失败需要在OnAuthorization方法中将参数context的Result设置为ActionResult表示验证失败后需要显示的页面接下来如果验证失败就会执行context的Result如果成功就要执行GetParameterValues获得Action的参数在这个方法内部会进行Model Binding这也是ASPNET的一个重要特性另文介绍再接下来会分别执行InvokeActionMethodWithFilters和InvokeActionResultWithFilters这两个方法的结构是类似的只是一个是执行Action方法和IActionFilter一个是执行ActionResult和IResultFilter以InvokeActionMethodWithFilters为例分析下

复制代码 代码如下:

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 = filtersReverse()Aggregate(continuation

(next filter) => () => InvokeActionMethodFilter(filter preContext next));

return thunk();

}

这段代码有点函数式的风格不熟悉这种风格的人看起来有点难以理解 用函数式编程语言的话来说这里的Aggregate其实就是foldr

foldr::(a>b>b)>b>[a]>b

foldr 接受一个函数作为第一个参数这个函数的参数有两个类型为ab返回类型为b第二个参数是类型b作为起始值第三个参数是一个类型为a的数组foldr的功能是依次将数组中的a 和上次调用第一个参数函数(f )的返回值作为f的两个参数进行调用第一次调用f的时候用起始值对于C#来说用面向对象的方式表示是作为IEnummerable的一个扩展方法实现的由于C# 不能直接将函数作为函数的参数传入所以传入的是委托说起来比较拗口看一个例子

复制代码 代码如下:

static void AggTest()

{

int[] data = { };

var res = dataAggregate(String (str val) => str + valToString());

ConsoleWriteLine(res);

}

最后输出的结果是String 回到InvokeActionMethodWithFilters的实现上来这里对应的类型a是IActionFilter类型b是Func<ActionExecutedContext>初始值是continuation假设我们有个filter[fff]我们来看下thunk最终是什么

第一次 next=continue filter=f 返回值 ()=>InvokeActionMethodFilter(f preContext continue)

第二次next=()=>InvokeActionMethodFilter(f preContext continue) filter=f

返回值()=>InvokeActionMethodFilter(f preContext()=> InvokeActionMethodFilter(f preContext continue))

最终 thunk= ()=>InvokeActionMethodFilter(fpreContext()=>InvokeActionMethodFilter(f preContext ()=>InvokeActionMethodFilter(f preContext continue)));

直到 return thunk()之前所有真正的代码都没有执行关键是构建好了thunk这个委托把thunk展开成上面的样子应该比较清楚真正的调用顺序什么样的了这里花了比较多的笔墨介绍了如何通过Aggregate方法构造调用链这里有一篇文章专门介绍了这个也可以参考下想象下如果filter的功能就是先遍历调用f的Executing方法然后调用Action方法最后再依次调用f的Executed方法那么完全可以用迭代来实现大可不必如此抽象复杂关键是ASPNET MVC对于filter中异常的处理还有一些特殊之处看下InvokeActionMethodFilter的实现

复制代码 代码如下:

internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter ActionExecutingContext preContext Func<ActionExecutedContext> continuation) {

filterOnActionExecuting(preContext);

if (preContextResult != null) {

return new ActionExecutedContext(preContext preContextActionDescriptor true /* canceled */ null /* exception */) {

Result = preContextResult

};

}

bool wasError = false;

ActionExecutedContext postContext = null;

try {

postContext = continuation();

}

catch (ThreadAbortException) {

// This type of exception occurs as a result of ResponseRedirect() but we specialcase so that

// the filters dont see this as an error

postContext = new ActionExecutedContext(preContext preContextActionDescriptor false /* canceled */ null /* exception */);

filterOnActionExecuted(postContext);

throw;

}

catch (Exception ex) {

wasError = true;

postContext = new ActionExecutedContext(preContext preContextActionDescriptor false /* canceled */ ex);

filterOnActionExecuted(postContext);

if (!postContextExceptionHandled) {

throw;

}

}

if (!wasError) {

filterOnActionExecuted(postContext);

}

return postContext;

}

代码有点长首先就是触发了filter的OnActionExecuting方法这是方法的核心接下来的重点是 postContext = continuation(); 最后是OnActionExecuted方法结合上面的展开式我们可以知道真正的调用顺序将是:

复制代码 代码如下:

fExecuting>fExecuting>fExectuing>InvokeActionMethod>fExecuted>f>Executed>fExecuted

那么源代码中的注释 // need to reverse the filter list because the continuations are built up backward 的意思也很明了了需要将filter倒序排一下之后才是正确的执行顺序

还有一类filter是当异常发生的时候触发的在InvokeAction方法中可以看到触发它的代码放在一个catch块中IExceptionFilter的触发流程比较简单不多做解释了唯一需要注意的是ExceptionHandled属性设置为true的时候就不会抛出异常了这个属性在各种context下面都有他们是的效果是一样的比如在OnActionExecuted方法中也可以将他设置为true同样不会抛出异常这些都比较简单不再分析其源代码这篇文章比较详细的介绍了filter流程中出现异常之后的执行顺序

最后说下Action Method的执行前面我们已经得到了methodInfo和通过data binding获得了参数调用Action Method应该是万事俱备了 mvc这边的处理还是比较复杂的ReflectedActionDescriptor会去调用ActionMethodDispatcher的Execute方法这个方法如下:

复制代码 代码如下:

public object Execute(ControllerBase controller object[] parameters) {

return _executor(controller parameters);

}

此处的_executor是

delegate object ActionExecutor(ControllerBase controller object[] parameters);_exectuor被赋值是通过一个方法利用Expression拼出方法体参数代码在(ActionMethodDispatchercs)

static ActionExecutor GetExecutor(MethodInfo methodInfo)此处就不贴出了比较复杂这里让我比较费解的是既然MethodInfo和parameters都有了直接用反射就可以了为什么还要如此复杂我将上面的Execute方法改为:

复制代码 代码如下:

public object Execute(ControllerBase controller object[] parameters) {

return MethodInfoInvoke(controller parameters);

//return _executor(controller parameters);

}

运行结果是完全一样的我相信mvc源代码如此实现一定有其考虑这个需要继续研究

最后附上一张函数调用图以便理解仅供参考图片较大点击可看原图

               

上一篇:用ODBC连接MySQL和ASP.NET

下一篇:asp.net下日期和时间处理的类库