深入理解ASP.NET MVC(8)

系列目录

过滤器上下文参数

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

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


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作说明,书中有这样一张图:

这张图给了我们这样的信息,一个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

时间: 2024-10-05 23:28:01

深入理解ASP.NET MVC(8)的相关文章

深入理解ASP.NET MVC Day1

深入理解ASP.NET MVC ASP.NET vs MVC vs WebForms 许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系,是一个全新的Web开发,事实上ASP.NET是创建WEB应用的框架而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系,所以可以称之为ASP.NET MVC. 我们可将原来的ASP.NET称为 ASP.NET Webforms,新的MVC 称为ASP.NET MVC. ASP.NET Web Form ASP.NET 在过

七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC

ASP.NET vs MVC vs WebForms 许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系,是一个全新的Web开发,事实上ASP.NET是创建WEB应用的框架而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系,所以可以称之为ASP.NET MVC. 我们可将原来的ASP.NET称为 ASP.NET Webforms,新的MVC 称为ASP.NET MVC. ASP.NET Web Form ASP.NET 在过去的十二年里,已经服务并成功实现

深入理解ASP.NET MVC(6)

系列目录 Action全局观 在上一篇最后,我们进行到了Action调用的“门口”: 1 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) 在深入研究调用过程的细节前,先有一个总体的认识是很有帮助的.InvokeAction方法大致是按照这样的顺序进行的: 查找action:MVC内部查找action的方法似乎有点复杂,涉及到一个ActionDescriptor的东西,但是原理上是通过反射,在以后的文章中会有所涉及.

深入理解ASP.NET MVC(5)

系列目录 回顾 系列的前4节深入剖析了ASP.NET URL路由机制,以及MVC在此基础上是如何实现Areas机制的,同时涉及到inbound和outbound很多细节部分.第2节中提到MvcRouteHandler是MVC框架的入口,这节开始,从MvcRouteHandler往下说开去. Controller的创建过程:Builder和Factory MvcRouteHandler的实现仅仅是通过GetHttpHandler方法返回一个MvcHandler实例,MvcHandler从Route

[转载] ASP.NET MVC (一)——深入理解ASP.NET MVC

个人认为写得比较透彻得Asp.net mvc 文章,所以转载过来,原文链接在最后: ASP.NET vs MVC vs WebForms 许多ASP.NET开发人员开始接触MVC认为MVC与ASP.NET完全没有关系,是一个全新的Web开发,事实上ASP.NET是创建WEB应用的框架而MVC是能够用更好的方法来组织并管理代码的一种更高级架构体系,所以可以称之为ASP.NET MVC. 我们可将原来的ASP.NET称为 ASP.NET Webforms,新的MVC 称为ASP.NET MVC. A

理解ASP.NET MVC中的ActionResult

通常我们在一个ASP.NET MVC项目中创建一个Controller的时候,Index()方法默认的返回类型都是ActionResult,通过查看UML图,ActionResult实际上是一个抽象类,因此实际返回的类型是该抽象类的子类. Ø ActionResult及其子类的UML图   有关ActionResult及其子类的UML图如下所示: 由于图片比较大,所以在浏览器中看起来可能比较小,也不太方便,大家可以点击这里下载大图,使用专业的图片浏览器打开来看. 下载大图 Ø ActionRes

理解ASP.NET MVC中的ModelBinder

模型绑定的本质 任何控制器方法的执行都受action invoker组件(下文用invoker代替)控制.对于每个Action方法的参数,这个invoker组件都会获取一个Model Binder Object(模型绑定器对象).Model Binder的职责包括为Action方法参数寻找一个可能的值(从HTTP请求上下文).每个参数都可以绑定到不同的Model Binder:但是大部分情况我们都使用的是默认模型绑定器-DefaultModelBinder(如果我们没有显式设置使用自定义的Mod

深入理解ASP.NET MVC(7)

系列目录 Action的定位 再次回到Controller的ExecuteCore方法,回到action调用的入口: 1 if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) 这里的ActionInvoker是个IActionInvoke,它无疑是负责了所有action的调用逻辑,MVC中默认实现这个接口的是ControllerActionInvoke.可以想象ControllerActionInvoke面临的第一个问题是

深入理解ASP.NET MVC(1)

系列目录 ASP.NET MVC请求的服务过程 下图是书中的截图,表述了一次通常的ASP.NET MVC请求的服务过程: 从图中可以初步看出一个HttpRequest是如何被ASP.NET和ASP.NET MVC框架执行的:经过IIS和ASP.NET处理后,Core Routing会首先根据URL匹配物理路径上的文件,如果不能匹配则由核心路由模块执行路由,路由被匹配后,MvcRouteHandler会将这个请求“带入”MVC框架,执行Controller和Action,Action可以直接注入r

[学习笔记] 理解ASP.NET MVC的路由系统

引言 路由,正如其名,是决定消息经由何处被传递到何处的过程.也正如网络设备路由器Router一样,ASP.NET MVC框架处理请求URL的方式,同样依赖于一张预定义的路由表.以该路由表为转发依据,请求URL最终被传递给特定Controller的特定Action进行处理.而在相反的方向上,MVC框架的渲染器同样要利用这张路由表,生成最终的HTML页面并返回URL.所以,理解整个ASP.NET MVC的路由系统,有两个必须出现的关键元素:Controller与Action,有两个方向的操作:传入的