ASP.NET MVC - Filter (过滤器) - 源代码分析

创建时间:
2018-09-16 23:50
最近更新:
2018-09-17 00:06

ASP.NET MVC Framework支持以下四种不同类型的Filter

Authorization filters – 实现IAuthorizationFilter接口的属性
Action filters – 实现IActionFilter接口的属性
Result filters – 实现IResultFilter接口的属性
Exception filters – 实现IExceptionFilter接口的属性

从广义上来说,在ASP.NET MVC Framework中,任何实现filter的类型都是action filter。
为了让用户更简单的创建一个自定义action filter,ASP.NET MVC Framework提供了一个基类:
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
注意abstract:不能实例化,只能继承它。

filter相关接口方法的执行顺序

如上所述,与filter相关的总共有四个接口、六个方法,这些方法如果在同一个类中实现时,优先级如下:
IAuthorizationFilter > IActionFilter > IResultFilter > IExceptionFilter
下面的程序用来验证这个顺序:

public class TestOrderAttribute : FilterAttribute, IActionFilter, IResultFilter, IAuthorizationFilter, IExceptionFilter
{
    //IActionFilter 成员
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        Write("OnActionExecuted");
    }
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        Write("OnActionExecuting");
    }

    //IResultFilter 成员
    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        Write("OnResultExecuting");
    }
    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        Write("OnResultExecuted");
    }

    //IAuthorizationFilter 成员
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        Write("OnAuthorization");
    }

    //IExceptionFilter 成员
    public void OnException(ExceptionContext filterContext)
    {
        Write("OnException");
        //Let the system know that the exception has been handled:
        filterContext.ExceptionHandled = true;
    }

    private static void Write(string methodName)
    {
        StreamWriter sw = new StreamWriter("c:\\test.txt", true);
        sw.WriteLine(methodName);
        sw.Close();
    }
}

调用:

[TestOrder]
public ActionResult TestFilterOrder()
{
    // throw new Exception("lfm");
    return View();
}

页面:

@(throw new Exception("异常出现"))

结果:

c:\test.txt内容为:
OnAuthorization
OnActionExecuting
OnActionExecuted
OnResultExecuting
OnResultExecuted
OnException

当filter应用于action之上时:
执行 OnActionExecuting
执行 Action
执行 OnActionExecuted
执行 OnResultExecuting
执行 Action返回的ActionRsult的executeResult()
执行 OnResultExecuted

controller, action 二处的filter的执行次序:
执行 OnActionExecuting of controller
执行 OnActionExecuting of action
执行 Action
执行 OnActionExecuted of action
执行 OnActionExecuted of controller
执行 OnResultExecuting of controller
执行 OnResultExecuting of action
执行 Action返回的ActionRsult的ExecuteResult()
执行 OnResultExecuted of action
执行 OnResultExecuted of controller

global, controller, action 三处的filter的执行次序:
执行 OnActionExecuting of global
执行 OnActionExecuting of controller
执行 OnActionExecuting of action
执行 Action
执行 OnActionExecuted of action
执行 OnActionExecuted of controller
执行 OnActionExecuted of global
执行 OnResultExecuting of global
执行 OnResultExecuting of controller
执行 OnResultExecuting of action
执行 Action返回的ActionRsult的ExecuteResult()
执行 OnResultExecuted of action
执行 OnResultExecuted of controller
执行 OnResultExecuted of global

当filter应用于controller之上时,将作用于controller下的所有action。
当filter应用于action之上、同时重复应用于controller和/或global之上时,只有action上的生效。如需同时生效,必须将filter定义为AllowMultiple=true。

filter相关接口方法的执行顺序 的 另一组测试

public class TestActionFilterAttribute : 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 />");
    }
}

//调用示例:

public class TestTestActionFilterAttribute : Controller
{
    //如果我们将此Attribute附在Controller上,将作用于其下的所有的Action。
    [TestActionFilterAttribute(Message = "Action")]
    public ActionResult Index()
    {
        HttpContext.Response.Write("Action正在执行...<br />");
        return Content("正在返回Result...<br />");
    }
}

//上述Action执行结果:
//Action执行之前Action
//Action正在执行...
//Action执行之后Action
//返回Result之前Action
//正在返回Result...
//返回Result之后Action

RegisterGlobalFilters

在Global.asax中:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());
    //注册全局过滤器
    filters.Add(new TestFilterAttribute() { Message = "全局" });
}

在Web.config中配置全局过滤器:

<GlobalFilter>
    <Allfilters>
        <filter name="GlobalFilterTest.FilterProvider.ActionLogFilterAttribute" assembly="GlobalFilterTest">
            <except controller="Account" action="LogOn"/>
            <except controller="Home" action="*"/>
        </filter>
    </Allfilters>
</GlobalFilter>

其中:
<filter></filter>节点用于配置一个过滤器
name为需要全局注册的Filter类的类名(格式为 命名空间.类名)
assembly为该Filter类的程序集名称
<except/>节点用于设置该过滤器不需要应用的Controller和Action,如果该Filter需要全局注册,不需要配置该节点;否则,需要按情况依次配置不需要应用的Controller和Action。其中 * 表示该Controller或者Action中所有的情况。
例子中的配置表示:
ActionLogFilterAttribute需要全局注册,但名为Account的Controller下的名为LogOn的Action不需要添加,名为Home的Controller下的所有Action都不需要添加。

/// <summary>
/// T: 这是一个GlobalFilter的示例,用于将未登录的用户重定向至/Account/Login。
/// T: 注意,对于Account控制器内的所有action,将不执行重定向。
/// </summary>
public class ValidateSessionStateAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if(filterContext.RouteData.Values["controller"].ToString() == "Account") {
            return;
        }
        var session = filterContext.HttpContext.Session["Login"];
        if(session == null) {
            filterContext.Result = new RedirectResult("/Account/Login");
        }
        //base.OnActionExecuting(filterContext);
    }
}

以下类/接口的公共信息

命名空间: System.Web.Mvc
程序集: System.Web.Mvc(在 System.Web.Mvc.dll 中)

Filter Inheritance Hierarchy

"B250_G:\z\D1526Backup\CodeAccumulation\AspNet & C#\Mvc\FilterInheritanceHierarchy.png"

IMvcFilter

Defines members that specify the order of filters and whether multiple filters are allowed.
全部源码:

public interface IMvcFilter
{
    bool AllowMultiple { get; }
    int Order { get; }
}

IActionFilter

Defines the methods that are used in an action filter.
全部源码:

public interface IActionFilter
{
    void OnActionExecuting(ActionExecutingContext filterContext);
    void OnActionExecuted(ActionExecutedContext filterContext);
}

IResultFilter

Defines the methods that are required for a result filter.
全部源码:

public interface IResultFilter
{
    void OnResultExecuting(ResultExecutingContext filterContext);
    void OnResultExecuted(ResultExecutedContext filterContext);
}

FilterAttribute (派生自 Attribute, IMvcFilter)

Represents the base class for action and result filter attributes.

全部源码:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public abstract class FilterAttribute : Attribute, IMvcFilter
{
    private readonly static ConcurrentDictionary<Type, bool> _multiuseAttributeCache = new ConcurrentDictionary<Type, bool>();
    private int _order = Filter.DefaultOrder;

    private static bool AllowsMultiple(Type attributeType)
    {
        return _multiuseAttributeCache.GetOrAdd(
            attributeType,
            type => type.GetCustomAttributes(typeof(AttributeUsageAttribute), true)
                        .Cast<AttributeUsageAttribute>()
                        .First()
                        .AllowMultiple
        );
    }

    public bool AllowMultiple
    {
        get
        {
            return AllowsMultiple(GetType());
        }
    }

    public int Order
    {
        get
        {
            return _order;
        }
        set
        {
            if(value < Filter.DefaultOrder) {
                throw new ArgumentOutOfRangeException("value", MvcResources.FilterAttribute_OrderOutOfRange);
            }
            _order = value;
        }
    }
}

继承层次结构

System.Object
    System.Attribute
        System.Web.Mvc.FilterAttribute
            System.Web.Mvc.ActionFilterAttribute
            System.Web.Mvc.AuthorizeAttribute
            System.Web.Mvc.ChildActionOnlyAttribute
            System.Web.Mvc.HandleErrorAttribute
            System.Web.Mvc.RequireHttpsAttribute
            System.Web.Mvc.ValidateAntiForgeryTokenAttribute
            System.Web.Mvc.ValidateInputAttribute

自定义:NoCacheFilterAttribute

/*
以下ActionFilter,在实现的OnActionExecuted方法中,我们调用当前Response的SetCacheability()将缓存选项设置为NoCache。
该Attribute被应用到Controller.Action方法后,客户端将不再缓存对此Action的响应。

SetCacheability(HttpCacheability.NoCache)最终控制响应的Cache-Control报头,并将其设置为"no-cache",指示浏览器不要对结果进行缓存。例如:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Thu, 03 Jan 2013 12:54:56 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 4.0
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/html; charset=utf-8
Content-Length: 10
Connection: Close
8:54:56 PM

Tony测试发现,此特性应用于VerificationCode后,点击<img src="省略" onclick="this.src=this.src"/>并不能让浏览器发出新的请求。
*/

public class NoCacheFilterAttribute : FilterAttribute, IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    }
    public void OnActionExecuting(ActionExecutingContext filterContext) { }
}

ActionFilterAttribute (派生自 FilterAttribute, IActionFilter, IResultFilter)

Represents the base class for filter attributes.

全部源码:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
    //The OnXxx() methods are virtual rather than abstract so that a developer need override only the ones that interest him.
    public virtual void OnActionExecuting(ActionExecutingContext filterContext) { }
    public virtual void OnActionExecuted (ActionExecuted Context filterContext) { }
    public virtual void OnResultExecuting(ResultExecutingContext filterContext) { }
    public virtual void OnResultExecuted (ResultExecuted Context filterContext) { }
}

继承层次结构

System.Object
    System.Attribute
        System.Web.Mvc.FilterAttribute
            System.Web.Mvc.ActionFilterAttribute
                System.Web.Mvc.AsyncTimeoutAttribute
                System.Web.Mvc.OutputCacheAttribute

AsyncTimeoutAttribute (派生自 ActionFilterAttribute)

全部源码:

[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed so that subclassed types can set properties in the default constructor.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class AsyncTimeoutAttribute : ActionFilterAttribute
{
    // duration is specified in milliseconds
    public AsyncTimeoutAttribute(int duration)
    {
        if(duration < -1) {
            throw Error.AsyncCommon_InvalidTimeout("duration");
        }
        Duration = duration;
    }

    public int Duration { get; private set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if(filterContext == null) {
            throw new ArgumentNullException("filterContext");
        }

        IAsyncManagerContainer container = filterContext.Controller as IAsyncManagerContainer;
        if(container == null) {
            throw Error.AsyncCommon_ControllerMustImplementIAsyncManagerContainer(filterContext.Controller.GetType());
        }

        container.AsyncManager.Timeout = Duration;

        base.OnActionExecuting(filterContext);
    }
}

IAuthorizationFilter

Defines the methods that are required for an authorization filter. 定义授权筛选器所需的方法。
全部源码:

public interface IAuthorizationFilter
{
    //Called when authorization is required.
    void OnAuthorization(AuthorizationContext filterContext);
}

ChildActionOnlyAttribute (派生自 FilterAttribute, IAuthorizationFilter)

全部源码:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ChildActionOnlyAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if(filterContext == null) {
            throw new ArgumentNullException("filterContext");
        }
        if(!filterContext.IsChildAction) {
            throw System.Web.Mvc.Error.ChildActionOnlyAttribute_MustBeInChildRequest(filterContext.ActionDescriptor);
        }
    }
}

RequireHttpsAttribute (派生自 FilterAttribute, IAuthorizationFilter)

全部源码:

[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Unsealed because type contains virtual extensibility points.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class RequireHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        if(filterContext == null) {
            throw new ArgumentNullException("filterContext");
        }
        if(!filterContext.HttpContext.Request.IsSecureConnection) {
            HandleNonHttpsRequest(filterContext);
        }
    }

    protected virtual void HandleNonHttpsRequest(AuthorizationContext filterContext)
    {
        // only redirect for GET requests, otherwise the browser might not propagate the verb and request body correctly.
        if(!String.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) {
            throw new InvalidOperationException(MvcResources.RequireHttpsAttribute_MustUseSsl);
        }
        // redirect to HTTPS version of page
        string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
        filterContext.Result = new RedirectResult(url);
    }
}

ValidateAntiForgeryTokenAttribute (派生自 FilterAttribute, IAuthorizationFilter)

全部源码:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
    private string _salt;
    public string Salt
    {
        get
        {
            return _salt ?? String.Empty;
        }
        set
        {
            _salt = value;
        }
    }

    internal Action<HttpContextBase, string> ValidateAction { get; private set; }

    public ValidateAntiForgeryTokenAttribute() : this(AntiForgery.Validate) { }

    internal ValidateAntiForgeryTokenAttribute(Action<HttpContextBase, string> validateAction)
    {
        Debug.Assert(validateAction != null);
        ValidateAction = validateAction;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if(filterContext == null) {
            throw new ArgumentNullException("filterContext");
        }
        ValidateAction(filterContext.HttpContext, Salt);
    }
}

ValidateInputAttribute (派生自 FilterAttribute, IAuthorizationFilter)

全部源码:

[SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "No compelling performance reason to seal this type.")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ValidateInputAttribute : FilterAttribute, IAuthorizationFilter
{
    public ValidateInputAttribute(bool enableValidation)
    {
        EnableValidation = enableValidation;
    }

    public bool EnableValidation { get; private set; }

    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        if(filterContext == null) {
            throw new ArgumentNullException("filterContext");
        }
        filterContext.Controller.ValidateRequest = EnableValidation;
    }
}

IExceptionFilter

Defines the methods that are required for an exception filter.

全部源码:
public interface IExceptionFilter
{
    void OnException(ExceptionContext filterContext); //Called when an exception occurs.
}

In ASP.NET MVC versions 1 and 2, the OnException(ExceptionContext) filters ran in forward order. In ASP.NET MVC version 3 and higher, the order is reversed. Exception filters in ASP.NET MVC have a similar behavior to exception handlers in the .NET Framework, which unwind from the inside out.
在 ASP.NET MVC 版本 1 和 2 中,OnException(ExceptionContext) 筛选器以正向顺序运行。 在 ASP.NET MVC 版本 3 及更高版本中,此顺序已反转。 ASP.NET MVC 中的异常筛选器的行为类似于 .NET Framework 中的异常处理程序,后者由内而外展开。

自定义:AjaxExceptionFilterAttribute

//2013-08-01 Tony copy from: http://www.cnblogs.com/wintersun/archive/2011/12/18/2291800.html

/// <summary>
/// Ajax Exception Handle Attribute
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class AjaxExceptionFilterAttribute : ActionFilterAttribute, IExceptionFilter
{
    /// <summary>
    /// Called when an exception occurs.
    /// </summary>
    public void OnException(ExceptionContext filterContext)
    {
        if(!filterContext.HttpContext.Request.IsAjaxRequest()) {
            return;
        }
        filterContext.Result = AjaxError(filterContext.Exception.Message, filterContext);
        //Let the system know that the exception has been handled
        filterContext.ExceptionHandled = true;
    }

    /// <summary>
    /// Ajaxes the error.
    /// </summary>
    protected JsonResult AjaxError(string message, ExceptionContext filterContext)
    {
        //If message is null or empty, then fill with generic message
        if(String.IsNullOrEmpty(message)) {
            message = "Something went wrong while processing your request. Please refresh the page and try again.";
        }
        //Set the response status code to 500
        filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
        //Needed for IIS7.0
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        return new JsonResult {
            //can extend more properties 
            Data = new AjaxExceptionModel() { ErrorMessage = message },
            ContentEncoding = System.Text.Encoding.UTF8,
            JsonRequestBehavior = JsonRequestBehavior.DenyGet
        };
    }
}
public class AjaxExceptionModel
{
    /// <summary>
    /// Gets or sets the error message.
    /// </summary>
    /// <value>
    /// The error message.
    /// </value>
    public string ErrorMessage { get; set; }
}

//调用示例:

[AjaxExceptionFilter]
public class HomeController : BaseController
{
    [HttpPost]
    public ActionResult YouKnow(FormCollection fc)
    {
        throw new ArgumentNullException("YouKnow method: fc should not be null");
    }
}

自定义:ExceptionFilterAttribute

public class ExceptionFilterAttribute : FilterAttribute, IExceptionFilter
{
    void IExceptionFilter.OnException(ExceptionContext filterContext)
    {
        filterContext.Controller.ViewData["ErrorMessage"] = filterContext.Exception.Message;
        filterContext.Result = new ViewResult() {
            ViewName = "Error",
            ViewData = filterContext.Controller.ViewData,
        };
        //Let the system know that the exception has been handled:
        filterContext.ExceptionHandled = true;
    }
}

调用示例:

[ExceptionFilter]
public ActionResult GetView(string name)
{
    if(name == null) {
        throw new ArgumentNullException("名字为空");
    }
    return View();
}

自定义:TimerAttribute

监控Action运行时间的Timer

public class TimerAttribute : ActionFilterAttribute
{
    public TimerAttribute()
    {
        //By default, we should be the last filter to run so we run just before and after the action method.
        this.Order = int.MaxValue;
    }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controller = filterContext.Controller;
        if(controller != null) {
            var stopwatch = new Stopwatch();
            controller.ViewData["__StopWatch"] = stopwatch;
            stopwatch.Start();
        }
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var controller = filterContext.Controller;
        if(controller != null) {
            var stopwatch = (Stopwatch)controller.ViewData["__StopWatch"];
            stopwatch.Stop();
            controller.ViewData["__Duration"] = stopwatch.Elapsed.TotalMilliseconds;
        }
    }
}

调用示例:

[Timer]
public ActionResult TestTimer()
{
    Thread.Sleep(100);
    return View();
}

页面显示:

@ViewData["__Duration"]

系统自带的filter

[AcceptVerbs(HttpVerbs.Post)] //限制页面只能以Post形式访问
[ActionName("class")] //如果不想用方法名做为Action名,或Action名为关键字,可使用此filter指定别名。
[NonAction] //当前方法仅是普通方法不解析为Action
[OutputCache(Duration = 60, VaryByParam = "*")] //为Action添加缓存
[ValidateInput(false)] //使得Action可以接受HTML等危险代码(ASP.NET MVC在aspx中设置%@Page的属性无法完成等同任务)
[ValidateAntiForgeryToken] //验证篡改

异步filter示例

//要注意,操作过滤器是同步执行的(原因很明显),但我可不想让用户在访问我的牛逼的站点之前还要等着记录日志。因此,我将使用fire and forget设计模式。

public class LogRequestAttribute : ActionFilterAttribute, IActionFilter
{
    private static LoggerDataStoreEntities DataStore = new LoggerDataStoreEntities();
    void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
    {
        //将方法排入队列以便执行。此方法在有线程池线程变得可用时执行。
        System.Threading.ThreadPool.QueueUserWorkItem(delegate {
            DataStore.AddToSiteLog(new SiteLog {
                Action = filterContext.ActionMethod.Name,
                Controller = filterContext.Controller.ToString(),
                TimeStamp = filterContext.HttpContext.Timestamp,
                IPAddress = filterContext.HttpContext.Request.UserHostAddress,
            });
            DataStore.SaveChanges();
        });
    }
}