MVC之自定义过滤器(ActionFilterAttribute)
一、自定义Filter
自定义Filter需要继承ActionFilterAttribute抽象类,重写其中需要的方法,来看下ActionFilterAttribute类的方法签名。
//表示所有操作-筛选器特性的基类 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter { protected ActionFilterAttribute(); //在Action执行之前由 MVC 框架调用。 public virtual void OnActionExecuting(ActionExecutingContext filterContext); //在Action执行之后由 MVC 框架调用。 public virtual void OnActionExecuted(ActionExecutedContext filterContext); //在执行Result之前由 MVC 框架调用。 public virtual void OnResultExecuting(ResultExecutingContext filterContext); //在执行Result后由 MVC 框架调用。 public virtual void OnResultExecuted(ResultExecutedContext filterContext); }
过滤器代码
//自定义过滤器 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class LoginFilterAttribute : ActionFilterAttribute { ILogger log = LoggerFactory.GetChannelLog(typeof(LogInAuthorizeAttribute)); /// <summary> /// 默认登录页面 /// </summary> private static readonly string logUrl = ConfigFactory.WebEnvConfig.Items["LoginToGMS"]; //通过配置cookie名称 private static readonly string cookieName = ConfigFactory.WebEnvConfig.Items["CookieName"]; public override void OnActionExecuting(ActionExecutingContext filterContext) { try { log.Debug("自定义Cookie名称,CookieName:" + cookieName); var cookie = filterContext.HttpContext.Request.Cookies[cookieName]; if (cookie == null) { RedirectUrl(filterContext, logUrl); } log.Debug("cookieName:" + cookieName + "cookieValue:" + cookie.Value); FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); if (ticket == null || string.IsNullOrEmpty(ticket.UserData)) { log.Debug("解密失败! tick is null or ticket UserData is null :"); RedirectUrl(filterContext, logUrl); } log.Debug("解密成功!tick:" + ticket.Name); LoginUser userData = JsonConvert.DeserializeObject<LoginUser>(ticket.UserData); HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new System.Web.Security.FormsIdentity(ticket),new string[0]); log.Debug("!HttpContext.Current.Request.IsAuthenticated:" + HttpContext.Current.Request.IsAuthenticated); log.Debug("获取成功! Name:" + userData.User_Nm); } catch (Exception ex) { log.Error("解密cookie异常:"+ex.Message); log.Error(ex); filterContext.HttpContext.Response.Cookies.Clear(); RedirectUrl(filterContext, logUrl); } base.OnActionExecuting(filterContext); } /// <summary> /// 跳转报错解决办法 /// </summary> /// <param name="url"></param> public void RedirectUrl(ActionExecutingContext filterContext, string url) { filterContext.HttpContext.Response.Clear();//这里是关键,清除在返回前已经设置好的标头信息,这样后面的跳转才不会报错 filterContext.HttpContext.Response.BufferOutput = true;//设置输出缓冲 if (!filterContext.HttpContext.Response.IsRequestBeingRedirected)//在跳转之前做判断,防止重复 { //filterContext.HttpContext.Response.Redirect(url, true);//在Filter中用Response.Redirect,虽然URL是跳转了,但是之后的Filter和Action还是会执行,不仅浪费资源,还会产生一些不必要的错误 filterContext.Result = new RedirectResult(url); } }
注意过滤器也可以放在整个Controller类的顶部,表示该Controller下的所有Action都执行该项检查。这样一来,控制器里的代码非常漂亮,再也不用所有的Action里都充斥着判断登录的代码了。
顺便说下登录成功后如何将用户信息写入Cookie中
LoginUser loginUser = new LoginUser(); loginUser.UserId = userfile.User_Id; loginUser.User_Cd = userfile.User_Cd; loginUser.User_Nm = userfile.User_Nm; FormsAuthentication.SetAuthCookie(UserName, false); FormsAuthenticationTicket Ticket = new FormsAuthenticationTicket(1, UserName, DateTime.Now, DateTime.Now.AddTicks(FormsAuthentication.Timeout.Ticks), false, JsonConvert.SerializeObject(loginUser)); string HashTicket = FormsAuthentication.Encrypt(Ticket); HttpCookie UserCookie = new HttpCookie(FormsAuthentication.FormsCookieName, HashTicket); UserCookie.Domain = FormsAuthentication.CookieDomain; UserCookie.Path = "/";
同时在Web.config中配置如下:
<authentication mode="Forms"> <forms path="/" loginUrl="/login" defaultUrl="/" protection="All" name=".newpower" domain="" timeout="60" slidingExpiration="true" /> </authentication>
二、带参数的自定义Filter
首先,还是按照之前添加自定义过滤器的方法,添加一个自定义过滤器,只是里面多了一个属性,代码如下:
public class FilterAttribute : 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代码如下:
[Filter(Message="刘备")] //参数给上 public ActionResult Index() { return View(); }
如果标签打到Controller上的话,FilterAttribute将作用到Controller下的所有的Action。
默认情况下Action上打了某个自定义标签后,虽然在Controller上也打上了此标签,但它只有Action上的标签起作用了。
补充:如果Action没有打上该标签,那么Controller上的标签便会被执行。
如果想让Action上的标签执行一次,然后Controller上的标签也执行一次,那么应该如何操作呢?
我们只需在FilterAttribute类的定义上打上标记[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]即可【下面类的最上面红色字体部分】,也就是让其成为可以多次执行的Action。代码如下:
[AttributeUsage(AttributeTargets.All,AllowMultiple = true)] public class FilterAttribute : ActionFilterAttribute { public string Message { get; set; } ......
三、全局过滤器
创建一个全局action过滤器 (在appstart 的 filterconfig中注册 filters.Add(new LoginFilterAttribute());)
这样就每个Action都会执行此过滤器,而不必每个Controller顶部都加上标签。
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //注册全局过滤器 filters.Add(new LoginFilterAttribute() { Message = "全局" }); } }