Home >Backend Development >C#.Net Tutorial >[ASP.NET MVC Mavericks Road]11 - Filter
[ASP.NET
MVC Mavericks Road] 11 - Filter
Filter (filter) is based on AOP (aspect-oriented programming) design, its function is to process customers in the MVC framework Inject additional logic into side requests to implement cross-cutting concerns in a very simple and beautiful way. Cross-cutting concerns refer to functions that span multiple or even all modules of the program. Classic cross-cutting concerns include logging, cache processing, exception handling, and permission verification. This article will introduce the creation and use of different types of Filters supported by the MVC framework, and how to control their execution.
Contents of this article
Overview of four basic Filters
Filters supported by the MVC framework can be classified into four categories, and each category can Introduce additional logic processing at different points in time when processing requests. These four types of Filter are as follows:
Before the MVC framework calls acion, it will first determine whether the features of the interface in the above table are implemented. If so, then request The appropriate point in the pipeline calls the method defined in the attribute.
The MVC framework implements default attribute classes for these types of Filter interfaces. As shown in the table above, the ActionFilterAttribute class implements two interfaces, IActionFilter and IResultFilter. This class is an abstract class and must be implemented. The other two attribute classes, AuthorizeAttribute and HandleErrorAttribute, already provide some useful methods that can be used directly.
Filter can be applied to a single ation method or to the entire controller, and multiple Filters can be applied to acion and controller. As shown below:
[Authorize(Roles="trader")] // 对所有action有效 public class ExampleController : Controller { [ShowMessage] // 对当前ation有效 [OutputCache(Duration=60)] // 对当前ation有效 public ActionResult Index() { // ... } }
Note that for a custom controller base class, the Filter applied to the base class will also be effective for all subclasses inherited from the base class.
Authorization Filter
Authorization Filter runs before action methods and other types of Filters. Its role is to enforce permission policies and ensure that action methods can only be called by authorized users. The interface implemented by Authorization Filter is as follows:
namespace System.Web.Mvc { public interface IAuthorizationFilter { void OnAuthorization(AuthorizationContext filterContext); } }
Custom Authorization Filter
You can implement the IAuthorizationFilter interface yourself to create your own security authentication logic, but this is generally not necessary and is not recommended. If you want to customize the security authentication policy, a safer way is to inherit the default AuthorizeAttribute class.
We will demonstrate the custom Authorization Filter by inheriting the AuthorizeAttribute class. Create a new empty MVC application, add an Infrastructure folder as usual, and then add a CustomAuthAttribute.cs class file. The code is as follows:
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; } } } }
This simple Filter allows us to override the AuthorizeCore method. Block local requests. When applying the Filter, you can specify whether to allow local requests through the constructor. The AuthorizeAttribte class helps us implement many things built-in. We only need to focus on the AuthorizeCore method and implement the permission authentication logic in this method.
In order to demonstrate the function of this Filter, we create a new controller named Home, and then apply this Filter on the Index action method. The parameter is set to false to protect this action from local access, as follows:
public class HomeController : Controller { [CustomAuth(false)] public string Index() { return "This is the Index action on the Home controller"; } }
Run the program and request /Home/Index according to the default routing value generated by the system. The result is as follows:
We have customized a simple Filter by using the AuthorizeAttribute class as the base class. So what are the useful functions of the AuthorizeAttribute class itself as a Filter?
Use the built-in Authorization Filter
当我们直接使用 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中文网(www.php.cn)!