Filter在请求管道注入额外的逻辑。他们提供简单优雅的方法实现横切点关注。这个术语指的是在穿越整个应用程序中使用,而且不适合使用在任何单独的地方,所以这会打破关注模式的分离。经典的横切点关注的例子比如日志,认证,缓存。
Filter也被认为是横切点关注,因为这个术语在其他web application框架,包括Ruby也是实现同样功能。然而MVC Framework Filter完全不同于ASP.NET 平台的Request.Filter和Response.Filter对象。Request.Filter和Response.Filter对象在request和response流上执行转换。你可以在MVC application中使用Request.Filter 和Response.Filter,但是,通常当ASP.NET MVC程序员谈到filter时,指的是接下要谈的类型,在本文,我们会展示MVC Framework提供的filter不同的之类,如何创建使用filter,如何控制他们的执行。
使用Filter
我们想要让action方法只能由认证过的用户使用。我们可以在每个action方法检查请求的认证状态。如下面的代码,显式的在action方法检测认证信息。
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
}
}
可以看到这方法中有很多重复代码,所以我们决定使用filter。如下代码:
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
}
}
Filter是.NET特性,它是加在request处理管道上的额外步骤。上例中,我们使用Authorize filter,和之前那种重复检查的方法,有相同的效果。
特性是从System.Attribute继承的特殊的.NET类。你可以把它们附加到其它代码上,包括类,方法,属性,字段。目的就是在编译的代码中植入额外的信息,在运行事情你就可以读回它们。
C#中,特性使用的时候要加上方括号,你能通过命名参数语法组合它们的公共属性(比如,[MyAttribute(SomeProperty=value)])。在C#编译器命名约定中,如果特性类以单词Attribute结尾,你可以忽略这部分(比如,应用AuthorizeAttribute时,可以直接写[Authorize])
Filter的四种基本类型
MVC Framework 支持四种不同的filter类型。每个都允许你在请求管道的不同点处引入自己的逻辑,下表列出了这四种类型。
在MVC Framework调用action之前,它会检查方法定义,查看实现了上述表格中的接口的特性是否存在。如果存在,那么在request管道的恰当处,有这些接口定义的方法就会被调用。framework包括了实现filter接口的默认的特性类。
注意ActionFilterAttribute类实现了IActionFilter和IResultFilter 接口。此类是抽象类,迫使你提供一个实现。其它类,AuthorizeAttribute和HandleErrorAttribute包含了有用的功能,可以不需要继承直接使用。
在controller和action方法上应用Filter
Filter可以应用在单个action方法上,或者整个controller上,在上面的例子,我们把Authorize filter应用于 AdminController类,这和应用在每一个controller中的action方法有同样的效果,如下:
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
}
}
你可以应用多个filter,无论他们匹配到controller或者单个action方法。下面展示3种不同的使用filter的方法。
[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
}
}
注意,如果你为controller自定义了基类,那么任何应用在基类上的filter也会应用在子类上。
使用Authorization Filter
Authorization filter 是最先运行的filter,它在其他filter和action方法之前被调用,就如它的命名,这些filter强迫你的认证策略,确保action方法只能被认证过的用户调用。Authorization filter 实现IAuthorizationFilter 接口,如下代码:
namespace System.Web.Mvc {
public interface IAuthorizationFilter {
void OnAuthorization(AuthorizationContext filterContext);
}
}
MVC Framework从浏览器收到一个请求,路由系统已经处理了请求的URL,抽取了controller和action的名字。一个controller的实例创建,但是在action方法调用之前,MVC Framework检查是否有任何authorization filter应用在action方法。如果有,那么这个由IAuthorizationFilter接口定义的方法——OnAuthorization方法会被调用,如果authentication filter同意了请求,那么处理管道中下一步才会开始,否则请求就会拒绝。
创建Authentication Filter
理解authentication filter工作原理的最好方法就是创建一个。下面的代码是一段简单的示例。它仅仅检查用户是否登录了。(Request.IsAuthenticated是ture),同时username要出现在允许用户列表中。
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);
}
}
}
这个创建了一个authorization filter最简单的方法是继承AuthorizeAttribute类,重写AuthorizeCore方法。这确保了从AuthorizeAttribute内建功能中获益。
警告:编写安全保障代码是危险行为
我们写了一个自定义authorization filter的例子,因为我们认为它可以展示filter的运行方式,但是对于编写自己安全代码,我们要小心。这种技能很少有人会去使用,这会让我们的应用程序安全出现未考虑到的漏洞。
我们尽可能的使用那些广泛使用,并且被实践过的安全代码,在这种情况下,MVC Framework提供了一个功能全面的authorization filter,可以通过继承它来实现自定义认证策略。我们尽可能使用它,并且我们建议你也这样做,这样,当你的安全数据在网上泄露了,你至少可以骂微软。
我们的filter构造方法带有一组名字的数组,这数组内容是被认证的用户的信息。我们的filter包含一个PerformAuthenticationCheck方法,确保请求是认证的,用户也是在已认证的集合中。这个类中有趣的部分是OnAuthorization方法的实现。传给这个方法的参数是AuthorizationContext 类的一个实例,AuthorizationContext是继承于ControllerContext的。ControllerContext 能使我们访问一些有用的对象,不仅仅是HttpContextBase(通过HttpContextBase,我们可以访问request的细节)。ControllerContext的属性在下表中列出,所以这些用于不同的action filer的context 对象都继承自这个类,所以你可以始终如一的使用这些属性。
当我们想检查是否请求被认证了,我们就如下的写法:
... filterContext.HttpContext.Request.IsAuthenticated ...
AuthorizationContext定义了2个额外的属性,如下表:
第一个属性ActionDescriptor,返回一个System.Web.Mvc.ActionDescriptor实例,你可以通过这个属性得到你应用了filter的action的信息。第二个属性,Result,是让你filter工作的关键。如果你要对一个action方法认证请求,那么你在OnAuthorization方法上不用做什么,MVC Framework认为这个请求应该处理。但是,如果你设置context对象的Result属性为一个ActionResult对象,MVC
Framework将使用使用它作为整个请求的结果。管道中剩下的步骤则不会被运行了,你提供的result会运行输出给用户。
在我们的例子中,如果我们的PerformAuthenticationCheck返回false(表面整个请求没有认证通过,或者用户不是认证用户),然后我们创建一个HttpUnauthorizedResultaction result,然后制定context的 Result 属性,如下:
filterContext.Result = new HttpUnauthorizedResult();
要使用我们的自定义authorization filter。我们只需要将特性放在action方法上,如下代码:
...
[CustomAuth("adam", "steve", "bob")]
public ActionResult Index() {
return View();
}
...
使用内建的认证Filter
MVCFramework包含了一个实用的认证filter,AuthorizeAttribute。我们可以使用两个公共属性指定认证策略,如下:
...
[Authorize(Users="adam, steve, bob", Roles="admin")]
public ActionResult Index() {
return View();
}
...
上例中,我们指定了用户和权限。这意味着只有当用户和权限同时满足的情况下才认证系统才会通过。这里还有一个隐式的条件,就是这个请求必须是被认证的。如果我们不指定任何用户和权限,那么任何用户都可使用此action方法。
对大多数应用程序来说,AuthorizeAttribute提供的认证策略已经足够了。如果你需要实现一些特殊的功能,你可以继承这个类。直接实现IAuthorizationFilter接口风险比较小,但是你应该仔细思考,你的策虑所带来的影响,而且要彻底的测试。
AuthorizeAttribute类提供2个不同方法。
AuthorizeCore方法,由AuthorizeAttribute的OnAuthorization调用,实现认证检查。
HandleUnauthorizedRequest 方法,当认证检查失败的时候调用。
实现自定义认证失败策略
默认的处理认证失败的策略是定向到用户登录页面。我们不总希望如此。比如,如果使用ajax,发送重定向肯能会让用户在页面中间看到一个登录页面。幸运的是,我们可以重写AuthorizeAttribute类的HandleUnauthorizedRequest方法来创建一个自定义策略。
下面的例子实现了自定义认证失败的策略:
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);
}
}
}
}
当Filter发现是ajax请求,就会按照JSON数据的方式响应。正常的请求还是由基类中默认的策略处理,ajax客户端必须也要写响应的代码响应。
使用Exception Filters
Exception filters只有在运行action方法抛出异常的时候才会运行。这个异常可能从以下几个地方抛出:
其他类型的filter(认证,action,result filter)
action方法自身
当action结果运行后。
创建Exception Filter
Exception filters必须实现IExceptionFilter接口,接口如下:
namespace System.Web.Mvc {
public interface IExceptionFilter {
void OnException(ExceptionContext filterContext);
}
}
OnException方法在抛出异常的时候调用。参数是ExceptionContext。这个类和认证filter的参数类似,也是继承自ControllerContext类。所以你可以获得request信息,并且定义一下额外的指定的filter属性,这些属性如下表:
exception filter的Result属性是告诉MVC Framework要做什么。两个主要的用处是记录异常和显示恰当的消息给用户。下例显示了一个演示,当特殊的未处理异常发生时,就会跳转到一个指定的错误页面
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;
}
}
}
}
filter回应NullReferenceException实例,而且仅当没有其他exception filter指示处理了异常才发生。我们把用户重定向到一个错误页面,如下代码
...
[MyException]
public ActionResult Index() {
...
如果Index action方法抛出异常,是一个NullReferenceException,而且没有其他 exception filter处理这一个异常,那么我们的filter将重定向到SpecialErrorPage.html。
使用内建的Exception Filter
HandleErrorAttribute是 IExceptionFilter 接口一个实现,它使得创建exception filter更简单。你可以通过下面的属性指定异常和view和layout的名字,从而指定一个异常,如下所示:
当一个未处理的类型异常出现是,filter会把HTTP result code设置为500,意思是服务器错误,同时显示用户通过View属性指定的view(通过Master使用layout),下面的例子展示了如何使用HandleErrorAttribute filter
[HandleError(ExceptionType=typeof(NullReferenceException), View="SpecialError")]
public ActionResult Index() {
...
上述例子,我们对NullReferenceException类型感兴趣,希望出现异常时显示SpecialErrorView
注意,The HandleErrorAttribute filter 仅在Web.config中自定义error是enabled的情况下工作。比如,在
当呈现一个view时,HandleErrorAttribute filter传递一个HandleErrorInfo view model对象,你可以在消息中包含异常的细节,展示给用户。如下例子,当现实错误信息时,使用View Model对象:
@Model HandleErrorInfo
@{
ViewBag.Title = "Sorry, there was a problem!";
}
There was a @Model.Exception.GetType().Name
while rendering @Model.ControllerName's
@Model.ActionName action.
The exception message is: <@Model.Exception.Message>
Stack trace:
@Model.Exception.StackTrace
效果图如下:
使用Action和Result Filter
Action和Result Filter是常用的filter,可以用于任何目的。它们都遵循一个公共模式。内建的类创建filter,IActionFilter的类型,实现全部的接口。接口如下:
namespace System.Web.Mvc {
public interface IActionFilter {
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
}
此接口定义了2个方法,MVC Framework 在action方法调用之前,先调用OnActionExecutin方法。在action方法调用之后,调用OnActionExecuted方法。
实现OnActionExecuting方法
The OnActionExecuting 方法在action方法在action方法调用之前先调用。你可以通过这个来检查请求,选择取消请求,修改请求,或者启动另一个活动跨越action的调用。此方法的参数是一个ActionExecutingContext ,是ControllerContext的子类,有2个同样的属性,和你在其他context对象中看到的一样。详细描述如下表:
你可以有选择的通过设置parameter的result属性来取消请求,如下演示代码:
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-Not Found响应给用户。
注意,如上例所示,你不需要将IActionFilter接口中的方法都实现,如果你不需要加任何逻辑,就置空。消息不要抛出NotImplementedException异常,因为如果你这样做,exception filter就会被执行。
实现OnActionExecuted方法
你也可以使用filter去执行一些任务,跨越执行action方法。下面是一个简单的例子计算action方法执行的时间开销。
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));
}
}
}
}
此例中,我们使用OnActionExecuting方法,启动一个计数器。当action方法完成后,OnActionExecuted方法调用。然后我们停止计时器,写出响应,输出所花的时间。结果图如下:
传递给OnActionExecuted方法的参数是一个ActionExecutedContext对象。此类定义了一些额外的属性,如下表。Exception属性返回任何action方法抛出的异常,ExceptionHandled属性指示了是否有其他filter处理了它。
属性Canceled,如果其他filter cancel了一个请求(通过设置result属性的值),那么返回true。但我们的OnActionExecuted方法仍然调用,只有这样我们才能是否使用的资源。
实现Result Filter
Action filters和result filters有一些共同点. Result filters对于action results 就好比action filters
对于action methods. Result filters 实现IResultFilter 接口, 接口代码如下:
namespace System.Web.Mvc {
public interface IResultFilter {
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
}
之前,我们说过action方法如何返回action result。这运行我们分离action的意图和执行。当我们应用一个result filter到action方法上,一旦action方法返回action result,OnResultExecuting方法就被调用,但是在action result执行之前 。OnResultExecuted方法在action result执行后调用。这些方法的参数分别是ResultExecutingContext 和ResultExecutedContext 对象,它们和action filter的那部分非常相似。它们有相同的属性,相同的效果。下例代码展示了一个简单的 result filter的例子。
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));
}
}
}
这个filter计算了执行result所需要的时间。把这个filter附加的action方法上。
...
[ProfileResult]
public ActionResult Index() {
return View();
}
...
现在我们转到action方法,输出的内容如下。
注意,从filter中获得的性能信息出现在页面底部。这是因为我们在 action result 执行后——也就是view已经呈现了,才写下我们的消息。我们先前的filte在action result执行之前写到response的,因此它出现在page的顶部。
使用内建的Action和Result Filter类
MVC Framework 包含了一个内建的类,可以创建action 和result filters.
但是不像内建的authorization 和exception filter, 它不提供任何有用的细节. 这个类是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) {
}
}
}
使用这个类的好处就是你不需要实现你用不到的方法。如下例子,展示了继承ActionFilterAttribute的filter,使用action方法和action result执行,合并了我们的性能测量。
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));
}
}
}
ActionFilterAttribute 类实现了IActionFilter 和IResultFilter 接口,意味着MVC Framework把这个继承类看作两种filter类型, 即使并不是所有的方法都被重写。如果我们在上述的例子应用这个filter到action方法,我们的输出就如下: