Asp.Net MVC Filter
说到Asp.Net MVC中的过滤器Filter,首先我们还得来说说AOP。
AOP(Aspect Oriented Programming 面向切面编程),简单的说,它是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,它可以在不改变现有代码逻辑的情况下额外为其添加一些功能。
Asp.Net MVC的过滤器采用基于AOP的设计,根据用途和执行时机的不同,.Net MVC框架为我们提供了5种过滤器类型,分别是AuthenticationFilter、AuthorizationFilter、ActionFilter、ExceptionFilter和ResultFilter。下面我们就来分别说明一下。
一、AuthenticationFilter:身份认证过滤器,我们之前看源码的时候知道ActionInvoker会调用我们的Action,实际上所有的过滤器的执行也都是ActionInvoker来驱动的,而在源码中我们也可以看到第一个调用的过滤器就是AuthenticationFilter。我们先看一下IAutheticationFilter接口的代码
namespace System.Web.Mvc.Filters { // // 摘要: // 定义一个用于执行身份验证的筛选器。 public interface IAuthenticationFilter { // // 摘要: // 对请求进行身份验证。 // // 参数: // filterContext: // 用于身份验证的上下文。 void OnAuthentication(AuthenticationContext filterContext); // // 摘要: // 向当前 System.Web.Mvc.ActionResult 添加身份验证质询。 // // 参数: // filterContext: // 用于身份验证质询的上下文。 void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext); } }
该接口有两个方法,OnAuthentication方法用于对请求实施认证,OnAuthenticationChallenge负责将相应的认证质询发送给请求者。如果在其方法当中设置了Result,则该Result会直接相应结果。下面是它的执行流程。
接着我们来自定义一个AuthenticationFilter,以此来实现对Basic的一个认证。
/// <summary> /// 基于Basic的认证过滤器 /// </summary> public class AuthenticationAttribute :FilterAttribute, IAuthenticationFilter { public const string AuthorizationHeaderName = "Authorization"; public const string WwwAuthenticationHeaderName = "WWW-Authenticate"; public const string BasicAuthenticationScheme = "Basic"; private static Dictionary<string, string> UserAccouters; //Mock数据 static AuthenticationAttribute() { UserAccouters = new Dictionary<string, string>(); UserAccouters.Add("zhangsan", "123456"); } /// <summary> /// 在此方法中执行验证 /// </summary> /// <param name="filterContext"></param> public void OnAuthentication(AuthenticationContext filterContext) { IPrincipal user; if(this.IsAuthenticated(filterContext,out user)) { filterContext.Principal = user; } else { //验证不通过处理方法 this.ProcessUnauthenticatedRequest(filterContext); } } private void ProcessUnauthenticatedRequest(AuthenticationContext filterContext) { string parameter = string.Format("realm={0}", filterContext.RequestContext.HttpContext.Request.Url.DnsSafeHost); AuthenticationHeaderValue challenge = new AuthenticationHeaderValue(BasicAuthenticationScheme, parameter); filterContext.HttpContext.Response.Headers[WwwAuthenticationHeaderName] = challenge.ToString(); filterContext.Result = new HttpUnauthorizedResult(); } private bool IsAuthenticated(AuthenticationContext filterContext, out IPrincipal user) { user = filterContext.Principal; if(null != user && user.Identity.IsAuthenticated) { return true; } AuthenticationHeaderValue token = this.GetAuthenticationHeaderValue(filterContext); if(null != token && token.Scheme == BasicAuthenticationScheme) { string credential = Encoding.Default.GetString(Convert.FromBase64String(token.Parameter)); string[] split = credential.Split(':'); if(split.Length == 2) { string userName = split[0]; string password; if(UserAccouters.TryGetValue(userName,out password)) { if(password == split[1]) { GenericIdentity identity = new GenericIdentity(userName); user = new GenericPrincipal(identity, new string[0]); return true; } } } } return false; } private AuthenticationHeaderValue GetAuthenticationHeaderValue(AuthenticationContext filterContext) { string rawValue = filterContext.RequestContext.HttpContext.Request.Headers[AuthorizationHeaderName]; if (string.IsNullOrEmpty(rawValue)) { return null; } string[] split = rawValue.Split(' '); if(split.Length != 2) { return null; } return new AuthenticationHeaderValue(split[0], split[1]); } public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext) { } }
在我们需要验证的Controller上添加该特性,浏览器访问会弹出登录框
二、AuthorizationFilter:授权校验,此过滤器在执行完AuthenticationFilter后调用。
1、AuthorizeAttribute:如果要求某个Action只能被认证的用户访问,可以在Controller或Action方法上添加该特性
2、RequiredHttpsAttribute:针对Get请求只能是https请求
3、ValidateInputAttribute:避免用户在请求中嵌入一些不合法的内容(XSS跨站脚本攻击)而对请求的输入进行验证,默认Controller自带了。
4、ValidateAntiForgeryTokenAttribute:解决CSRF跨站请求伪造的网络攻击,通过在Action添加该特性,并且在View中使用@Html.AntiForgeryToken()方法实现。他们会创建一个“防伪令牌”字符串并以此生成一个类型为hidden的input元素。还会根据这个防伪令牌设置一个具有HttpOnly标记的Cookie。
5、ChildActoinOnlyAttribute:如果我们不希望定义在Controller中的某个方法能够以HTTP请求被直接调用,而是仅仅希望它在某个View中被调用以生成组成页面某个部分的HTML,可以使用此特性定义在Action上。那么他是如何判断是子Action的呢,当调用HtmlHelper的Action或RenderAction时会将当前的ViewContext作为Parent Context保存到RouteData的DataTokens中,对应的Key为ParentActionViewContext。
public virtual bool IsChildAction { get { RouteData routeDate = this.RouteData; if (RouteData == null) return false; return RouteData.DataTokens.ContainsKey("ParentActionViewContext"); } }
下面我们再看下自定义的AuthorizeAttribute是怎么实现的
/// <summary>/// 检验登陆和权限的filter /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)] public class AuthorityFilterAttribute : AuthorizeAttribute { /// <summary> /// 未登录时返还的地址 /// </summary> private string _LoginPath = ""; public AuthorityFilterAttribute() { this._LoginPath = "/Home/Login"; } public AuthorityFilterAttribute(string loginPath) { this._LoginPath = loginPath; } /// <summary> /// 检查用户登录 /// </summary> /// <param name="filterContext"></param> public override void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)) { return;//表示支持Controller、Action的AllowAnonymousAttribute } var sessionUser = HttpContext.Current.Session["CurrentUser"];//使用session //var memberValidation = HttpContext.Current.Request.Cookies.Get("CurrentUser");//使用cookie if (sessionUser == null || !(sessionUser is CurrentUser)) { HttpContext.Current.Session["CurrentUrl"] = filterContext.RequestContext.HttpContext.Request.RawUrl; filterContext.Result = new RedirectResult(this._LoginPath); } } }
三、ActionFilter、ResultFilter : 这两个过滤器一般是结合起来使用,Asp.Net MVC为我们提供了一个ActionFilterAttribute类,该类实现了IActionFilter、IResultFilter接口
// // 摘要: // 表示筛选器特性的基类。 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter { // // 摘要: // 初始化 System.Web.Mvc.ActionFilterAttribute 类的新实例。 protected ActionFilterAttribute(); // // 摘要: // 在执行操作方法后由 ASP.NET MVC 框架调用。 // // 参数: // filterContext: // 筛选器上下文。 public virtual void OnActionExecuted(ActionExecutedContext filterContext); // // 摘要: // 在执行操作方法之前由 ASP.NET MVC 框架调用。 // // 参数: // filterContext: // 筛选器上下文。 public virtual void OnActionExecuting(ActionExecutingContext filterContext); // // 摘要: // 在执行操作结果后由 ASP.NET MVC 框架调用。 // // 参数: // filterContext: // 筛选器上下文。 public virtual void OnResultExecuted(ResultExecutedContext filterContext); // // 摘要: // 在执行操作结果之前由 ASP.NET MVC 框架调用。 // // 参数: // filterContext: // 筛选器上下文。 public virtual void OnResultExecuting(ResultExecutingContext filterContext); }
在调用Action到返回Result的执行过程是这样的: OnActionExecuting --> Action execute & return View() --> OnActionExecuted --> OnResultExecuting --> Render View --> OnResultExecuted。
下面定义的ActionFilter可以用来告诉浏览器采用的压缩方式
public class CompressFilter : ActionFilterAttribute { /// <summary> /// action执行前 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { HttpRequestBase request = filterContext.HttpContext.Request; string acceptEncoding = request.Headers["Accept-Encoding"]; if (string.IsNullOrEmpty(acceptEncoding)) return; acceptEncoding = acceptEncoding.ToUpperInvariant(); HttpResponseBase response = filterContext.HttpContext.Response; if (acceptEncoding.Contains("DEFLATE")) { response.AppendHeader("Content-encoding", "deflate"); response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); } else if (acceptEncoding.Contains("GZIP")) { response.AppendHeader("Content-encoding", "gzip"); response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); } } }
四、ExceptionFilter:异常处理过滤器,用于处理包括目标Action方法在内的整个ActionFilter链执行过程抛出的异常。应用到某个Action方法上的多个ExceptionFilter会根据Order和Scope属性排成一个ExceptionFilter链,下面三点需要注意:
- ExceptionFilter链是反向执行的。对于根据Order和Scope属性排好的链,排在后面的具有更高的执行优先级
- 将ExceptionContext的ExceptionHandle设置为True并不能阻止后续ExceptionFilter链的执行
- 如果ExceptionFilter在执行OnException的过程中出现异常,这个ExceptionFilter链会立即终止,并且抛出该异常
下面我们定义一个全局的异常处理过滤器,代码如下:
/// <summary> /// LogExceptionAttribute设置了自己的AttributeUsage特性, /// AttributeTargets.Class指定该过滤器只能用于类一级,即Controller; /// AllowMultiple = false设置不允许多次执行,即仅在Controller级执行一次。 /// /// </summary> [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] public class LogExceptionFilter : HandleErrorAttribute { private Logger logger = Logger.CreateLogger(typeof(LogExceptionFilter)); public override void OnException(ExceptionContext filterContext) { if (!filterContext.ExceptionHandled)//判断异常是否已经被处理过 { string controllerName = (string)filterContext.RouteData.Values["controller"]; string actionName = (string)filterContext.RouteData.Values["action"]; string msgTemplate = "在执行 controller[{0}] 的 action[{1}] 时产生异常"; logger.Error(string.Format(msgTemplate, controllerName, actionName), filterContext.Exception); if (filterContext.HttpContext.Request.IsAjaxRequest())//检查请求头 是不是XMLHttpRequest { filterContext.Result = new JsonResult() { Data = new AjaxResult() { Result = DoResult.Failed, PromptMsg = "系统出现异常,请联系管理员", DebugMessage = filterContext.Exception.Message } }; } else { filterContext.Result = new ViewResult() { ViewName = "~/Views/Shared/Error.cshtml", ViewData = new ViewDataDictionary<string>(filterContext.Exception.Message) }; } filterContext.ExceptionHandled = true; } } }
最后可以在全局中注册该过滤器
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { //filters.Add(new HandleErrorAttribute()); filters.Add(new Web.Core.Filter.LogExceptionFilter()); } }
至此,Asp.Net MVC 中重要的5个Filter也已经讲完了。在项目不紧张的这几天,花了一些时间好好整理复习了整个Asp.Net MVC的请求流程。接下来希望有时间开始写一些框架类的东西。