zoukankan      html  css  js  c++  java
  • 深入理解ASP.NET MVC(8)

    系列目录

    过滤器上下文参数

    前一节提到了四种MVC内建过滤器,它们无一例外都在关键的方法中提供了叫filterContext的参数,尽管它们各自类型不同,但是都继承自ControllerContext。

    image

    其中一个共同的重要属性是:

    1
    2
    3
    4
    public ActionResult Result {
        get;
        set;
    }

    Result是唯一通知MVC框架当前Filter执行结果的媒介,也就是说MVC框架总是在必要的时候判断filterContext.Result,如果Result不为空就表示可以继续,否则Result将被执行(因为它是个ActionResult),并且之后的过程将被跳过。在下面的讨论中你会逐步理解。

     

    IActionFilter和IResultFilter

    IActionFilterIResultFilter分别表示在Action执行前动作和Action执行后动作。由前一篇的类图,我们可以看到MVC内置了ActionFilterAttribute同时实现了这两个接口,只是所有的实现都是虚方法,没有任何实际的代码。因此,可以通过继承ActionFilterAttribute来实现IActionFilter和IResultFilter。除此之外,ActionFilterAttribute还继承了FilterAttribute,这个Attribute只定义了一个Order属性。事实上,对于多个相同的过滤器被定义在同一个action或controller上的时候,Order可以对他们的执行顺序进行排序。如果没有指定的话,默认的情况可以通过下面这个例子说明:

    1
    2
    3
    4
    5
    6
    7
    [ShowMessage(Message = "A")]
    [ShowMessage(Message = "B")]
    public ActionResult SomeAction()
    {
        Response.Write("Action is running");
        return Content("Result is running");
    }

    假设上面的ShowMessage继承自ActionFilterAttribute,并实现了所有的四个方法,那么将得到下面的输出(省略了ShowMessage的实现,不过大家可以猜出来):

    [BeforeAction B][BeforeAction A]Action is running[AfterAction A][AfterAction B]
    [BeforeResult B][BeforeResult A]Result is running[AfterResult A][AfterResult B]

    如果加上Order的话,可以改变这种默认的顺序:

    1
    2
    3
    4
    5
    6
    7
    [ShowMessage(Message = "A", Order = 1)]
    [ShowMessage(Message = "B", Order = 2)]
    public ActionResult SomeAction()
    {
        Response.Write("Action is running");
        return Content("Result is running");
    }

    输出:

    [BeforeAction A][BeforeAction B]Action is running[AfterAction B][AfterAction A]
    [BeforeResult A][BeforeResult B]Result is running[AfterResult B][AfterResult A]

    总之,IActionFilter和IResultFilter还是比较容易理解的,但是有个特殊的问题需要注意,如果在执行IActionFilterIResultFilter的代码时异常了怎么办?拿IActionFilter作说明,书中有这样一张图:

    image

    这张图给了我们这样的信息,一个Action上面加了3层IActionFilter,当第三层的OnActionExecuting抛出异常后,被第二层的OnActionExecuted捕获了,而且继续执行第一层的OnActionExecuted。其中跳过了ActionMethod和第三层的OnActionExecuted。

    IResultFilter实际上跟IActionFilter的行为完全相同。

    另外,Response.Redirect()方法将抛出ThreadAbortException异常,MVC自行捕获了这种特殊的异常,使得这种异常实际上不会影响我们,我们大可假装对此完全不知。下面的代码和注释是从InvokeActionMethodFilter方法中截取的,说明了MVC框架在这里的“用心良苦”。

    1
    2
    3
    4
    5
    6
    7
    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;
    }

    这部分的源代码有个十分精辟的地方,我将写一篇文章专门解读这部分源码,届时,上述逻辑将更容易理解。

    事实上,OutputCacheAttribute是一个IResultFilter,除了一些属性,它仅仅重写了OnResultExecuting,关于如何它的详细使用,参考书中341页。

    IAuthorizationFilter

    IAuthorizationFilter用于页面级别的用户验证,AuthorizeAttribute实现了IAuthorizationFilter,下面的代码是AuthorizeAttribute的核心验证逻辑,需要通知满足三个条件才能认证成功:

    1、HttpContext.User.Identity.IsAuthenticated必须为true

    2、用户名必须一致(注意StringComparer.OrdinalIgnoreCase说明了不区分大小写)

    3、角色必须一致

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
        if (httpContext == null) {
                        throw new ArgumentNullException("httpContext");
                    }
                    IPrincipal user = httpContext.User;
                    if (!user.Identity.IsAuthenticated) {
                        return false;
                    }
                    if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
                    return false;
                    }
                    if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
                        return false;
                    }
                    return true;
    }

    上述源代码十分清晰的说明了问题。另外,要实现角色验证,需要在web.config中配置一个roleManager,通常可以使用SqlRoleProvider,也可以自定义。

    如果考虑到Output Caching,Authorization Filters有什么tricky吗?我们看到OnAuthorization中的一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    if (AuthorizeCore(filterContext.HttpContext)) {
        // ** IMPORTANT **
        // Since we're performing authorization at the action level, the authorization code runs
        // after the output caching module. In the worst case this could allow an authorized user
        // to cause the page to be cached, then an unauthorized user would later be served the
        // cached page. We work around this by telling proxies not to cache the sensitive page,
        // then we hook our custom authorization code into the caching mechanism so that we have
        // the final say on whether a page should be served from the cache.
     
        HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
        cachePolicy.SetProxyMaxAge(new TimeSpan(0));
        cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
    }

    这段代码本没什么出奇的地方,先是调用AuthorizeCore,接着看到这段注释,大意是:我们把验证放到了Action部 分,使得验证代码将在缓存模块后面执行。考虑到最坏的情况,一个认证的用户得到了一个敏感的页面,并且这个页面被缓存了,接着,一个未验证的用户将得到缓 存页面而不需验证。我们绕过了这个问题,直接告诉代理不要缓存敏感页面,并且把我们的验证机制注入到缓存机制中,使我们最终决定是否返回缓存页面。

    这段注释清楚的说明了验证机制和缓存机制的矛盾,也给出了解决方案,因此,如果要自己实现IAuthorizationFilter一定要小心了,一定尽量继承AuthorizeAttribute并仅重写AuthorizeCore。如果还不明白看看下面回调函数CacheValidateHandler最终调用的OnCacheAuthorization的实现就知道了:

    1
    2
    3
    4
    5
    6
    7
    8
    protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) {
        if (httpContext == null) {
            throw new ArgumentNullException("httpContext");
        }
     
        bool isAuthorized = AuthorizeCore(httpContext);
        return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
    }

    在输出缓存之前仍然先调用AuthorizeCore,这样就避免了上面注释中提到的问题。

    如果AuthorizeAttribute认证失败,将构造一个HttpUnauthorizedResult并附给filterContext.Result。HttpUnauthorizedResult同样继承自ActionResult。其ExecuteResult方法如下:

    1
    context.HttpContext.Response.StatusCode = 401;

    简单的返回一个401错误,表示未验证错误,然后验证模块根据web.config的配置进行下一步操作,通常是跳转到一个登录页面。如果不希望这样,可以重写AuthorizeAttribute的HandleUnauthorizedRequest方法。比如在一个Ajax请求因为验证错误而拒绝,你显然不希望页面跳转。可以像下面这样处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected override void HandleUnauthorizedRequest(AuthorizationContext context)
    {
        if (context.HttpContext.Request.IsAjaxRequest()) {
            UrlHelper urlHelper = new UrlHelper(context.RequestContext);
            context.Result = new JsonResult {
                Data = new {
                    Error = "NotAuthorized",
                    LogOnUrl = urlHelper.Action("LogOn", "Account")
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
            base.HandleUnauthorizedRequest(context);
    }

    IExceptionFilter

    前面一节的伪代码中可以看到,IExceptionFilter被设计成能够捕获异常。然而需要注意:仅仅从action开始执行之后的异常可以用这种方式捕获(包括过滤器执行期间),在这之前的,诸如找不到controller,找不到action之类的异常是无法用IExceptionFilter捕获的。

    MVC内建了一个HandleErrorAttribute,它的作用是在捕获异常后注入500错误(对于404错误将不处理)。来看看其内部对filterContext.Result的处理:

    1
    2
    3
    4
    5
    6
    filterContext.Result = new ViewResult {
        ViewName = View,
        MasterName = Master,
        ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
        TempData = filterContext.Controller.TempData
    };

    注意到用户指定的View、Master会被返回,还会有一个HandleErrorInfo的Model被包 装成ViewData返回,还会附带上当前Controller的TempData。HandleErrorInfo封装了Exception对 象,Controller和Action的名字。这些信息可以在我们的错误页面中使用。filterContext.Result会被MVC框架执行,所 以我们可以用一个非ViewResult指定,比如RedirectToRouteResult。

    ControllerActionInvoker在执行filterContext.Result之前会判断一下filterContext.ExceptionHandled是 否为true,如果不为true,filterContext.Result将不会执行,那么该死的黄页还是会抛向ASP.NET。 HandleErrorAttribute将检查ExceptionHandled,如果为true则什么都不做返回,否则将 ExceptionHandled置为true。当我们需要自己实现IExceptionFilter,在同时有多个IExceptionFilter的 时候,可以通过ExceptionHandled通知后面执行的IExceptionFilter异常是否被处理了。还需要注意的是:上面提到 IActionFilter 也可以处理异常,可以猜到ActionExecutedContextResultExecutedContext也具有ExceptionHandled,对应的,如果在OnActionExecuted和OnResultExecuted中将ExceptionHandled设成了true,MVC框架将不会重新抛出异常,于是任何一个IExceptionFilter将没有机会执行。

    Controller自身实现过滤

    Controller自身继承自上述的四个接口,并且允许其继承类覆盖实现,所以我们也可以通过重写OnActionExecuting等方法,为Controller设置过滤,这种过滤将优先于用属性的方式设置的过滤执行。

    1
    public abstract class Controller : IActionFilter, IAuthorizationFilter,  IExceptionFilter, IResultFilter

    劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2010/12/07/details-asp-net-mvc-08.html

  • 相关阅读:
    1058 A+B in Hogwarts (20)
    1036. Boys vs Girls (25)
    1035 Password (20)
    1027 Colors in Mars (20)
    1009. Product of Polynomials (25)
    1006. Sign In and Sign Out
    1005 Spell It Right (20)
    1046 Shortest Distance (20)
    ViewPager页面滑动,滑动到最后一页,再往后滑动则执行一个事件
    IIS7.0上传文件限制的解决方法
  • 原文地址:https://www.cnblogs.com/feng-NET/p/4665300.html
Copyright © 2011-2022 走看看