• ASP.Net MVC探索之路 不想在多个Action上写同样的FitlerAttribute(上)


    (写完本文后,我去下载了ASP.NET MVC 3 RC,发现它对Filter的可控性方面进行了某些增强——不仅仅是针对全局Filter的
    GlobalFilterCollection——所以在此特别说明一下本文目前主要针对的是ASP.NET MVC 2.0 RTM,当然大部分都适用于3.0)

    以AuthorizeAttribute这个Filter举例,一个Controller有若干个Action,包括登录的Action(如Login)。这时我们有两种方式来实现:
    1、重新实现一个IAuthorizationFilter,在里面判断如果是Login这个Action,就不进行验证。然后将这个Filter作为FilterAttribute置于Controller定义上。或者Controller自身实现IAuthorizationFilter。
    2、除了Login这个Action之外的所有Action加上个Authorize。
    这两种方式虽然能够达到目的,但总觉得不够优雅。

    如果我想给所有Action注入一段html到页面底部,这种注入可能是临时的,我必须去修改Controller吗?(ASP.NET MVC 3 可以将Filter加入GlobalFilters集合中)
    如果我想动态控制某个Action允许由哪些角色访问,我通过修改Controller能实现吗?
    如果我想这时候控制的由哪些角色来访问,需求改变时我想要控制由哪些用户来访问呢?我还得去修改Controller吗?或者增加或修改Filter吗?

    在ASP.NET MVC 2 中,以上需求好像都需要重新编译Controller。

    这里整理一下我们的需求:能不能增删改Filter时不去修改Controller?能让所有Controller和Action定义处都干干净净的那就最好了。

    那就把Filter放在Controller外部来管理,在Action或ActionResult等执行Filter之前保证将需要的Filter准备好就行了。

    在解决问题之前,先简单回顾一下Action执行前后发生的事。

    我们知道,在ASP.NET MVC中,每一次请求通常都定位到一个具体的Controller的Action中。
    在默认情况下,由Action执行器(ControllerActionInvoker去控制Action的执行(或不执行),实际做事的是InvokeAction方法。

    InvokeAction方法首先去查找Action(由FindAction方法),如果Action被找到了,会通过反射的方式去检索该Action所属Controller的拥有的Filter以及Action拥有的Filter,如果Controller本身也实现了某些Filter接口,也会被检索到(由GetFilters方法)。将找到的所有Filter放入一个FilterInfo变量中(当然放入FilterInfo变量的Filter是经过排序和重复清理的),FilterInfo中保存的Filter不完全是Action自己的Attribute上定义的

    然后先执行找到的所有IAuthorizationFilter(由InvokeAuthorizationFilters方法)。在InvokeAuthorizationFilters方法中,只要有一个IAuthorizationFilter的ActionResult不为null就会立即返回到调用处,不会执行其他的IAuthorizationFilter了。InvokeAuthorizationFilters执行完成后,InvokeAction方法检测其执行结果,如果ActionResult不为null,则执行该Result,其他的IActionFilter,IActionResult就不管啦,否则InvokeAction方法继续。

    接着获取要传给Action的参数集(交给GetParameterValue方法),就执行InvokeActionMethodWithFilters方法,方法名已经足够说明它是干什么的了。

    如果一切正常,根据InvokeActionMethodWithFilters方法返回的结果去接着就执行InvokeActionResultWithFilters方法。

    在执行InvokeAuthorizationFilters一直到执行InvokeActionResultWithFilters的这一整个过程中如果发生异常,则根据捕获的异常执行InvokeExceptionFilters进行异常处理。

    代码
    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
    <stringobject> 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;
    }

    这里需要注意一点:InvokeAction、GetFilters、InvokeAuthorizationFilters、GetParameterValue、InvokeActionMethodWithFilters、InvokeActionResultWithFilters、InvokeExceptionFilters等全是虚方法,除非有足够的原因去继承IActionInvoker重写一个Action执行器,否则我觉得重写某些方法足够满足我们的扩展需求了。

    甚至ControllerActionInvoker类本身,在ASP.NET MVC基础架构中也是可以替换的,怎么替换呢?在继承Controller类实现我们自己的Controller时重写CreateActionInvoker方法就可以。

    另外还可以在构造Controller对象给它的ActionInvoker属性赋值,这又怎么赋值?重写DefaultControllerFactory创建Controller实例的GetControllerInstance方法。 然后在Applicaion_Start中设置新的ControllerFactor:
    ControllerBuilder.Current.SetControllerFactory(new YourControllerFactory());

    回到主题。 我们将Filter和Action的对应关系或Filter和Controller的对应关系存于一个集合中并缓存起来。在合适的位置“注入”进去就行了。

    从找到Action到执行InvokeAuthorizationFilters之前,必须将IAuthorizationFilter准备好;从找到Action到执行InvokeActionMethodWithFilters内部执行Action之前,必须将IActionFilter准备好;从找到Action到执行InvokeActionResultWithFilters内部执行ActionResult之前,必须将IResultFilter准备好。异常发生InvokeExceptionFilters执行之前,必须将IExceptionFilter准备好。 基于以上几点,我们好像可以在FindAction方法
    GetFilters方法、InvokeAuthorizationFilters方法、InvokeActionResultWithFilters方法和GetFilters方法内部,或InvokeExceptionFilters执行前将必要的Filter准备好就可以了。当然,最合适的莫过于GetFilters方法了。

    在GetFilters方法中,我们可以根据当前Action的特征(如方法名,或包括请求方式Get或Post等),在“Filter和Action对应表”进行检索;或者根据当前Controller的特征(类型、完整类名都可以),在"Filter和Controller对应表"中进行检索。将匹配的Filter累加入FilterInfo变量就可以了。

    感兴趣的可以去看看Oxite ,它实现了本章提到的一部分的需求。
  • 相关阅读:
    [LeetCode]Valid Parentheses
    LeetCode & Q219-Contains Duplicate II
    LeetCode & Q217-Contains Duplicate-Easy
    LeetCode & Q189-Rotate Array-Easy
    LeetCode & Q169-Majority Element-Easy
    LeetCode & Q167-Two Sum II
    LeetCode & Q122-Best Time to Buy and Sell Stock II-Easy
    LeetCode & Q121-Best Time to Buy and Sell Stock-Easy
    MapReduce工作机制——Word Count实例(一)
    LeetCode & Q119-Pascal's Triangle II-Easy
  • 原文地址:https://www.cnblogs.com/alby/p/1900555.html
Copyright © 2020-2023  润新知