在Asp.net MVC中,filter为cross-cutting concerns提供一个简单的实现方式。它共有4类Filter:
下边分别来讲述。
1. Authorization Filter
Authorize filter可以用于action:
[
Authorize(Users =
"
adam, steve, bob
", Roles =
"
admin
")]
public ActionResult Index()
也可以直接用于controller:
[Authorize(Roles =
"
Trader
")]
public
class AdminController : Controller
基于AuthorizeAttribute,我们扩展一个:
public
class CustomAuthorizeAttribute : AuthorizeAttribute
{
private
string[] allowedUsers;
public CustomAuthorizeAttribute() :
this(
new
string[]{})
{
}
public CustomAuthorizeAttribute(
params
string[] users)
{
this.allowedUsers = users;
}
protected
override
bool AuthorizeCore(HttpContextBase httpContext)
{
return httpContext.Request.IsAuthenticated &&
allowedUsers !=
null &&
allowedUsers.Contains(httpContext.User.Identity.Name, StringComparer.OrdinalIgnoreCase);
//
return httpContext.Request.IsLocal || base.AuthorizeCore(httpContext);
}
}
使用方式:
[CustomAuthorize(
"
Tom
",
"
bob
")]
public ActionResult Manage()
{
return Content(
"");
}
这时候可以看到,Manage只有Tom和Bob可以访问,其他人都权限不足。如果没有登录,系统会跳转到登录界面,强制让你登录。
显然,这种方式对于ajax请求不是很合理。好,让我们写一个ajax权限验证的filter:
public
class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected
override
void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if(filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result =
new JsonResult
{
Data =
new {
Error =
"
Not Authorized
",
LogOnUrl =
new UrlHelper(filterContext.RequestContext).Action(
"
LogOn
",
"
Account
")
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
可以看到,我们是通过重写HandleUnauthorizedRequest方法,给访问权限不足的请求的response中不仅包含错误信息,还包含一个登录url,这样js中可以自由裁决如果处理了。
2. Exception Filter
exception filter只有当action被调用,有异常抛出时被触发。当然,异常可能来源于其他filer(authorization, action, 或者 result filter)、action自身、action结果被执行时。当异常被抛出时,如果没有exception filter将ExceptionContext的ExceptinHandled设置为true,mvc将调用默认的exception filter。
来自定义一个:
public
class CustomExceptionAttribute : FilterAttribute, IExceptionFilter
{
public
void OnException(ExceptionContext filterContext)
{
if(!filterContext.ExceptionHandled && filterContext.Exception
is NullReferenceException)
{
filterContext.Result =
new RedirectResult(
"
/SpecialErrorPage.html
");
filterContext.ExceptionHandled =
true;
}
}
}
可以看到,当有它捕获到异常后,跳往SpecialErrorPage.html。当异常发生时,就可以以一个人性化的页面来展示。使用如下:
[CustomException]
public ActionResult About()
{
//
object nullObj = null;
//
var str = nullObj.ToString();
return View();
}
另外,内置HandleExceptionAttribute具有ExceptionType、View、Master等属性,也可以灵活利用。如:
[HandleError(ExceptionType =
typeof(NullReferenceException), Master =
null, View =
"
NullRefer
")]
public ActionResult IamError()
{
object nullObj =
null;
return Content(nullObj.ToString());
}
它可以捕获IamError内部的空引用异常,同时将以NullRefer页面来替代黄页。看页面代码:
@Model HandleErrorInfo
@{
ViewBag.Title =
"
Sorry, there was a problem!
";
}
<p>
There was a <b>@Model.Exception.GetType().Name</b>
while rendering <b>@Model.ControllerName</b>
'
s
<b>@Model.ActionName</b> action.
</p>
<p>
The exception message
is: <b><@Model.Exception.Message></b>
</p>
<p>Stack trace:</p>
<pre>@Model.Exception.StackTrace</pre>
注意标黄部分,HandleErrorInfo是一个内置ViewModel,它宝航Exception、ControllerName、ActionName等属性。
3. Action Filter
先看IActionFilter接口的定义:
public
interface IActionFilter
{
void OnActionExecuting(ActionExecutingContext filterContext);
void OnActionExecuted(ActionExecutedContext filterContext);
}
它们一个在action执行之前执行,一个在action执行之后执行。如下:
public
class ProfileAttribute : FilterAttribute, IActionFilter
{
private Stopwatch timer;
public
void OnActionExecuting(ActionExecutingContext filterContext)
{
//
if(!filterContext.HttpContext.Request.IsSecureConnection)
//
{
//
filterContext.Result = new HttpNotFoundResult();
//
}
timer = Stopwatch.StartNew();
}
public
void OnActionExecuted(ActionExecutedContext filterContext)
{
//
do nothing
timer.Stop();
if (filterContext.Exception ==
null)
{
filterContext.HttpContext.Response.Write(
string.Format(
"
Action method elapsed time: {0}
",
timer.Elapsed.TotalSeconds));
}
}
}
你可以通过自定义action filter,来对action执行时间进行监控。也可以如上文被注释的代码那样,直接在OnActionExecuting方法中给ActionResult赋值,而在OnActionExecuted中不做任何事情。
4. Result Filter
IResultFilter接口定义:
public
interface IResultFilter
{
void OnResultExecuting(ResultExecutingContext filterContext);
void OnResultExecuted(ResultExecutedContext filterContext);
}
和action filter一样,你也可以用上述2个方法对result执行时间监控计时。mvc有个内置filter,同时实现了action filter和result filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple =
false, Inherited =
true)]
public
abstract
class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
#region IActionFilter Members
public
virtual
void OnActionExecuting(ActionExecutingContext filterContext);
public
virtual
void OnActionExecuted(ActionExecutedContext filterContext);
#endregion
#region IResultFilter Members
public
virtual
void OnResultExecuting(ResultExecutingContext filterContext);
public
virtual
void OnResultExecuted(ResultExecutedContext filterContext);
#endregion
}
利用它,可以更方便的做监控了。如下:
public
class ProfileAllAttribute : ActionFilterAttribute
{
private Stopwatch timer;
public
override
void OnActionExecuting(ActionExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public
override
void OnActionExecuted(ActionExecutedContext filterContext)
{
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format(
"
Action method elapsed time: {0}
", timer.Elapsed.TotalSeconds));
}
public
override
void OnResultExecuting(ResultExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public
override
void OnResultExecuted(ResultExecutedContext filterContext)
{
timer.Stop();
filterContext.HttpContext.Response.Write(
string.Format(
"
Action result elapsed time: {0}
", timer.Elapsed.TotalSeconds));
}
}
它可以同时输出action方法和action result消耗的时间。
5 Controller 和 filter
实际上controller类同时实现了4类filter:
public
abstract
class Controller : ControllerBase, IActionFilter, IAuthorizationFilter, IDisposable, IExceptionFilter, IResultFilter
所以,你可以在自己的controller里,重写上述多个方法,而不用再另外添加filter。
6. 注册全局filter:
如在RegisterGlobalFilters中加入:
filters.Add(
new ProfileAllAttribute());
filters.Add(
new HandleErrorAttribute()
{
ExceptionType =
typeof(NullReferenceException),
View =
"
SpecialError
"
});
即可。
7. filter排序执行:
自定义一个filter,来试探它们的执行顺序:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple =
true)]
public
class SimpleMessageAttribute : FilterAttribute, IActionFilter
{
public
string Message {
get;
set; }
public
void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Response.Write(
string.Format(
"
[Before Action: {0}]
", Message));
}
public
void OnActionExecuted(ActionExecutedContext filterContext)
{
filterContext.HttpContext.Response.Write(
string.Format(
"
[After Action: {0}]
", Message));
}
}
测试如下:
public
class SimpleController : Controller
{
//
[SimpleMessage(Message = "A")]
//
[SimpleMessage(Message = "B")]
[SimpleMessage(Message =
"
A
", Order =
2)]
[SimpleMessage(Message =
"
B
", Order =
1)]
public ActionResult Index()
{
return View();
}
}
启用备注时,注释后两行时,输出为:
before A -> Before B -> after B -> after A,但是MVC不保证每次都是这样的(A和B的执行顺序不能保证)。为了确保顺序额,如上文代码,指定Order顺序,这个时候铁定是:Before B -> Before A -> after A -> after B。
如果你在多个地方指定不同filter的order,它会优先global中的,然后是controller的,最后才是action的。
mvc还有其他一些内置的filter,如OutputCache等,这里不再描述。
源码download