ホームページ  >  記事  >  バックエンド開発  >  ASP.NET MVCフィルターの詳しい説明

ASP.NET MVCフィルターの詳しい説明

高洛峰
高洛峰オリジナル
2016-12-24 13:32:581093ブラウズ

ActionInvokerによるActionの実行処理では、ActionDescriptorを利用したActionメソッドの実行や、先ほどのModelバインディングと検証に加えて、関連するフィルター(Filter)の実行という重要なタスクもあります。 。 ASP.NET MVC のフィルターは、AOP (アスペクト指向プログラミング) に基づいた設計であり、対応するフィルターにいくつかの非ビジネス ロジックを実装し、それを横断的な方法で対応するフィルターに適用します。これらのフィルターは、Action メソッドの実行前後に自動的に実行されます。 ASP.NET MVC は、対応するフィルター インターフェイス (IAuthorizationFilter、IActionFilter、IResultFilter、および IExceptionFilter) に対応する 4 種類のフィルター (AuthorizationFilter、ActionFilter、ResultFilter、および ExceptionFilter) を提供します。 [この記事は「How ASP.NET MVC Works?」と同期されています]

目次

1、Filter

2、FilterProvider

3、FilterAttributeとFilterAttributeFilterProvider

4、ControllerとControllerInstanceFilterProvider

5、 GlobalFilterCollection

6. デモ例: フィルター提供メカニズムと実行シーケンスを検証する

1. フィルター

ASP.NET MVC が提供する 4 種類のフィルターには独自の実装インターフェイスがありますが、フィルター提供システムではすべてのフィルターが表現されます以下の定義を持つフィルタータイプによって。 Filter の中心となるのは Instance 属性です。これは、フィルタリング関数を実際に実装するオブジェクトを表すためです。このオブジェクトは、上記の 4 つのフィルタ タイプに基づいて 1 つ以上のインターフェイスを実装します。

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
}


注: System.Web.Mvc.Filter と、IAuthorizationFilter、IActionFilter、IResultFilter、および IExceptionFilter を実装する型はすべて「フィルター」と呼ぶことができるため、混乱を招かないように、明確にはしていません。説明の際には、英語の「フィルター」と中国語の「フィルター」を使ってそれぞれ表します。

フィルターの Order プロパティと Scope プロパティは、最終的にフィルターの実行順序を決定します。 Order 属性の対応する値が小さいほど、実行優先度は高くなります。この属性のデフォルト値は -1 (Filter で定義された定数 DefaultOrder に対応) です。 2 つのフィルターが同じ Order プロパティ値を持つ場合、Scope プロパティによって最終的にどちらが最初に実行されるかが決まります。 Filter の Scope プロパティ タイプは、FilterScope タイプの列挙です。この列挙はアプリケーションのフィルターのスコープを表し、アクションとコントローラーはアクション メソッドを表し、コントローラーのクラス レベルは最初と最後のフィルターとして実行されることが期待されることを意味します。

上記のコード スニペットから、FilterScope の 5 つの列挙オプションのそれぞれが値に設定されていることがわかります。この値により、小さい列挙値が最初に実行されます。 FilterScope の定義から、同じ Order 属性値を持つ複数のフィルターの場合、コントローラーに適用されるフィルターの実行優先順位が、Action メソッドに適用されるフィルターよりも高く、グローバル フィルターの実行が優先されるという結論を導き出すことができます。優先度はアクションベースのフィルターよりも高くなります。

2. FilterProvider

Filter のプロビジョニング メカニズムは、以前に紹介した ModelBinder と ModelValidator に基づくプロビジョニング メカニズムに似ており、対応するプロバイダーを通じて提供されます。次のコード スニペットに示すように、フィルターを提供する FilterProvider インターフェイス IFilterProvider は、指定されたコントローラー コンテキストとターゲット アクションの記述に使用される ActionDescriptor オブジェクトに基づいて Filter オブジェクトのコレクションを取得するための唯一のメソッド GetFilters を定義します。

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


静的型 FilterProviders を通じて、現在のアプリケーションで使用される FilterProvider を登録または取得できます。次のコード スニペットに示すように、FilterProviders には FilterProviderCollection 型の読み取り専用属性 Providers があり、これは Web アプリケーション全体内で使用される FilterProvider のリストを表します。 FilterProviderCollection は、要素タイプが IFilterProvider であるコレクションです。GetFilters メソッドは、コレクション内のすべての FilterProvider オブジェクトによって提供される Filter オブジェクトに使用されます。

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


ASP.NET MVC は、FilterAttributeFilterProvider、ControllerInstanceFilterProvider、GlobalFilterCollection という 3 つのネイティブ FilterProvider を提供します。次に、それらを個別に紹介します。

3. FilterAttribute と FilterAttributeFilterProvider

通常、フィルターは、宣言的な方法でコントローラー タイプまたはアクション メソッドに適用される属性として定義され、抽象タイプ FilterAttribute はすべてのフィルターの基本クラスです。以下のコード スニペットに示すように、FilterAttribute 属性は IMvcFilter インターフェイスを実装します。このインターフェイスは、フィルターの実行順序を制御し、同じタイプの複数のフィルターを適用できるようにするために使用される 2 つの読み取り専用属性、Order とAllowMultiple を定義します。同時に同じフィルターをターゲット要素 (クラスまたはメソッド) に適用します。

りー


从应用在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); 
}

   


针对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);
}

   


对于通过调用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,
   ...
{
  //省略成员
}

   


针对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); 
}

   


五、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; }
}

   


通过命名以及上面给出的定义可以看出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; }
}

   


到目前为止,我们已经介绍了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;}
}

   


六、实例演示:验证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
{}

   


我们首先在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());
  }
}

   


最后我们创建如下一个默认的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()
  { }
}

   


运行我们的程序之后会在浏览器中呈现如图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()));
  }
}

   


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

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

   


我们再次运行我们的程序,并在浏览器上指定正确的地址访问定义在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)
  { }
}

   


现在我们将该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());
  }
}

   


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

ASP.NET MVC的筛选器

AllowMultiple属性がFalseのFilterAttributeについて、スコープの異なるものを複数登録した場合、最終的にどれが有効になるのでしょうか?上の図からわかるように、Action メソッド (スコープが Action) に適用された FooAttribute は有効です。実際、具体的なロジックは次のとおりです。作成されたすべてのフィルターは Order+Scope (つまり、フィルターが実行される順序) に従って並べ替えられ、最後のフィルターが選択されます。この例では、提供された 3 つのフィルターは同じ Order 属性値 (-1) を持ち、最終的にはすべてスコープ (スコープ、コントローラー、アクション) に従って並べ替えられます。最後のフィルターは当然、スコープが Action です。

以上がこの記事の内容です。この記事の内容が皆さんの勉強や仕事に少しでも役立つことを願っています。ご質問があれば、メッセージを残していただければ幸いです。 PHPの中国語サイトです!


ASP.NET MVC フィルターに関する詳細な記事については、PHP 中国語 Web サイトに注目してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。