[ASP.NET
MVC Mavericks Road] 11 - 필터
필터(filter)는 AOP(Aspect-Oriented 프로그래밍) 설계를 기반으로 하며, MVC 프레임워크 Inject의 고객을 처리하는 기능입니다. 매우 간단하고 아름다운 방식으로 교차 문제를 구현하기 위해 측면 요청에 추가 논리를 추가합니다. 크로스커팅 문제는 프로그램의 여러 모듈 또는 심지어 모든 모듈에 걸쳐 있는 기능을 의미합니다. 전통적인 크로스커팅 문제에는 로깅, 캐시 처리, 예외 처리 및 권한 확인이 포함됩니다. 이 문서에서는 MVC 프레임워크에서 지원하는 다양한 유형의 필터 생성 및 사용과 실행을 제어하는 방법을 소개합니다.
이 글의 내용
네 가지 기본 필터 개요
MVC 프레임워크에서 지원하는 필터는 네 가지 범주로 분류할 수 있으며, 각 범주는 요청을 처리할 때 서로 다른 시점에 추가 논리 처리를 도입할 수 있습니다. 이 네 가지 유형의 Filter는 다음과 같습니다.
MVC 프레임워크는 acion을 호출하기 전에 먼저 위 표의 인터페이스 기능이 구현되어 있는지 여부를 결정합니다. , 요청 파이프라인의 적절한 지점이 속성에 정의된 메서드를 호출합니다.
MVC 프레임워크는 이러한 유형의 필터 인터페이스에 대한 기본 속성 클래스를 구현합니다. 위 표에 표시된 것처럼 ActionFilterAttribute 클래스는 IActionFilter 및 IResultFilter라는 두 가지 인터페이스를 구현합니다. 이 클래스는 추상 클래스이므로 구현해야 합니다. 다른 두 특성 클래스인 AuthorizeAttribute 및 HandleErrorAttribute는 이미 직접 사용할 수 있는 몇 가지 유용한 메서드를 제공합니다.
필터는 단일 ation 방식 또는 컨트롤러 전체에 적용할 수 있으며, acion 및 컨트롤러에는 여러 개의 필터를 적용할 수 있습니다. 아래와 같이:
[Authorize(Roles="trader")] // 对所有action有效 public class ExampleController : Controller { [ShowMessage] // 对当前ation有效 [OutputCache(Duration=60)] // 对当前ation有效 public ActionResult Index() { // ... } }
사용자 정의 컨트롤러 기본 클래스의 경우 기본 클래스에 적용된 필터는 기본 클래스에서 상속되는 모든 하위 클래스에도 적용됩니다.
인증 필터
인증 필터는 작업 방법 및 기타 유형의 필터보다 먼저 실행됩니다. 그 역할은 권한 정책을 시행하고 승인된 사용자만 작업 메서드를 호출할 수 있도록 하는 것입니다. Authorization Filter가 구현하는 인터페이스는 다음과 같습니다.
namespace System.Web.Mvc { public interface IAuthorizationFilter { void OnAuthorization(AuthorizationContext filterContext); } }
Custom Authorization Filter
IAuthorizationFilter 인터페이스를 직접 구현하여 자체 보안 인증 논리를 만들 수 있지만 일반적으로 필요하지는 않습니다. 권장되지 않습니다. 보안 인증 정책을 사용자 정의하려는 경우 더 안전한 방법은 기본 AuthorizeAttribute 클래스를 상속하는 것입니다.
AuthorizeAttribute 클래스를 상속하여 사용자 정의 인증 필터를 시연하겠습니다. 새로운 빈 MVC 애플리케이션을 생성하고 평소대로 인프라 폴더를 추가한 다음 CustomAuthAttribute.cs 클래스 파일을 추가합니다. 코드는 다음과 같습니다.
namespace MvcApplication1.Infrastructure { public class CustomAuthAttribute : AuthorizeAttribute { private bool localAllowed; public CustomAuthAttribute(bool allowedParam) { localAllowed = allowedParam; } protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext.Request.IsLocal) { return localAllowed; } else { return true; } } } }
이 간단한 필터는 AuthorizeCore 메서드를 재정의하여 다음을 허용합니다. 필터를 적용할 때 생성자를 통해 로컬 요청을 허용할지 여부를 지정할 수 있습니다. AuthorizeAttribte 클래스는 내장된 많은 기능을 구현하는 데 도움이 되며 AuthorizeCore 메서드에만 집중하고 이 메서드에서 권한 인증 논리를 구현하면 됩니다.
이 필터의 기능을 보여주기 위해 Home이라는 새 컨트롤러를 만든 다음 이 필터를 Index 작업 메서드에 적용합니다. 이 작업을 로컬 액세스로부터 보호하기 위해 매개 변수는 다음과 같이 false로 설정됩니다.
public class HomeController : Controller { [CustomAuth(false)] public string Index() { return "This is the Index action on the Home controller"; } }
프로그램을 실행하고 시스템에서 생성된 기본 라우팅 값에 따라 /Home/Index를 요청합니다.
AuthorizeAttribute 클래스를 기본 클래스로 사용하여 간단한 필터를 사용자 정의했습니다. 그렇다면 AuthorizeAttribute 클래스 자체의 유용한 기능은 무엇입니까?
내장된 인증 필터 사용
当我们直接使用 AuthorizeAttribute 类作为Filter时,可以通过两个属性来指定我们的权限策略。这两个属性及说明如下:
Users属性,string类型,指定允许访问action方法的用户名,多个用户名用逗号隔开。
Roles属性,string类型,用逗号分隔的角色名,访问action方法的用户必须属于这些角色之一。
使用如下:
public class HomeController : Controller { [Authorize(Users = "jim, steve, jack", Roles = "admin")] public string Index() { return "This is the Index action on the Home controller"; } }
这里我们为Index方法应用了Authorize特性,并同时指定了能访问该方法的用户和角色。要访问Index action,必须两者都满足条件,即用户名必须是 jim, steve, jack 中的一个,而且必须属性 admin 角色。
另外,如果不指定任何用户名和角色名(即 [Authorize] ),那么只要是登录用户都能访问该action方法。
你可以通过创建一个Internet模板的应用程序来看一下效果,这里就不演示了。
对于大部分应用程序,AuthorizeAttribute 特性类提供的权限策略是足够用的。如果你有特殊的需求,则可以通过继承AuthorizeAttribute 类来满足。
Exception Filter
Exception Filter,在下面三种来源抛出未处理的异常时运行:
另外一种Filter(如Authorization、Action或Result等Filter)。
Action方法本身。
Action方法执行完成(即处理ActionResult的时候)。
Exception Filter必须实现 IExceptionFilter 接口,该接口的定义如下:
namespace System.Web.Mvc { public interface IExceptionFilter { void OnException(ExceptionContext filterContext); } }
ExceptionContext 常用属性说明
在 IExceptionFilter 的接口定义中,唯一的 OnException 方法在未处理的异常引发时执行,其中参数的类型:ExceptionContext,它继承自 ControllerContext 类,ControllerContext 包含如下常用的属性:
Controller,返回当前请求的controller对象。
HttpContext,提供请求和响应的详细信息。
IsChildAction,如果是子action则返回true(稍后将简单介绍子action)。
RequestContext,提供请求上下文信息。
RouteData,当前请求的路由实例信息。
作为继承 ControllerContext 类的子类,ExceptionContext 类还提供了以下对处理异常的常用属性:
ActionDescriptor,提供action方法的详细信息。
Result,是一个 ActionResult 类型,通过把这个属性值设为非空可以让某个Filter的执行取消。
Exception,未处理异常信息。
ExceptionHandled,如果另外一个Filter把这个异常标记为已处理则返回true。
一个Exception Filter可以通过把 ExceptionHandled 属性设置为true来标注该异常已被处理过,这个属性一般在某个action方法上应用了多个Exception Filter时会用到。ExceptionHandled 属性设置为true后,就可以通过该属性的值来判断其它应用在同一个action方法Exception Filter是否已经处理了这个异常,以免同一个异常在不同的Filter中重复被处理。
示例演示
在 Infrastructure 文件夹下添加一个 RangeExceptionAttribute.cs 类文件,代码如下:
public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter { public void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) { filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html"); filterContext.ExceptionHandled = true; } } }
这个Exception Filter通过重定向到Content目录下的一个静态html文件来显示友好的 ArgumentOutOfRangeException 异常信息。我们定义的 RangeExceptionAttribute 类继承了FilterAttribute类,并且实现了IException接口。作为一个MVC Filter,它的类必须实现IMvcFilter接口,你可以直接实现这个接口,但更简单的方法是继承 FilterAttribute 基类,该基类实现了一些必要的接口并提供了一些有用的基本特性,比如按照默认的顺序来处理Filter。
在Content文件夹下面添加一个名为RangeErrorPage.html的文件用来显示友好的错误信息。如下所示:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Range Error</title> </head> <body> <h2>Sorry</h2> <span>One of the arguments was out of the expected range.</span> </body> </html>
在HomeController中添加一个值越限时抛出异常的action,如下所示:
public class HomeController : Controller { [RangeException] public string RangeTest(int id) { if (id > 100) { return String.Format("The id value is: {0}", id); } else { throw new ArgumentOutOfRangeException("id", id, ""); } } }
当对RangeTest应用自定义的的Exception Filter时,运行程序URL请求为 /Home/RangeTest/50,程序抛出异常后将重定向到RangeErrorPage.html页面:
由于静态的html文件是和后台脱离的,所以实际项目中更多的是用一个View来呈现友好的错误信息,以便很好的对它进行一些动态的控制。如下面把示例改动一下,RangeExceptionAttribute 类修改如下:
public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter { public void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) { int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue); filterContext.Result = new ViewResult { ViewName = "RangeError", ViewData = new ViewDataDictionary<int>(val) }; filterContext.ExceptionHandled = true; } } }
我们创建一个ViewResult对象,指定了发生异常时要重定向的View名称和传递的model对象。然后我们在Views/Shared文件夹下添加一个RangeError.cshtml文件,代码如下:
@model int <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Range Error</title> </head> <body> <h2>Sorry</h2> <span>The value @Model was out of the expected range.</span> <div> @Html.ActionLink("Change value and try again", "Index") </div> </body> </html>
运行结果如下:
禁用异常跟踪
很多时候异常是不可预料的,在每个Action方法或Controller上应用Exception Filter是不现实的。而且如果异常出现在View中也无法应用Filter。如RangeError.cshtml这个View加入下面代码:
@model int @{ var count = 0; var number = Model / count; } ...
运行程序后,将会显示如下信息:
显然程序发布后不应该显示这些信息给用户看。我们可以通过配置Web.config让应用程序不管在何时何地引发了异常都可以显示统一的友好错误信息。在Web.config文件中的
<system.web> ... <customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/> </system.web>
这个配置只对远程访问有效,本地运行站点依然会显示跟踪信息。
使用内置的 Exceptin Filter
通过上面的演示,我们理解了Exceptin Filter在MVC背后是如何运行的。但我们并不会经常去创建自己的Exceptin Filter,因为微软在MVC框架中内置的 HandleErrorAttribute(实现了IExceptionFilter接口) 已经足够我们平时使用。它包含ExceptionType、View和Master三个属性。当ExceptionType属性指定类型的异常被引发时,这个Filter将用View属性指定的View(使用默认的Layout或Mast属性指定的Layout)来呈现一个页面。如下面代码所示:
... [HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")] public string RangeTest(int id) { if (id > 100) { return String.Format("The id value is: {0}", id); } else { throw new ArgumentOutOfRangeException("id", id, ""); } } ...
使用内置的HandleErrorAttribute,将异常信息呈现到View时,这个特性同时会传递一个HandleErrorInfo对象作为View的model。HandleErrorInfo类包含ActionName、ControllerName和Exception属性,如下面的 RangeError.cshtml 使用这个model来呈现信息:
@model HandleErrorInfo @{ ViewBag.Title = "Sorry, there was a problem!"; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Range Error</title> </head> <body> <h2>Sorry</h2> <span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue) was out of the expected range.</span> <div> @Html.ActionLink("Change value and try again", "Index") </div> <div style="display: none"> @Model.Exception.StackTrace </div> </body> </html>
Action Filter
顾名思义,Action Filter是对action方法的执行进行“筛选”的,包括执行前和执行后。它需要实现 IActionFilter 接口,该接口定义如下:
namespace System.Web.Mvc { public interface IActionFilter { void OnActionExecuting(ActionExecutingContext filterContext); void OnActionExecuted(ActionExecutedContext filterContext); } }
其中,OnActionExecuting方法在action方法执行之前被调用,OnActionExecuted方法在action方法执行之后被调用。我们来看一个简单的例子。
在Infrastructure文件夹下添加一个ProfileActionAttribute类,代码如下:
using System.Diagnostics; using System.Web.Mvc; namespace MvcApplication1.Infrastructure { public class ProfileActionAttribute : 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("<div>Action method elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); } } } }
在HomeController中添加一个Action并应用该Filter,如下:
... [ProfileAction] public string FilterTest() { return "This is the ActionFilterTest action"; } ...
运行程序,URL定位到/Home/FilterTest,结果如下:
我们看到,ProfileAction的 OnActionExecuted 方法是在 FilterTest 方法返回结果之前执行的。确切的说,OnActionExecuted 方法是在action方法执行结束之后和处理action返回结果之前执行的。
OnActionExecuting方法和OnActionExecuted方法分别接受ActionExecutingContext和ActionExecutedContext对象参数,这两个参数包含了ActionDescriptor、Canceled、Exception等常用属性。
Result Filter
Result Filter用来处理action方法返回的结果。用法和Action Filter类似,它需要实现 IResultFilter 接口,定义如下:
namespace System.Web.Mvc { public interface IResultFilter { void OnResultExecuting(ResultExecutingContext filterContext); void OnResultExecuted(ResultExecutedContext filterContext); } }
IResultFilter 接口和之前的 IActionFilter 接口类似,要注意的是Result Filter是在Action Filter之后执行的。两者用法是一样的,不再多讲,直接给出示例代码。
在Infrastructure文件夹下再添加一个 ProfileResultAttribute.cs 类文件,代码如下:
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("<div>Result elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); } }
应用该Filter:
... [ProfileAction] [ProfileResult] public string FilterTest() { return "This is the ActionFilterTest action"; } ...
内置的 Action 和 Result Filter
MVC框架内置了一个 ActionFilterAttribute 类用来创建action 和 result 筛选器,即可以控制action方法的执行也可以控制处理action方法返回结果。它是一个抽象类,定义如下:
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) { } } }
使用这个抽象类方便之处是你只需要实现需要加以处理的方法。其他和使用 IActionFilter 和 IResultFilter 接口没什么不同。下面是简单做个示例。
在Infrastructure文件夹下添加一个 ProfileAllAttribute.cs 类文件,代码如下:
public class ProfileAllAttribute : ActionFilterAttribute { private Stopwatch timer; public override void OnActionExecuting(ActionExecutingContext filterContext) { timer = Stopwatch.StartNew(); } public override void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop(); filterContext.HttpContext.Response.Write( string.Format("<div>Total elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); } }
在HomeController中的FilterTest方法上应用该Filter:
... [ProfileAction] [ProfileResult] [ProfileAll] public string FilterTest() { return "This is the FilterTest action"; } ...
运行程序,URL定位到/Home/FilterTest,可以看到一个Action从执行之前到结果处理完毕总共花的时间:
我们也可以Controller中直接重写 ActionFilterAttribute 抽象类中定义的四个方法,效果和使用Filter是一样的,例如:
public class HomeController : Controller { private Stopwatch timer; ... public string FilterTest() { return "This is the FilterTest action"; } protected override void OnActionExecuting(ActionExecutingContext filterContext) { timer = Stopwatch.StartNew(); } protected override void OnResultExecuted(ResultExecutedContext filterContext) { timer.Stop(); filterContext.HttpContext.Response.Write( string.Format("<div>Total elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); }
注册为全局 Filter
全局Filter对整个应用程序的所有controller下的所有action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,可以把一个Filter类注册为全局,如:
using System.Web; using System.Web.Mvc; using MvcApplication1.Infrastructure; namespace MvcApplication1 { public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new ProfileAllAttribute()); } } }
我们增加了filters.Add(new ProfileAllAttribute())这行代码,其中的filters参数是一个GlobalFilterCollection类型的集合。为了验证 ProfileAllAttribute 应用到了所有action,我们另外新建一个controller并添加一个简单的action,如下:
public class CustomerController : Controller { public string Index() { return "This is the Customer controller"; } }
运行程序,将URL定位到 /Customer ,结果如下:
其它常用 Filter
MVC框架内置了很多Filter,常见的有RequireHttps、OutputCache、AsyncTimeout等等。下面例举几个常用的。
RequireHttps,强制使用HTTPS协议访问。它将浏览器的请求重定向到相同的controller和action,并加上 https:// 前缀。
OutputCache,将action方法的输出内容进行缓存。
AsyncTimeout/NoAsyncTimeout,用于异步Controller的超时设置。(异步Controller的内容请访问 xxxxxxxxxxxxxxxxxxxxxxxxxxx)
ChildActionOnlyAttribute,使用action方法仅能被Html.Action和Html.RenderAction方法访问。
这里我们选择 OutputCache 这个Filter来做个示例。新建一个 SelectiveCache controller,代码如下:
public class SelectiveCacheController : 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 filter,这个action将在view内被调用,它的父action是Index。
现在我们分别创建两个View,一个是ChildAction.cshtml,代码如下:
@{ Layout = null; } <h4>This is the child action view</h4>
另一个是它的Index.cshtml,代码如下:
@{ ViewBag.Title = "Index"; } <h2>This is the main action view</h2> @Html.Action("ChildAction")
运行程序,将URL定位到 /SelectiveCache ,过几秒刷新一下,可看到如下结果:
以上就是[ASP.NET MVC 小牛之路]11 - Filter的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!