27-动作过滤器
先来看看一个例子演示过滤器有什么用:
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
} ...
AdminController控制器的众多Action中我们都需要判定当前验证用户,这里有很多重复的代码,我们可以简化为:
[Authorize]
public class AdminController : Controller {
// ... instance variables and constructor
public ViewResult Index() {
// ...rest of action method
}
public ViewResult Create() {
// ...rest of action method
}
...
Authorize特性类AuthorizeAttribute就称作MVC的Filter,它在横向为MVC框架扩展功能,让我们可以更方便的处理日志、授权、缓存等而不影响纵向主体功能。
有时在运行Action之前或之后会需要运行一些逻辑运算,以及处理一些运行过程中所生成的异常状况,为了满足这个需求,ASP.NET MVC提供动作过滤器(Action Filter)来处理这些需求。
Asp.NET MVC包含以下四种不同类型的过滤器,如下表所示。
过滤器类型 | 使用时机 | 接口 | 实现 |
授权过滤器 (Authorization Filter) |
在运行任何Filter或Action之前被执行,经常用于身份验证或需要尽早运行特殊检查的时候 | IAuthorizationFilter |
AuthorizeAttribute ChildActionOnlyAttribute RequireHttpsAttribute ValidateAntiForgeryTokenAttribute ValidateInputAttribute |
动作过滤器 (Action Filter) |
在运行Action的前后被运行,用于运行Action需要生成的记录或者缓存信息时 | IActionFilter |
ActionFilterAttribute OutputCacheAttribute AsyncTimeoutAttribute |
结果过滤器 (Result Filter) |
在运行ActionResult的前后被运行,在View呈现到浏览器之前,可以运行一些逻辑运算,或用来更改View Result的输出结果 | IResultFilter |
ActionFilterAttribute |
异常过滤器 (Exception Filters) |
从授权过滤器开始到ActionResult运行完成后这段过程中,如果有任何异常发生,可以使用此Filter来针对异常进一步做处理,例如记录错误细节或导向友善的错误界面 | IExceptionFilter |
HandleErrorAttribute OutputCacheAttribute |
四种不同类型的动作过滤器的运行顺序如下图。
执行顺序为:IAuthorizationFilter(OnAuthorization)----->IActionFilter(OnActionExecuting)---->控制器Action---->IActionFilter(OnActionExecuted) ---->IResultFilter(OnResultExecuting)---->视图---->IResultFilter(OnResultExecuted) 。
动作过滤器特性可以套用在Action之上,也可以套用在Controller类上,若动作过滤器属性套用在Controller类上等于套用此特性在此Controller的所有Action之上。
[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 } }
1.授权过滤器
授权过滤特器(Authorization Filter)是在ASP.NET MVC运行Controller与Action之前最早运行的过滤器,可用来对Action在正式运行前做一些额外的判断,例如,授权检查、是否为SSL安全联机、验证输入信息是否包含XSS攻击字符串等等。所有授权过滤器特性都必须实现IAuthorizationFilter接口。
(1)Authorize特性
Authorize特性可用来与ASP.NET框架的Membership或FormsAuthentication机制配合使用,当你登录会员,拥有会员身份或角色后,可以设置此Action必须符合哪些用户或角色的要求才能运行特定Action,如果授权验证失败,就会被自动导入登录页面,此部分机制与ASP.NET Web Form的用户验证时一模一样的。
[Authorize(Users = "Tom,Mary")]
public ActionResult Edit(int id)
{
return View();
}
上面的代码是Edit动作仅允许Tom与Mary两位用户使用,如果没有登录或不是这两个用户名称的话,就会自动被导入到登录页面。
[Authorize(Roles="Admin")]
public ActionResult Edit(int id)
{
return View();
}
上面的代码是Edit动作仅允许拥有Admin角色的登录者才能运行这个动作,如果没有登录或不是这两个用户名称的话,就会自动被导入到登录页面。
权限不足时应该转向的登录页面,可以在web.config的<system.web>节下的<authentication>里设置,代码如下。
<authentication mode="Forms"> <forms loginUrl="~/Account/Login" timeout="2880" /> </authentication>
***具体应用***
a.只要登录就可以
登录验证的代码如下。
[HttpPost] public ActionResult Login(Administrator admin) { var user=db.Administrators.Where(a=>a.UserName==admin.UserName && a.Password==admin.Password).FirstOrDefault(); if (user != null) { FormsAuthentication.SetAuthCookie(user.UserName, false); return RedirectToAction("Index", "Admin"); } return View(); }
登录后,需要权限验证通过后,才可以访问的控制器代码入下。
[Authorize] public class AdminController : Controller { LearningMVCDBContext db = new LearningMVCDBContext(); public ActionResult Index() { return View(db.Messages.ToList()); }
相对应而言,注销的代码如下。
public ActionResult LogOff() { //清除窗体验证的Cookies FormsAuthentication.SignOut(); //清除所有曾经写入过的Session信息 Session.Clear(); return RedirectToAction("Login", "Account"); }
b.登录通过,且用户名为admin
登录动作代码如上编写。登录后需要权限才能访问的控制器代码如下。
[Authorize(Users="admin")] public class AdminController : Controller { LearningMVCDBContext db = new LearningMVCDBContext(); public ActionResult Index() { return View(db.Messages.ToList()); }
如果在视图中需要使用该用户的名字,可以用如下代码。
<h2>后台管理系统首页</h2> <p>欢迎你,@User.Identity.Name</p> @foreach (var item in Model) {
c.登录通过,且角色为超级管理员
登录动作代码,可以和a部分的代码一样,也可以如下,更安全地存入用户信息。
[HttpPost] public ActionResult Login(Administrator admin) { var user=db.Administrators.Where(a=>a.UserName==admin.UserName && a.Password==admin.Password).FirstOrDefault(); if (user != null) { //创建一个新的票据,将用户的名字记入ticket的userdata FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, user.UserName, DateTime.Now, DateTime.Now.AddMinutes(20), false, user.AdminRole.ToString()); //将票据加密 string encryptedTicket = FormsAuthentication.Encrypt(authTicket); //将加密后的票据保存为cookie System.Web.HttpCookie authCookie = new System.Web.HttpCookie (FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.HttpOnly = true; //客户端的js不能访问cookie
//使用加入了userdata的新cookie System.Web.HttpContext.Current.Response.Cookies.Add(authCookie); return RedirectToAction("Index", "Admin"); } return View(); }
如果使用角色验证,那么必须要添加Application_AuthorizeRequest。global.asax.cs文件中,添加如下代码。
protected void Application_AuthorizeRequest(object sender, System.EventArgs e) { HttpApplication App = (HttpApplication)sender; HttpContext Ctx = App.Context; //获取本次Http请求相关的HttpContext对象 if (Ctx.Request.IsAuthenticated == true) //验证过的用户才进行role的处理 { FormsIdentity Id = (FormsIdentity)Ctx.User.Identity; FormsAuthenticationTicket Ticket = Id.Ticket; //取得身份验证票 string[] Roles = Ticket.UserData.Split(','); //将身份验证票中的role数据转成字符串数组 Ctx.User = new GenericPrincipal(Id, Roles); //将原有的Identity加上角色信息新建一个GenericPrincipal表示当前用户,这样当前用户就拥有了role信息 } }
如果使用a部分的代码,那么相应的global.asax.cs文件中的代码如下。
protected void Application_AuthorizeRequest(object sender, System.EventArgs e) { var id = Context.User.Identity as FormsIdentity; if (id != null && id.IsAuthenticated) { var roles = id.Name.Split(','); Context.User = new GenericPrincipal(id, roles); } }
登录后需要权限才能访问的控制器代码如下。
[Authorize(Roles="超级管理员")] public class AdminController : Controller { LearningMVCDBContext db = new LearningMVCDBContext(); public ActionResult Index() { return View(db.Messages.ToList()); }
如果要传递更多的信息到视图,可以使用ViewBag或者Session。
Session["name"]=user.Name;
(2)AllowAnonymous特性
AllowAnonymous特性通常与Authorize特性搭配使用,如下代码段所示。
[Authorize]
[InitializeSimpleMembership]
public class AccountController : Controller
{
//
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
我们将Authorize特性套用在控制器层级,这意味着该控制器中所有Action都将受到Authorize特性的影响。也就是当任何人企图运行该控制器中任何动作时,都会受到登录状态检查,如果用户尚未登录就会自动被导向到登录页面。
不过,如果希望在该控制器中设置几个Action拥有例外,也就是在不登录的情况下也可以运行Action,那么这时你就可以套用AllowAnonymous特性。
(3)ChildActionOnly特性
ASP.NET MVC的View相关技术有个Html.RenderAction辅助方法,通过这个方法可以在View中再次发出另一个子请求,再运行一次ASP.NET MVC的运行过程,让其运行完后回传的HTML结果再插入到View中。
这个子要求所运行的Action其实跟通用Action差不多,但如果你希望要通过RenderAction运行的Action只允许通过Html.RenderAction辅助方法运行的话,就可以套用这个属性,演示程序如下。
[ChildActionOnly]
public ActionResult GetBanner()
{
return Content("<img src=\"/Content/Banner1jpg\" />");
}
(4)RequireHttps特性
套用RequireHttps特性,可以让此Action仅能显示在HTTPS安全联机的状态下,如果客户端使用HTTP链接,该Action就会自动重新转向至同一个Action的HTTPS网址上。
例如,联机到http://localhost/Home/SecuredPage时,就会自动重新转向到https://localhost/Home/SecuredPage。程序演示如下所示:
[RequireHttps] public ActionResult SecuredPage() { return View(); }
如果客户端使用HTTP POST发出请求到套用RequireHttps的Action,将会引发InvalidOperationException异常,因此,RequireHttps特性并不建议与HttpPost属性同时使用。
(5)ValidateInput特性
ASP.NET框架默认会验证所有通过窗体来的输入信息,并检查是否含有恶意的标签或程序代码,当需要通过网页输入HTML标签时必须手动关闭此验证。
在写ASP.NET Web Form时,可以在web.config更改<page>的validateRequest属性为false来关闭全站的输入验证:
<pages validateRequest="false">
也可以在页面最上方设置ValidateRequest="false",如下图所示。
但在ASP.NET MVC中这两种设置都没有用,而是通过在Action上设置ValidateInput特性来关闭输入验证的工作,程序如下:
[HttpPost, ValidateInput(false)] public ActionResult Edit(int id, FormCollection formValues) { //... }
(6)ValidateAntiForgeryToken特性
ValidateAntiForgeryToken特性是ASP.NET MVC为了预防跨网站造假点击(Cross-Site Request Forgery, CSRF)的攻击而生成的。
[ValidateAntiForgeryToken] public ActionResult ComplexModelBinding(GuestbookForm form1) { //... }
@using(Html.BeginForm())
{
@Html.LabelFor(x=>x.Name)
@Html.TextBoxFor(x=>x.Name)
@Html.ValidationMessageFor(x=>x.Name)
<br />
@Html.AntiForgeryToken()
<input type="submit" />
}
2.动作过滤器
动作过滤器(Action Filters)属性提供了两个事件会在Action的前后运行,分别是OnActionExecuting与OnActionExecuted事件,特性类型实现IActionFilter接口就会被要求必须实现这两个方法。
在ASP.NET MVC里内建了几个动作过滤器,以下将逐一介绍。
(1)ActionFilter特性
ASP.NET MVC内建的ActionFilter特性是一个抽象类,这里面其实没有任何程序代码,只是一个基本Action Filter程序代码框架,当你想自定义动作过滤器时可以直接继承这个ActionFilterAttribute,方便让你替换(override)其中的四个方法,即OnActionExecuting、OnActionExecuted、OnResultExecuting、OnResultExecuted。
(2)AsyncTimeout特性
AsyncTimeout特性可让你设置在运行异步控制器时的逾时毫秒数(ms)。
(3)NoAsyncTimeout特性
NoAsyncTimeout特性可让你设置在运行异步控制器时不要有逾期时间。
3.结果过滤器
结果过滤器(Result Filter)特性提供了两个事件会在运行视图的前后运行,分别是OnResultExecuting与OnResultExecuted事件,特性类型实现了IResultFilter接口会被要修必须实现这两个方法。
由于从Action回传的ActionResult一定各有个ExecuteResult方法用来运行视图的结果,所以,通过这个过滤器可以在运行之前整理一些信息给ActionResult使用,或在运行之后针对结果进行,最常见的例子就是实现输出缓存机制,ASP.NET MVC内建的特性有OutputCache特性。例如:指定的返回值被缓存10秒。
public class ActionFilterDemoController : Controller { [HttpGet] OutputCache(Duration = 10)] public string Index() { return DateTime.Now.ToString("T"); } }
OutputCache此特性用来实现ASP.NET MVC的输出缓存机制,由于从Action回传的ActionResult一定有个ExecuteResult方法,可用来运行要输出到客户端的结果,所以,通过这个结果过滤器可以在运行之前,将输出缓存的环境准备好,让所有之后通过ActionResult.ExecuteResult运行的输出特性全部塞进输出缓存,这个特性其实仍然沿用ASP.NET Web Form的输出缓存架构,设置的方式大家应该不算陌生。
4.异常过滤器
异常过滤器(Exception Filters)特性提供了一个事件,从第一个授权过滤器(Authorization Filters)运行开始,到ActionResult运行完后这段过程中,如果有任何异常发生,都可以在例外过滤器(Exception Filters)属性里做进一步的处理,例如,记录错误细节或导向友好的错误界面等,特性类型实现IExceptionFilter接口会被要求必须实现OnException方法。
由于异常过滤器是ASP.NET MVC运行的最后一个事件,通常用它来做ASP.NET MVC的异常处理,因此,它内建了一个HandlerError特性,用来处理运行过程中发生异常的状况。
HandleError特性套用后,不管在Action运行时发生异常,还是在View运行时发生异常,都会去运行HandleError.OnException方法。默认的HandleError特性套用后,当运行过程发生任何异常,就会通过ViewEngine寻找名叫Error的视图页面,并直接响应至客户端。
搜索Error视图的顺序与平常使用ViewResult回传所搜索的顺序一样。在VS的ASP.NET MVC项目模板里,默认有一个Error视图的网页,路径在/Views/Shared/Error.cshtml,例如如下代码。
@model System.Web.Mvc.HandleErrorInfo @{ ViewBag.Title = "错误"; } <hgroup class="title"> <h1 class="error">错误。</h1> <h2 class="error">处理您的要求时发生错误。</h2> </hgroup>
如果要针对特定的异常类型做错误处理,也可以指定明确的异常类型,甚至可以指定要显示错误的视图名称,演示程序如下。
[HandleError(ExceptionType=typeof(ArgumentNullException),View="ArgNullError")] public ActionResult Index() { return View(); }
如果要针对整个Controller所有Action都套用HandleError属性的话,可将该属性套用在类型层级,即可默认套用到所有Action。
通过ASP.NET MVC 4项目模板创建的ASP.NET MVC项目中,在项目的/App_Start/FilterConfig.cs文档里默认已经注册了一个全局的异常过滤器,因此,默认情况下,你可以不用特别在每个Controller上套用HandleError特性。
5.自定义动作过滤器
在实际工作中,经常通过自定义的动作过滤器特性来帮助我们达成一些目的,在ASP.NET MVC中有一个实现ActionFilterAttribute抽象类,此类型继承自FilterAttribute类型,并同时操作了IActionFilter与IResultFilter接口,自定义的动作过滤器特性通常都直接继承ActionFilterAttribute抽象类即可。
最常用来自定义动作过滤器特性的情况,大多用于多个Action要读取同一份信息的时候,假设有五个Action需要读取同一份信息,读取信息的而程序代码如下:
IShoppingService s=new ShoppingService(); ViewData["MyCart"]=s.GetMyCart();
可以将这段程序代码写五遍,也可以将这段程序代码抽离成一个独立的Method来运行,但无论如何都不会比将这段程序写在自定义的动作过滤器属特性里来的漂亮。
假设自定义一个动作过滤器特性为ShoppingCartInfoAttribute,其代码如下。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace LearningMVC.ActionFilters { public class ShoppingCartInfoAttribute:ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.Controller.ViewData["MyCart"] = Util.GetShoppingCartInfo(); } } }
仅仅替换OnActionExecuting方法,并将数据库取得的数据写入到ViewData["MyCart"]之中,之后只要套用此属性到Action上,即可取得ViewData["MyCart"]的数据,当然也可以在View中使用。Action的程序代码演示入戏啊,是不是简洁很多呢!
[ShoppingCartInfoAttribute] public ActionResult ShoppingIndex() { return View(); } [ShoppingCartInfoAttribute] public ActionResult ShoppingDetail() { return View(); }