Heim > Artikel > Backend-Entwicklung > Detaillierte Erläuterung der ASP.NET MVC-Filter
Während der Ausführung von Action durch ActionInvoker gibt es zusätzlich zur Ausführung der Action-Methode mithilfe von ActionDescriptor sowie der vorherigen Modellbindung und -überprüfung auch eine wichtige Aufgabe, nämlich den zugehörigen Filter (Filter). Ausführung. Der Filter von ASP.NET MVC ist ein auf AOP (Aspektorientierte Programmierung) basierendes Design. Wir implementieren einige nicht-geschäftliche Logiken in den entsprechenden Filter und wenden sie dann auf übergreifende Weise auf den entsprechenden Filter an. Diese Filter werden automatisch vor und nach der Ausführung der Action-Methode ausgeführt. ASP.NET MVC bietet vier Arten von Filtern (AuthorizationFilter, ActionFilter, ResultFilter und ExceptionFilter), die den entsprechenden Filterschnittstellen (IAuthorizationFilter, IActionFilter, IResultFilter und IExceptionFilter) entsprechen. [Dieser Artikel wurde mit „Funktionsweise von ASP.NET MVC?“ synchronisiert.]
Verzeichnis
1. Filter
3. FilterAttribute und FilterAttributeFilterProvider
4. Controller und ControllerInstanceFilterProvider
5. GlobalFilterCollection
6. Beispieldemonstration: Überprüfen Sie den Bereitstellungsmechanismus und die Ausführungssequenz von Filter
1. Filter
Obwohl die vier von ASP.NET MVC bereitgestellten Filtertypen über eigene implementierte Schnittstellen verfügen, werden für das Filterbereitstellungssystem alle Filter durch den Filtertyp mit der folgenden Definition dargestellt. Der Kern von Filter ist das Instanzattribut, da es das Objekt darstellt, das die Filterfunktion tatsächlich implementiert. Dieses Objekt implementiert eine oder mehrere Schnittstellen basierend auf den oben genannten vier Filtertypen.
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 }
Hinweis: Da System.Web.Mvc.Filter und die Typen, die IAuthorizationFilter, IActionFilter, IResultFilter und IExceptionFilter implementieren, alle aufgerufen werden können „Filter“. Um keine Verwirrung zu stiften, verwenden wir zur Darstellung jeweils das englische „Filter“ und das chinesische „Filter“, ohne explizite Erklärung.
Die Eigenschaften „Reihenfolge“ und „Bereich“ des Filters bestimmen letztendlich die Ausführungsreihenfolge des Filters. Je kleiner der entsprechende Wert des Order-Attributs ist, desto höher ist die Ausführungspriorität. Der Standardwert dieses Attributs ist -1 (entsprechend der in Filter definierten Konstante DefaultOrder). Wenn zwei Filter denselben Order-Eigenschaftswert haben, bestimmt die Scope-Eigenschaft letztendlich, welcher zuerst ausgeführt wird. Der Scope-Eigenschaftstyp von Filter ist eine Aufzählung vom Typ FilterScope. Diese Aufzählung stellt den Anwendungsbereich von Filter dar. Action und Controller stellen die Action-Methode dar, und die Controller-Klassenebene bedeutet, dass sie als erster und letzter Filter ausgeführt werden soll.
Anhand des obigen Codeausschnitts können wir sehen, dass jede der fünf Aufzählungsoptionen auf einen Wert festgelegt ist. Dieser Wert bestimmt die Ausführungsreihenfolge von Filter. Kleinere Aufzählungswerte werden zuerst ausgeführt. Aus der Definition von FilterScope können wir die folgende Schlussfolgerung ziehen: Bei mehreren Filtern mit demselben Order-Attributwert hat der auf den Controller angewendete Filter eine höhere Ausführungspriorität als der auf die Action-Methode angewendete Filter und die Ausführung eines globalen Filters Die Priorität ist höher als beim aktionsbasierten Filter.
2. FilterProvider
Der Bereitstellungsmechanismus von Filter ähnelt dem Bereitstellungsmechanismus basierend auf ModelBinder und ModelValidator, den wir zuvor eingeführt haben, und wird über den entsprechenden Anbieter bereitgestellt. Der FilterProvider, der Filter bereitstellt, implementiert die Schnittstelle IFilterProvider, wie im folgenden Codeausschnitt gezeigt, der die einzige Methode GetFilters definiert, um eine Sammlung von Filterobjekten basierend auf dem angegebenen Controller-Kontext und dem ActionDescriptor-Objekt zu erhalten, das zur Beschreibung der Zielaktion verwendet wird.
public interface IFilterProvider { IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor); }
Wir können den von der aktuellen Anwendung verwendeten FilterProvider über FilterProvider vom statischen Typ registrieren oder abrufen. Wie im folgenden Codeausschnitt gezeigt, verfügt FilterProviders über ein schreibgeschütztes Attribut Providers vom Typ FilterProviderCollection, das eine Liste von FilterProviders darstellt, die in der gesamten Webanwendung verwendet werden. FilterProviderCollection ist eine Sammlung, deren Elementtyp IFilterProvider ist. Die GetFilters-Methode wird für die Filterobjekte verwendet, die von oder allen FilterProvider-Objekten in der Sammlung bereitgestellt werden.
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 bietet drei native FilterProvider, nämlich FilterAttributeFilterProvider, ControllerInstanceFilterProvider und GlobalFilterCollection.
3. FilterAttribute und FilterAttributeFilterProvider
Wir definieren Filter normalerweise als Attribute, die deklarativ auf Controller-Typen oder Aktionsmethoden angewendet werden, und der abstrakte Typ FilterAttribute ist die Basisklasse aller Filter. Wie im Codeausschnitt unten gezeigt, implementiert das FilterAttribute-Attribut die IMvcFilter-Schnittstelle, die zwei schreibgeschützte Attribute definiert, Order und AllowMultiple, die zur Steuerung der Ausführungsreihenfolge von Filtern verwendet werden und die Anwendung mehrerer Filter desselben Typs ermöglichen denselben Filter gleichzeitig verwenden.
[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; } }
从应用在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最终是有效的。
Wenn wir für das FilterAttribute, dessen AllowMultiple-Attribut False ist, mehrere mit unterschiedlichen Scopes registrieren, welches wird am Ende wirksam sein? Wie aus der obigen Abbildung ersichtlich ist, ist das auf die Action-Methode angewendete FooAttribute (Scope ist Action) gültig. Tatsächlich ist die spezifische Logik folgende: Alle erstellten Filter werden nach Reihenfolge + Umfang sortiert (d. h. der Reihenfolge, in der der Filter ausgeführt wird), und der Filter mit dem letzten Rang wird ausgewählt. In unserem Beispiel haben die drei bereitgestellten Filter den gleichen Order-Attributwert (-1) und alle werden schließlich nach Scope (Scope, Controller und Action) sortiert. Der letzte ist natürlich der Filter, dessen Scope Action ist.
Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels jedem beim Lernen oder Arbeiten helfen kann. Wenn Sie Fragen haben, können Sie mir auch eine Nachricht hinterlassen Ich hoffe, die chinesische PHP-Website unterstützen zu können!
Ausführlichere Artikel zu ASP.NET MVC-Filtern finden Sie auf der chinesischen PHP-Website!