C H A P T E R 13
■ ■ ■
Filters inject extra logic into the request processing pipeline. They provide a simple and elegant way to implement cross-cutting concerns. This term refers to functionality that is used all over an application and doesn’t fit neatly into any one place, so it would break the separation of concerns pattern. Classic examples of cross-cutting concerns are logging, authorization, and caching.
过滤器将额外的逻辑注入到请求处理管道之中。它们提供一种简单且雅致的方式实现交叉关注。这个术语意指运用于整个应用程序而不合适放置于某个局部位置的功能,否则会打破关注分离模式。交叉关注的典型例子是登录、授权、以及缓存等。
Filters are so-called because this term is used for the same facility in other web application frameworks, including Ruby on Rails. However, MVC Framework filters are entirely different from the ASP.NET platform’s Request.Filter and Response.Filter objects, which perform transformations on the request and response streams (an advanced and infrequently performed activity). You can use Request.Filter and Response.Filter in an MVC application, but in general, when ASP.NET MVC programmers speak of filters, they are referring to the type covered in this chapter.
之所以称为过滤器,是因为这个术语在其它web应用程序框架,包括Ruby on Rails,中使用了同样的功能。然而,MVC框架的过滤器与ASP.NET平台的Request.Filter和Response.Filter对象完全不同,它们是在请求和响应流上进行传输的(一种高级且频繁执行的活动)。你可以在MVC应用程序中使用Request.Filter和Response.Filter,但一般而言,ASP.NET MVC程序员所说的过滤器,实际是指本章所涉及的类型。
In this chapter, we will show you the different categories of filters that the MVC Framework supports, how to create and use filters, and how to control their execution.
在本章中,我们将向你演示MVC框架所支持的不同种类的过滤器、如何生成和使用过滤器、以及如何控制它们的执行。
You have already seen an example of a filter in Chapter 9, when we applied authorization to the action methods of the SportsStore administration controller. We wanted the action method to be used only by users who had authenticated themselves, which presented us with a choice of approaches. We could have checked the authorization status of the request in each and every action method, as shown in Listing 13-1.
你在第9章已经看到过一个过滤器的例子,那是当我们把授权运用于SportsStore的管理控制器的动作方法时。我们希望该动作方法只能被已认证的用户所使用,这给我们提供了一个可选的办法。我们可以在每一个动作方法中检查请求的授权状态,如清单13-1所示。
Listing 13-1. Explicitly Checking Authorization in Action Methods
清单13-1. 在动作方法中明确检查授权
namespace SportsStore.WebUI.Controllers { 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 } }
You can see that there is a lot of repetition in this approach, which is why we decided to use a filter instead, as shown in Listing 13-2.
你可以看到,在这种办法中有许多重复,这是我们为什么要选择使用过滤器来代替的原因,如清单13-2所示。
Listing 13-2. Applying a Filter
清单13-2. 运用一个过滤器
namespace SportsStore.WebUI.Controllers { [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 } }
Filters are .NET attributes that add extra steps to the request processing pipeline. We used the Authorize filter in Listing 13-2, which has the same effect as all of the duplicated checks in Listing 13-1.
过滤器是.NET的性质,它把额外的步骤加入到请求处理管道上。我们在清单13-2中使用了Authorize过滤器,它具有与清单13-1中所有重复检查同样的效果。
.NET ATTRIBUTES: A REFRESHER
.NET性质:一个新鲜的事物
Attributes are special .NET classes derived from System.Attribute. You can attach them to other code elements, including classes, methods, properties, and fields. The purpose is to embed additional information into your compiled code that you can later read back at runtime.
性质是派生于System.Attribute的特殊的.NET类。你可以把它们附加到其它代码元素上,包括类、方法、属性、以及字段等。其目的是把附加信息嵌入到你的编译代码中,以便之后在运行时读回这些信息。
In C#, attributes are attached by using a square-bracket syntax, and you can populate their public properties with a named parameter syntax (for example, [MyAttribute(SomeProperty=value)]). In the C# compiler’s naming convention, if the attribute class name ends with the word Attribute, you can omit that portion (for example, you can apply AuthorizeAttribute by writing just [Authorize]).
在C#中,性质是用方括号语法进行附加的,而且你可以用一个命名的参数语法来组装它们的公用属性(如,[MyAttribute(SomeProperty=value)])。在C#的编译器命名约定中,如果性质类名以单词Attribute结尾,你可以忽略这一部分(例如,你可以只写[Authorize]来运用AuthorizeAttribute)。
The MVC Framework supports four different types of filters. Each allows you to introduce logic at different points in the request processing pipeline. The four filter types are described in Table 13-1.
MVC框架支持四种不同的过滤器类型。每一种类型允许你在请求处理管道的不同点上引入逻辑。这四种过滤器类型描述于表13-1。
Filter Type 过滤器类型 |
Interface 接口 |
Default Implementation 默认实现 |
Description 描述 |
---|---|---|---|
Authorization | IAuthorizationFilter | AuthorizeAttribute | Runs first, before any other filters or the action method 首先运行,在任何其它过滤器或动作方法之前 |
Action | IActionFilter | ActionFilterAttribute | Runs before and after the action method 在动作方法之前及之后运行 |
Result | IResultFilter | ActionFilterAttribute | Runs before and after the action result is executed 在动作结果被执行之前和之后运行 |
Exception | IExceptionFilter | HandleErrorAttribute | Runs only if another filter, the action method, or the action result throws an exception 只在另一个过滤器、动作方法、动作结果弹出异常时运行 |
Before the MVC Framework invokes an action, it inspects the method definition to see if it has attributes that implement the interfaces listed in Table 13-1. If so, then at the appropriate point in the request pipeline, the methods defined by these interfaces are invoked. The framework includes default attribute classes that implement the filter interfaces. We’ll show you how to use these classes later in this chapter.
在MVC框架调用一个动作之前,它首先检测该方法的定义,以查看它是否有实现表13-1所列接口的性质。如果有,那么在请求管道的相应点上,调用这些接口所定义的方法。框架包含了实现过滤器接口的默认性质类。本章稍后,我们将向你演示如何使用这些类。
■ Note The ActionFilterAttribute class implements both the IActionFilter and IResultFilter interfaces. This class is abstract, which forces you to provide an implementation. The other classes, AuthorizeAttribute and HandleErrorAttribute, contain useful features and can be used without creating a derived class.
注:ActionFilterAttribute类既实现IActionFilter接口,也实现IResultFilter接口。这个类是抽象类,它强迫你提供一个实现。其它类,AuthorizeAttribute和HandleErrorAttribute,包含一些有用的特性,并且可以不必生成派生类进行使用。
Filters can be applied to individual action methods or to an entire controller. In Listing 13-2, we applied the Authorize filter to the AdminController class, which has the same effect as applying it to each action method in the controller, as shown in Listing 13-3.
过滤器可以被运用于个别的动作方法,或整个控制器。在清单13-2中,我们把Authorize过滤器运用于AdminController类,它具有把过滤器运用于该控制器中每个动作方法同样的效果,如清单13-3所示。
Listing 13-3. Applying a Filter to Action Methods Individually
清单13-3. 将一个过滤器运用于个别动作方法
namespace SportsStore.WebUI.Controllers { 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 } }
You can apply multiple filters, and mix and match the levels at which they are applied—that is, whether they are applied to the controller or an individual action method. Listing 13-4 shows three different filters in use.
你可以运用多个过滤器,并且混用和匹配运用它们的等级 — 即,它们是运用于整个控制器,还是个别动作方法。清单13-4显示了使用三个不同的过滤器。
Listing 13-4. Applying Multiple Filters in a Controller Class
清单13-4. 在一个控制器类中使用多个过滤器
[Authorize(Roles="trader")] // applies to all actions public class ExampleController : Controller { [ShowMessage] // applies to just this action [OutputCache(Duration=60)] // applies to just this action public ActionResult Index() { // ... action method body } }
Some of the filters in this listing take parameters. We’ll show you how these work as we explore the different kinds of filters.
在这个清单中有些过滤器带有参数。我们将在考察各种不同种类的过滤器时,向你演示这些是如何工作的。
■ Note If you have defined a custom base class for your controllers, any filters applied to the base class will affect the derived classes.
注:如果你已经为你的控制器定义了一个自定义基类,那么,运用在这个基类上的任何过滤器都会影响其派生类。
Authorization filters are the filters that are run first—before the other kinds of filters and before the action method is invoked. As the name suggests, these filters enforce your authorization policy, ensuring that action methods can be invoked only by approved users. Authorization filters implement the IAuthorizationFilter interface, which is shown in Listing 13-5.
授权过滤器是首先运行的过滤器 — 在其它过滤器之前、以及动作方法被调用之前。正如名字的含义那样,这些过滤器强迫你的授权策略,确保动作方法只可以被已认证的用户所调用。授权过滤器实现IAuthorizationFilter接口,它如清单13-5所示。
Listing 13-5. The IAuthorizationFilter Interface
清单13-5. IAuthorizationFilter接口
namespace System.Web.Mvc { public interface IAuthorizationFilter { void OnAuthorization(AuthorizationContext filterContext); } }
Let’s set the scene. The MVC Framework has received a request from a browser. The routing system has processed the requested URL, and extracted the name of the controller and action that are targeted. A new instance of the controller class is created, but before the action method is called, the MVC Framework checks to see if there are any authorization filters applied to the action method. If there are, then the sole method defined by the IAuthorizationFilter interface, OnAuthorization, is invoked. If the authentication filter approves the request, then the next stage in the processing pipeline is performed. If not, then the request is rejected.
让我们来建立这个场景。MVC框架已经从浏览器接收到一个请求。路由系统已经处理了这个请求的URL,并提取了控制器和目标动作的名字。生成了一个新控制器类实例,但在这个动作方法被调用之前,MVC框架会查看是否有授权过滤器被运用于这个动作方法。如果有,那么,由IAuthorizationFilter接口定义的方法,OnAuthorization,将被调用。如果认证过滤器通过了这个请求,那么便执行请求管道中的下一个阶段。如果没有,那么该请求被拒绝。
The best way to understand how an authentication filter works is to create one. Listing 13-6 shows a simple example. It merely checks that the visitor has previously logged in (Request.IsAuthenticated is true), and that the username appears in a fixed list of allowed users.
理解认证过滤器是如何工作的最好方式是自己生成一个。清单13-6显示了一个简单的例子。它只检查之前已经登录的访问者,并且其用户名已经出现在一个固定的允许用户列表中。
Listing 13-6. A Custom Authentication Filter
清单13-6. 一个自定义认证过滤器
using System; 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); } } }
The simplest way to create an authorization filter is to subclass the AuthorizeAttribute class and override the AuthorizeCore method. This ensures that we benefit from the features built in to AuthorizeAttribute.
生成一个授权过滤器最简单的办法是生成AuthorizeAttribute类的子类,并覆盖其AuthorizeCore方法。这样确保我们能受益于内建在AuthorizeAttribute上的特性。
WARNING: WRITING SECURITY CODE IS DANGEROUS
警告:编写安全性代码是危险的
We included an example of a custom authorization filter because we think it neatly shows how the filter system works, but we are always wary of writing our own security code. Programming history is littered with the wreckage of applications whose programmers thought they knew how to write good security code. That’s actually a skill that very few people possess. There is usually some forgotten wrinkle or untested corner case that leaves a gaping hole in the application’s security.
我们包括了一个自定义授权过滤器示例,因为我们认为它清楚地演示了过滤器系统是如何工作的,但我们总是提防编写我们自己的安全性代码。很多程序员都认为他们能够写出很好的安全性代码,但编程的历史恰恰被他们所写的应用程序的灾难搞得一团糟。事实上,安全性代码是很少人才能拥有的技能。通常有一些遗漏的缺陷或未经测试的角落留下了应用程序的安全性漏洞。
Wherever possible, we like to use security code that is widely tested and proven. In this case, the MVC Framework has provided a full-featured authorization filter, which can be derived to implement custom authorization policies. We try to use this whenever we can, and we recommend that you do the same. At the very least, you can pass some of the blame to Microsoft when your secret application data is spread far and wide on the Internet.
在任何可能的地方,我们都喜欢使用经过广泛测试、并得到证明的安全性代码。当前,MVC框架已经提供了完备特性的授权过滤器,它能够被派生用来实现自定义授权策略。我们试图尽可能使用这种过滤器,而且我们建议你也要这样做。至少,当你的应用程序的秘密数据在Internet上被到处传播时,你可以把谴责转嫁给微软。
The constructor for our filter takes an array of names. These are the users who are authorized. Our filter contains a method called PerformAuthenticationCheck, which ensures that the request is authenticated and that the user is one of the authorized set.
我们过滤器的构造器以一个名字数组为参数。这些是被授权的用户。我们的过滤器含有一个名为PerformAuthenticationCheck的方法,它确保该请求被认证,并且该用户是授权用户之一。
The interesting part of this class is the implementation of the OnAuthorization method. The parameter that is passed to this method is an instance of the AuthorizationContext class, which is derived from ControllerContext. ControllerContext gives us access to some useful objects, not least of which is an HttpContextBase, through which we can access details of the request. The properties of the base class, ControllerContext, are shown in Table 13-2. All of the context objects used by the different kinds of action filters are derived from this class, so you can use these properties consistently.
这个类有趣的部分是OnAuthorization方法的实现。被传递给该方法的参数是AuthorizationContext类的一个实例,它派生于ControllerContext。ControllerContext使我们能够访问一些有用的对象,而不仅仅是HttpContextBase(通过它,我们可以访问请求的细节)。这个基类的属性,ControllerContext,如表13-2所示。所有这些由不同动作种类的过滤器所使用的上下文对象,都派生于这个类,因此你可以一致性地使用这些属性。
Name 名称 |
Type 类型 |
Description 描述 |
---|---|---|
Controller | ControllerBase | Returns the controller object for this request 返回该请求的控制器对象 |
HttpContext | HttpContextBase | Provides access to details of the request and access to the response 提供对请求细节的访问,以及对响应的访问 |
IsChildAction | Bool | Returns true if this is a child action (discussed later in this chapter and in Chapter 15) 如果是子动作,返回true(本意后面部分及第15章加以讨论) |
RequestContext | RequestContext | Provides access to the HttpContext and the routing data, both of which are available through other properties 提供对HttpContext以及路由数据的访问,这两者都是通过其它属性才可以使用的 |
RouteData | RouteData | Returns the routing data for this request 返回该请求的路由数据 |
Recall that when we wanted to check to see if the request was authenticated in Listing 13-6, we did it like this:
回顾清单13-6,当我们想查看该请求是否被认证时,我们是这样做的:
... filterContext.HttpContext.Request.IsAuthenticated ...
Using the context object, we can get all of the information we need to make decisions about the request. The AuthorizationContext defines two additional properties, which are shown in Table 13-3.
利用这个上下文对象,我们可以获得对请求作出决策所需要的所有信息。AuthorizationContext定义了两个额外属性,它们如表13-3所示。
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 用于动作方法的结果;通过把这个属性设置为非空值的办法,过滤器可以取消这个请求 |
The first of these properties, ActionDescriptor, returns an instance of System.Web.Mvc.ActionDescriptor, which you can use to get information about the action to which your filter has been applied. The second property, Result, is the key to making your filter work. If you are happy to authorize a request for an action method, then you do nothing in the OnAuthorization method. Your silence is interpreted by the MVC Framework as agreement that the request should proceed.
第一个属性,ActionDescriptor,返回System.Web.Mvc.ActionDescriptor的一个实例,你可以把它用来获得运用了过滤器的动作的信息。第二个属性,Result,是使你的过滤器进行工作的关键。如果你乐于对一个动作方法的请求进行授权,那么,你在OnAuthorization方法中什么也不用做。你的沉默被MVC框架解释为请求应该继续进行。
However, if you set the Result property of the context object to be an ActionResult object, the MVC Framework will use this as the result for the entire request. The remaining steps in the pipeline are not performed, and the result you have provided is executed to produce output for the user.
然而,如果你把上下文对象的Result属性设置成是一个ActionResult对象,MVC框架将把它作为整个请求的结果。管道中剩下的步骤不再执行,你已经提供的这个结果被执行,为用户产生输出。
In our example, if our PerformAuthenticationCheck returns false (indicating that either the request is not authenticated or the user is not authorized), then we create an HttpUnauthorizedResult action result (HttpUnauthorizedResult class and other action results are discussed in Chapter 12), and then assign it to the context Result property, like this:
在我们的例子中,如果我们的PerformAuthenticationCheck返回false(指示该请求未被认证,或该用户未被授权),那么,我们便生成一个HttpUnauthorizedResult动作结果(HttpUnauthorizedResult类以及其它在第12章讨论的动作结果),然后把它赋给上下文的Result属性,像这样:
filterContext.Result = new HttpUnauthorizedResult();
To use our custom authorization filter, we simply apply an attribute to the action methods that we want to protect, as shown in Listing 13-7.
为了使用我们的授权过滤器,我们简单地把一个性质运用于我们想要保护的动作方法,如清单13-7所示。
Listing 13-7. Applying a Custom Authorization Filter
清单13-7. 运用自定义授权过滤器
... [CustomAuth("adam", "steve", "bob")] public ActionResult Index() { return View(); } ...
The MVC Framework includes a very useful built-in authorization filter called AuthorizeAttribute. We can specify our authorization policy using two public properties of this class, as shown in Table 13-4.
MVC框架包含了一个非常有用的内建授权过滤器,名为AuthorizeAttribute。我们可以用这个类的两个public属性来指定我们的授权策略,如表13-4所示。
Name 名称 |
Type 类型 |
Description 描述 |
---|---|---|
Users | string | Comma-separated list of usernames that are allowed to access the action method. 逗号分隔的用户名列表,允许这些用户访问该动作方法。 |
Roles | string | Comma-separated list of role names. To access the action method, users must be in at least one of these roles. 逗号分隔的角色列表。为了访问该动作方法,用户必须至少在这些角色之一中。 |
Listing 13-8 shows how we can use the built-in filter to protect an action method.
清单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(); } ...
We have specified both users and roles in the listing. This means that authorization won’t be granted unless both conditions are met: the user’s name is adam, steve, or bob and the user has the admin role. There is an implicit condition as well, which is that the request is authenticated. If we don’t specify any users or roles, then any authenticated user can use the action method. This is the effect we created in Listing 13-2.
我们在清单中既指定了用户,也指定了角色。这意味着,除非两个条件都满足,否则将不予授权:用户名是adam、steve或bob,并且该用户具有admin角色。还有一个隐含的条件,即,该请求已被认证。如果我们未指定任何用户或角色,那么任何已被认证的用户都可以使用这个动作方法。这是我们在清单13-2中生成的效果。
For most applications, the authorization policy that AuthorizeAttribute provides is sufficient. If you want to implement something special, you can derive from this class. This is much less risky than implementing the IAuthorizationFilter interface directly, but you should still be very careful to think through the impact of your policy and test it thoroughly.
对于大多数应用程序,AuthorizeAttribute提供的授权策略已经足够了。如果你想实现一些特殊的事情,你可以从这个类进行派生。这要比直接实现IAuthorizationFilter接口的风险小得多,但你仍然应该小心地考虑你的策略的影响,并充分地测试它。
The AuthorizeAttribute class provides two different points of customization:
AuthorizeAttribute类提供了两个不同的自定义点:
We have provided examples of using both in the following sections.
我们在下一小节提供了使用这两个方法的例子。
■ Note A third method that you can override is OnAuthorization. We recommend that you don’t do this, since the default implementation of this method includes support for securely dealing with content cached using the OutputCache filter, which we describe later in this chapter.
注:你可以覆盖的第三个方法是OnAuthorization。我们建议你不要做这种事,因为这个方法的默认实现包含了使用OutputCache过滤器对缓存内容进行安全处理的支持,我们将在本章后面部分进行描述。
To demonstrate using a custom authentication policy, we will create a custom AuthorizeAttribute subclass. This policy will grant access to anyone accessing the site from a browser running directly on the server’s desktop (Request.IsLocal is true), as well as to remote visitors whose usernames and roles match the normalAuthorizeAttribute rules. This could be useful to allow server administrators to bypass the site’s login process. We can tell if this is the case by reading the IsLocal property of the HttpRequestBase class. Listing 13-9 shows our custom filter.
为了演示使用自定义认证策略,我们将生成一个自定义AuthorizeAttribute子类。这个策略将允许这样一些人进行访问:直接通过服务器桌面(Request.IsLocal为true)的浏览器访问该网站,以及远程访问者,其用户名及角色匹配normalAuthorizeAttribute规则。这对允许服务器管理员绕过网站登录过程是有用的。我们可以通过读取HttpRequestBase类的IsLocal属性,得到对这种情况的判断。清单13-9演示了我们的过滤器。
Listing 13-9. Implementing a Custom Authorization Filter
清单13-9. 实现一个自定义授权过滤器
using System.Web; 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); } } }
We can use this filter just as we would apply the standard AuthorizeAttribute class:
我们可以像运用标准的AuthorizeAttribute类一样来使用这个过滤器:
... [OrAuthorization(Users = "adam, steve, bob", Roles = "admin")] public ActionResult Index() { return View(); } ...
Now local users who are not specified in the list of names and have not been granted the admin role will be able to use the action method. Users who are not local are subject to the default authorization policy.
现在,未在用户名列表中指定的、且未被授予admin角色的本地用户将能够使用这个动作方法。非本地用户将受默认授权策略的支配。
The default policy for handling failed authorization attempts is to redirect the user to the login page. We don’t always want to do this. For example, if we are using AJAX, sending a redirect can cause the login page to appear in the middle of whatever page the user is viewing. Fortunately, we can override the HandleUnauthorizedRequest method of the AuthorizeAttribute class to create a custom policy. Listing 13-10 provides a demonstration.
处理失败的授权企图的默认策略是把用户重定向到登录页面。我们并不总是要这样做。例如,如果我们在使用AJAX,发送一个重定向可能会引起登录页面显示在用户正在查看的一个页面的中间。幸运的是,我们可以覆盖AuthorizeAttribute类的HandleUnauthorizedRequest方法,以生成一个自定义策略。清单13-10提供了一个演示。
Listing 13-10. Implementing a Custom Authorization Failure Policy
清单13-10. 实现自定义授权失败策略
using System.Web.Mvc; 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); } } } }
When this filter detects an AJAX request, it responds with JSON data that reports the problem and provides the URL for the login page. Regular requests are handled using the default policy, as defined in the base class. The AJAX client must be written to expect and understand this response. We’ll return to the topics of AJAX and JSON in Chapter 19.
当这个过滤器检测到一个AJAX请求时,它用报告此问题的JSON数据进行响应,并提供登录页面的URL。规则的请求用默认策略进行处理,就像在基类中所定义的那样。我们必须把AJAX客户端写成期望这个响应,并能够理解它。我们将在第19章回到AJAX和JSON这些论题。
Exception filters are run only if an unhandled exception has been thrown when invoking an action method. The exception can come from the following locations:
异常过滤器只当在调用一个动作方法时弹出未处理异常才会运行。这种异常来自以下位置:
Exception filters must implement the IExceptionFilter interface, which is shown in Listing 13-11.
异常过滤器必须实现IExceptionFilter接口,如清单13-11所示。
Listing 13-11. The IExceptionFilter Interface
清单13-11. IExceptionFilter接口
namespace System.Web.Mvc { public interface IExceptionFilter { void OnException(ExceptionContext filterContext); } }
The OnException method is called when an unhandled exception arises. The parameter for this method is an ExceptionContext object. This class is similar to the parameter for the authorization filter, in that is derives from the ControllerContext class (so that you can get information about the request) and defines some additional filter-specific properties, as described in Table 13-5.
当一个未处理异常出现时,OnException方法被调用。该方法的参数是一个ExceptionContext对象。这个类(指ExceptionContext — 译者注)类似于授权过滤器的参数,也派生于ControllerContext类(因此你可以获得关于请求的信息),而且它定义了一些额外的过滤器专用属性,如表13-5的描述。
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 用于动作方法的结果;通过把这个属性设置为一个非空值的办法,过滤器可以取消这个请求 |
Exception | Exception | The unhandled exception 未处理异常 |
ExceptionHandled | bool | Returns true if another filter has marked the exception as handled 如果另一个过滤器已经把这个异常标记为已处理,则返回true |
The exception that has been thrown is available through the Exception property. An exception filter can report that it has handled the exception by setting the ExceptionHandled property to true. All of the exception filters applied to an action are invoked even if this property is set to true, so it is good practice to check whether another filter has already dealt with the problem, to avoid attempting to recover from a problem that another filter has resolved.
弹出异常通过Exception属性是可访问的。通过把ExceptionHandled属性设置为true,一个异常过滤器可以报告它已经处理了该异常。运用于一个动作的所有异常过滤器都会被调用,即使这个属性被设置为true,因此,好的实践是检查是否另一个过滤器已经处理了这个问题,以避免恢复另一个过滤器已经解决了的问题。
■ Note If none of the exception filters for an action method set the ExceptionHandled property to true, the MVC Framework uses the default ASP.NET exception handling procedure. This will display the dreaded yellow screen of death by default.
注:如果一个动作方法的所有异常过滤器均未把ExceptionHandled属性设置为true,MVC框架将使用默认的ASP.NET异常处理程序。这将默认地显示恐怖的黄色死屏。
The Result property is used by the exception filter to tell the MVC Framework what to do. The two main uses for exception filters are to log the exception and to display a suitable message to the user. Listing 13-12 shows a demonstration of the latter, redirecting the user to a specific error page when a particular kind of unhandled exception arises.
Result属性由异常过滤器使用,以告诉MVC框架要做什么。异常过滤器的两个主要应用是日志该异常,并把适当的消息显示给用户。清单13-12显示了后者的一个演示,当一个特定种类的未处理异常出现时,把该用户重定向到一个指定的错误页面。
Listing 13-12. Implementing an Exception Filter
清单13-12. 实现一个异常过滤器
using System.Web.Mvc; 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; } } } }
This filter responds to NullReferenceException instances and will act only if no other exception filter has indicated that the exception is handled. We redirect the user to a special error page, which we have done using a literal URL. We can apply this filter as follows:
这个过滤器对NullReferenceException实例进行响应,并且只在其它异常过滤器没有指示该异常已被处理时起作用。我们把该用户重定向到一个指定的错误页面,这是我们用一个字面URL完成的。我们可以像如下这样来使用这个过滤器:
... [MyException] public ActionResult Index() { ...
If the Index action method throws an exception, and that exception is an instance of NullReferenceException, and no other exception filter has handled the exception, then our filter will redirect the user to the URL /SpecialErrorPage.html.
如果Index动作方法弹出一个异常,并且这个异常是NullReferenceException的一个实例,而且没有其它异常过滤器已经处理了这个异常,那么,我们这个过滤器就会把该用户重定向到/SpecialErrorPage.html这个URL。
The HandleErrorAttribute is the built-in implementation of the IExceptionFilter interface and makes it easier to create exception filters. With it, you can specify an exception and the names of a view and layout using the properties described in Table 13-6.
HandleErrorAttribute是内建的IExceptionFilter接口实现,而且使它更易于生成异常过滤器。通过它,你可以运用表13-6所描述的属性来指定一个异常以及视图和布局名。
Name 名称 |
Type 类型 |
Description 描述 |
---|---|---|
ExceptionType | Type | The exception type handled by this filter. It will also handle exception types that inherit from the specified value, but will ignore all others. The default value is System.Exception, which means that, by default, it will handle all standard exceptions. 由这个过滤器处理的异常类型。它也将处理由特定值继承而来的异常类型,但会忽略所有其它类型。其默认值是System.Exception,其含义为,默认地,它将处理所有标准异常。 |
View | string | The name of the view template that this filter renders. If you don’t specify a value, it takes a default value of Error, so by default, it renders /Views/<currentControllerName>/Error.cshtml or /Views/Shared/Error.cshtml. 该过滤器渲染的视图模板名。如果你未指定一个值,它取默认的Error值,因此,默认地,它渲染/Views/<currentControllerName>/Error.cshtml或/Views/Shared/Error.cshtml。 |
Master | string | The name of the layout used when rendering this filter’s view. If you don’t specify a value, the view uses its default layout page. 在渲染这个过滤器的视图时所使用的布局名。如果不指定一个值,视图使用其默认布局页面。 |
When an unhandled exception of the type specified by ExceptionType is encountered, this filter will set the HTTP result code to 500 (meaning server error) and render the view specified by the View property (using the default layout or the one specified by Master). Listing 13-13 shows how to use the HandleErrorAttribute filter.
当遇到由ExceptionType所指定类型的未处理异常时,此过滤器将把HTTP结果代码设置为500(意为“服务器错误”),并渲染由View属性指定的视图(用默认布局或Master指定的布局)。清单13-13演示了如何使用这个HandleErrorAttribute过滤器。
Listing 13-13. Using the HandleErrorAttribute Filter
清单13-13. 使用HandleErrorAttribute过滤器
... [HandleError(ExceptionType=typeof(NullReferenceException), View="SpecialError")] public ActionResult Index() { ...
In this example, we are interested in the NullReferenceException type and want the SpecialError view to be rendered when one is encountered.
在这个例子中,我们感兴趣的是NullReferenceException类型,并且希望在遇到一个异常时,渲染SpecialError视图。
■ Caution The HandleErrorAttribute filter works only when custom errors are enabled in the Web.config file—for example, by adding <customErrors mode="On" /> inside the <system.web> node. The default custom errors mode is RemoteOnly, which means that during development, HandleErrorAttribute will not intercept exceptions, but when you deploy to a production server and make requests from another computer, HandleErrorAttribute will take effect. To see what end users are going to see, make sure you’ve set the custom errors mode to On.
注意:HandleErrorAttribute过滤器只当Web.config文件中的自定义错误设置打开时才可以工作 — 例如,在<system.web>节点内添加<customErrors mode="On" />。默认的自定义错误模式是RemoteOnly,意为在开发期间,HandleErrorAttribute将不会截取异常,但当你部署到产品服务器并从另一台计算机发出请求时,HandleErrorAttribute才会起作用。为了看到最终用户将看到的样子,要确保你已经把自定义错误模式设置为On。
When rendering a view, the HandleErrorAttribute filter passes a HandleErrorInfo view model object, which means that you can include details about the exception in the message displayed to the user. Listing 13-14 shows an example.
当渲染一个视图时,HandleErrorAttribute过滤器会传递一个HandleErrorInfo视图模型对象,这意味着你可以在显示给用户的消息中包含该异常的细节。清单13-14显示了一个例子。
Listing 13-14. Using the View Model Object When Displaying an Error Message
清单13-14. 在显示一个错误消息时使用视图模型对象
@Model HandleErrorInfo @{ 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>
You can see how this view appears in Figure 13-1.
你可以在图13-1中看到这个视图是如何显示的。
Action and result filters are general-purpose filters that can be used for any purpose. Both kinds follow a common pattern. The built-in class for creating these types of filters, IActionFilter, implements both interfaces. Listing 13-15 shows this interface.
动作和结果过滤器是可以被用于任何目的的多用途过滤器。两种过滤器都遵循一个通用模式。生成这些过滤器类型的内建类,IActionFilter,实现两个接口。清单13-15演示了这个接口。
Listing 13-15. The IActionFilter Interface
清单13-15. IActionFilter接口
namespace System.Web.Mvc { public interface IActionFilter { void OnActionExecuting(ActionExecutingContext filterContext); void OnActionExecuted(ActionExecutedContext filterContext); } }
This interface defines two methods. The MVC Framework calls the OnActionExecuting method before the action method is invoked. It calls the OnActionExecuted method after the action method has been invoked.
这个接口定义了两个方法。MVC框架在动作方法被调用之前调用OnActionExecuting方法。在动作方法被调用之后调用OnActionExecuted方法。
The OnActionExecuting method is called before the action method is invoked. You can use this opportunity to inspect the request and elect to cancel the request, modify the request, or start some activity that will span the invocation of the action. The parameter to this method is an ActionExecutingContext object, which subclasses the ControllerContext class and defines the same two properties you have seen in the other context objects, as described in Table 13-7.
OnActionExecuting方法在动作方法被调用之前被调用。你可以利用这个机会检测请求,并选择取消或修改该请求,或启动越过该动作调用的某些活动。传递给这个方法的参数是一个ActionExecutingContext对象,它是ControllerContext类的子类,并定义了你在其它上下文对象中已经看到的同样两个属性,如表13-7所示。
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 动作方法的结果;通过把这个属性设置为一个非空值,过滤器可以取消该请求 |
You can selectively cancel the request by setting the Result property of the parameter to an action result, as shown in Listing 13-16.
通过把参数的Result属性设置成一个动作结果,你可以有选择地取消该请求,如清单13-16所示。
Listing 13-16. Canceling a Request in the OnActionExecuting Method
清单13-16. 在OnActionExecuting方法中取消一个请求
using System.Web.Mvc; 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 } } }
In this example, we use the OnActionExecuting method to check whether the request has been made using SSL. If not, we return a 404 – Not Found response to the user.
在这个例子中,我们用OnActionExecuting方法来检查请求是否是用SSL(安全套接字连接 — 译者注)形成的。如果不是,我们把一个“404 — 未找到”响应返回给用户。
■ Note You can see from Listing 13-16 that you don’t need to implement both of the methods defined in the IActionFilter interface. If you don’t need to add any logic to a method, then just leave it empty. But be careful not to throw a NotImplementedException, because if you do, the exception filters will be executed.
注:你可以从清单13-16看出,对于IActionFilter接口中定义的两个方法,你不需要都实现。如果你不需要把任何逻辑添加到某一个方法,那么只要让它为空即可。但要小心不要去弹出NotImplementedException,因为如果你这样做了,异常过滤器将被执行。
You can also use the filter to perform some task that spans the execution of the action method. As a simple example, Listing 13-17 shows an action filter that measures the amount of time it takes for the action method to execute.
你也可以把这个过滤器用于动作方法执行之后要执行的一些任务。作为一个简单的例子,清单13-17演示了一个动作过滤器,它测量动作方法执行所消耗的时间。
Listing 13-17. A More Complex Action Filter
清单13-17. 一个更复杂些的动作过滤器
using System.Diagnostics; 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)); } } } }
In this example, we use the OnActionExecuting method to start a timer (using the high-resolution Stopwatch timer in the System.Diagnostics namespace). The OnActionExecuted method is invoked when the action method has completed. In the listing, we use this method to stop the timer and write a message to the response, reporting the elapsed time. You can see how this appears in Figure 13-2.
在这个例子中,我们用OnActionExecuting方法启动一个记时器(采用System.Diagnostics命名空间中的高解析度Stopwatch记时器)。OnActionExecuted方法在动作方法完成时被调用。在这个清单中,我们用这个方法停止记时器,并把一条消息写到响应,报告流逝的时间。你可以在图13-2看到其显示。
The parameter that is passed to the OnActionExecuted method is an ActionExecutedContext object. This class defines some additional properties, as shown in Table 13-8. The Exception property returns any exception that the action method has thrown, and the ExceptionHandled property indicates if another filter has dealt with it.
传递给OnActionExecuted方法的参数是一个ActionExecutedContext对象。这个类定义了一些额外属性,如表13-8所示。Exception属性返回动作方法所弹出的异常,而ExceptionHandled属性指示是否另一个过滤器已经处理了这个异常。
Name 名称 |
Type 类型 |
Description 描述 |
---|---|---|
ActionDescriptor | ActionDescriptor | Provides details of the action method 提供动作方法的细节 |
Canceled | bool | Returns true if the action has been canceled by another filter 如果该动作已经被另一个过滤器取消,返回true |
Exception | Exception | Returns an exception thrown by another filter or by the action method 返回由另一个过滤器或动作方法弹出的异常 |
ExceptionHandled | bool | Returns true if the exception has been handled 如果异常已经被处理,返回true |
Result | ActionResult | The result for the action method; a filter can cancel the request by setting this property to a non-null value 动作方法的结果;通过把这个属性设置为一个非空值,过滤器可以取消这个请求 |
The Canceled property will return true if another filter has canceled the request (by setting a value for the Result property) since the time that the filter’s OnActionExecuting method was invoked. Our OnActionExecuted method is still called, but only so that we can tidy up and release any resources we were using.
如果另一个过滤器已经取消了这个请求(通过对Result属性设置一个值的办法),从OnActionExecuting 方法被调用的时刻开始,Canceled属性便被设置为true。我们的OnActionExecuted方法仍然会被调用,但只是为了清理和释放已被占用的资源。
Action filters and result filters have a lot in common. Result filters are to action results what action filters are to action methods. Result filters implement the IResultFilter interface, which is shown in Listing 13-18.
动作过滤器和结果过滤器有很多是共有的。结果过滤器是动作过滤器到动作方法的动作结果(这句话有点绕口,似乎我们可以这样理解:动作过滤器是针对动作方法的,而结果过滤器则是针对动作结果的;动作过滤器的两个方法(OnActionExecuting和OnActionExecuted)在动作方法的执行前后执行,而结果过滤器的两个方法(OnResultExecuting和OnResultExecuted,参见清单13-18)在动作结果的执行前后执行 — 译者注)。结果过滤器实现IResultFilter接口,如清单13-18所示。
Listing 13-18. The IResultFilter Interface
清单13-18. IResultFilter接口
namespace System.Web.Mvc { public interface IResultFilter { void OnResultExecuting(ResultExecutingContext filterContext); void OnResultExecuted(ResultExecutedContext filterContext); } }
In Chapter 12, we explained how action methods return action results. This allows us to separate the intent of an action method from its execution. When we apply a result filter to an action method, the OnResultExecuting method is invoked once the action method has returned an action result, but before the action result is executed. The OnResultExecuted method is invoked after the action result is executed.
在第12章,我们解释了动作方法如何返回动作结果。这允许我们把动作方法的目的与动作方法的执行分离开来。当我们把一个结果过滤器运用于一个动作方法时,一旦动作方法返回一个动作结果,但在这个动作结果被执行之前,OnResultExecuting方法便被调用。OnResultExecuted方法在动作结果被执行之后被调用。
The parameters to these methods are ResultExecutingContext and ResultExecutedContext objects, respectively, and they are very similar to their action filter counterparts. They define the same properties, which have the same effects (see Table 13-8). Listing 13-19 shows a simple result filter.
送给这些方法的参数分别是ResultExecutingContext和ResultExecutedContext对象,它们十分类似于动作过滤器的对应参数。它们定义了同样的属性,具有同样的效果(见表13-8)。清单13-19显示了一个简单的结果过滤器。
Listing 13-19. A Simple Result Filter
清单13-19. 一个简单的结果过滤器
using System.Diagnostics; 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)); } } }
This filter measures the amount of time taken to execute the result. Let’s attach the filter to an action method:
这个过滤器测量执行该结果所花费的时间。让我们把这个过滤器附加到一个动作方法上:
... [ProfileResult] public ActionResult Index() { return View(); } ...
Now let’s navigate to the action method, where we see the output shown in Figure 13-3. Notice that the performance information from this filter appears at the bottom of the page. This is because we have written our message after the action result has been executed—that is, after the view has been rendered. Our previous filter wrote to the response before the action result was executed, which is why the message appeared at the top of the page.
现在,让我们导航到这个动作方法,这里我们看到了图13-3所示的输出。注意,从这个过滤器得到的性能信息显示在页面的底部。这是因为在动作结果被执行之后,我们写出了我们的消息 — 即,在视图被渲染之后。我们的上一个过滤器是在动作结果被执行之前写到响应的,这是为什么消息出现在页面的顶部的原因。
The MVC Framework includes a built-in class that can be used to create both action and result filters. But unlike the built-in authorization and exception filters, it doesn’t provide any useful features. The class, called ActionFilterAttribute, is shown in Listing 13-20.
MVC框架包含一个内建的类,它可以被用来生成动作过滤器和结果过滤器。但与内建的认证和异常过滤器不同,它没有提供任何有用的特性。这个类,名为ActionFilterAttribute,如清单13-20所示。
Listing 13-20. The ActionFilterAttribute Class
清单13-20. ActionFilterAttribute类
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter{ public virtual void OnActionExecuting(ActionExecutingContext filterContext) { } public virtual void OnActionExecuted(ActionExecutedContext filterContext) { } public virtual void OnResultExecuting(ResultExecutingContext filterContext) { } public virtual void OnResultExecuted(ResultExecutedContext filterContext) { } }}
The only benefit to using this class is that you don’t need to implement the methods that you don’t intend to use. As an example, Listing 13-21 shows an ActionFilterAttribute-derived filter that combines our performance measurements for both the action method and the action result being executed.
使用这个类的唯一好处是你不需要实现你不打算使用的方法。作为一个例子,清单13-21演示了一个ActionFilterAttribute驱动的过滤器,它组合了执行动作方法和动作结果的性能测试。
Listing 13-21. Using the ActionFilterAttribute Class
清单13-21. 使用ActionFilterAttribute类
using System.Diagnostics; 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)); } } }
The ActionFilterAttribute class implements the IActionFilter and IResultFilter interfaces, which means that the MVC Framework will treat derived classes as both types of filters, even if not all of the methods are overridden. If we apply the filter in Listing 13-21 to our action method, we see the output shown in Figure 13-4.
ActionFilterAttribute类实现了IActionFilter和IResultFilter接口,这意味着,即使未覆盖所有方法,MVC框架也会把这个派生类作为两种过滤器的类型来处理。如果我们把清单13-21的过滤器运用于我们的动作方法,我们将看到如图13-4所示的输出。
The previous examples have given you all the information you need to work effectively with filters. Along with the features you’ve learned about, there are some other features that are interesting but not as widely used. In the following sections, we’ll show you some of the advanced MVC Framework filtering capabilities.
前面的例子已经给出了用过滤器进行有效工作所需要的所有信息。伴随这些你已经学到的特性,还有一些其它有趣但不常用的特性。在以下小节中,我们将向你演示一些高级的MVC框架过滤能力。
The normal way of using filters is to create and use attributes, as we have demonstrated in the previous sections. However, there is an alternative to using attributes. The Controller class implements the IAuthorizationFilter, IActionFilter, IResultFilter, and IExceptionFilter interfaces. It also provides empty virtual implementations of each of the OnXXX methods you have already seen, such as OnAuthorization and OnException. Listing 13-22 shows a controller that measures its own performance.
使用过滤器的常规办法是生成并使用一些性质,正如我们在前几小节所演示的那样。然而,还有使用性质的另一种办法。Controller类实现了IAuthorizationFilter、IActionFilter、IResultFilter、和IExceptionFilter接口。它也提供了你已经看到过的每一个OnXXX方法的虚拟实现,如OnAuthorization和OnException。清单13-22演示了一个测量自身性能的控制器。
Listing 13-22. Using the Controller Filter Methods
清单13-22. 使用Controller过滤器方法
using System.Diagnostics; using System.Web.Mvc; namespace MvcFilters.Controllers { public class SampleController : Controller { private Stopwatch timer; public ActionResult Index() { return View(); } protected override void OnActionExecuting(ActionExecutingContext filterContext) { timer = Stopwatch.StartNew(); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { timer.Stop(); filterContext.HttpContext.Response.Write( string.Format("Action method elapsed time: {0}", timer.Elapsed.TotalSeconds)); } protected override void OnResultExecuting(ResultExecutingContext filterContext) { timer = Stopwatch.StartNew(); } protected override void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop(); filterContext.HttpContext.Response.Write( string.Format("Action result elapsed time: {0}", timer.Elapsed.TotalSeconds)); } } }
This technique is most useful when you are creating a base class from which multiple controllers in your project are derived. The whole point of filtering is to put code that is required across the application in one reusable location, so using these methods in a controller that will not be derived from doesn’t make much sense.
当你要生成一个基类,以派生你项目中的多个控制器时,这项技术是最有用的。整个过滤点是,在一个可重用位置,放置整个应用程序所需要的代码。于是,在一个非派生的控制器中使用这些方法就没太大意义了。
For our projects, we prefer to use attributes. We like the separation between the controller logic and the filter logic. If you are looking for a way to apply a filter to all of your controllers, continue reading to see how to do that with global filters.
对于我们的项目,我们更喜欢使用性质。我们喜欢控制器逻辑与过滤逻辑器之间的这种分离。如果你正在寻找一种把一个过滤器运用于你的所有控制器的办法,进一步阅读,以看看如何用全局过滤器做这种事。
Global filters are applied to all of the action methods in your application. We make a regular filter into a global filter through the RegisterGlobalFilters method in Global.asax. Listing 13-23 shows how to change the ProfileAll filter we created in Listing 13-21 into a global filter.
全局过滤器被用于应用程序的所有动作方法。我们通过Global.asax中的RegisterGlobalFilters方法,把一个规则过滤器做成一个全局过滤器。清单13-23演示了如何把我们在清单13-21中生成的ProfileAll过滤器改成一个全局过滤器。
Listing 13-23. Creating a Global Filter
清单13-23. 生成一个全局过滤器
public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new ProfileAllAttribute()); } ...
The RegisterGlobalFilters method is called from the Application_Start method, which ensures that the filters are registered when your MVC application starts. The RegisterGlobalFilters method and the call from Application_Start are set up for you when Visual Studio creates the MVC project. The parameter to the RegisterGlobalFilters method is a GlobalFilterCollection. You register global filters using the Add method, like this:
RegisterGlobalFilters方法是通过Application_Start方法调用的,这确保过滤在MVC应用程序启动时便被注册。RegisterGlobalFilters方法及其在Application_Start中的调用是在Visual Studio生成MVC项目时建立的。RegisterGlobalFilters方法的参数是一个GlobalFilterCollection。你要用Add方法来注册全局过滤器,像这样:
filters.Add(new ProfileAllAttribute());
Notice that you must refer to the filter by the full class name (ProfileAllAttribute), rather than the short name used when you apply the filter as an attribute (ProfileAll). Once you have registered a filter like this, it will apply to every action method.
注意,你必须用全类名(ProfileAllAttribute)来引用这个过滤器,而不是你把过滤用作为性质时的缩略名(ProfileAll)。一旦你已经像这样注册了一个过滤器,它将施加到每个动作方法上。
■ Note The first statement in the default RegisterGlobalFilters method created by Visual Studio sets up the default MVC exception handling policy. This will render the /Views/Shared/Error.cshtml view when an unhandled exception arises. This exception handling policy is disabled by default for development. See the “Creating an Exception Filter” section later in the chapter for a note on how to enable it.
注:Visual Studio生成的默认RegisterGlobalFilters方法中的第一条语句建立了默认的MVC异常处理策略。当一个未处理异常发生时,将渲染/Views/Shared/Error.cshtml视图。这条异常处理策略在开发期间是默认失效的。参见本章稍后的“生成异常过滤器”小节如何启用它的说明。
The format for specifying property values for a global filter is the same as for regular classes, as shown in Listing 13-24.
对一个全局过滤器指定属性值的格式与规则的类相同,如清单13-24所示。
Listing 13-24. Creating a Global Filter That Requires Properties
清单13-24. 生成需要属性的全局过滤器
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new ProfileAllAttribute()); filters.Add(new HandleErrorAttribute() { ExceptionType = typeof(NullReferenceException), View = "SpecialError" }); }
We have already explained that filters are executed by type. The sequence is authorization filters, action filters, and then result filters. The framework executes your exception filters at any stage if there is an unhandled exception. However, within each type category, you can take control of the order in which individual filters are used.
我们已经解释了过滤器是通过类型执行的。其顺序是授权过滤器、动作过滤器、然后是结果过滤器。如果有未处理异常,框架在任一阶段都可以执行异常过滤器。然而,在每个类型种类中,你可以控制个别过滤器的顺序。
Listing 13-25 shows a simple action filter that we will use to demonstrate ordering filter execution.
清单13-25显示了一个简单的动作过滤器,我们将用它来演示过滤器执行的顺序。
Listing 13-25. A Simple Action Filter
清单13-25. 一个简单的动作过滤器
using System; using System.Web.Mvc; namespace MvcFilters.Infrastructure.Filters { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true)] public class SimpleMessageAttribute : FilterAttribute, IActionFilter { public string Message { get; set; } public void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.HttpContext.Response.Write( string.Format("[Before Action: {0}]", Message)); } public void OnActionExecuted(ActionExecutedContext filterContext) { filterContext.HttpContext.Response.Write( string.Format("[After Action: {0}]", Message)); } } }
This filter writes a message to the response when the OnXXX methods are invoked. We can specify part of the message using the Message parameter. We can apply multiple instances of this filter to an action method, as shown in Listing 13-26 (notice that in the AttributeUsage attribute, we set the AllowMultiple property to true).
当OnXXX方法被调用时,这个过滤器把一条消息写到响应。我们可以用Message参数指定消息内容。我们可以把这个过滤器的多个实例运用于一个动作方法,如清单13-26所示(注意,在AttributeUsage性质中,我们把AllowMultiple属性设置为true)。
Listing 13-26. Applying Multiple Filters to an Action
清单13-26. 把多个过滤器运用于一个动作
... [SimpleMessage(Message="A")] [SimpleMessage(Message="B")] public ActionResult Index() { Response.Write("Action method is running"); return View(); } ...
We have created two filters with different messages: the first has a message of A, and the other has a message of B. We could have used two different filters, but this approach allows us to create a simpler example. When you run the application and navigate to a URL that invoked the action method, you see the output shown in Figure 13-5.
我们用不同的消息生成了两个过滤器:第一个消息为A,另一个消息为B。我们可以使用两个不同的过滤器,但这种办法允许我们生成一个简单的例子。当你运行这个应用程序,并导航到调用这个动作方法的URL时,你将看到如图13-5所示的输出。
When we ran this example, the MVC Framework executed the A filter before the B filter, but it could have been the other way around. The MVC Framework doesn’t guarantee any particular order or execution. Most of the time, the order doesn’t matter. When it does, you can use the Order property, as shown in Listing 13-27.
当我们运行这个例子时,MVC框架在B过滤器之前执行A过滤器,但它也可以按另一种方式。MVC框架不会保证任何特定的顺序或执行。大多数情况下,顺序无关紧要。但当有必要时,你可以使用Order属性,如清单13-27所示。
Listing 13-27. Using the Order Property in a Filter
清单13-27. 在一个过滤器中使用Order属性
... [SimpleMessage(Message="A", Order=2)] [SimpleMessage(Message="B", Order=1)] public ActionResult Index() { Response.Write("Action method is running"); return View(); } ...
The Order parameter takes an int value, and the MVC Framework executes the filters in ascending order. In the listing, we have given the B filter the lowest value, so the framework executes it first, as shown in Figure 13-6.
Order参数取一个int值,MVC框架以升序执行这些过滤器。在这个清单中,我们已经给了B过滤器一个最小值,因此,框架首先执行它,如图13-6所示。
■ Note Notice that the OnActionExecuting methods are executed in the order we specified, but the OnActionExecuted methods are executed in the reverse order. The MVC Framework builds up a stack of filters as it executes them before the action method, and then unwinds the stack afterward. This unwinding behavior cannot be changed.
注:注意,OnActionExecuting方法是以我们指定的顺序执行的,但OnActionExecuted方法是以反序执行的。在动作方法之前MVC框架执行过滤器时会建立一个过滤器堆栈,并在之后释放这个堆栈。这种释放行为是不可改变的。
If we don’t specify a value for the Order property, it is assigned a default value of -1. This means that if you mix filters so that some have Order values and others don’t, the ones without these values will be executed first, since they have the lowest Order value.
如果我们不指定Order属性的值,它被赋为默认的-1值。其含义为,如果你把有Order值和没有Order值的过滤器混用在一起,那些没有值的过滤将首先执行,因为它们具有最低的Order值。
If multiple filters of the same type (say, action filters) have the same Order value (say 1), then the MVC Framework determines the execution order based on where the filter has been applied. Global filters are executed first, then filters applied to the controller class, and then filters applied to the action method.
如果同类型的多个过滤器具有相同的Order值(如1),那么MVC框架基于过滤器被运用的位置来执行。全局过滤器首先被执行,然后是运用于控制器类的过滤器,再然后是运用于动作方法的过滤器。
■ Note Two other categories are First and Last. The filters in the First category are executed before all others, and the filters in the Last category are executed after all others.
注:两个其它种类是First和Last。First种类的过滤器在所有其它种类之前执行,而Last种类的过滤在所有其它种类之后执行。
As a demonstration, we have created a global filter in Global.asax, as shown in Listing 13-28.
作为一个演示,我们在Global.asax中生成了一个全局过滤器,如清单13-28所示。
Listing 13-28. Defining a Global Filter with an Order Value
清单13-28. 定义一个带有Order值的全局过滤器
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new SimpleMessageAttribute() { Message = "Global", Order = 1 }); }
We specify a value for Order using the standard property-initialization syntax. We have also defined filters on the controller class and the action method, as shown in Listing 13-29. All three of these filters have the same Order value.
我们用标准的属性初始化语法指定了Order值。我们也定义了控制器类和动作方法上的过滤器,如清单13-29所示。所有这三个过滤器具有同样的Order值。
Listing 13-29. Defining Ordered Filters at the Controller and Action Levels
清单13-29. 定义控制器和动作级别上的排序的过滤器
... [SimpleMessage(Message="Controller", Order=1)] public class ExampleController : Controller { [SimpleMessage(Message="Action", Order=1)] public ActionResult Index() { Response.Write("Action method is running"); return View(); } } ...
When you run the application and navigate to the action method, you see the output shown in Figure 13-7.
当你运行这个应用程序并导航到该动作方法时,你将看到如图13-7所示的输出。
■ Note The order of execution is reversed for exception filters. If exception filters are applied with the same Order value to the controller and to the action method, the filter on the action method will be executed first. Global exception filters with the same Order value are executed last.
注:异常过滤器的执行顺序是倒过来的。如果异常过滤器用同样的Order值被运用于控制器和动作方法,动作方法上的过滤器首先被执行。带有同样Order值的全局过滤器最后被执行。
The MVC Framework supplies some built-in filters, which are ready to be used in applications. as shown in Table 13-9.
MVC框架提供了一些内建的过滤器,随时可以在应用程序中运用它们。如表13-9所示。
Filter 过滤器 |
Description 描述 |
---|---|
RequireHttps | Enforces the use of the HTTPS protocol for actions 强迫对动作使用HTTPS协议 |
OutputCache | Caches the output from an action method 缓存一个动作方法的输出 |
ValidateInput and ValidationAntiForgeryToken | Authorization filters related to security 与安全性有关的授权过滤器 |
AsyncTimeout NoAsyncTimeout |
Used with asynchronous controllers 使用异步控制器 |
ChildActionOnlyAttribute | An authorization filter that supports the Html.Action and Html.RenderAction helper methods 一个支持Html.Action和Html.RenderAction辅助方法的授权过滤器 |
Most of these are covered in other parts of the book. However, two filters—RequireHttps and OutputCache —don’t really belong elsewhere, so we are going to explain their use here.
以上大多数过滤器涉及本书的其它部分。然而,有两个过滤器 — RequireHttps和OutputCache — 并不真正属于其它内容,因此,我们打算在这里解释它们的使用。
The RequireHttps filter allows you to enforce the use of the HTTPS protocol for actions. It redirects the user’s browser to the same action, but using the https:// protocol prefix.
RequireHttps过滤器允许你对动作强制使用HTTPS(超文本安全传输协议 — 译者注)协议。它把用户的浏览器重定向到同一个动作,但使用https://协议前缀。
You can override the HandleNonHttpsRequest method to create custom behavior when an unsecured request is made. This filter applies to only GET requests. Form data values would be lost if a POST request were redirected in this way.
当形成一个不安全的请求时,你可以覆盖HandleNonHttpsRequest方法,以生成自定义行为。这个过滤器仅运用于GET请求。如果POST请求以这种方式重定向时,表单的数据值将被丢弃。
■ Note If you are having problems controlling the order of filter execution when using the RequireHttps filter, it is because this is an authorization filter and not an action filter. See the “Ordering Filter Execution” section earlier in the chapter for details on how different types of filter are ordered.
注:如果你在使用RequireHttps过滤器时出现控制过滤器执行顺序的问题,这是因为它是一个授权过滤器,而不是一个动作过滤器。参见本章前面的“过滤器执行顺序排序”小节关于过滤器不同类型的排序细节。
The OutputCache filter tells the MVC Framework to cache the output from an action method so that the same content can be reused to service subsequent requests for the same URL. Caching action output can offer a significant increase in performance, because most of the time-consuming activities required to process a request (such as querying a database) are avoided. Of course, the downside of caching is that you are limited to producing the exact same response to all requests, which isn’t suitable for all action methods.
OutputCache过滤器告诉MVC框架缓存一个动作方法的输出,以便同样的内容可以被用于对后继的相同的URL请求进行服务。缓存动作输出可以明显地改善性能,因为避免了对一个请求进行处理的大部分耗时的活动。当然,缓存的缺点是你受到这样的限制:对所有请求都产生完全相同的响应,这对所有动作方法是不合适的。
The OutputCache filter uses the output caching facility from the core ASP.NET platform, and you will recognize the configuration options if you have ever used output caching in a Web Forms application.
OutputCache过滤器使用ASP.NET平台内核的输出缓存工具,如果你曾经在Web表单应用程序中使用过输出缓存,你应该知道其配置选项。
The OutputCache filter can be used to control client-side caching by affecting the values sent in the Cache-Control header. Table 13-10 shows the parameters you can set for this filter.
通过影响在Cache-Control头部发送的值,OutputCache可以被用来控制客户端缓存。表13-10列出了你可以对这个过滤器设置的参数。
Parameter 参数 |
Type 类型 |
Description 描述 |
---|---|---|
Duration | int | Required—specifies how long (in seconds) the output remains cached. 必须的 — 指定维持输出缓存的时间(以秒计)。 |
VaryByParam | string (semicolon-separated list) (逗号分隔的列表) |
Tells ASP.NET to use a different cache entry for each combination of Request.QueryString and Request.Form values matching these names. The default value, none, means “don’t vary by query string or form values.” The other option is *, which means “vary by all query string and form values.” If unspecified, the default value of none is used. 告诉ASP.NET,为每个与这些名字匹配的Request.QueryString和Request.Form组合使用不同的缓存条目。默认值none意为“不随查询字串或表单值而变”。其它选项是*,意为“随所有查询字串和表单值而变”。如果不指定,则使用默认的none值。 |
VaryByHeader | string (semicolon-separated list) (逗号分隔的列表) |
Tells ASP.NET to use a different cache entry for each combination of values sent in these HTTP header names. 告诉ASP.NET,为每个以这些HTTP头的名字发送的组合值使用不同的缓存条目。 |
VaryByCustom | string | If specified, ASP.NET calls the GetVaryByCustomString method in Global.asax, passing this arbitrary string value as a parameter, so you can generate your own cache key. The special value browser is used to vary the cache by the browser’s name and major version data. 如果指定,ASP.NET调用Global.asax中的GetVaryByCustomString方法,把这个专用字符串值作为参数进行传递,因此你可以生成自己的缓存键值。通过浏览器的名字及其主版本数据,特定值的浏览器使用不同的缓存。 |
VaryByContentEncoding | string (semicolon-separated list) (逗号分隔的列表) |
Allows ASP.NET to create a separate cache entry for each content encoding (e.g., gzip and deflate) that may be requested by a browser. 允许ASP.NET为每个可能由浏览器请求的内容编码(如,gzip、deflate等)生成独立的缓存条目。 |
Location | OutputCacheLocation | Specifies where the output is to be cached. It takes the enumeration value Server (in the server’s memory only), Client (in the visitor’s browser only), Downstream (in the visitor’s browser or any intermediate HTTP-caching device, such as a proxy server), ServerAndClient (combination of Server and Client), Any (combination of Server and Downstream), or None (no caching). If not specified, it takes the default value of Any. 指定输出在什么地方缓存。它是一个枚举值,Server(只在服务器的内存中)、Client(只在客户端浏览器中)、DownStream(在客户端浏览器,或任何HTTP缓存的中间设备中,如一个proxy服务)、ServerAndClient(Server和Client的组合)、Any(Server和DownStream组合)、或None(不缓存)。如果不指定,默认值为Any。 |
NoStore | bool | If true, tells ASP.NET to send a Cache-Control: no-store header to the browser, instructing the browser not to cache the page for any longer than necessary to display it. This is used only to protect very sensitive data. 如果为true,告诉ASP.NET发送一个Cach-Control:no-store(不存储)头给浏览器,指定该浏览器缓存页面的时间不要长于显示它的时间。它只用于保护十分敏感的数据。 |
CacheProfile | string | If specified, instructs ASP.NET to take cache settings from a particular section named <outputCacheSettings> in Web.config. 如果指定,指示ASP.NET获取Web.config中名为< outputCacheSettings>的特定小节的缓存设置。 |
SqlDependency | string | If you specify a database/table name pair, the cached data will expire automatically when the underlying database data changes. This requires the ASP.NET SQL cache dependency feature, which can be quite complicated to set up. See http://msdn.microsoft.com/en-us/library/ms178604.aspx for further details. 如果你指定了一个“数据库/表”名字对,在当前数据库数据变化时,缓存数据将自动过期。这需要ASP.NET SQL的缓存依赖性特性,它的设置是比较复杂的。进一步细节请参阅http://msdn.microsoft.com/en-us/library/ms178604.aspx |
One of the nice features of the OutputCache filter is that you can apply it to child actions. A child action is invoked from within a view using the Html.Action helper method. This is a new feature in MVC 3 that allows you to be selective about which parts of a response are cached and which are generated dynamically. We discuss child actions in Chapter 15, but Listing 13-30 provides a simple demonstration.
OutputCache过滤器的一个很好的特性是,你可以把它运用于一个子动作。子动作是通过在视图中使用Html.Action辅助方法来调用的。这是MVC 3的一个新特性,它允许你在缓存的响应和动态生成的内容之间进行选择。我们在第15章讨论子动作,但清单13-30提供了一个简单的演示。
Listing 13-30. Caching the Output of a Child Action
清单13-30. 组织子动作的输出
public class ExampleController : Controller { public ActionResult Index() { Response.Write("Action method is running: " + DateTime.Now); return View(); } [OutputCache(Duration = 30)] public ActionResult ChildAction() { Response.Write("Child action method is running: " + DateTime.Now); return View(); } }
The controller in the listing defines two action methods:
清单中的控制器定义了两个动作方法:
Both action methods write the time that they were executed to the Response object.
两个动作方法都是把它们执行的时间写到Response对象。
Listing 13-31 shows the Index.cshtml view (which is associated with the Index action method).
清单13-31显示了Index.cshtml视图(它与Index动作方法相关联)。
Listing 13-31. A View That Calls a Cached Child Action
清单13-31. 调用缓存的子动作的视图
@{ ViewBag.Title = "Index"; } <h2>This is the main action view</h2> @Html.Action("ChildAction")
You can see that we call the ChildAction method at the end of the view. The view for the ChildAction method is shown in Listing 13-32.
你可以看到,我们在视图的末尾调用了ChildAction方法。用于ChildAction方法的视图如清单13-32所示。
Listing 13-32. The ChildAction.cshtml View
清单13-32. ChildAction.cshtml视图
@{ Layout = null; } <h4>This is the child action view</h4>
To test the effect of caching a child action, start the application and navigate to a URL that will invoke the Index action. The first time that you do this, you will see that the parent action and the child action both report the same time in the messages included in their response. If you reload the page (or navigate to the same URL using a different browser), you can see that the time reported by the parent action changes, but that the child action time stays the same. This tells us that we are seeing the cached output from the original invocation, as shown in Figure 13-8.
为了测试缓存一个子动作的效果,启动这个应用程序,并导航到调用Index动作的URL。第一次,你将看到父动作和子动作在它们的响应消息中都报告了同样的时间。如果你刷新这个页面(或用不同的浏览器导航到同样的URL),你可以看到父动作报告的时间变了,但子动作的时间保持不变。这告诉我们,我们所看到的是原先请求缓存的输出,如图13-8所示。
In this chapter, you have seen how to encapsulate logic that addresses cross-cutting concerns as filters. We showed you the different kinds of filters available and how to implement each of them. You saw how filters can be applied as attributes to controllers and action methods, and how they can be applied as global filters. Filters are a means of extending the logic that is applied when a request is processed, without needing to include that logic in the action method. This is a powerful and elegant feature.
本章中,你已经看到了如何把交叉关注的逻辑封装为过滤器。我们向你演示了各种可用的不同过滤器,以及如何实现它们。你也看到了可以把过滤器如何用于控制器和动作方法,以及如何把它们用作为全局过滤器。过滤器是在对请求进行处理时对动作逻辑进行扩展的手段,而不需要把这种逻辑包含在动作方法中。这是一种功能强大而雅致的特性。