Maison > Article > développement back-end > Explication détaillée des filtres ASP.NET MVC
Dans le processus d'exécution d'Action par ActionInvoker, en plus de l'exécution de la méthode Action à l'aide d'ActionDescriptor, ainsi que de la liaison et de la vérification du modèle précédent, il existe également une tâche importante, à savoir le filtre associé (Filtre) exécution. Le filtre d'ASP.NET MVC est une conception basée sur AOP (programmation orientée aspect). Nous implémentons une logique non métier dans le filtre correspondant, puis l'appliquons au filtre correspondant de manière transversale. Ces filtres sont automatiquement exécutés avant et après l'exécution de la méthode Action. ASP.NET MVC fournit quatre types de filtres (AuthorizationFilter, ActionFilter, ResultFilter et ExceptionFilter), qui correspondent aux interfaces de filtre correspondantes (IAuthorizationFilter, IActionFilter, IResultFilter et IExceptionFilter). [Cet article a été synchronisé avec "Comment fonctionne ASP.NET MVC ?"]
Répertoire
1 Filtre
2 FilterProvider
3. FilterAttribute et FilterAttributeFilterProvider
4. Controller et ControllerInstanceFilterProvider
5. GlobalFilterCollection
6. Exemple de démonstration : vérifier le mécanisme de fourniture et la séquence d'exécution du filtre
1. Filtre
Bien que les quatre types de filtres fournis par ASP.NET MVC aient leurs propres interfaces implémentées, pour le système de fourniture de filtres, tous les filtres sont représentés par le type Filter avec la définition suivante. Le cœur de Filter est l'attribut Instance, car il représente l'objet qui implémente réellement la fonction de filtrage. Cet objet implémente une ou plusieurs interfaces basées sur les quatre types de filtres ci-dessus.
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 }
Remarque : Puisque System.Web.Mvc.Filter et les types qui implémentent IAuthorizationFilter, IActionFilter, IResultFilter et IExceptionFilter peuvent tous être appelés "Filtre". Afin de ne pas prêter à confusion, nous utilisons respectivement le "Filter" anglais et le "Filter" chinois pour les représenter sans explication explicite.
Les propriétés Ordre et Portée du filtre déterminent en fin de compte l'ordre d'exécution du filtre. Plus la valeur correspondante de l'attribut Order est petite, plus la priorité d'exécution est élevée. La valeur par défaut de cet attribut est -1 (correspondant à la constante DefaultOrder définie dans Filter). Si deux filtres ont la même valeur de propriété Order, la propriété Scope détermine finalement lequel est exécuté en premier. Le type de propriété Scope de Filter est une énumération de type FilterScope. Cette énumération représente le champ d'application de l'application Filter, Action et Controller représentent la méthode Action et le niveau de classe Controller ; First et Last signifient qu'il est censé être exécuté en tant que premier et dernier Filter Global représente un filtre global ;
Grâce à l'extrait de code ci-dessus, nous pouvons voir que chacune des cinq options d'énumération de FilterScope est définie sur une valeur. Cette valeur détermine l'ordre d'exécution du filtre. Les valeurs d'énumération les plus petites seront exécutées en premier. De la définition de FilterScope, nous pouvons tirer la conclusion suivante : pour plusieurs Filtres avec la même valeur d'attribut Order, le Filtre appliqué au Contrôleur a une priorité d'exécution plus élevée que le Filtre appliqué à la méthode Action, et l'exécution d'un Filtre global La priorité est supérieure à celle du filtre basé sur l'action.
2. FilterProvider
Le mécanisme de fourniture de Filter est similaire au mécanisme de fourniture basé sur ModelBinder et ModelValidator que nous avons introduit précédemment et est fourni via le fournisseur correspondant. Le FilterProvider qui fournit des filtres implémente l'interface IFilterProvider, comme indiqué dans l'extrait de code suivant, qui définit la seule méthode GetFilters pour obtenir une collection d'objets Filter en fonction du contexte Controller spécifié et de l'objet ActionDescriptor utilisé pour décrire l'action cible.
public interface IFilterProvider { IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor); }
Nous pouvons enregistrer ou obtenir le FilterProvider utilisé par l'application actuelle via des FilterProviders de type statique. Comme le montre l'extrait de code suivant, FilterProviders possède un attribut en lecture seule Providers de type FilterProviderCollection, qui représente une liste de FilterProviders utilisés dans l'ensemble de l'application Web. FilterProviderCollection est une collection dont le type d'élément est IFilterProvider. La méthode GetFilters est utilisée pour les objets Filter fournis par ou pour tous les objets FilterProvider de la collection.
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 fournit trois FilterProviders natifs, à savoir FilterAttributeFilterProvider, ControllerInstanceFilterProvider et GlobalFilterCollection. Ensuite, nous effectuerons l'introduction séparément.
3. FilterAttribute et FilterAttributeFilterProvider
Nous définissons généralement les filtres comme des attributs appliqués aux types de contrôleurs ou aux méthodes d'action de manière déclarative, et le type abstrait FilterAttribute est la classe de base de tous les filtres. Comme le montre l'extrait de code ci-dessous, l'attribut FilterAttribute implémente l'interface IMvcFilter, qui définit deux attributs en lecture seule, Order et AllowMultiple, qui sont utilisés pour contrôler l'ordre d'exécution des filtres et permettre d'appliquer plusieurs filtres du même type à le même filtre en même temps. Élément cible (classe ou méthode).
[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最终是有效的。
Pour le FilterAttribute dont l'attribut AllowMultiple est False, si nous en enregistrons plusieurs avec des Scopes différents, lequel sera efficace au final ? Comme le montre la figure ci-dessus, le FooAttribute appliqué à la méthode Action (Scope is Action) est valide. En fait, la logique spécifique est la suivante : tous les filtres créés sont triés en fonction de la portée de l'ordre (c'est-à-dire l'ordre dans lequel les filtres sont exécutés) et le dernier est sélectionné. Pour notre exemple, les trois filtres fournis ont la même valeur d'attribut Order (-1), et tous seront finalement triés selon Scope (Scope, Controller et Action).
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article pourra apporter une certaine aide aux études ou au travail de chacun. Si vous avez des questions, vous pouvez également laisser un message. j'espère soutenir le site Web PHP chinois !
Pour des articles plus détaillés sur les filtres ASP.NET MVC, veuillez faire attention au site Web PHP chinois !