Asp.Net MVC3 简单入门详解过滤器Filter
前言
在开发大项目的时候总会有相关的AOP面向切面编程的组件,而MVC(特指:Asp.Net MVC,以下皆同)项目中不想让MVC开发人员去关心和写类似身份验证,日志,异常,行为截取等这部分重复的代码,那我们可以通过AOP截取实现,而在MVC项目中我们就可以直接使用它提供的Filter的特性帮我们解决,不用自己实现复杂的AOP了。
在Asp.NET Mvc中当你有以下及类似以下需求时你可以使用Filter功能
- 判断登录与否或用户权限
- 决策输出缓存
- 防盗链
- 防蜘蛛
- 本地化与国际化设置
- 实现动态Action
第一节:知识储备
Asp.Net MVC提供了以下几种默认的Filter:
Filter Type |
实现接口 |
执行时间 |
Default Implementation |
Authorization filter |
IAuthorizationFilter |
在所有Filter和Action执行之前执行 |
AuthorizeAttribute |
Action filter |
IActionFilter |
分别在Action执行之前和之后执行。 |
ActionFilterAttribute |
Result filter |
IResultFilter |
分别在Action Result执行之后和之前 |
ResultFilterAttribute |
Exception filter |
IExceptionFilter |
只有在filter, 或者 action method, 或者 action result 抛出一个异常时候执行
|
HandleErrorAttribute |
大家注意一点,Asp.net MVC提供的ActionFilterAttribute默认实现了IActionFilter和IResultFilter。而ActionFilterAttribute是一个Abstract的类型,所以不能直接使用,因为它不能实例化,所以我们想使用它必须继承一下它然后才能使用。
Filter继承于ActionFilterAttribute抽象类,并可以覆写 void OnActionExecuting(ActionExecutingContext) 和 voidOnActionExecuted(ActionExecutedContext) 以及 void OnResultExecuting(ResultExecutingContext)和 voidOnResultExecuted(ResultExecutedContext)。
它们的执行先后顺序如下:
OnActionExecuting是Action执行前的操作
OnActionExecuted则是Action执行后的操作
OnResultExecuting是解析ActionResult前执行
OnResultExecuted是解析ActionResult后执行
接下来我们只要对以上的方法进行重写就可以在相应的步骤做一些操作了。
第二节:Filter实战
光说不练假把式,下面我给大家一个示例,来看看它们的执行顺序
首先添加一个普通的类,这个类要继承ActionFilterAttribute,,直接上代码了
public class TestFilterAttribute:ActionFilterAttribute { public string Message { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); filterContext.HttpContext.Response.Write("Action执行之前"+Message+"<br />"); } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); filterContext.HttpContext.Response.Write("Action执行之后"+Message+"<br />"); } public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); filterContext.HttpContext.Response.Write("返回Result之前"+Message+"<br />"); } public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); filterContext.HttpContext.Response.Write("返回Result之后"+Message+"<br />"); } }
写完这个代码后,我们回到Action上,打上上面的标记如下所示:
[TestFilter(Message="Action")] public ActionResult Index() { HttpContext.Response.Write("Action正在执行···<br />"); return Content("正在返回Result···<br />"); }
然后通过浏览器访问上面的Action便可以看到下面的执行顺序
总的执行顺序是:
Action执行前:OnActionExecuting方法先执行→Action执行→OnActionExecuted方法执行→OnResultExecuting方法执行→返回的ActionRsult中的executeResult方法执行→OnResultExecuted执行。最终显示的效果就是如上图所示。
感觉很爽吧!呵呵!这要想用到这个过滤机制的地方的时候,只要在Action上面添加标记便可以实现效果。
如果我们将此标签打到Controller上的话,TestFilterAttributeFilter将作用到Controller下的所有的Action。例如如下代码所示:
[TestFilter(Message="Controller")] public class TestFilterController : Controller { // // GET: /TestFilter/ [TestFilter(Message="Action")] public ActionResult Index() { HttpContext.Response.Write("Action正在执行···<br />"); return Content("正在返回Result···<br />"); } }
如果单纯的按照上面的代码来做就有个问题了我们再执行显示的页面会有什么情况呢?Controller上的Filter会执行吗?那标签的作用会执行两次吗?下面是最后的执行结果如下图所示:
结果说明:默认情况下Action上打了TestFilterAttribute 标签后,虽然在Controller上也打上了此标签,但它只有Action上的标签起作用了。
补充:如果Action没有打上TestFilterAttribute标签,那么Controller上的标签便会被执行。
Index 执行时,Filter的方法只执行了一次,而某些情况下我们也想让Controller上的FilterAttribute也执行一次TestFilterAttribute,那我们怎么才能让Controller上的[TestFilter(Message = "controller")]也起作用呢?
答案是:我们只需在TestFilterAttribute类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]即可【下面类的最上面红色字体部分】,也就是让其成为可以多次执行的Action。代码如下:
[AttributeUsage(AttributeTargets.All,AllowMultiple = true)] public class TestFilterAttribute:ActionFilterAttribute { public string Message { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); filterContext.HttpContext.Response.Write("Action执行之前"+Message+"<br />"); } public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); filterContext.HttpContext.Response.Write("Action执行之后"+Message+"<br />"); } public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); filterContext.HttpContext.Response.Write("返回Result之前"+Message+"<br />"); } public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); filterContext.HttpContext.Response.Write("返回Result之后"+Message+"<br />"); } }
浏览效果如下图:
我们看到的结果是Controller上的ActionFilter先于Action上打的标记执行。同样Result执行executeResult方法之前也是先执行Controller上的Filter标记中的OnResultexecuteing方法。
最后的执行顺序是:Controller上的OnActionExecuting→Action上的OnActionExecuting→Action执行→Action上的OnActionExecuted→Controller上的OnActionExecuted
到此Action就执行完毕了,我们看到是一个入栈出栈的顺序。后面是Action返回ActionResult后执行了ExecuteResult方法,但在执行之前要执行Filter。具体顺序为:
接上面→Controller的OnResultExecuting方法→Action上的OnResultExecuting→Action返回ActionResult后执行了ExecuteResult方法→Action上的OnResultExecuted执行→Controller上的OnResultExecuted执行→结束。
又接着一个问题也来了,我们想有些公共的方法需要每个Action都执行以下,而在所有的Controller打标记是很痛苦的。幸好Asp。Net MVC3带来了一个美好的东西,全局Filter。而怎么注册全局Filter呢?答案就在Global.asax中。让我们看以下代码,我是如何将上面我们定义的TestFilterAttribute 注册到全局Filter中。上代码:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //注册全局过滤器 filters.Add(new TestFilterAttribute() { Message="全局"}); }
效果如下图:
我们看到的结果是全局的Action首先执行,然后才是Controller下的Filter执行,最后才是Action上的标签执行。当然这是在TestFilterAttribute类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]的前提下。不然 如果Action打上了标签跟Controller的相同则它只会执行Action上的Filter。
下面我们说几个系统的Filter
三、AcceptVerbs
规定页面的访问形式,如
[AcceptVerbs(HttpVerbs.Post)] public ActionResult Example(){ return View(); }
页面只能以Post形式访问,即表单提交。
四、ActionName
规定Action的名称。
应用场景:如果不想用方法名做为Action名,或Action名为关键字的话,如
[ActionName("class")] public ActionResult Example(){ return View(); }
五、NonAction
当前方法仅是普通方法不解析为Action
六、OutputCache
为Action添加缓存
[OutputCache(Duration = 60, VaryByParam = "*")] public ActionResult Example() { return View(); }
七、ValidateInput
该Action可以接受Html等危险代码(ASP.Net MVC在aspx中设置<%@ Page 的属性无法完成等同任务。)
[ValidateInput(false)] public ActionResult Example() { return View(); }
八、ValidateAntiForgeryTokenAttribute
用于验证服务器篡改。
[ValidateAntiForgeryToken] public ActionResult Example() { return View(); }
处理错误过滤器HandleError
九 、’默认HandleError使用
在往常的开发中,想到异常处理的马上就会想到try/catch/finally语句块。在MVC里面,万一在行为方法里面抛出了什么异常的,而那个行为方法或者控制器有用上HandleError过滤器的,异常的信息都会在某一个视图显示出来,这个显示异常信息的视图默认是在Views/Shared/Error
这个HandleError的属性如下
属性名称 |
类型 |
描述 |
ExceptionType |
Type |
要处理的异常的类型,相当于Try/Catch语句块里Catch捕捉的类型,如果这里不填的话则表明处理所有异常 |
View |
String |
指定需要展示异常信息的视图,只需要视图名称就可以了,这个视图文件要放在Views/Shared文件夹里面 |
Master |
String |
指定要使用的母版视图的名称 |
Order |
Int |
指定过滤器被应用的顺序,默认是-1,而且优先级最高的是-1 |
这个Order属性其实不只这个HandleError过滤器有,其优先级规则跟其他过滤器的都是一样。
下面则故意弄一个会抛异常的行为方法
[HandleError(ExceptionType = typeof(Exception))] public ActionResult ThrowErrorLogin() { throw new Exception("this is ThrowErrorLogin Action Throw"); }
光是这样还不够,还要到web.config文件中的<system.web>一节中添加以下代码
<customErrors mode="On" />
因为默认的开发模式中它是关闭的,要等到部署到服务器上面才会开启,让异常信息比较友好的用一个视图展现。
像这里访问ThrowErrorLogin视图时,由于抛出了一次,就转到了一个特定的视图
在这里看到的异常视图是这样的,除了用这个建项目时默认生成的异常视图之外,我们还可以自己定义异常视图,视图里面要用到的异常信息,可以通过@Model获取,它是一个ExceptionInfo类型的实例,例如这里建了一个异常视图如下
@{ Layout = null; } <!DOCTYPE html> <html> <head> <title>MyErrorPage</title> </head> <body> <div> <p> There was a <b>@Model.Exception.GetType().Name</b> while rendering <b>@Model.ControllerName</b>'s <b>@Model.ActionName</b> action. </p> <p style="color:Red"> <b>@Model.Exception.Message</b> </p> <p>Stack trace:</p> <pre style=" padding: 0px; color: rgb(0, 0, 255); line-height: 1.5 !important;">>@Model.Exception.StackTrace</pre> </div> </body> </html>
它存放的路径是~/Views/Shared里面,像上面的行为方法如果要用异常信息渲染到这个视图上面,在控制器的处改成这样就可以了
[HandleError(ExceptionType = typeof(Exception), View = "MyErrorPage")]
2.2 自定义错误异常处理
这里的错误处理过滤器也可以自己来定义,做法是继承HandleErrorAttribute类,重写void OnException(ExceptionContext filterContext)方法,这个方法调用是为了处理未处理的异常,例如
public override void OnException(ExceptionContext filterContext) { //base.OnException(filterContext); if (!filterContext.ExceptionHandled && filterContext.Exception.Message == "this is ThrowErrorLogin Action Throw") { filterContext.ExceptionHandled=true; filterContext.HttpContext.Response.Write("5洗ten No Problem<br/>" + filterContext.Exception.ToString()); } }
这里用到的传入了一个ExceptionContext的对象,既可以从它那里获得请求的信息,又可以获取异常的信息,它部分属性如下
属性名称 | 类型 | 描述 |
ActionDescriptor | ActionDescriptor | 提供详细的操作方法 |
Result | ActionResult | 结果的操作方法,过滤器可以取消,要求将此属性设置为一个非空值 |
Exception | Exception | 未处理的异常 |
ExceptionHandled | bool | 另一个过滤器,则返回true,如果有明显的异常处理 |
这里的ExceptionHandler属性要提一下的是,如果这个异常处理完的话,就把它设为true,那么即使有其他的错误处理器捕获到这个异常,也可以通过ExceptionHandler属性判断这个异常是否经过了处理,以免重复处理一个异常错误而引发新的问题。
总结
经过这一篇文章的介绍我们大体了解了Filter的使用方法,还了解到全局Filter的用法,尤其是当相同的Filter重复作用到同一个Action上时,如果没有设置可多次执行的标签那只有Action上的Filter执行,而Controller和全局Filter都被屏蔽掉,但是设置可多次执行,那首先执行全局Filter其次是Controller再次之就是Action上的Filter了。同时还了解了系统的Filter的用法。