ASP.NET MVC 过滤器 PART1
以下内容摘自:http://www.cnblogs.com/r01cn/archive/2011/12/17/2291217.html
感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework
第十三章 过滤器 PART1
过滤器将额外的逻辑注入到请求处理管道之中。它们提供一种简单且雅致的方式实现交叉关注(cross-cutting concerns)。过滤器被用于整个应用程序而不合适放置于某个局部位置,否则会打破关注分离模式。交叉关注的典型例子是登录、授权、以及缓存等。
之所以称为过滤器,是因为这个术语在其它web应用程序框架,包括Ruby on Rails,中使用了同样的功能。然而,MVC框架的过滤器与ASP.NET平台的Request.Filter和Response.Filter对象完全不同,它们是在请求和响应流上进行传输的(一种高级且很少执行的活动)。你可以在MVC应用程序中使用Request.Filter和Response.Filter,但一般而言,ASP.NET MVC程序员所说的过滤器,实际是指本章所涉及的类型。
在本章中,我们将向你演示MVC框架所支持的不同种类的过滤器、如何构建和使用过滤器、以及如何控制它们的执行。
使用过滤器
你在第9章已经看到过一个过滤器的例子(Applying Authorization with Filters),那是当我们把授权运用于SportsStore的管理控制器的动作方法时。我们希望该动作方法只能被已认证的用户所使用,这给我们提供了一个可选的办法。我们可以在每一个动作方法中检查请求的授权状态,如清单13-1所示。
清单13-1. 在动作方法中明确检查授权
public class AdminController : Controller {
// ... instance variables and constructor
public ViewResult Index() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
public ViewResult Create() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
public ViewResult Edit(int productId) {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
// ... other action methods
}
}
你可以看到,在这种办法中有许多重复,这是我们为什么要选择使用过滤器来代替的原因,如清单13-2所示。
清单13-2. 运用一个过滤器
[Authorize]
public class AdminController : Controller {
// ... instance variables and constructor
public ViewResult Index() {
// ...rest of action method
}
public ViewResult Create() {
// ...rest of action method
}
public ViewResult Edit(int productId) {
// ...rest of action method
}
// ... other action methods
}
}
过滤器是.NET的特性(Attributes),它把额外的步骤加入到请求处理管道上。我们在清单13-2中使用了Authorize过滤器,它具有与清单13-1中所有重复检查同样的效果。
.NET 特性(ATTRIBUTES):一个新鲜的事物
特性(attributes)是派生于System.Attribute的特有.NET类。你可以把它们附加到其它代码元素上,包括类、方法、属性、以及字段等。其目的是把附加信息嵌入到你的编译代码中,以便之后在运行时读回这些信息。
在C#中,特性(attributes)是用方括号语法进行附加的,而且你可以用一个命名的参数语法来组装它们的公用属性(如,[MyAttribute(SomeProperty=value)])。在C#的编译器命名约定中,如果特性(attributes)类名以单词Attribute结尾,你可以忽略这一部分(例如,你可以只写[Authorize]来运用AuthorizeAttribute)。
介绍过滤器的四个基本类型
MVC框架支持四种不同的过滤器类型。每一种类型允许你在请求处理管道的不同点上引入逻辑。这四种过滤器类型描述于表13-1。
表13-1. MVC框架的过滤器类型 | |||
过滤器类型 | 接口 | 默认实现 | 描述 |
Authorization | IAuthorizationFilter | AuthorizeAttribute | 首先运行,在任何其它过滤器或动作方法之前 |
Action | IActionFilter | ActionFilterAttribute | 在动作方法之前及之后运行 |
Result | IResultFilter | ActionFilterAttribute | 在动作结果被执行之前和之后运行 |
Exception | IExceptionFilter | HandleErrorAttribute | 只在另一个过滤器、动作方法、动作结果弹出异常时运行 |
在MVC框架调用一个动作之前,它首先检测该方法的定义,以查看它是否有实现表13-1所列接口的特性(attributes)。如果有,那么在请求管道的相应点上,调用这些接口所定义的方法。框架包含了实现滤器接口的默认特性(attributes)类。本章稍后,我们将向你演示如何使用这些类。
注意:ActionFilterAttribute类既实现IActionFilter接口,也实现IResultFilter接口。这个类是抽象类,它迫使你提供一个实现类。AuthorizeAttribute和HandleErrorAttribute,包含一些有用的功能,并且不必生成派生类。
把过滤器运用于控制器和动作方法
过滤器可以被运用于单个的动作方法,或整个控制器。在清单13-2中,我们把Authorize过滤器运用于AdminController类,它具有把过滤器运用于该控制器中每个动作方法同样的效果,如清单13-3所示。
清单13-3. 将一个过滤器运用于个别动作方法
public class AdminController : Controller {
// ... instance variables and constructor
[Authorize]
public ViewResult Index() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
[Authorize]
public ViewResult Create() {
if (!Request.IsAuthenticated) {
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
// ... other action methods
}
}
你可以运用多个过滤器,混用和匹配运用它们的等级 — 即,不论它们是运用于整个控制器,还是个别动作方法。清单13-4显示了使用三个不同的过滤器。
清单13-4. 在一个控制器类中使用多个过滤器
public class ExampleController : Controller {
[ShowMessage] // applies to just this action
[OutputCache(Duration=60)] // applies to just this action
public ActionResult Index() {
// ... action method body
}
}
在这个清单中有些过滤器带有参数。我们探究各种不同种类的过滤器时,向你演示这些是如何工作的。
注意:如果你已经为你的控制器定义了一个自定义基类,那么,运用在这个基类上的任何过滤器都会影响其派生类。
使用授权过滤器
授权过滤器是首先运行的过滤器 — 在其它过滤器之前、以及动作方法被调用之前。正如名字的含义那样,这些过滤器强迫你的授权策略,确保动作方法只可以被已认证的用户所调用。授权过滤器实现IAuthorizationFilter接口,它如清单13-5所示。
清单13-5. IAuthorizationFilter接口
public interface IAuthorizationFilter {
void OnAuthorization(AuthorizationContext filterContext);
}
}
让我们来设置一个场景:MVC框架已经从浏览器接收到一个请求。路由系统已经处理了这个请求的URL,并提取了控制器和目标动作的名字。生成了一个新控制器类实例,但在这个动作方法被调用之前,MVC框架会查看是否有授权过滤器被运用于这个动作方法。如果有,那么由IAuthorizationFilter接口定义的唯一方法,OnAuthorization,将被调用。如果认证过滤器通过了这个请求,那么便执行请求管道中的下一个阶段。如果没有,那么该请求被拒绝。
生成认证过滤器
理解认证过滤器是如何工作的最好方式是自己生成一个。清单13-6显示了一个简单的例子。它只是检查之前已经登录的访问者(Request.IsAuthenticated 验证通过),并且其用户名已经出现在一个固定的允许用户列表中。
清单13-6. 一个自定义认证过滤器
using System.Linq;
using System.Web.Mvc;
using System.Web;
namespace MvcFilters.Infrastructure.Filters {
public class CustomAuthAttribute : AuthorizeAttribute {
private string[] allowedUsers;
public CustomAuthAttribute(params string[] users) {
allowedUsers = users;
}
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return httpContext.Request.IsAuthenticated &&
allowedUsers.Contains(httpContext.User.Identity.Name,
StringComparer.InvariantCultureIgnoreCase);
}
}
}
生成一个授权过滤器最简单的办法是生成AuthorizeAttribute类的子类,并其覆盖(override)AuthorizeCore方法。这样确保我们能受益于内建在AuthorizeAttribute上的功能。
WARNING: WRITING SECURITY CODE IS DANGEROUS
警告:编写安全性代码是危险的
我们包括了一个自定义授权过滤器示例,因为我们认为它清楚地演示了过滤器系统是如何工作的,但我们总是小心翼翼的编写我们自己的安全性代码。很多程序员都认为他们能够写出很好的安全性代码,但编程的历史恰恰被他们所写的应用程序碎片搞得一团糟。事实上,安全性代码是很少人才能拥有的技能。通常有一些遗漏的缺陷或未经测试的角落留下了应用程序的安全性漏洞。
在任何可能的地方,我们都喜欢使用经过广泛测试、并得到证明的安全性代码。当前,MVC框架已经提供了完备特性的授权过滤器,它能够被派生用来实现自定义授权策略。我们试图尽可能使用这种过滤器,而且我们建议你也要这样做。至少,当你机密的应用程序数据在Internet上被到处传播时,你可以把谴责转嫁给微软。
我们过滤器的构造器以一个名字数组为参数。这些是被授权的用户。我们的过滤器含有一个名为PerformAuthenticationCheck的方法,它确保该请求被认证,并且该用户是授权用户之一。
这个类有趣的部分是OnAuthorization方法的实现。被传递给该方法的参数是AuthorizationContext类的一个实例,AuthorizationContext派生于ControllerContext。ControllerContext使我们能够访问一些有用的对象,而不仅仅是HttpContextBase,通过ControllerContext,我们可以访问请求的细节。这个基类(ControllerContext)的属性如表13-2所示。所有这些不同种类的动作(Action)过滤器所使用的上下文对象,都派生于这个类,因此你可以一致性地使用这些属性。
表13-2. ControllerContext属性 | ||
Name | Type | Description |
Controller | ControllerBase | 返回该请求的控制器对象 |
HttpContext | HttpContextBase | 提供对请求细节的访问,以及对响应的访问 |
IsChildAction | Bool | 如果是子动作,返回true(本意后面部分及第15章加以讨论) |
RequestContext | RequestContext | 提供对HttpContext以及路由数据的访问,这两者都是通过其它属性才可以使用的 |
RouteData | RouteData | 返回该请求的路由数据 |
回顾清单13-6,当我们想查看该请求是否被认证时,我们是这样做的:
用这个上下文对象(AuthorizationContext),我们可以获得对请求作出决策所需要的所有信息。AuthorizationContext定义了两个额外属性,它们如表13-3所示。
表13-3. AuthorizationContex属性 | ||
Name | Type | Description |
ActionDescriptor | ActionDescriptor | 提供动作方法的细节 |
Result | ActionResult | 用于动作方法的结果;通过把这个属性设置为非空值的办法,过滤器可以取消这个请求 |
第一个属性ActionDescriptor返回System.Web.Mvc.ActionDescriptor的一个实例,你可以用它获得应用了过滤器的动作的信息。第二个属性Result,是使你的过滤器进行工作的关键。如果你乐于对一个动作方法的请求进行授权,那么,你在OnAuthorization方法中什么也不用做。你的这种做法被MVC框架解释为请求应该继续进行。
然而,如果你把上下文对象的Result属性设置成一个ActionResult对象,MVC框架将把您设置的对象作为整个请求的结果。管道中剩下的步骤不再执行,你已经提供的这个结果被执行,为用户产生输出。
在我们的例子中,如果我们的PerformAuthenticationCheck返回false(指示该请求未被认证,或该用户未被授权),那么,我们便生成一个HttpUnauthorizedResult动作结果(HttpUnauthorizedResult类以及其它ActionResult在第12章讨论的动作结果),然后把它赋给上下文的Result属性,像这样:
为了使用我们的授权过滤器,我们简单地把一个自定义的特性(attributes)应用于我们想要保护的动作方法,如清单13-7所示。
清单13-7. 应用自定义授权过滤器
[CustomAuth("adam", "steve", "bob")]
public ActionResult Index() {
return View();
}
...
使用内建的授权过滤器
MVC框架包含了一个非常有用的内建授权过滤器,名为AuthorizeAttribute。我们可以用这个类的两个public属性来指定我们的授权策略,如表13-4所示。
表13-4. AuthorizeAttribute属性 | ||
Name | Type | Description |
Users | string | 逗号分隔的用户名列表,允许这些用户访问该动作方法。 |
Roles | string | 逗号分隔的角色列表。为了访问该动作方法,用户必须至少在这些角色之一中。 |
清单13-8显示了我们可以使用这种内建滤过器来保护一个动作方法。
Listing 13-8. Using the Built-in Authorization Filter
清单13-8. 使用内建的授权过滤器
[Authorize(Users="adam, steve, bob", Roles="admin")]
public ActionResult Index() {
return View();
}
...
我们在清单中既指定了用户,也指定了角色。这意味着,除非两个条件都满足,否则将不予授权:用户名是adam、steve或bob,并且该用户具有admin角色。还有一个隐含的条件,即该请求已被认证。如果我们未指定任何用户或角色,那么任何已被认证的用户都可以使用这个动作方法。这是我们在清单13-2中生成的效果。
对于大多数应用程序,AuthorizeAttribute提供的授权策略已经足够了。如果你想实现一些特殊的事情,你可以从这个类进行派生。这要比直接实现IAuthorizationFilter接口的风险小得多,但你仍然应该小心地考虑你的策略的影响,并充分地测试它。
AuthorizeAttribute类提供了两个不同的自定义点:
· AuthorizeCore方法,它是通过OnAuthorization的AuthorizeAttribute实现被调用的,并实现授权检查
· HandleUnauthorizedRequest方法,它是在授权检查失败时被调用的
我们在下一小节提供了使用这两个方法的例子。
注意:你可以覆盖的第三个方法是OnAuthorization。我们建议你不要做这种事,因为这个方法的默认实现包含了使用OutputCache过滤器对缓存内容进行安全处理的支持,我们将在本章后面部分进行描述。
实现自定义授权策略
为了演示使用自定义认证策略,我们将生成一个自定义AuthorizeAttribute子类。这个策略将允许这样一些人进行访问:直接通过服务器桌面(Request.IsLocal为true)的浏览器访问该网站,以及远程访问者,其用户名及角色匹配normalAuthorizeAttribute规则。这对允许服务器管理员绕过网站登录过程是有用的。我们可以通过读取HttpRequestBase类的IsLocal属性,得到对这种情况的判断。清单13-9演示了我们的过滤器。
清单13-9. 实现一个自定义授权过滤器
using System.Web.Mvc;
namespace MvcFilters.Infrastructure.Filters {
public class OrAuthorizationAttribute : AuthorizeAttribute {
protected override bool AuthorizeCore(HttpContextBase httpContext) {
return httpContext.Request.IsLocal || base.AuthorizeCore(httpContext);
}
}
}
我们可以像运用标准的AuthorizeAttribute类一样来使用这个过滤器:
[OrAuthorization(Users = "adam, steve, bob", Roles = "admin")]
public ActionResult Index() {
return View();
}
...
现在,未在用户名列表中指定的、且未被授予admin角色的本地用户将能够使用这个动作方法。非本地用户将受默认授权策略的约束。
实现自定义授权失败策略
处理失败的授权默认策略是把用户重定向到登录页面。我们并不总是要这样做。例如,如果我们在使用AJAX,发送一个重定向可能会引起登录页面显示在用户正在查看的一个页面的中间。幸运的是,我们可以覆盖AuthorizeAttribute类的HandleUnauthorizedRequest方法,以生成一个自定义策略。清单13-10提供了一个演示。
清单13-10. 实现自定义授权失败策略
namespace MvcFilters.Infrastructure.Filters {
public class AjaxAuthorizeAttribute : AuthorizeAttribute {
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);
}
}
}
}
当这个过滤器检测到一个AJAX请求时,它用报告此问题的JSON数据进行响应,并提供登录页面的URL。规则的请求用默认策略进行处理,就像在基类中所定义的那样。我们必须把AJAX客户端写成期望这个响应,并能够理解它。我们将在第19章回到AJAX和JSON这些论题。
使用异常过滤器
异常过滤器只当在调用一个动作方法时而抛出未处理异常才会运行。这种异常来自以下位置:
· 另一种过滤器(授权、动作、或结果过滤器)
· 动作方法本身
· 当动作结果被执行时(参阅第12章关于动作结果的细节)
构建异常过滤器
异常过滤器必须实现IExceptionFilter接口,如清单13-11所示。
清单13-11. IExceptionFilter接口
public interface IExceptionFilter {
void OnException(ExceptionContext filterContext);
}
}
当一个未处理异常发生时,OnException方法被调用。该方法的参数是一个ExceptionContext对象。这个类(指ExceptionContext — 译者注)类似于授权过滤器的参数,也派生于ControllerContext类(因此你可以获得关于请求的信息),而且它定义了一些额外的过滤器专特有属性,如表13-5的描述。
表13-5. ExceptionContext属性 | ||
Name | Type | Description |
ActionDescriptor | ActionDescriptor | 提供动作方法的细节 |
Result | ActionResult | 用于动作方法的结果;通过把这个属性设置为一个非空值的办法,过滤器可以取消这个请求 |
Exception | Exception | 未处理异常 |
ExceptionHandled | bool | 如果另一个过滤器已经把这个异常标记为已处理,则返回true |
抛出的异常通过Exception属性是可访问的。通过把ExceptionHandled属性设置为true,一个异常过滤器可以报告它已经处理了该异常。应用于一个动作的所有异常过滤器都会被调用,即使这个属性被设置为true,因此,检查是否另一个过滤器已经处理了这个问题,以避免恢复另一个过滤器已经解决了的问题是好的实践。
注意:如果一个动作方法的所有异常过滤器均未把ExceptionHandled属性设置为true,MVC框架将使用默认的ASP.NET异常处理程序。这将默认地显示恐怖的黄色死屏。
Result属性由异常过滤器使用以告诉MVC框架要做什么。异常过滤器的两个主要应用是记录该异常到日志,并把适当的消息显示给用户。清单13-12显示了后者的一个演示,当一个特定种类的未处理异常出现时,把该用户重定向到一个指定的错误页面。
清单13-12. 实现一个异常过滤器
using System;
namespace MvcFilters.Infrastructure.Filters {
public class MyExceptionAttribute: FilterAttribute, IExceptionFilter {
public void OnException(ExceptionContext filterContext) {
if (!filterContext.ExceptionHandled &&
filterContext.Exception is NullReferenceException) {
filterContext.Result = new RedirectResult("/SpecialErrorPage.html");
filterContext.ExceptionHandled = true;
}
}
}
}
这个过滤器对NullReferenceException实例进行响应,并且只在其它异常过滤器没有指示该异常已被处理时起作用。我们把该用户重定向到一个指定的错误页面,这是我们用一个字面URL完成的。我们可以像如下这样来使用这个过滤器:
[MyException]
public ActionResult Index() {
...
如果Index动作方法thorow一个异常,并且这个异常是一个NullReferenceException,而且没有其它异常过滤器处理这个异常,那么,我们这个过滤器就会把该用户重定向到/SpecialErrorPage.html这个URL。
使用内建的异常过滤器
HandleErrorAttribute是内建的IExceptionFilter接口实现,而且使它更易于构建异常过滤器。通过它,你可以运用表13-6所描述的属性来指定一个异常以及视图和布局名。
表13-6. HandleErrorAttribute属性 | ||
Name | Type | Description |
ExceptionType | Type | 由这个过滤器处理的异常类型。它也将处理由特定值继承而来的异常类型,但会忽略所有其它类型。其默认值是System.Exception,其含义为,默认地,它将处理所有标准异常。 |
View | string | 该过滤器渲染的视图模板名。如果你未指定一个值,它取默认的Error值,因此,默认地,它渲染/Views/<currentControllerName>/Error.cshtml或/Views/Shared/Error.cshtml。 |
Master | string | 在渲染这个过滤器的视图时所使用的布局名。如果不指定一个值,视图使用其默认布局页面。 |
当遇到由ExceptionType所指定类型的未处理异常时,此过滤器将把HTTP结果代码设置为500(意为“服务器错误”),并渲染由View属性指定的视图(用默认布局或Master指定的布局)。清单13-13演示了如何使用这个HandleErrorAttribute过滤器。
清单13-13. 使用HandleErrorAttribute过滤器
[HandleError(ExceptionType=typeof(NullReferenceException), View="SpecialError")]
public ActionResult Index() {
...
在这个例子中,我们感兴趣的是:在遇到一个异常并且异常类型是NullReferenceException,那么我们用SpecialError来渲染视图。
警告:HandleErrorAttribute过滤器只当Web.config文件中的自定义错误设置打开时才可以工作 — 例如,在<system.web>节点内添加<customErrors mode="On" />。默认的自定义错误模式是RemoteOnly,即在开发期间,HandleErrorAttribute将不会拦截异常,但当你部署到产品服务器并从另一台计算机发出请求时,HandleErrorAttribute将会生效。为了看到最终用户将看到的效果,要确保你已经把自定义错误模式设置为On。
当渲染一个视图时,HandleErrorAttribute过滤器会传递一个HandleErrorInfo视图模型对象,这意味着你可以在显示给用户的消息中包含该异常的细节。清单13-14显示了一个例子。
清单13-14. 在显示一个错误消息时使用视图模型对象
@{
ViewBag.Title = "Sorry, there was a problem!";
}
<p>
There was a <b>@Model.Exception.GetType().Name</b>
while rendering <b>@Model.ControllerName</b>'s
<b>@Model.ActionName</b> action.
</p>
<p>
The exception message is: <b><@Model.Exception.Message></b>
</p>
<p>Stack trace:</p>
<pre>@Model.Exception.StackTrace</pre>
你可以在图13-1中看到这个视图是如何显示的。

图13-1. 使用一个视图模型显示一个错误消息
使用动作和结果过滤器
动作和结果过滤器是可以被用于任何目的的多用途过滤器。两种过滤器都遵循一个通用模式。构建这些类型的滤器的内建类实现IActionFilter, IResultFilter两个接口。
清单13-15演示了IActionFilter接口。
清单13-15. IActionFilter接口
public interface IActionFilter {
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
}
这个接口定义了两个方法。MVC框架在动作方法被调用之前调用OnActionExecuting方法。在动作方法被调用之后调用OnActionExecuted方法。
实现OnActionExecuting方法
OnActionExecuting方法在动作方法被调用之前被调用。你可以利用这个机会检测请求,并选择取消或修改该请求,或启动某些越过该动作调用的活动。传递给这个方法的参数是一个ActionExecutingContext对象,它是ControllerContext类的子类,并定义了你在其它上下文对象中已经看到的同样两个属性,如表13-7所示。
表13-7. ActionExecutingContext属性 | ||
Name | Type | Description |
ActionDescriptor | ActionDescriptor | Provides details of the action method |
Result | ActionResult | The result for the action method; a filter can cancel the request by setting this property to a non-null value |
通过把参数的Result属性设置成一个动作结果,你可以有选择地取消该请求,如清单13-16所示。
清单13-16. 在OnActionExecuting方法中取消一个请求
namespace MvcFilters.Infrastructure.Filters {
public class MyActionFilterAttribute : FilterAttribute, IActionFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
if (!filterContext.HttpContext.Request.IsSecureConnection) {
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
// do nothing
}
}
}
在这个例子中,我们用OnActionExecuting方法来检查请求是否是用SSL(安全套接字连接 — 译者注)形成的。如果不是,我们把一个“404 — 未找到”响应返回给用户。
注意:你可以从清单13-16看出,对于IActionFilter接口中定义的两个方法,你不需要都实现。如果你不需要把任何逻辑添加到某一个方法,那么只要让它为空即可。但要小心不要去throw NotImplementedException,因为如果你这样做了,异常过滤器将被执行。
实现OnActionExecuted方法
你也可以把这个过滤器用于动作方法执行之后要执行的一些任务。作为一个简单的例子,清单13-17演示了一个动作过滤器,它测量动作方法执行所消耗的时间。
清单13-17. 一个更复杂些的动作过滤器
using System.Web.Mvc;
namespace MvcFilters.Infrastructure.Filters {
public class ProfileAttribute : FilterAttribute, IActionFilter {
private Stopwatch timer;
public void OnActionExecuting(ActionExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext filterContext) {
timer.Stop();
if (filterContext.Exception == null) {
filterContext.HttpContext.Response.Write(
string.Format("Action method elapsed time: {0}",
timer.Elapsed.TotalSeconds));
}
}
}
}
在这个例子中,我们用OnActionExecuting方法启动一个记时器(采用System.Diagnostics命名空间中的高解析度Stopwatch记时器)。OnActionExecuted方法在动作方法完成时被调用。在这个清单中,我们用这个方法停止记时器,并把一条消息写到响应,报告流逝的时间。你可以在图13-2看到其显示。

图13-2. 用一个动作过滤器测量性能
传递给OnActionExecuted方法的参数是一个ActionExecutedContext对象。这个类定义了一些额外属性,如表13-8所示。Exception属性返回动作方法所弹出的异常,而ExceptionHandled属性指示是否另一个过滤器已经处理了这个异常。
表13-8. ActionExecutedContext属性 | ||
Name | Type | Description |
ActionDescriptor | ActionDescriptor | 提供动作方法的细节 |
Canceled | bool | 如果该动作已经被另一个过滤器取消,返回true |
Exception | Exception | 返回由另一个过滤器或动作方法弹出的异常 |
ExceptionHandled | bool | 如果异常已经被处理,返回true |
Result | ActionResult | 动作方法的结果;通过把这个属性设置为一个非空值,过滤器可以取消这个请求 |
如果另一个过滤器已经取消了这个请求(通过对Result属性设置一个值的办法),从OnActionExecuting 方法被调用的时刻开始.即使Canceled属性便被设置为true。我们的OnActionExecuted方法仍然会被调用,但只是为了清理和释放已被占用的资源。
实现Result过滤器
动作过滤器和结果过滤器有很多是共有的。结果过滤器是动作过滤器到动作方法的动作结果(这句话有点绕口,似乎我们可以这样理解:动作过滤器是针对动作方法的,而结果过滤器则是针对动作结果的;动作过滤器的两个方法(OnActionExecuting和OnActionExecuted)在动作方法的执行前后执行,而结果过滤器的两个方法(OnResultExecuting和OnResultExecuted,参见清单13-18)在动作结果的执行前后执行 — 译者注)。结果过滤器实现IResultFilter接口,如清单13-18所示。
清单13-18. IResultFilter接口
public interface IResultFilter {
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
}
在第12章,我们解释了动作方法如何返回动作结果。这允许我们把动作方法的目的与动作方法的执行分离开来。当我们把一个结果过滤器应用于一个动作方法时,一旦动作方法返回一个动作结果,但在这个动作结果被执行之前,OnResultExecuting方法便被调用。OnResultExecuted方法在动作结果被执行之后被调用。
这些方法的参数分别是ResultExecutingContext和ResultExecutedContext对象,它们十分类似于动作过滤器的对应参数。它们定义了同样的属性,具有同样的效果(见表13-8)。清单13-19显示了一个简单的结果过滤器。
清单13-19. 一个简单的结果过滤器
using System.Web.Mvc;
namespace MvcFilters.Infrastructure.Filters {
public class ProfileResultAttribute : FilterAttribute, IResultFilter {
private Stopwatch timer;
public void OnResultExecuting(ResultExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public void OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("Result execution - elapsed time: {0}",
timer.Elapsed.TotalSeconds));
}
}
}
这个过滤器测量执行该结果所花费的时间。让我们把这个过滤器附加到一个动作方法上:
[ProfileResult]
public ActionResult Index() {
return View();
}
...
现在,让我们导航到这个动作方法,这里我们看到了图13-3所示的输出。注意,从这个过滤器得到的性能信息显示在页面的底部。这是因为在动作结果被执行之后,我们写出了我们的消息 — 即,在视图被渲染之后。我们的上一个过滤器是在动作结果被执行之前写到响应的,这是为什么消息出现在页面的顶部的原因。

图13-3. 一个结果过滤器的输出
使用内建的动作和结果过滤器类
MVC框架包含一个内建的类,它可以被用来生成动作过滤器和结果过滤器。但与内建的认证和异常过滤器不同,它没有提供任何有用的功能。这个类名为ActionFilterAttribute,如清单13-20所示。
清单13-20. ActionFilterAttribute类
public virtual void OnActionExecuting(ActionExecutingContext filterContext) {
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext) {
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext) {
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext) {
}
}
使用这个类的唯一好处是你不需要实现你不打算使用的方法。作为一个例子,清单13-21演示了一个ActionFilterAttribute驱动的过滤器,它组合了执行动作方法和动作结果的性能测试。
清单13-21. 使用ActionFilterAttribute类
using System.Web.Mvc;
namespace MvcFilters.Infrastructure.Filters {
public class ProfileAllAttribute : ActionFilterAttribute {
private Stopwatch timer;
public override void OnActionExecuting(ActionExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public override void OnActionExecuted(ActionExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("Action method elapsed time: {0}",
timer.Elapsed.TotalSeconds));
}
public override void OnResultExecuting(ResultExecutingContext filterContext) {
timer = Stopwatch.StartNew();
}
public override void OnResultExecuted(ResultExecutedContext filterContext) {
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format("Action result elapsed time: {0}",
timer.Elapsed.TotalSeconds));
}
}
}
ActionFilterAttribute类实现了IActionFilter和IResultFilter接口,这意味着,即使未覆盖所有方法,MVC框架也会把这个派生类作为两种过滤器的类型来处理。如果我们把清单13-21的过滤器应用于我们的动作方法,我们将看到如图13-4所示的输出。

图13-4. 组合的动作/结果过滤器的输出
未完待续...