Detailed explanation of ASP.NET MVC filters

高洛峰
Release: 2016-12-24 13:32:58
Original
1093 people have browsed it

In the execution process of Action by ActionInvoker, in addition to the execution of Action method by using ActionDescriptor, as well as the previous Model binding and verification, there is also an important task, that is, the execution of related filters (Filter) . The filter of ASP.NET MVC is a design based on AOP (aspect-oriented programming). We implement some non-business logic in the corresponding filter, and then apply it to the corresponding filter in a crosscutting way. Action method. These filters are automatically executed before and after the Action method is executed. ASP.NET MVC provides four types of filters (AuthorizationFilter, ActionFilter, ResultFilter and ExceptionFilter), which correspond to the corresponding filter interfaces (IAuthorizationFilter, IActionFilter, IResultFilter and IExceptionFilter). [This article has been synchronized to "How ASP.NET MVC Works?"]

Table of Contents

1, Filter

2, FilterProvider

3, FilterAttribute and FilterAttributeFilterProvider

4, Controller and ControllerInstanceFilterProvider

5, GlobalFilterCollection

6. Example demonstration: Verify the filter provision mechanism and execution sequence

1. Filter

Although the four types of filters provided by ASP.NET MVC have their own implemented interfaces, for the filter provision system all Filters are represented by the Filter type with the following definition. The core of Filter is the Instance attribute, because it represents the object that actually implements the filtering function. This object implements one or more interfaces based on the above four filter types.

public class Filter
{ 
  public const int DefaultOrder = -1; 
  public Filter(object instance, FilterScope scope, int? order);
   
  public object Instance { get; protected set; }
  public int Order { get; protected set; }
  public FilterScope Scope { get; protected set; }
}
public enum FilterScope
{
  Action    = 30,
  Controller  = 20,
  First     = 0,
  Global    = 10,
  Last     = 100
}
Copy after login


Note: Since System.Web.Mvc.Filter and the types that implement IAuthorizationFilter, IActionFilter, IResultFilter and IExceptionFilter can all be called "filters", in order not to cause confusion, they are not made clear In the case of explanation, we use the English "Filter" and the Chinese "filter" to represent them respectively.

Filter’s Order and Scope properties ultimately determine the execution order of the filter. The smaller the corresponding value of the Order attribute, the higher the execution priority. The default value of this attribute is -1 (corresponding to the constant DefaultOrder defined in Filter). If two Filters have the same Order property value, the Scope property ultimately determines which one is executed first. The Scope property type of Filter is an enumeration of type FilterScope. This enumeration represents the scope of applying Filter. Action and Controller represent the Action method and Controller class level; First and Last mean that it is expected to be executed as the first and last Filter; Global represents a global Filter.

Through the above code snippet, we can see that each of the 5 enumeration options of FilterScope is set to a value. This value determines the execution order of Filter. Smaller enumeration values ​​will be executed first. From the definition of FilterScope, we can draw the conclusion that for multiple Filters with the same Order attribute value, the Filter applied to the Controller has a higher execution priority than the Filter applied to the Action method, and the execution of a global Filter The priority is higher than Action-based Filter.

2. FilterProvider

Filter’s provisioning mechanism is similar to the provisioning mechanism based on ModelBinder and ModelValidator we introduced before, and is provided through the corresponding Provider. The FilterProvider that provides filters implements the interface IFilterProvider, as shown in the following code snippet, which defines the only method GetFilters to obtain a collection of Filter objects based on the specified Controller context and the ActionDescriptor object used to describe the target Action.

public interface IFilterProvider
{ 
  IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}
Copy after login


We can register or get the FilterProvider used by the current application through static type FilterProviders. As shown in the following code snippet, FilterProviders has a read-only attribute Providers of type FilterProviderCollection, which represents a list of FilterProviders used within the entire Web application. FilterProviderCollection is a collection whose element type is IFilterProvider. The GetFilters method is used for the Filter objects provided by or all FilterProvider objects in the collection.

public static class FilterProviders
{ 
  public static FilterProviderCollection Providers { get; }
}
public class FilterProviderCollection : Collection<IFilterProvider>
{  
  //其他成员
  public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor); 
}
Copy after login


ASP.NET MVC provides three native FilterProviders, namely FilterAttributeFilterProvider, ControllerInstanceFilterProvider and GlobalFilterCollection. Next, we will introduce them separately.

3. FilterAttribute and FilterAttributeFilterProvider

We usually define filters as attributes that are applied to Controller types or Action methods in a declarative manner, and the abstract type FilterAttribute is the base class of all filters. As shown in the code snippet below, the FilterAttribute attribute implements the IMvcFilter interface, which defines two read-only attributes, Order and AllowMultiple, which are used to control the execution order of filters and allow multiple filters of the same type to be applied to the same filter at the same time. Target element (class or method).

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
public abstract class FilterAttribute : Attribute, IMvcFilter
{ 
  protected FilterAttribute();
   
  public bool AllowMultiple { get; }
  public int Order { get; set; }
}
public interface IMvcFilter
{ 
  bool AllowMultiple { get; }
  int Order { get; }
}
Copy after login


从应用在FilterAttribute上的AttributeUsageAttribute的定义可以看出该特性可以应用在类型和方法上,这意味着筛选器一般都可以应用在Controller类型和Action方法上。只读属性AllowMultiple实际上返回的是AttributeUsageAttribute的同名属性,通过上面的定义我们可以看到默认情况下该属性值为False。

用于描述Controller和Action的ControllerDescriptor和ActionDescriptor均实现了ICustomAttributeProvider接口,我们可以直接利用它们获取应用在对应的Controller类型或者Action方法上包括FilterAttribute在内的所有特性。实际上,这两个描述类型提供了单独的方法GetFilterAttributes专门用于获取FilterAttribute特性列表。如下面的代码片断所示,该方法具有一个布尔类型的参数useCache,表示是否需要对解析出来的FilterAttribute特性进行缓存以缓解频繁的反射操作对性能造成的影响。

public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
  //其他成员
  public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);
}
public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{ 
  //其他成员
  public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache); 
}
Copy after login


针对FilterAttribute特性的Filter通过FilterAttributeFilterProvider对象来提供。FilterAttributeFilterProvider直接调用当前ControllerDescriptor和ActionDescriptor的GetFilterAttributes方法获取所有应用在Controller类型和当前Action方法的FilterAttribute特性,并借此创建相应的Filter对象。FilterAttributeFilterProvider构造函数的参数cacheAttributeInstances表示是否启用针对FilterAttribute的缓存,它将作为调用GetFilterAttributes方法的参数。在默认的情况下(通过调用默认无参的构造函数创建的FilterAttributeFilterProvider)会采用针对FilterAttribute的缓存。

public class FilterAttributeFilterProvider : IFilterProvider
{
  public FilterAttributeFilterProvider();
  public FilterAttributeFilterProvider(bool cacheAttributeInstances);
  protected virtual IEnumerable<FilterAttribute> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
  protected virtual IEnumerable<FilterAttribute> GetControllerAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
  public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}
Copy after login


对于通过调用GetFilters得到的Filter,对应的FilterAttribute特性作为其Instance属性。Order属性来源于FilterAttribute的同名属性,而Scope属性则取决于FilterAttribute特性是应用在Controller类型上(Scope属性值为Controller)还是当前的Action方法上(Scope属性值为Action)。

四、Controller与ControllerInstanceFilterProvider

提到ASP.NET MVC的筛选器,大部分的都只会想到通过FilterAttribute特性,实际上Controller本身(继承自抽象类Controller)就是一个筛选器。如下面的代码片断所示,抽象类Controller实现了IActionFilter、IAuthorizationFilter、IExceptionFilter和IResultFilter这四个对应着不同筛选器类型的接口。

public abstract class Controller : ControllerBase,
  IActionFilter,
  IAuthorizationFilter,
  IExceptionFilter,
  IResultFilter,
   ...
{
  //省略成员
}
Copy after login


针对Controller对象这种独特筛选器的FilterProvider类型为具有如下定义的ControllerInstanceFilterProvider。在实现的GetFilters方法中,它会根据指定的Controller上下文获取对应的Controller对象,并以此创建一个Filter(Controller对象作为Filter对象的Instance属性值)。该Filter的Scope不是Controller,而是First,而Order的值为-2147483648(Int32.MinValue),毫无疑问这样的Filter肯定第一个被执行。

public class ControllerInstanceFilterProvider : IFilterProvider
{ 
  public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor); 
}
Copy after login


五、GlobalFilterCollection

通过FilterAttribute的形式定义的筛选器需要显式地标注到目标Controller类型或者Action方法上,而在有些情况下需要一种全局的Filter。所谓全局筛选器,就是不需要显式与某个Controller或者Action进行匹配,而是默认使用到所有的Action执行过程中。用于提供这种全局Filter的FilterProvider对应的类型为具有如下定义的GlobalFilterCollection。

public sealed class GlobalFilterCollection : IEnumerable<Filter>, IEnumerable, IFilterProvider
{
  public GlobalFilterCollection();
  public void Add(object filter);
  public void Add(object filter, int order);
  private void AddInternal(object filter, int? order);
  public void Clear();
  public bool Contains(object filter);
  public IEnumerator<Filter> GetEnumerator();
  public void Remove(object filter);
  IEnumerator IEnumerable.GetEnumerator();
  IEnumerable<Filter> IFilterProvider.GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
  public int Count { get; }
}
Copy after login


通过命名以及上面给出的定义可以看出GlobalFilterCollection就是一个Filter的列表而已,实现的GetFilters方法返回的就是它自己。通过GlobalFilterCollection提供的方法我们可以实现对全局Filter的添加、删除和清除操作。用于添加Filter的Add方法的参数filter不是一个Filter对象,而是一个具体筛选器(实现了相应的筛选器接口),添加的Filter对象根据该筛选器对象创建,其Scope属性被设置成Global。我们通过在Add方法指定添加Filter对象的Order属性,如果没有显示指定Order并且指定的筛选器是一个FilterAttribute特性,那么该特性的Order将会作为Filter对象的Order;否则使用-1作为Order属性值。

针对整个Web应用的全局Filter(或者说全局FilterProvider)的注册和获取可以通过静态类型GlobalFilters来实现。如下面的代码片断所示,GlobalFilters具有一个静态只读属性Filters返回一个GlobalFilterCollection对象。

public static class GlobalFilters
{ 
  public static GlobalFilterCollection Filters { get; }
}
Copy after login


到目前为止,我们已经介绍了ASP.NET MVC默认提供的三种FilterProvider,以及各自采用得Filter提供机制。当用于注册FilterProvider的静态类型在加载的时候,会默认创建这三种类型的对象并将其作为表示全局FilterProvider集合的Providers属性值,具体的逻辑体现在如下的代码片断中。也就是说,在默认的情况下ASP.NET MVC会采用这三种FilterProvider来提供所有的Filter对象。

public static class FilterProviders
{
  static FilterProviders()
  {
    Providers = new FilterProviderCollection();
    Providers.Add(GlobalFilters.Filters);
    Providers.Add(new FilterAttributeFilterProvider());
    Providers.Add(new ControllerInstanceFilterProvider());
  }
  public static FilterProviderCollection Providers{get;private set;}
}
Copy after login


六、实例演示:验证Filter的提供机制和执行顺序

为了让读者对上面介绍的Filter提供机制具有一个更加深刻的映像,我们来做一个简单的实例演示。在一个通过Visual Studio的ASP.NET MVC项目模板创建的空Web项目中,我们定义了如下一个几个FilterAttribute。FilterBaseAttribute是一个实现了IActionFilter接口的抽象类型,三个具体的FilterAttribute(FooAttribute、BarAttribute和BazAttribute)是它的继承者。

public abstract class FilterBaseAttribute:FilterAttribute, IActionFilter
{
  public void OnActionExecuted(ActionExecutedContext filterContext)
  {}
  public void OnActionExecuting(ActionExecutingContext filterContext)
  {}
}
public class FooAttribute : FilterBaseAttribute
{}
public class BarAttribute : FilterBaseAttribute
{}
public class BazAttribute : FilterBaseAttribute
{}
Copy after login


我们首先在Global.asax中通过如下的方式将BazAttribute注册为一个全局筛选器。需要注意的是定义在默认创建的Global.asax中的Application_Start方法会调用RegisterGlobalFilters方法注册一个类型为HandleErrorAttribute的ExceptionFilter,我们需要将这行代码注释。

public class MvcApplication : System.Web.HttpApplication
{
  //其他成员
  protected void Application_Start()
  {   
    //其他操作
    //RegisterGlobalFilters(GlobalFilters.Filters);   
    GlobalFilters.Filters.Add(new BazAttribute());
  }
}
Copy after login


最后我们创建如下一个默认的HomeController,一个空的Action方法Data上应用了我们定义的BarAttribute特性,而HomeController类上则应用了FooAttribute特性。在默认的Action方法Index中,我们通过FilterProviders的静态属性Providers表示的全局FilterProvider列表得到针对于Action方法Data的所有Filter对象,并将它们的基本信息(类型、Order和Scope属性)呈现出来。

[Foo]
public class HomeController : Controller
{
  public void Index()
  {
    ReflectedControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(typeof(HomeController));
    ActionDescriptor actionDescriptor = controllerDescriptor.FindAction(ControllerContext, "Data");
    foreach (var filter in FilterProviders.Providers.GetFilters(ControllerContext, actionDescriptor))
    {
      Response.Write(string.Format("{0}<br/>",filter.Instance));
      Response.Write(string.Format("    {0}: {1}<br/>", "Order",filter.Order));
      Response.Write(string.Format("    {0}: {1}<br/><br/>", "Scope",filter.Scope));
    }
  }
  [Bar]
  public void Data()
  { }
}
Copy after login


运行我们的程序之后会在浏览器中呈现如图7-5所示的结果。我们可以清楚地看到,不仅仅应用在自身Action方法的FilterAttribute会应用到目标Action上,应用在Controller类的FilterAttribute、全局注册的Filter以及Controller对象本身体现的Filter都回最终应用在所有的Action上面。


上图将应用于Action方法Data的4个Filter的Order和Scope属性显示出来。我们在前面提到这两个属性决定了同类筛选器执行的顺序,我们现在利用这个程序要证实这一点。为此我们需要对FilterBaseAttribute作如下的修改,在OnActionExecuting中我们将当前执行的FilterAttribute的类型的方法名呈现出来。

public abstract class FilterBaseAttribute:FilterAttribute, IActionFilter
{
  public void OnActionExecuted(ActionExecutedContext filterContext)
  {}
  public void OnActionExecuting(ActionExecutingContext filterContext)
  {
    filterContext.HttpContext.Response.Write(string.Format("{0}.OnActionExecuting()<br/>", this.GetType()));
  }
}
Copy after login


然后我们按照相同的方式重写了HomeController的OnActionExecuting方法,将HomeController自身的类型的当前方法名称呈现出来。

[Foo]
public class HomeController : Controller
{
  //其他成员
  protected override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    Response.Write("HomeController.OnActionExecuting()<br/>");
  }
  [Bar]
  public void Data()
  { }
}
Copy after login


我们再次运行我们的程序,并在浏览器上指定正确的地址访问定义在HomeController的Action方法Data,会在浏览器中呈现如下图所示的结果。输出的结果体现了应用到Action方法Data上的四个ActionFilter执行的顺序,而这是和Filter对应的Order和Scope属性值是一致的。


关于Filter的提供还另一个值得深究的问题:我们在定义FilterAttribute的时候可以将应用在该类型上的AttributeUsageAttribute的AllowMultiple属性设置为False使它只能在同一个目标元素上应用一次。但是,我们依然可以在Action方法和所在的Controller类型上应用它们,甚至可以将它们注册为全局Filter,那么这些FilterAttribute都将有效吗?

我们现在就来通过实例来验证这一点。现在我们删除所有的FilterAttribute,定义如下一个类型为FooAttribute的ActionFilter,我们将应用在它上面的AttributeUsageAttribute特性的AllowMultiple属性设置为False。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class FooAttribute : FilterAttribute, IActionFilter
{
  public void OnActionExecuted(ActionExecutedContext filterContext)
  { }
  public void OnActionExecuting(ActionExecutingContext filterContext)
  { }
}
Copy after login


现在我们将该FooAttribute特性同时应用在HomeController类型和Action方法Data上,然后在Global.asax中注册一个针对FooAttribute特性的全局Filter。

[Foo]
public class HomeController : Controller
{
  //其他成员
  [Foo]
  public void Data()
  { }
}
public class MvcApplication : System.Web.HttpApplication
{
  //其他成员
  protected void Application_Start()
  {
    //其他操作
    //RegisterGlobalFilters(GlobalFilters.Filters);
    GlobalFilters.Filters.Add(new FooAttribute());
  }
}
Copy after login

   


现在我们直接运行我们的程序,开启的浏览器中会呈现出如图7-7所示的结果。可以清楚地看到虽然我们 在三个地方注册了FooAttribute,但是由于该特性的AllowMultiple属性为False,所以只有其中一个FooAttribute最终是有效的。

ASP.NET MVC的筛选器

For the FilterAttribute whose AllowMultiple attribute is False, if we register multiple ones with different Scopes, which one will be effective in the end? As can be seen from the above figure, the FooAttribute applied to the Action method (Scope is Action) is valid. In fact, the specific logic is this: all created Filters are sorted according to Order+Scope (that is, the order in which Filters are executed), and the last one is selected. For our example, the three provided Filters have the same Order attribute value (-1), and all will eventually be sorted according to Scope (Scope, Controller and Action). The last one is naturally the Filter whose Scope is Action. .

The above is the entire content of this article. I hope that the content of this article can bring some help to everyone's study or work. If you have any questions, you can leave a message to communicate. I also hope to support the PHP Chinese website!


For more detailed articles on ASP.NET MVC filters, please pay attention to the PHP Chinese website!


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template