【ASP.NET MVC 学习笔记】- 12 Filter

时间:2023-03-09 16:14:44
【ASP.NET MVC 学习笔记】- 12 Filter

本文参考:http://www.cnblogs.com/willick/p/3331520.html

1、Filter(过滤器)是基于AOP(Aspect-Oriented Programming 面向切面编程)的设计。作用是对MVC框架处理客户端请求注入额外的逻辑,以非常优美简单的方式实现横切关注点(Cross-cutting Concerns)。所谓横切关注点是指横越应用程序多个甚至所有模块的功能,经典的横切关注点有日志记录、缓存处理、异常处理和权限验证等。

2、MVC框架支持的Filter可以分为四类,每一个类都可以在处理请求的不同时间点注入额外的逻辑处理。这四类Filter如下图:

【ASP.NET MVC 学习笔记】- 12 Filter

其中ActionFilter是一个抽象类,使用之前必须对它进行实现;而另外两个则有默认实现可以直接调用。这些Filter既可以用在单个Action上,也可以用在整个Controller上。

   对于自定义的Controller基类,应用于该基类的Filter也将对继承自该基类的子类有效。

2、Authorization Filter是在Action和其他种类的Filter之前运行的,作用是强制实施权限策略,保证Action只被授权了的用户调用。它实现的接口如下:

namespace System.Web.Mvc
{
public interface IAuthorizationFilter
{
void OnAuthorization(AuthorizationContext filterContext);
}
}

我们可以通过继承IAuthorizationFilter接口自定义Authorization Filter。下列示例自定义了一个Filter用于验证是否允许本地请求。

//AuthorizeAttribte 类帮我们内置地实现了很多东西,我们只需把重点放在 AuthorizeCore 方法上,在该方法中实现权限认证的逻辑。
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;
}
}
}
[Authorize(Users = "jim, steve, jack", Roles = "admin")]//使用内置的Authorization Filter,该语句的意思是只允许角色为admin、且用户名必须是jim、steve、jack中的一个的用户访问该Action
public string Index()
{
return "This is the Index action on the Home controller";
}

3、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继承自ControllerContext,后者的常用属性说明:

  • Controller,返回当前请求的controller对象。
  • HttpContext,提供请求和响应的详细信息。
  • IsChildAction,如果是子action则返回true(稍后将简单介绍子action)。
  • RequestContext,提供请求上下文信息。
  • RouteData,当前请求的路由实例信息。

ExceptionContext的常用属性说明:

  • ActionDescriptor,提供action方法的详细信息。
  • Result,是一个 ActionResult 类型,通过把这个属性值设为非空可以让某个Filter的执行取消。
  • Exception,未处理异常信息。
  • ExceptionHandled,如果另外一个Filter把这个异常标记为已处理则返回true。

一个Exception Filter可以通过把 ExceptionHandled 属性设置为true来标注该异常已被处理过,这个属性一般在某个action方法上应用了多个Exception Filter时会用到。ExceptionHandled 属性设置为true后,就可以通过该属性的值来判断其它应用在同一个action方法上的Exception Filter是否已经处理了这个异常,以免同一个异常在不同的Filter中重复被处理。示例:

//1、Filter的定义,通过重定向到Content目录下的一个静态html文件来显示友好的 ArgumentOutOfRangeException 异常信息。
//RangeExceptionAttribute 类继承了FilterAttribute类,并且实现了IException接口。
//作为一个MVC Filter,它的类必须实现IMvcFilter接口,你可以直接实现这个接口,但更简单的方法是继承 FilterAttribute 基类
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;
}
}
} //2、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> //3、HomeController中添加一个值越限时抛出异常的action
public class HomeController : Controller
{
[RangeException]
public string RangeTest(int id)
{
if (id > )
{
return String.Format("The id value is: {0}", id);
}
else
{
throw new ArgumentOutOfRangeException("id", id, "");
}
}
}

由于静态的html文件是和后台脱离的,所以实际项目中更多的是用一个View来呈现友好的错误信息,以便很好的对它进行一些动态的控制:

//1、定义Filter
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;
}
}
} //2、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>

4、程序发布后不应该显示异常信息给用户看。我们可以通过配置Web.config让应用程序不管在何时何地引发了异常(即使是在View中的异常)都可以显示统一的友好错误信息。在Web.config文件中的<system.web>节点下添加如下子节点:

<system.web><customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>//这个配置只对远程访问有效,本地运行站点依然会显示跟踪信息。
</system.web>

5、MVC框架内置的 HandleErrorAttribute包含ExceptionType、View和Master三个属性。当ExceptionType属性指定类型的异常被引发时,这个Filter将用View属性指定的View(使用默认的Layout或Mast属性指定的Layout)来呈现一个页面。示例:

[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")]
public string RangeTest(int id)
{
if (id > )
{
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>

6、ActionFilter是对Action方法的执行进行筛选的,包括执行前和执行后。它实现了以下接口:

namespace System.Web.Mvc
{
public interface IActionFilter
{
//在action方法执行之前被调用
void OnActionExecuting(ActionExecutingContext filterContext); //在action方法执行之后被调用
void OnActionExecuted(ActionExecutedContext filterContext);
}
}

下列示例自定义了一个ActionFilter:

//自定义ActionFilter
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";
}

7、Result Filter用来处理action方法返回的结果,是在Action Filter之后执行的。用法和Action Filter类似,它需要实现 IResultFilter 接口,定义如下:

namespace System.Web.Mvc
{
public interface IResultFilter
{
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
}

示例代码:

//1、Filter 定义
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));
}
} //2、应用
[ProfileAction]
[ProfileResult]
public string FilterTest()
{
return "This is the ActionFilterTest action";
}

8、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)
{
}
}
}

使用这个抽象类方便之处是你只需要实现需要加以处理的方法。示例:

//1、定义
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));
}
} //2、应用
[ProfileAction]
[ProfileResult]
[ProfileAll]
public string FilterTest()
{
return "This is the 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));
}
}

9、全局Filter对整个应用程序的所有controller下的所有action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,可以把一个Filter类注册为全局,如:

public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ProfileAllAttribute()); //如此,ProfileAllAttribute将对所有的action有效
}
}

10、MVC框架内置了很多Filter,常见的有RequireHttps、OutputCache、AsyncTimeout等等。下面是几个常用的:

  • RequireHttps,强制使用HTTPS协议访问。它将浏览器的请求重定向到相同的controller和action,并加上 https:// 前缀。
  • OutputCache,将action方法的输出内容进行缓存。
  • AsyncTimeout/NoAsyncTimeout,用于异步Controller的超时设置。
  • ChildActionOnlyAttribute,使用action方法仅能被Html.Action和Html.RenderAction方法访问。