翔如菲菲

其实天很蓝,阴云总会散;其实海不宽,此岸连彼岸.

导航

ASP.NET MVC 过滤器 PART2

以下内容摘自:http://www.cnblogs.com/r01cn/archive/2011/12/17/2291217.html

感谢作者的翻译,这里只是译文。原书名:Pro ASP.NET MVC 3 Framework

 

第十三章 过滤器 PART2


 

使用其它过滤器特性(Features

前面的例子已经给出了用过滤器进行有效工作所需要的所有信息。伴随这些你已经学到的特性,还有一些其它有趣但不常用的特性。在以下小节中,我们将向你演示一些高级的MVC框架过滤能力。

无特性过滤器(Filtering Without Attributes

使用过滤器的常规办法是生成并使用一些特性(attributes),正如我们在前几小节所演示的那样。然而还有使用性质的另一种办法。Controller类实现了IAuthorizationFilterIActionFilterIResultFilter、和IExceptionFilter接口。它也提供了你已经看到过的每一个OnXXX方法的虚拟实现,如OnAuthorizationOnException。清单13-22演示了一个测量自身性能的控制器。

清单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));
        }
    }
}

当你要生成一个基类,以派生你项目中的多个控制器时,这项技术是最有用的。整个过滤点是,在一个可重用位置,放置整个应用程序所需要的代码。于是,在一个非派生的控制器中使用这些方法就没太大意义了。

对于我们的项目,我们更喜欢使用性质。我们喜欢控制器逻辑与过滤逻辑器之间的这种分离。如果你正在寻找一种把一个过滤器运用于你的所有控制器的办法,进一步阅读,以看看如何用全局过滤器做这种事。

使用全局过滤器

全局过滤器被用于应用程序的所有动作方法。我们通过Global.asax中的RegisterGlobalFilters方法,把一个规则(regular)过滤器做成一个全局过滤器。

清单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());
    }
    ...
}

RegisterGlobalFilters方法是通过Application_Start方法调用的,这确保过滤在MVC应用程序启动时便被注册。RegisterGlobalFilters方法及其在Application_Start中的调用是在Visual Studio生成MVC项目时建立的。RegisterGlobalFilters方法的参数是一个GlobalFilterCollection。你要用Add方法来注册全局过滤器,像这样:

filters.Add(new ProfileAllAttribute());

注意你必须用ProfileAllAttribute来引用这个过滤器,而不是ProfileAll。一旦你已经像这样注册了一个过滤器,它将作用于每个动作方法上。

注意:Visual Studio生成的默认RegisterGlobalFilters方法中的第一条语句建立了默认的MVC异常处理策略。当一个未处理异常发生时,将渲染/Views/Shared/Error.cshtml视图。这条异常处理策略在开发期间是默认失效的。参见本章稍后的生成异常过滤器小节如何启用它的说明。

对一个全局过滤器指定属性值的格式与规则的类相同,如清单13-24所示。

清单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"
    });
}

过滤器执行顺序排序

我们已经解释了过滤器是通过类型执行的。其顺序是授权过滤器、动作过滤器、然后是结果过滤器。如果有未处理异常,框架在任一阶段都可以执行异常过滤器。然而,在每个类型种类中,你可以控制个别过滤器的顺序。

清单13-25显示了一个简单的动作过滤器,我们将用它来演示过滤器执行的顺序。

清单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 { getset; }
        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));
        }
    }
}

OnXXX方法被调用时,这个过滤器把一条消息写到响应。我们可以用Message参数指定消息内容。我们可以把这个过滤器的多个实例运用于一个动作方法,如清单13-26所示(注意,在AttributeUsage性质中,我们把AllowMultiple属性设置为true)。

清单13-26. 把多个过滤器运用于一个动作

...
[SimpleMessage(Message="A")]
[SimpleMessage(Message="B")]
public ActionResult Index() {
    Response.Write("Action method is running");
    return View();
}
...

我们用不同的消息生成了两个过滤器:第一个消息为A,另一个消息为B。我们可以使用两个不同的过滤器,但这种办法允许我们生成一个简单的例子。当你运行这个应用程序,并导航到调用这个动作方法的URL时,你将看到如图13-5所示的输出。

图13-5

13-5. 同一个动作方法上的多个过滤器

当我们运行这个例子时,MVC框架在B过滤器之前执行A过滤器,但它也可以按另一种方式。MVC框架不会保证任何特定的顺序或执行。大多数情况下,顺序无关紧要。但当有必要时,你可以使用Order属性,如清单13-27所示。

清单13-27. 在一个过滤器中使用Order属性

...
[SimpleMessage(Message="A", Order=2)]
[SimpleMessage(Message="B", Order=1)]
public ActionResult Index() {
    Response.Write("Action method is running");
    return View();
}
...

Order参数取一个int值,MVC框架以升序执行这些过滤器。在这个清单中,我们已经给了B过滤器一个最小值,因此,框架首先执行它,如图13-6所示。

图13-6

13-6. 指定过滤器执行的顺序

注意: OnActionExecuting方法是以我们指定的顺序执行的,但OnActionExecuted方法是以反序执行的。在动作方法之前MVC框架执行过滤器时会建立一个过滤器堆栈,并在之后释放这个堆栈。这种释放行为是不可改变的。

如果我们不指定Order属性的值,它被赋为默认的-1值。其含义为,如果你把有Order值和没有Order值的过滤器混用在一起,那些没有值的过滤将首先执行,因为它们具有最低的Order值。

如果同类型的多个过滤器具有相同的Order值(如1),那么MVC框架基于过滤器被运用的位置来执行。全局过滤器首先被执行,然后是运用于控制器类的过滤器,再然后是运用于动作方法的过滤器。

注意:两个其它种类是FirstLastFirst种类的过滤器在所有其它种类之前执行,而Last种类的过滤在所有其它种类之后执行。

作为一个演示,我们在Global.asax中生成了一个全局过滤器,如清单13-28所示。

清单13-28. 定义一个带有Order值的全局过滤器

public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
    filters.Add(new HandleErrorAttribute());
    filters.Add(new SimpleMessageAttribute() { Message = "Global", Order = 1 });
}

我们用标准的属性初始化语法指定了Order值。我们也定义了控制器类和动作方法上的过滤器,如清单13-29所示。所有这三个过滤器具有同样的Order值。

清单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();
    }
}
...

当你运行这个应用程序并导航到该动作方法时,你将看到如图13-7所示的输出。

图13-7

13-7. 过滤器执行的顺序

注意:异常过滤器的执行顺序是倒过来的。如果异常过滤器用同样的Order值被运用于控制器和动作方法,动作方法上的过滤器首先被执行。带有同样Order值的全局过滤器最后被执行。

使用内建的过滤器

MVC框架提供了一些内建的过滤器,随时可以在应用程序中运用它们。如表13-9所示。

Table 13-9. Built-In Filters
13-9. 内建过滤器

Filter
过滤器

Description
描述

RequireHttps

强迫对动作使用HTTPS协议

OutputCache

缓存一个动作方法的输出

ValidateInput and ValidationAntiForgeryToken

与安全性有关的授权过滤器

AsyncTimeout
NoAsyncTimeout

使用异步控制器

ChildActionOnlyAttribute

一个支持Html.ActionHtml.RenderAction辅助方法的授权过滤器

以上大多数过滤器涉及本书的其它部分。然而,有两个过滤器 — RequireHttpsOutputCache — 并不真正属于其它内容,因此,我们打算在这里解释它们的使用。

使用RequireHttps过滤器

RequireHttps过滤器允许你对动作强制使用HTTPS(超文本安全传输协议译者注)协议。它把用户的浏览器重定向到同一个动作,但使用https://协议前缀。

当发送一个不安全的请求时,你可以覆盖(override)HandleNonHttpsRequest方法来构建自定义行为。这个过滤器仅运用于GET请求。如果POST请求以这种方式重定向时,表单的数据值将被丢失。

注意:如果你在使用RequireHttps过滤器时出现控制过滤器执行顺序的问题,这是因为它是一个授权过滤器,而不是一个动作过滤器。参见本章前面的过滤器执行顺序排序小节关于过滤器不同类型的排序细节。

使用OutputCache过滤器

OutputCache过滤器告诉MVC框架缓存一个动作方法的输出,以便同样的内容可以被用于对后继的相同的URL请求进行服务。缓存动作输出可以明显地改善性能,因为对一个请求进行处理的大部分耗时的操作被避免(比如一个数据库查询)。当然,缓存的缺点是你受到这样的限制:对所有请求都产生完全相同的响应,这并不是适用所有的动作方法的。

OutputCache过滤器使用ASP.NET平台内核的输出缓存工具,如果你曾经在Web表单应用程序中使用过输出缓存,你应该知道其配置选项。

通过影响在Cache-Control头部发送的值,OutputCache可以被用来控制客户端缓存。表13-10列出了你可以对这个过滤器设置的参数。

13-10. OutputCache过滤器的参数

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.QueryStringRequest.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为每个可能由浏览器请求的内容编码(如,gzipdeflate等)生成独立的缓存条目。

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服务)、ServerAndClientServerClient的组合)、AnyServerDownStream组合)、或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-Controlno-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

OutputCache过滤器的一个很好的特性(features)是,你可以把它运用于一个子动作(child actions)。子动作是通过在视图中使用Html.Action辅助方法来调用的。这是MVC 3的一个新特性features),它允许你在缓存的响应和动态生成的内容之间进行选择。我们在第15章讨论子动作,但清单13-30提供了一个简单的演示。

清单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();
    }
}

清单中的控制器定义了两个动作方法:

·         ChildAction方法运用了OutputCache过滤器。这是我们将在视图中调用的动作方法。

·         Index方法将是父动作。

两个动作方法都是把它们执行的时间写到Response对象。

清单13-31显示了Index.cshtml视图(它与Index动作方法相关联)。

清单13-31. 调用缓存的子动作的视图

@{
ViewBag.Title = "Index";
}
<h2>This is the main action view</h2>
@Html.Action("ChildAction")

你可以看到,我们在视图的末尾调用了ChildAction方法。用于ChildAction方法的视图如清单13-32所示。

清单13-32. ChildAction.cshtml视图

@{
Layout = null;
}
<h4>This is the child action view</h4>

为了测试缓存一个子动作的影响,启动这个应用程序,并导航到调用Index动作的URL。第一次,你将看到父动作和子动作在它们的响应消息中都报告了同样的时间。如果你刷新这个页面(或用不同的浏览器导航到同样的URL),你可以看到父动作报告的时间变了,但子动作的时间保持不变。这告诉我们,我们所看到的是原先请求缓存的输出,如图13-8所示。

图13-8

13-8. 缓存子动作的效果

小结

本章中,你已经看到了如何把交叉关注的逻辑封装为过滤器。我们向你演示了各种可用的不同过滤器,以及如何实现它们。你也看到了可以把过滤器如何用于控制器和动作方法,以及如何把它们用作为全局过滤器。过滤器是在对请求进行处理时对动作逻辑进行扩展的手段,而不需要把这种逻辑包含在动作方法中。这是一种功能强大而雅致的特性。


 

posted on 2012-03-16 17:30  翔如飞飞  阅读(2105)  评论(0编辑  收藏  举报