Filtres dans ASP.NET Core

Par Kirk Larkin, Rick Anderson, Tom Dykstra et Steve Smith

Les filtres dans ASP.NET Core permettent d’exécuter du code avant ou après des étapes spécifiques dans le pipeline de traitement des requêtes.

Les filtres intégrés gèrent notamment les tâches suivantes :

  • Autorisation, pour empêcher un utilisateur non autorisé d’accéder à des ressources.
  • Mise en cache des réponses, pour court-circuiter le pipeline de requêtes pour retourner une réponse mise en cache.

Il est possible de créer des filtres personnalisés pour gérer les problèmes transversaux. Les exemples de problèmes transversaux incluent la gestion des erreurs, la mise en cache, la configuration, l’autorisation et la journalisation. Les filtres évitent la duplication de code. Par exemple, un filtre d’exceptions de gestion des erreurs peut servir à consolider la gestion des erreurs.

Ce document s’applique à Razor Pages, aux contrôleurs d’API et aux contrôleurs avec vues. Les filtres ne fonctionnent pas directement avec les composants Razor. Un filtre peut uniquement affecter indirectement un composant dans les cas suivants :

  • Le composant est incorporé dans une page ou une vue.
  • La page ou le contrôleur et la vue utilisent le filtre.

Fonctionnement des filtres

Les filtres s’exécutent dans le pipeline des appels d’action ASP.NET Core, parfois appelé pipeline de filtres. Le pipeline de filtres s’exécute après la sélection par ASP.NET Core de l’action à exécuter :

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Types de filtre

Chaque type de filtre est exécuté dans une étape différente du pipeline de filtres :

  • Les filtres d’autorisations :

    • Ils sont exécutés en premier.
    • Ils déterminent si l’utilisateur est autorisé pour la demande.
    • Ils court-circuitent le pipeline si la demande n’est pas autorisée.
  • Filtres de ressources :

    • Exécuter après l’autorisation.
    • OnResourceExecuting exécute du code avant le reste du pipeline de filtres. Par exemple, OnResourceExecuting exécute du code avant la liaison de données.
    • OnResourceExecuted exécute du code une fois le reste du pipeline terminé.
  • Filtres d’actions :

    • Ils sont exécutés immédiatement avant et après l’appel d’une méthode d’action.
    • Ils peuvent modifier les arguments transmis dans une action.
    • Ils peuvent modifier le résultat retourné par l’action.
    • Ils ne sont pas pris en charge dans Razor Pages.
  • Filtres de points de terminaison :

    • Ils sont exécutés immédiatement avant et après l’appel d’une méthode d’action.
    • Ils peuvent modifier les arguments transmis dans une action.
    • Ils peuvent modifier le résultat retourné par l’action.
    • Ils ne sont pas pris en charge dans Razor Pages.
    • Ils peuvent être appelés sur les actions et les points de terminaison basés sur le gestionnaire de routage.
  • Les filtres d’exceptions appliquent des stratégies globales à des exceptions non gérées qui se produisent avant l’écriture dans le corps de la réponse.

  • Filtres de résultats :

    • Ils sont exécutés immédiatement avant ou après l’exécution de résultats d’action.
    • Ils sont exécutés uniquement quand la méthode d’action s’exécute correctement.
    • Ils sont utiles pour la logique qui doit entourer l’exécution de la vue ou du formateur.

Le diagramme suivant montre comment ces types de filtres interagissent dans le pipeline de filtres :

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages prend également en charge les filtres Razor Pages, qui s’exécutent avant et après un gestionnaire Razor Pages.

Implémentation

Les filtres prennent en charge les implémentations synchrones et asynchrones via différentes définitions d’interface.

Les filtres synchrones peuvent s’exécuter avant et après leur étape de pipeline. Par exemple, OnActionExecuting est appelé avant l’appel de la méthode d’action. OnActionExecuted est appelé après le retour de la méthode d’action :

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Les filtres asynchrones définissent une méthode On-Stage-ExecutionAsync. Par exemple, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

Dans le code précédent, SampleAsyncActionFilter a un paramètre ActionExecutionDelegatenext qui exécute la méthode d’action.

Plusieurs étapes de filtres

Vous pouvez implémenter des interfaces pour plusieurs étapes de filtres dans une même classe. Par exemple, la classe ActionFilterAttribute implémente :

Implémentez la version synchrone ou bien la version asynchrone d’une interface de filtre, mais pas les deux. Le runtime vérifie d’abord si le filtre implémente l’interface asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la ou les méthodes de l’interface synchrone. Si des interfaces synchrones et asynchrones sont implémentées dans une classe, seule la méthode asynchrone est appelée. Quand vous utilisez des classes abstraites comme ActionFilterAttribute, remplacez seulement les méthodes synchrones ou les méthodes asynchrones pour chaque type de filtre.

Attributs de filtre intégrés

ASP.NET Core inclut les filtres intégrés basés sur des attributs que vous pouvez définir dans une sous-classe et personnaliser. Par exemple, le filtre de résultats suivant ajoute un en-tête à la réponse :

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Les attributs autorisent les filtres à accepter des arguments, comme indiqué dans l’exemple ci-dessus. Appliquez le ResponseHeaderAttribute à un contrôleur ou une méthode d’action et spécifiez le nom et la valeur de l’en-tête HTTP :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Utilisez un outil tel que les outils de développement du navigateur pour examiner les en-têtes. Sous En-têtes de réponse, filter-header: Filter Value s’affiche.

Le code suivant applique ResponseHeaderAttribute à un contrôleur et à une action :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Les réponses de l’action Multiple incluent les en-têtes suivants :

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Plusieurs des interfaces de filtre ont des attributs correspondants qui peuvent être utilisés comme classes de base pour des implémentations personnalisées.

Les attributs de filtre :

Les filtres ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages. Ils peuvent être appliqués au modèle Razor Pages ou globalement.

Étendues de filtre et ordre d’exécution

Un filtre peut être ajouté au pipeline à une des trois étendues :

  • À l’aide d’un attribut sur un contrôleur ou dans Razor Pages.
  • À l’aide d’un attribut sur une action de contrôleur. Les attributs de filtre ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages.
  • Globalement pour tous les contrôleurs et actions et Razor Pages comme indiqué dans le code suivant :
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Ordre d’exécution par défaut

Quand il existe plusieurs filtres pour une étape particulière du pipeline, l’étendue détermine l’ordre par défaut de l’exécution du filtre. Les filtres globaux entourent les filtres de classe, qui à leur tour entourent les filtres de méthode.

En raison de l’imbrication de filtres, le code après des filtres s’exécute dans l’ordre inverse du code avant. La séquence de filtre :

  • Le code avant des filtres globaux.
    • Le code avant des filtres du contrôleur.
      • Le code avant des filtres de méthodes d’action.
      • Le code après des filtres de méthodes d’action.
    • Le code après des filtres du contrôleur.
  • Le code après des filtres globaux.

Voici un exemple qui illustre l’ordre dans lequel les méthodes de filtre sont exécutées pour des filtres d’actions synchrones :

Sequence Étendue de filtre Méthode de filtre
1 Global OnActionExecuting
2 Contrôleur OnActionExecuting
3 Action OnActionExecuting
4 Action OnActionExecuted
5 Contrôleur OnActionExecuted
6 Global OnActionExecuted

Filtres au niveau du contrôleur

Chaque contrôleur qui hérite de Controller inclut les méthodes OnActionExecuting, OnActionExecutionAsync et OnActionExecuted. Ces méthodes incluent dans un wrapper les filtres qui s’exécutent pour une action donnée :

  • OnActionExecuting est exécuté avant tous les filtres de l’action.
  • OnActionExecuted est exécuté après tous les filtres de l’action.
  • OnActionExecutionAsync est exécuté avant tous les filtres de l’action. Le code après un appel à next est exécuté après les filtres de l’action.

La classe ControllerFiltersController suivante :

  • Applique SampleActionFilterAttribute ([SampleActionFilter]) au contrôleur.
  • Remplace OnActionExecuting et OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

La navigation vers https://localhost:<port>/ControllerFilters exécute le code suivant :

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Les filtres au niveau du contrôleur définissent la propriété Order sur int.MinValue. Les filtres au niveau du contrôleur ne peuvent pas être définis pour s’exécuter après les filtres appliqués aux méthodes. L’ordre est expliqué dans la section suivante.

Pour Pages, consultez la section RazorImplémenter des filtres Razor Pages en remplaçant les méthodes de filtre.

Substituer l’ordre par défaut

Vous pouvez remplacer la séquence d’exécution par défaut en implémentant IOrderedFilter. IOrderedFilter expose la propriété Order qui est prioritaire sur l’étendue afin de déterminer l’ordre d’exécution. Un filtre avec une valeur Order inférieure :

  • Exécute le code avant avant celui d’un filtre avec une valeur supérieure à Order.
  • Exécute le code après après celui d’un filtre avec une valeur Order supérieure.

Dans l’exemple de filtres au niveau du contrôleur, GlobalSampleActionFilter a une étendue globale de sorte qu’il s’exécute avant SampleActionFilterAttribute, qui a l’étendue du contrôleur. Pour que SampleActionFilterAttribute s’exécute en premier, définissez son ordre sur int.MinValue :

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Pour que le filtre global GlobalSampleActionFilter s’exécute en premier, définissez Order sur int.MinValue :

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Annulation et court-circuit

Vous pouvez court-circuiter le pipeline de filtres en définissant la propriété Result sur le paramètre ResourceExecutingContext fourni à la méthode de filtre. Par exemple, le filtre de ressources suivant empêche l’exécution du reste du pipeline :

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

Dans le code suivant, les filtres [ShortCircuitingResourceFilter] et [ResponseHeader] ciblent tous deux la méthode d’action Index. Le filtre ShortCircuitingResourceFilterAttribute :

  • S’exécute en premier (puisqu’il s’agit d’un filtre de ressources et que ResponseHeaderAttribute est un filtre d’action).
  • Court-circuite le reste du pipeline.

Le filtre ResponseHeaderAttribute ne s’exécute donc jamais pour l’action Index. Ce comportement est le même si les deux filtres sont appliqués au niveau de la méthode d’action, à condition que ShortCircuitingResourceFilterAttribute soit exécuté en premier. Le filtre ShortCircuitingResourceFilterAttribute s’exécute d’abord en raison de son type :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Injection de dépendances

Les filtres peuvent être ajoutés par type ou par instance. Si vous ajoutez une instance, celle-ci sera utilisée pour chaque requête. Si un type est ajouté, il est activé par type. Un filtre activé par type signifie :

Les filtres qui sont implémentés en tant qu’attributs et ajoutés directement à des classes de contrôleur ou à de méthodes d’action ne peut pas avoir de dépendances de constructeur fournies par injection de dépendances. Les dépendances de constructeur ne peuvent pas être fournies par DI, car les attributs doivent avoir leurs paramètres de constructeur fournis là où ils sont appliqués.

Les filtres suivants prennent en charge les dépendances de constructeur fournies à partir de l’injection de dépendances :

Les filtres précédents peuvent être appliqués à un contrôleur ou à une action.

Des enregistreurs d’événements sont disponibles à partir de l’injection de dépendances. Toutefois, évitez la création et l’utilisation de filtres à des fins purement d’enregistrement. L’enregistrement d’infrastructure intégrée fournit généralement ce qui est nécessaire pour l’enregistrement. Enregistrement ajouté aux filtres :

  • Doit se concentrer sur les problèmes ou le comportement du domaine métier spécifique au filtre.
  • Ne doit pas enregistrer des actions ou d’autres événements d’infrastructure. Les filtres intégrés enregistrent déjà les actions et les événements d’infrastructure.

ServiceFilterAttribute

Les types d’implémentation du filtre de services sont enregistrés dans Program.cs. Un ServiceFilterAttribute récupère une instance du filtre à partir de l’injection de dépendances.

Le code suivant montre la classe LoggingResponseHeaderFilterService, qui utilise DI :

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

Dans le code suivant, LoggingResponseHeaderFilterService est ajouté au conteneur d’injection de dépendance :

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

Dans le code suivant, l’attribut ServiceFilter récupère une instance du filtre LoggingResponseHeaderFilterService à partir de l’injection de dépendances :

[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

Lors de l’utilisation de ServiceFilterAttribute, la définition de ServiceFilterAttribute.IsReusable :

  • Conseille que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne garantit pas :
    • Qu’une seule instance du filtre sera créée.
    • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.
  • Ne doit pas être utilisée avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L'objet ServiceFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir de l’injection de dépendances.

TypeFilterAttribute

TypeFilterAttribute est similaire à ServiceFilterAttribute, mais son type n’est pas résolu directement à partir du conteneur d’injection de dépendances. Il instancie le type en utilisant Microsoft.Extensions.DependencyInjection.ObjectFactory.

Étant donné que les types TypeFilterAttribute ne sont pas résolus directement à partir du conteneur d’injection de dépendances :

  • Les types qui sont référencés avec TypeFilterAttribute ne doivent pas d’abord être inscrits auprès du conteneur d’injection de dépendances. Leurs dépendances ne sont pas remplies par le conteneur d’injection de dépendances.
  • TypeFilterAttribute peut éventuellement accepter des arguments de constructeur pour le type.

Lors de l’utilisation de TypeFilterAttribute, la définition de TypeFilterAttribute.IsReusable :

  • Indique que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne fournit aucune garantie qu’une seule instance du filtre sera créée.

  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L’exemple suivant indique comment passer des arguments vers un type en utilisant TypeFilterAttribute :

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtres d’autorisations

Les filtres d’autorisations :

  • Sont les premiers filtres à être exécutés dans le pipeline de filtres.
  • Contrôlent l’accès aux méthodes d’action.
  • Ont une méthode avant, mais pas de méthode après.

Les filtres d’autorisations personnalisés nécessitent une infrastructure d’autorisation personnalisée. Préférez la configuration des stratégies d’autorisation ou l’écriture d’une stratégie d’autorisation personnalisée à l’écriture d’un filtre personnalisé. Le filtre d'autorisations intégré :

  • Appelle le système d’autorisation.
  • N’autoriser les requêtes.

Ne levez pas d’exceptions dans les filtres d’autorisations :

  • L’exception ne sera pas gérée.
  • Les filtres d’exceptions ne gèreront pas l’exception.

Songez à émettre une stimulation quand un filtre d’autorisations se produit.

Découvrez plus d’informations sur l’autorisation.

Filtres de ressources

Filtres de ressources :

Les filtres de ressources sont utiles pour court-circuiter la majeure partie du pipeline. Par exemple, un filtre de mise en cache peut éviter le reste du pipeline dans une correspondance dans le cache.

Exemples de filtre de ressources :

Filtres d'actions

Les filtres d’actions ne s’appliquent pas à Razor Pages. Razor Pages prend en charge IPageFilter et IAsyncPageFilter. Pour plus d’informations, consultez méthodes de filtres pour Razor Pages.

Filtres d’actions :

Le code suivant montre un exemple de filtre d’actions :

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

ActionExecutingContext fournit les propriétés suivantes :

  • ActionArguments : permet de lire les entrées vers une méthode d’action.
  • Controller : permet de manipuler l’instance de contrôleur.
  • Result : la définition de Result court-circuite l’exécution de la méthode d’action et les filtres d’actions suivants.

Levée d’une exception dans une méthode d’action :

  • Empêche l’exécution des filtres suivants.
  • Contrairement au paramètre Result, est traité comme un échec plutôt que comme un résultat positif.

ActionExecutedContext fournit Controller et Result, en plus des propriétés suivantes :

  • Canceled : sa valeur est true si l’exécution de l’action a été court-circuitée par un autre filtre.
  • Exception : sa valeur est non Null si l’action ou un filtre d’actions précédemment exécuté a lancé une exception. Paramètre de cette propriété sur null :
    • Gère effectivement une exception.
    • Result est exécuté comme s’il avait été retourné à partir de la méthode d’action.

Pour un IAsyncActionFilter, un appel à ActionExecutionDelegate :

  • Exécute tous les filtres d’actions suivants et la méthode d’action.
  • Retourne ActionExecutedContext.

Pour court-circuiter, attribuez Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result à une instance de résultat et n’appelez pas le next (le ActionExecutionDelegate).

L’infrastructure fournit un ActionFilterAttribute abstrait que vous pouvez placer dans une sous-classe.

Le filtre d’actions OnActionExecuting peut être utilisé pour :

  • Valider l’état du modèle.
  • Renvoyer une erreur si l’état n’est pas valide.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Remarque

Les contrôleurs annotés avec l’attribut [ApiController] valident automatiquement l’état du modèle et retournent une réponse 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

La méthode OnActionExecuted s’exécute après la méthode d’action :

  • Et peut voir et manipuler les résultats de l’action via la propriété Result.
  • Canceled est défini sur true si l’exécution de l’action a été court-circuitée par un autre filtre.
  • Exception est défini sur une valeur non Null si l’action ou un filtre d’actions suivant a levé une exception. Le fait de définir Exception avec la valeur null :
    • Gère effectivement une exception.
    • ActionExecutedContext.Result est exécuté comme s’il avait été retourné normalement à partir de la méthode d’action.

Filtres d’exceptions

Les filtres d’exceptions :

L’exemple de filtre d’exceptions suivant affiche des détails sur les exceptions qui se produisent pendant que l’application est en développement :

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

Le code suivant teste le filtre d’exceptions :

[TypeFilter<SampleExceptionFilter>]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Les filtres d’exceptions :

  • N’ont pas d’événements avant et après.
  • Implémentent OnException ou OnExceptionAsync.
  • Gèrent les exceptions non prises en charge qui se produisent dans Razor Pages ou dans la création des contrôleurs, la liaison de données, les filtres d’actions ou les méthodes d’action.
  • N’interceptent pas les exceptions qui se produisent dans l’exécution des filtres de ressources, des filtres de résultats ou des résultats MVC.

Pour gérer une exception, définissez la propriété ExceptionHandled sur true ou attribuez la propriété Result. Ceci arrête la propagation de l’exception. Un filtre d’exceptions ne peut pas changer une exception en « réussite ». Seul un filtre d’actions peut le faire.

Les filtres d’exceptions :

  • Conviennent pour intercepter des exceptions qui se produisent dans des actions.
  • N’offrent pas la même souplesse que le middleware (intergiciel) de gestion des erreurs.

Privilégiez le middleware pour la gestion des exceptions. Utilisez les filtres d’exceptions uniquement lorsque la gestion des erreurs diffère selon la méthode d’action appelée. Par exemple, votre application peut avoir des méthodes d’action à la fois pour des points de terminaison d’API et pour des affichages/HTML. Les points de terminaison d’API peuvent retourner des informations d’erreur au format JSON, tandis que les actions basées sur une vue peuvent retourner une page d’erreur au format HTML.

Filtres de résultats

Filtres de résultats :

IResultFilter et IAsyncResultFilter

Le code suivant montre un exemple de filtre de résultats :

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

Le type de résultat à exécuter dépend de l’action. Une action retournant une vue inclut tous les traitements Razor dans le cadre de l’exécution de ViewResult. Une méthode d’API peut effectuer une sérialisation dans le cadre de l’exécution du résultat. En savoir plus sur les résultats d’actions.

Les filtres de résultats ne sont exécutés que quand une action ou un filtre d’actions produit un résultat d’action. Les filtres de résultats ne sont pas exécutés quand :

  • Un filtre d’autorisations ou un filtre de ressources court-circuite le pipeline.
  • Un filtre d’exception gère une exception en générant un résultat d’action.

La méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting peut court-circuiter l’exécution du résultat d’action et les filtres de résultats suivants en définissant Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel sur true. Écrivez dans l’objet de réponse quand vous court-circuitez pour éviter de générer une réponse vide. La levée d’une exception dans IResultFilter.OnResultExecuting :

  • Empêche l’exécution du résultat d’action et des filtres suivants.
  • Est traitée comme une erreur et non comme une réussite.

Lors de l’exécution de la méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, la réponse a probablement déjà été envoyée au client. Si la réponse a déjà été envoyée au client, elle ne peut plus être modifiée.

ResultExecutedContext.Canceled est défini sur true si l’exécution du résultat d’action a été court-circuitée par un autre filtre.

ResultExecutedContext.Exception est défini sur une valeur non Null si le résultat d’action ou un filtre de résultats suivant a levé une exception. La définition de Exception sur la valeur Null gère effectivement une exception et évite à l’exception d’être à nouveau levée plus loin dans le pipeline. Il n’existe aucun moyen fiable pour écrire des données dans une réponse lors de la gestion d’une exception dans un filtre de résultats. Si les en-têtes ont été vidés pour le client lorsqu’un résultat d’action lance une exception, il n’existe aucun mécanisme fiable pour envoyer un code d’échec.

Pour un IAsyncResultFilter, un appel à await next sur le ResultExecutionDelegate exécute tous les filtres de résultats suivants et le résultat de l’action. Pour court-circuiter, définissez ResultExecutingContext.Cancel sur true et n’appelez pas ResultExecutionDelegate :

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

L’infrastructure fournit un ResultFilterAttribute abstrait que vous pouvez placer dans une sous-classe. La classe ResponseHeaderAttribute ci-dessus est un exemple d’un attribut de filtre de résultats.

IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter

Les interfaces IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter déclarent une implémentation IResultFilter qui s’exécute pour tous les résultats d’action. Cela inclut les résultats d’action générés par :

  • Les filtres d’autorisations et les filtres de ressources qui court-circuitent.
  • Les filtres d’exceptions.

Par exemple, le filtre suivant exécute et définit toujours un résultat d’action (ObjectResult) avec un code d’état 422 Entité non traitée en cas d’échec de la négociation de contenu :

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

L'objet IFilterFactory implémente l'objet IFilterMetadata. Par conséquent, une instance de IFilterFactory peut être utilisée comme instance de IFilterMetadata n’importe où dans le pipeline de filtres. Quan se prépare à appeler le filtre, il tente de le caster en IFilterFactory. Si ce cast réussit, la méthode CreateInstance est appelée pour créer l’instance IFilterMetadata qui sera appelée. La conception est flexible, car il n’est pas nécessaire de définir explicitement le pipeline de filtres exact quand l’application démarre.

IFilterFactory.IsReusable:

  • indique que l’instance de filtre créée par la fabrique peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée.
  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

Le runtime ASP.NET Core ne garantit pas :

  • Qu’une seule instance du filtre sera créée.
  • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.

Avertissement

Configurez IFilterFactory.IsReusable pour retourner true uniquement si la source des filtres n’est pas ambiguë, si les filtres sont sans état et si les filtres peuvent être utilisés en toute sécurité sur plusieurs requêtes HTTP. Par exemple, ne retournez pas de filtres de DI qui sont inscrits comme délimités ou temporaires si IFilterFactory.IsReusable retourne true.

Une autre approche pour la création de filtres est d’implémenter IFilterFactory à l’aide des implémentations d’attribut personnalisé :

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

Le filtre est appliqué dans le code suivant :

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory implémenté sur un attribut

Les filtres qui implémentent IFilterFactory sont utiles pour les filtres qui :

  • Ne nécessitent pas le passage de paramètres.
  • Disposent de dépendances de constructeur qui doivent être remplies par l’injection de dépendances.

L'objet TypeFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir du conteneur de services (injection de dépendances).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

Le code suivant illustre trois approches pour appliquer le filtre :

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter<SampleActionTypeFilterAttribute>]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

Dans le code précédent, la première approche pour appliquer le filtre est recommandée.

Utiliser un intergiciel dans le pipeline de filtres

Les filtres de ressources fonctionnent comme un intergiciel dans la mesure où ils entourent l’exécution de tout ce qui vient plus tard dans le pipeline. Les filtres diffèrent cependant d’un intergiciel dans la mesure où ils font partie du runtime, ce qui signifie qu’ils ont accès au contexte et aux constructions.

Pour utiliser un intergiciel comme filtre, créez un type avec une méthode Configure qui spécifie l’intergiciel que vous voulez injecter dans le pipeline de filtres. L’exemple suivant utilise un intergiciel pour définir un en-tête de réponse :

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Utilisez le MiddlewareFilterAttribute pour exécuter l’intergiciel :

[MiddlewareFilter<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Les filtres d’intergiciels s’exécutent à la même étape du pipeline de filtres en tant que filtres de ressources, avant la liaison de modèle et après le reste du pipeline.

Sécurité des threads

Lors de la transmission d’une instance d’un filtre dans Add, au lieu de Type, le filtre est un singleton et n’est pas thread-safe.

Ressources supplémentaires

Par Kirk Larkin, Rick Anderson, Tom Dykstra et Steve Smith

Les filtres dans ASP.NET Core permettent d’exécuter du code avant ou après des étapes spécifiques dans le pipeline de traitement des requêtes.

Les filtres intégrés gèrent notamment les tâches suivantes :

  • Autorisation, pour empêcher un utilisateur non autorisé d’accéder à des ressources.
  • Mise en cache des réponses, pour court-circuiter le pipeline de requêtes pour retourner une réponse mise en cache.

Il est possible de créer des filtres personnalisés pour gérer les problèmes transversaux. Les exemples de problèmes transversaux incluent la gestion des erreurs, la mise en cache, la configuration, l’autorisation et la journalisation. Les filtres évitent la duplication de code. Par exemple, un filtre d’exceptions de gestion des erreurs peut servir à consolider la gestion des erreurs.

Ce document s’applique à Razor Pages, aux contrôleurs d’API et aux contrôleurs avec vues. Les filtres ne fonctionnent pas directement avec les composants Razor. Un filtre peut uniquement affecter indirectement un composant dans les cas suivants :

  • Le composant est incorporé dans une page ou une vue.
  • La page ou le contrôleur et la vue utilisent le filtre.

Fonctionnement des filtres

Les filtres s’exécutent dans le pipeline des appels d’action ASP.NET Core, parfois appelé pipeline de filtres. Le pipeline de filtres s’exécute après la sélection par ASP.NET Core de l’action à exécuter :

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Types de filtre

Chaque type de filtre est exécuté dans une étape différente du pipeline de filtres :

  • Les filtres d’autorisations :

    • Ils sont exécutés en premier.
    • Ils déterminent si l’utilisateur est autorisé pour la demande.
    • Ils court-circuitent le pipeline si la demande n’est pas autorisée.
  • Filtres de ressources :

    • Exécuter après l’autorisation.
    • OnResourceExecuting exécute du code avant le reste du pipeline de filtres. Par exemple, OnResourceExecuting exécute du code avant la liaison de données.
    • OnResourceExecuted exécute du code une fois le reste du pipeline terminé.
  • Filtres d’actions :

    • Ils sont exécutés immédiatement avant et après l’appel d’une méthode d’action.
    • Ils peuvent modifier les arguments transmis dans une action.
    • Ils peuvent modifier le résultat retourné par l’action.
    • Ils ne sont pas pris en charge dans Razor Pages.
  • Filtres de points de terminaison :

    • Ils sont exécutés immédiatement avant et après l’appel d’une méthode d’action.
    • Ils peuvent modifier les arguments transmis dans une action.
    • Ils peuvent modifier le résultat retourné par l’action.
    • Ils ne sont pas pris en charge dans Razor Pages.
    • Ils peuvent être appelés sur les actions et les points de terminaison basés sur le gestionnaire de routage.
  • Les filtres d’exceptions appliquent des stratégies globales à des exceptions non gérées qui se produisent avant l’écriture dans le corps de la réponse.

  • Filtres de résultats :

    • Ils sont exécutés immédiatement avant ou après l’exécution de résultats d’action.
    • Ils sont exécutés uniquement quand la méthode d’action s’exécute correctement.
    • Ils sont utiles pour la logique qui doit entourer l’exécution de la vue ou du formateur.

Le diagramme suivant montre comment ces types de filtres interagissent dans le pipeline de filtres :

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages prend également en charge les filtres Razor Pages, qui s’exécutent avant et après un gestionnaire Razor Pages.

Implémentation

Les filtres prennent en charge les implémentations synchrones et asynchrones via différentes définitions d’interface.

Les filtres synchrones peuvent s’exécuter avant et après leur étape de pipeline. Par exemple, OnActionExecuting est appelé avant l’appel de la méthode d’action. OnActionExecuted est appelé après le retour de la méthode d’action :

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

Les filtres asynchrones définissent une méthode On-Stage-ExecutionAsync. Par exemple, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}

Dans le code précédent, SampleAsyncActionFilter a un paramètre ActionExecutionDelegatenext qui exécute la méthode d’action.

Plusieurs étapes de filtres

Vous pouvez implémenter des interfaces pour plusieurs étapes de filtres dans une même classe. Par exemple, la classe ActionFilterAttribute implémente :

Implémentez la version synchrone ou bien la version asynchrone d’une interface de filtre, mais pas les deux. Le runtime vérifie d’abord si le filtre implémente l’interface asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la ou les méthodes de l’interface synchrone. Si des interfaces synchrones et asynchrones sont implémentées dans une classe, seule la méthode asynchrone est appelée. Quand vous utilisez des classes abstraites comme ActionFilterAttribute, remplacez seulement les méthodes synchrones ou les méthodes asynchrones pour chaque type de filtre.

Attributs de filtre intégrés

ASP.NET Core inclut les filtres intégrés basés sur des attributs que vous pouvez définir dans une sous-classe et personnaliser. Par exemple, le filtre de résultats suivant ajoute un en-tête à la réponse :

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

Les attributs autorisent les filtres à accepter des arguments, comme indiqué dans l’exemple ci-dessus. Appliquez le ResponseHeaderAttribute à un contrôleur ou une méthode d’action et spécifiez le nom et la valeur de l’en-tête HTTP :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

Utilisez un outil tel que les outils de développement du navigateur pour examiner les en-têtes. Sous En-têtes de réponse, filter-header: Filter Value s’affiche.

Le code suivant applique ResponseHeaderAttribute à un contrôleur et à une action :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Les réponses de l’action Multiple incluent les en-têtes suivants :

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

Plusieurs des interfaces de filtre ont des attributs correspondants qui peuvent être utilisés comme classes de base pour des implémentations personnalisées.

Les attributs de filtre :

Les filtres ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages. Ils peuvent être appliqués au modèle Razor Pages ou globalement.

Étendues de filtre et ordre d’exécution

Un filtre peut être ajouté au pipeline à une des trois étendues :

  • À l’aide d’un attribut sur un contrôleur ou dans Razor Pages.
  • À l’aide d’un attribut sur une action de contrôleur. Les attributs de filtre ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages.
  • Globalement pour tous les contrôleurs et actions et Razor Pages comme indiqué dans le code suivant :
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Ordre d’exécution par défaut

Quand il existe plusieurs filtres pour une étape particulière du pipeline, l’étendue détermine l’ordre par défaut de l’exécution du filtre. Les filtres globaux entourent les filtres de classe, qui à leur tour entourent les filtres de méthode.

En raison de l’imbrication de filtres, le code après des filtres s’exécute dans l’ordre inverse du code avant. La séquence de filtre :

  • Le code avant des filtres globaux.
    • Le code avant des filtres du contrôleur.
      • Le code avant des filtres de méthodes d’action.
      • Le code après des filtres de méthodes d’action.
    • Le code après des filtres du contrôleur.
  • Le code après des filtres globaux.

Voici un exemple qui illustre l’ordre dans lequel les méthodes de filtre sont exécutées pour des filtres d’actions synchrones :

Sequence Étendue de filtre Méthode de filtre
1 Global OnActionExecuting
2 Contrôleur OnActionExecuting
3 Action OnActionExecuting
4 Action OnActionExecuted
5 Contrôleur OnActionExecuted
6 Global OnActionExecuted

Filtres au niveau du contrôleur

Chaque contrôleur qui hérite de Controller inclut les méthodes OnActionExecuting, OnActionExecutionAsync et OnActionExecuted. Ces méthodes incluent dans un wrapper les filtres qui s’exécutent pour une action donnée :

  • OnActionExecuting est exécuté avant tous les filtres de l’action.
  • OnActionExecuted est exécuté après tous les filtres de l’action.
  • OnActionExecutionAsync est exécuté avant tous les filtres de l’action. Le code après un appel à next est exécuté après les filtres de l’action.

La classe ControllerFiltersController suivante :

  • Applique SampleActionFilterAttribute ([SampleActionFilter]) au contrôleur.
  • Remplace OnActionExecuting et OnActionExecuted.
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

La navigation vers https://localhost:<port>/ControllerFilters exécute le code suivant :

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • ControllerFiltersController.Index
      • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

Les filtres au niveau du contrôleur définissent la propriété Order sur int.MinValue. Les filtres au niveau du contrôleur ne peuvent pas être définis pour s’exécuter après les filtres appliqués aux méthodes. L’ordre est expliqué dans la section suivante.

Pour Pages, consultez la section RazorImplémenter des filtres Razor Pages en remplaçant les méthodes de filtre.

Substituer l’ordre par défaut

Vous pouvez remplacer la séquence d’exécution par défaut en implémentant IOrderedFilter. IOrderedFilter expose la propriété Order qui est prioritaire sur l’étendue afin de déterminer l’ordre d’exécution. Un filtre avec une valeur Order inférieure :

  • Exécute le code avant avant celui d’un filtre avec une valeur supérieure à Order.
  • Exécute le code après après celui d’un filtre avec une valeur Order supérieure.

Dans l’exemple de filtres au niveau du contrôleur, GlobalSampleActionFilter a une étendue globale de sorte qu’il s’exécute avant SampleActionFilterAttribute, qui a l’étendue du contrôleur. Pour que SampleActionFilterAttribute s’exécute en premier, définissez son ordre sur int.MinValue :

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

Pour que le filtre global GlobalSampleActionFilter s’exécute en premier, définissez Order sur int.MinValue :

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});

Annulation et court-circuit

Vous pouvez court-circuiter le pipeline de filtres en définissant la propriété Result sur le paramètre ResourceExecutingContext fourni à la méthode de filtre. Par exemple, le filtre de ressources suivant empêche l’exécution du reste du pipeline :

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

Dans le code suivant, les filtres [ShortCircuitingResourceFilter] et [ResponseHeader] ciblent tous deux la méthode d’action Index. Le filtre ShortCircuitingResourceFilterAttribute :

  • S’exécute en premier (puisqu’il s’agit d’un filtre de ressources et que ResponseHeaderAttribute est un filtre d’action).
  • Court-circuite le reste du pipeline.

Le filtre ResponseHeaderAttribute ne s’exécute donc jamais pour l’action Index. Ce comportement est le même si les deux filtres sont appliqués au niveau de la méthode d’action, à condition que ShortCircuitingResourceFilterAttribute soit exécuté en premier. Le filtre ShortCircuitingResourceFilterAttribute s’exécute d’abord en raison de son type :

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}

Injection de dépendances

Les filtres peuvent être ajoutés par type ou par instance. Si vous ajoutez une instance, celle-ci sera utilisée pour chaque requête. Si un type est ajouté, il est activé par type. Un filtre activé par type signifie :

Les filtres qui sont implémentés en tant qu’attributs et ajoutés directement à des classes de contrôleur ou à de méthodes d’action ne peut pas avoir de dépendances de constructeur fournies par injection de dépendances. Les dépendances de constructeur ne peuvent pas être fournies par DI, car les attributs doivent avoir leurs paramètres de constructeur fournis là où ils sont appliqués.

Les filtres suivants prennent en charge les dépendances de constructeur fournies à partir de l’injection de dépendances :

Les filtres précédents peuvent être appliqués à un contrôleur ou à une action.

Des enregistreurs d’événements sont disponibles à partir de l’injection de dépendances. Toutefois, évitez la création et l’utilisation de filtres à des fins purement d’enregistrement. L’enregistrement d’infrastructure intégrée fournit généralement ce qui est nécessaire pour l’enregistrement. Enregistrement ajouté aux filtres :

  • Doit se concentrer sur les problèmes ou le comportement du domaine métier spécifique au filtre.
  • Ne doit pas enregistrer des actions ou d’autres événements d’infrastructure. Les filtres intégrés enregistrent déjà les actions et les événements d’infrastructure.

ServiceFilterAttribute

Les types d’implémentation du filtre de services sont enregistrés dans Program.cs. Un ServiceFilterAttribute récupère une instance du filtre à partir de l’injection de dépendances.

Le code suivant montre la classe LoggingResponseHeaderFilterService, qui utilise DI :

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

Dans le code suivant, LoggingResponseHeaderFilterService est ajouté au conteneur d’injection de dépendance :

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

Dans le code suivant, l’attribut ServiceFilter récupère une instance du filtre LoggingResponseHeaderFilterService à partir de l’injection de dépendances :

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

Lors de l’utilisation de ServiceFilterAttribute, la définition de ServiceFilterAttribute.IsReusable :

  • Conseille que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne garantit pas :
    • Qu’une seule instance du filtre sera créée.
    • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.
  • Ne doit pas être utilisée avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L'objet ServiceFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir de l’injection de dépendances.

TypeFilterAttribute

TypeFilterAttribute est similaire à ServiceFilterAttribute, mais son type n’est pas résolu directement à partir du conteneur d’injection de dépendances. Il instancie le type en utilisant Microsoft.Extensions.DependencyInjection.ObjectFactory.

Étant donné que les types TypeFilterAttribute ne sont pas résolus directement à partir du conteneur d’injection de dépendances :

  • Les types qui sont référencés avec TypeFilterAttribute ne doivent pas d’abord être inscrits auprès du conteneur d’injection de dépendances. Leurs dépendances ne sont pas remplies par le conteneur d’injection de dépendances.
  • TypeFilterAttribute peut éventuellement accepter des arguments de constructeur pour le type.

Lors de l’utilisation de TypeFilterAttribute, la définition de TypeFilterAttribute.IsReusable :

  • Indique que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne fournit aucune garantie qu’une seule instance du filtre sera créée.

  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L’exemple suivant indique comment passer des arguments vers un type en utilisant TypeFilterAttribute :

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");

Filtres d’autorisations

Les filtres d’autorisations :

  • Sont les premiers filtres à être exécutés dans le pipeline de filtres.
  • Contrôlent l’accès aux méthodes d’action.
  • Ont une méthode avant, mais pas de méthode après.

Les filtres d’autorisations personnalisés nécessitent une infrastructure d’autorisation personnalisée. Préférez la configuration des stratégies d’autorisation ou l’écriture d’une stratégie d’autorisation personnalisée à l’écriture d’un filtre personnalisé. Le filtre d'autorisations intégré :

  • Appelle le système d’autorisation.
  • N’autoriser les requêtes.

Ne levez pas d’exceptions dans les filtres d’autorisations :

  • L’exception ne sera pas gérée.
  • Les filtres d’exceptions ne gèreront pas l’exception.

Songez à émettre une stimulation quand un filtre d’autorisations se produit.

Découvrez plus d’informations sur l’autorisation.

Filtres de ressources

Filtres de ressources :

Les filtres de ressources sont utiles pour court-circuiter la majeure partie du pipeline. Par exemple, un filtre de mise en cache peut éviter le reste du pipeline dans une correspondance dans le cache.

Exemples de filtre de ressources :

Filtres d'actions

Les filtres d’actions ne s’appliquent pas à Razor Pages. Razor Pages prend en charge IPageFilter et IAsyncPageFilter. Pour plus d’informations, consultez méthodes de filtres pour Razor Pages.

Filtres d’actions :

Le code suivant montre un exemple de filtre d’actions :

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

ActionExecutingContext fournit les propriétés suivantes :

  • ActionArguments : permet de lire les entrées vers une méthode d’action.
  • Controller : permet de manipuler l’instance de contrôleur.
  • Result : la définition de Result court-circuite l’exécution de la méthode d’action et les filtres d’actions suivants.

Levée d’une exception dans une méthode d’action :

  • Empêche l’exécution des filtres suivants.
  • Contrairement au paramètre Result, est traité comme un échec plutôt que comme un résultat positif.

ActionExecutedContext fournit Controller et Result, en plus des propriétés suivantes :

  • Canceled : sa valeur est true si l’exécution de l’action a été court-circuitée par un autre filtre.
  • Exception : sa valeur est non Null si l’action ou un filtre d’actions précédemment exécuté a lancé une exception. Paramètre de cette propriété sur null :
    • Gère effectivement une exception.
    • Result est exécuté comme s’il avait été retourné à partir de la méthode d’action.

Pour un IAsyncActionFilter, un appel à ActionExecutionDelegate :

  • Exécute tous les filtres d’actions suivants et la méthode d’action.
  • Retourne ActionExecutedContext.

Pour court-circuiter, attribuez Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result à une instance de résultat et n’appelez pas le next (le ActionExecutionDelegate).

L’infrastructure fournit un ActionFilterAttribute abstrait que vous pouvez placer dans une sous-classe.

Le filtre d’actions OnActionExecuting peut être utilisé pour :

  • Valider l’état du modèle.
  • Renvoyer une erreur si l’état n’est pas valide.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}

Remarque

Les contrôleurs annotés avec l’attribut [ApiController] valident automatiquement l’état du modèle et retournent une réponse 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

La méthode OnActionExecuted s’exécute après la méthode d’action :

  • Et peut voir et manipuler les résultats de l’action via la propriété Result.
  • Canceled est défini sur true si l’exécution de l’action a été court-circuitée par un autre filtre.
  • Exception est défini sur une valeur non Null si l’action ou un filtre d’actions suivant a levé une exception. Le fait de définir Exception avec la valeur null :
    • Gère effectivement une exception.
    • ActionExecutedContext.Result est exécuté comme s’il avait été retourné normalement à partir de la méthode d’action.

Filtres d’exceptions

Les filtres d’exceptions :

L’exemple de filtre d’exceptions suivant affiche des détails sur les exceptions qui se produisent pendant que l’application est en développement :

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

Le code suivant teste le filtre d’exceptions :

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

Les filtres d’exceptions :

  • N’ont pas d’événements avant et après.
  • Implémentent OnException ou OnExceptionAsync.
  • Gèrent les exceptions non prises en charge qui se produisent dans Razor Pages ou dans la création des contrôleurs, la liaison de données, les filtres d’actions ou les méthodes d’action.
  • N’interceptent pas les exceptions qui se produisent dans l’exécution des filtres de ressources, des filtres de résultats ou des résultats MVC.

Pour gérer une exception, définissez la propriété ExceptionHandled sur true ou attribuez la propriété Result. Ceci arrête la propagation de l’exception. Un filtre d’exceptions ne peut pas changer une exception en « réussite ». Seul un filtre d’actions peut le faire.

Les filtres d’exceptions :

  • Conviennent pour intercepter des exceptions qui se produisent dans des actions.
  • N’offrent pas la même souplesse que le middleware (intergiciel) de gestion des erreurs.

Privilégiez le middleware pour la gestion des exceptions. Utilisez les filtres d’exceptions uniquement lorsque la gestion des erreurs diffère selon la méthode d’action appelée. Par exemple, votre application peut avoir des méthodes d’action à la fois pour des points de terminaison d’API et pour des affichages/HTML. Les points de terminaison d’API peuvent retourner des informations d’erreur au format JSON, tandis que les actions basées sur une vue peuvent retourner une page d’erreur au format HTML.

Filtres de résultats

Filtres de résultats :

IResultFilter et IAsyncResultFilter

Le code suivant montre un exemple de filtre de résultats :

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

Le type de résultat à exécuter dépend de l’action. Une action retournant une vue inclut tous les traitements Razor dans le cadre de l’exécution de ViewResult. Une méthode d’API peut effectuer une sérialisation dans le cadre de l’exécution du résultat. En savoir plus sur les résultats d’actions.

Les filtres de résultats ne sont exécutés que quand une action ou un filtre d’actions produit un résultat d’action. Les filtres de résultats ne sont pas exécutés quand :

  • Un filtre d’autorisations ou un filtre de ressources court-circuite le pipeline.
  • Un filtre d’exception gère une exception en générant un résultat d’action.

La méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting peut court-circuiter l’exécution du résultat d’action et les filtres de résultats suivants en définissant Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel sur true. Écrivez dans l’objet de réponse quand vous court-circuitez pour éviter de générer une réponse vide. La levée d’une exception dans IResultFilter.OnResultExecuting :

  • Empêche l’exécution du résultat d’action et des filtres suivants.
  • Est traitée comme une erreur et non comme une réussite.

Lors de l’exécution de la méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, la réponse a probablement déjà été envoyée au client. Si la réponse a déjà été envoyée au client, elle ne peut plus être modifiée.

ResultExecutedContext.Canceled est défini sur true si l’exécution du résultat d’action a été court-circuitée par un autre filtre.

ResultExecutedContext.Exception est défini sur une valeur non Null si le résultat d’action ou un filtre de résultats suivant a levé une exception. La définition de Exception sur la valeur Null gère effectivement une exception et évite à l’exception d’être à nouveau levée plus loin dans le pipeline. Il n’existe aucun moyen fiable pour écrire des données dans une réponse lors de la gestion d’une exception dans un filtre de résultats. Si les en-têtes ont été vidés pour le client lorsqu’un résultat d’action lance une exception, il n’existe aucun mécanisme fiable pour envoyer un code d’échec.

Pour un IAsyncResultFilter, un appel à await next sur le ResultExecutionDelegate exécute tous les filtres de résultats suivants et le résultat de l’action. Pour court-circuiter, définissez ResultExecutingContext.Cancel sur true et n’appelez pas ResultExecutionDelegate :

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

L’infrastructure fournit un ResultFilterAttribute abstrait que vous pouvez placer dans une sous-classe. La classe ResponseHeaderAttribute ci-dessus est un exemple d’un attribut de filtre de résultats.

IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter

Les interfaces IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter déclarent une implémentation IResultFilter qui s’exécute pour tous les résultats d’action. Cela inclut les résultats d’action générés par :

  • Les filtres d’autorisations et les filtres de ressources qui court-circuitent.
  • Les filtres d’exceptions.

Par exemple, le filtre suivant exécute et définit toujours un résultat d’action (ObjectResult) avec un code d’état 422 Entité non traitée en cas d’échec de la négociation de contenu :

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}

IFilterFactory

L'objet IFilterFactory implémente l'objet IFilterMetadata. Par conséquent, une instance de IFilterFactory peut être utilisée comme instance de IFilterMetadata n’importe où dans le pipeline de filtres. Quan se prépare à appeler le filtre, il tente de le caster en IFilterFactory. Si ce cast réussit, la méthode CreateInstance est appelée pour créer l’instance IFilterMetadata qui sera appelée. La conception est flexible, car il n’est pas nécessaire de définir explicitement le pipeline de filtres exact quand l’application démarre.

IFilterFactory.IsReusable:

  • indique que l’instance de filtre créée par la fabrique peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée.
  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

Le runtime ASP.NET Core ne garantit pas :

  • Qu’une seule instance du filtre sera créée.
  • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.

Avertissement

Configurez IFilterFactory.IsReusable pour retourner true uniquement si la source des filtres n’est pas ambiguë, si les filtres sont sans état et si les filtres peuvent être utilisés en toute sécurité sur plusieurs requêtes HTTP. Par exemple, ne retournez pas de filtres de DI qui sont inscrits comme délimités ou temporaires si IFilterFactory.IsReusable retourne true.

Une autre approche pour la création de filtres est d’implémenter IFilterFactory à l’aide des implémentations d’attribut personnalisé :

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

Le filtre est appliqué dans le code suivant :

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");

IFilterFactory implémenté sur un attribut

Les filtres qui implémentent IFilterFactory sont utiles pour les filtres qui :

  • Ne nécessitent pas le passage de paramètres.
  • Disposent de dépendances de constructeur qui doivent être remplies par l’injection de dépendances.

L'objet TypeFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir du conteneur de services (injection de dépendances).

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

Le code suivant illustre trois approches pour appliquer le filtre :

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

Dans le code précédent, la première approche pour appliquer le filtre est recommandée.

Utiliser un intergiciel dans le pipeline de filtres

Les filtres de ressources fonctionnent comme un intergiciel dans la mesure où ils entourent l’exécution de tout ce qui vient plus tard dans le pipeline. Les filtres diffèrent cependant d’un intergiciel dans la mesure où ils font partie du runtime, ce qui signifie qu’ils ont accès au contexte et aux constructions.

Pour utiliser un intergiciel comme filtre, créez un type avec une méthode Configure qui spécifie l’intergiciel que vous voulez injecter dans le pipeline de filtres. L’exemple suivant utilise un intergiciel pour définir un en-tête de réponse :

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

Utilisez le MiddlewareFilterAttribute pour exécuter l’intergiciel :

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Les filtres d’intergiciels s’exécutent à la même étape du pipeline de filtres en tant que filtres de ressources, avant la liaison de modèle et après le reste du pipeline.

Sécurité des threads

Lors de la transmission d’une instance d’un filtre dans Add, au lieu de Type, le filtre est un singleton et n’est pas thread-safe.

Ressources supplémentaires

Par Kirk Larkin, Rick Anderson, Tom Dykstra et Steve Smith

Les filtres dans ASP.NET Core permettent d’exécuter du code avant ou après des étapes spécifiques dans le pipeline de traitement des requêtes.

Les filtres intégrés gèrent notamment les tâches suivantes :

  • Autorisation, pour empêcher un utilisateur non autorisé d’accéder à des ressources.
  • Mise en cache des réponses, pour court-circuiter le pipeline de requêtes pour retourner une réponse mise en cache.

Il est possible de créer des filtres personnalisés pour gérer les problèmes transversaux. Les exemples de problèmes transversaux incluent la gestion des erreurs, la mise en cache, la configuration, l’autorisation et la journalisation. Les filtres évitent la duplication de code. Par exemple, un filtre d’exceptions de gestion des erreurs peut servir à consolider la gestion des erreurs.

Ce document s’applique à Razor Pages, aux contrôleurs d’API et aux contrôleurs avec vues. Les filtres ne fonctionnent pas directement avec les composants Razor. Un filtre peut uniquement affecter indirectement un composant dans les cas suivants :

  • Le composant est incorporé dans une page ou une vue.
  • La page ou le contrôleur et la vue utilisent le filtre.

Afficher ou télécharger l’échantillon (comment télécharger).

Fonctionnement des filtres

Les filtres s’exécutent dans le pipeline des appels d’action ASP.NET Core, parfois appelé pipeline de filtres. Le pipeline de filtres s’exécute après la sélection par ASP.NET Core de l’action à exécuter.

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Types de filtre

Chaque type de filtre est exécuté dans une étape différente du pipeline de filtres :

  • Les filtres d’autorisations s’exécutent en premier et sont utilisés pour déterminer si l’utilisateur est autorisé pour la requête. Les filtres d’autorisations peuvent court-circuiter le pipeline si la requête n’est pas autorisée.

  • Filtres de ressources :

    • Exécuter après l’autorisation.
    • OnResourceExecuting exécute du code avant le reste du pipeline de filtres. Par exemple, OnResourceExecuting exécute du code avant la liaison de données.
    • OnResourceExecuted exécute du code une fois le reste du pipeline terminé.
  • Filtres d’actions :

    • Ils exécutent du code immédiatement avant et après l’appel d’une méthode d’action.
    • Ils peuvent modifier les arguments transmis dans une action.
    • Ils peuvent modifier le résultat retourné par l’action.
    • Ils ne sont pas pris en charge dans Razor Pages.
  • Les filtres d’exceptions appliquent des stratégies globales à des exceptions non gérées qui se produisent avant l’écriture dans le corps de la réponse.

  • Les filtres de résultats exécutent du code juste avant et après l’exécution des résultats d’action. Ils s’exécutent uniquement au terme de l’exécution de la méthode d’action. Ils sont utiles pour la logique qui doit entourer l’exécution de la vue ou du formateur.

Le diagramme suivant montre comment ces types de filtres interagissent dans le pipeline de filtres.

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Implémentation

Les filtres prennent en charge les implémentations synchrones et asynchrones via différentes définitions d’interface.

Les filtres synchrones exécutent du code avant et après leur étape de pipeline. Par exemple, OnActionExecuting est appelé avant l’appel de la méthode d’action. OnActionExecuted est appelé après le retour de la méthode d’action.

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

Dans le code précédent, MyDebug est une fonction utilitaire dans l’exemple de téléchargement.

Les filtres asynchrones définissent une méthode On-Stage-ExecutionAsync. Par exemple, OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

Dans le code précédent, SampleAsyncActionFilter contient ActionExecutionDelegate (next) qui exécute la méthode d’action.

Plusieurs étapes de filtres

Vous pouvez implémenter des interfaces pour plusieurs étapes de filtres dans une même classe. Par exemple, la classe ActionFilterAttribute implémente :

Implémentez la version synchrone ou bien la version asynchrone d’une interface de filtre, mais pas les deux. Le runtime vérifie d’abord si le filtre implémente l’interface asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la ou les méthodes de l’interface synchrone. Si des interfaces synchrones et asynchrones sont implémentées dans une classe, seule la méthode asynchrone est appelée. Quand vous utilisez des classes abstraites comme ActionFilterAttribute, remplacez seulement les méthodes synchrones ou les méthodes asynchrones pour chaque type de filtre.

Attributs de filtre intégrés

ASP.NET Core inclut les filtres intégrés basés sur des attributs que vous pouvez définir dans une sous-classe et personnaliser. Par exemple, le filtre de résultats suivant ajoute un en-tête à la réponse :

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

Les attributs autorisent les filtres à accepter des arguments, comme indiqué dans l’exemple ci-dessus. Appliquez le AddHeaderAttribute à un contrôleur ou une méthode d’action et spécifiez le nom et la valeur de l’en-tête HTTP :

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

Utilisez un outil tel que les outils de développement du navigateur pour examiner les en-têtes. Sous En-têtes de réponse, author: Rick Anderson s’affiche.

Le code suivant implémente ActionFilterAttribute qui :

  • Lit le titre et le nom du système de configuration. Contrairement à l’exemple précédent, le code suivant ne nécessite pas l’ajout de paramètres de filtre au code.
  • Ajoute le titre et le nom à l’en-tête de réponse.
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

    public MyActionFilterAttribute(IOptions<PositionOptions> options)
    {
        _settings = options.Value;
        Order = 1;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_settings.Title, 
                                                 new string[] { _settings.Name });
        base.OnResultExecuting(context);
    }
}

Les options de configuration sont fournies à partir du système de configuration à l’aide du modèle d’options. Par exemple, à partir du fichier appsettings.json :

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Dans StartUp.ConfigureServices :

  • La classe PositionOptions est ajoutée au conteneur de service avec la zone de configuration "Position".
  • MyActionFilterAttribute est ajouté au conteneur de service.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

Le code suivant illustre la classe PositionOptions :

public class PositionOptions
{
    public string Title { get; set; }
    public string Name { get; set; }
}

Le code suivant applique MyActionFilterAttribute à la méthode Index2 :

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

Sous En-têtes de réponse, author: Rick Anderson et Editor: Joe Smith s’affichent quand le point de terminaison Sample/Index2 est appelé.

Le code suivant applique MyActionFilterAttribute et AddHeaderAttribute à Razor Pages :

[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

Les filtres ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages. Ils peuvent être appliqués au modèle Razor Pages ou globalement.

Plusieurs des interfaces de filtre ont des attributs correspondants qui peuvent être utilisés comme classes de base pour des implémentations personnalisées.

Les attributs de filtre :

Étendues de filtre et ordre d’exécution

Un filtre peut être ajouté au pipeline à une des trois étendues :

  • À l’aide d’un attribut sur une action de contrôleur. Les attributs de filtre ne peuvent pas être appliqués aux méthodes du gestionnaire Razor Pages.
  • À l’aide d’un attribut sur un contrôleur ou dans Razor Pages.
  • Globalement pour tous les contrôleurs et actions et Razor Pages comme indiqué dans le code suivant :
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Ordre d’exécution par défaut

Quand il existe plusieurs filtres pour une étape particulière du pipeline, l’étendue détermine l’ordre par défaut de l’exécution du filtre. Les filtres globaux entourent les filtres de classe, qui à leur tour entourent les filtres de méthode.

En raison de l’imbrication de filtres, le code après des filtres s’exécute dans l’ordre inverse du code avant. La séquence de filtre :

  • Le code avant des filtres globaux.
    • Le code avant des filtres du contrôleur et Razor Pages.
      • Le code avant des filtres de méthodes d’action.
      • Le code après des filtres de méthodes d’action.
    • Le code après des filtres du contrôleur et Razor Pages.
  • Le code après des filtres globaux.

Voici un exemple qui illustre l’ordre dans lequel les méthodes de filtre sont appelées pour les filtres d’actions synchrones.

Sequence Étendue de filtre Méthode de filtre
1 Global OnActionExecuting
2 Contrôleur ou Razor Pages OnActionExecuting
3 Méthode OnActionExecuting
4 Méthode OnActionExecuted
5 Contrôleur ou Razor Pages OnActionExecuted
6 Global OnActionExecuted

Filtres au niveau du contrôleur

Chaque contrôleur qui hérite de la classe de base Controller inclut des méthodes Controller.OnActionExecuting, Controller.OnActionExecutionAsync et Controller.OnActionExecutedOnActionExecuted. Ces méthodes :

  • Incluent dans un wrapper les filtres qui s’exécutent pour une action donnée.
  • OnActionExecuting est appelé avant tous les filtres de l’action.
  • OnActionExecuted est appelé après tous les filtres d’actions.
  • OnActionExecutionAsync est appelé avant tous les filtres de l’action. Le code dans le filtre après next s’exécute après la méthode d’action.

Par exemple, dans l’échantillon à télécharger, MySampleActionFilter est appliqué globalement au démarrage.

TestController :

  • Applique SampleActionFilterAttribute ([SampleActionFilter]) à l’action FilterTest2.
  • Remplace OnActionExecuting et OnActionExecuted.
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

MyDisplayRouteInfo est fourni par le package NuGet Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

La navigation vers https://localhost:5001/Test/FilterTest2 exécute le code suivant :

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

Les filtres au niveau du contrôleur définissent la propriété Order sur int.MinValue. Les filtres au niveau du contrôleur ne peuvent pas être définis pour s’exécuter après les filtres appliqués aux méthodes. L’ordre est expliqué dans la section suivante.

Pour Pages, consultez la section RazorImplémenter des filtres Razor Pages en remplaçant les méthodes de filtre.

Remplacement de l’ordre par défaut

Vous pouvez remplacer la séquence d’exécution par défaut en implémentant IOrderedFilter. IOrderedFilter expose la propriété Order qui est prioritaire sur l’étendue afin de déterminer l’ordre d’exécution. Un filtre avec une valeur Order inférieure :

  • Exécute le code avant avant celui d’un filtre avec une valeur supérieure à Order.
  • Exécute le code après après celui d’un filtre avec une valeur Order supérieure.

La propriété Order est définie avec un paramètre de constructeur :

[SampleActionFilter(Order = int.MinValue)]

Considérez les deux filtres d’actions dans le contrôleur suivant :

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Un filtre global est ajouté dans StartUp.ConfigureServices :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Les 3 filtres sont exécutés dans l’ordre suivant :

  • Test2Controller.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • MyAction2FilterAttribute.OnActionExecuting
        • Test2Controller.FilterTest2
      • MyAction2FilterAttribute.OnResultExecuting
    • MySampleActionFilter.OnActionExecuted
  • Test2Controller.OnActionExecuted

La propriété Order remplace l’étendue lors de la détermination de l’ordre dans lequel les filtres s’exécutent. Les filtres sont d’abord classés par ordre, puis l’étendue est utilisée pour couper les liens. Tous les filtres intégrés implémentent IOrderedFilter et affectent 0 à la valeur Order par défaut. Comme mentionné précédemment, les filtres au niveau du contrôleur définissent la propriété Order sur int.MinValue. Pour les filtres intégrés, l’étendue détermine l’ordre, sauf si Order est défini sur une valeur différente de zéro.

Dans le code précédent, MySampleActionFilter a une étendue globale de sorte qu’il s’exécute avant MyAction2FilterAttribute, qui a l’étendue du contrôleur. Pour que MyAction2FilterAttribute s’exécute en premier, définissez l’ordre sur int.MinValue :

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

Pour que le filtre global MySampleActionFilter s’exécute en premier, définissez Order sur int.MinValue :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter),
                            int.MinValue);
    });
}

Annulation et court-circuit

Vous pouvez court-circuiter le pipeline de filtres en définissant la propriété Result sur le paramètre ResourceExecutingContext fourni à la méthode de filtre. Par exemple, le filtre de ressources suivant empêche l’exécution du reste du pipeline :

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

Dans le code suivant, les filtres ShortCircuitingResourceFilter et AddHeader ciblent tous deux la méthode d’action SomeResource. ShortCircuitingResourceFilter :

  • S’exécute en premier (puisqu’il s’agit d’un filtre de ressources et que AddHeader est un filtre d’action).
  • Court-circuite le reste du pipeline.

Le filtre AddHeader ne s’exécute donc jamais pour l’action SomeResource. Ce comportement est le même si les deux filtres sont appliqués au niveau de la méthode d’action, à condition que ShortCircuitingResourceFilter soit exécuté en premier. ShortCircuitingResourceFilter s’exécute en premier en raison de son type de filtre ou de l’utilisation explicite de la propriété Order.

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Injection de dépendances

Les filtres peuvent être ajoutés par type ou par instance. Si vous ajoutez une instance, celle-ci sera utilisée pour chaque requête. Si un type est ajouté, il est activé par type. Un filtre activé par type signifie :

Les filtres qui sont implémentés en tant qu’attributs et ajoutés directement à des classes de contrôleur ou à de méthodes d’action ne peut pas avoir de dépendances de constructeur fournies par injection de dépendances. Les dépendances de constructeur ne peuvent pas être fournies par l’injection de dépendance, car :

  • Les paramètres du constructeur des attributs doivent être fournis là où ils sont appliqués.
  • Il s’agit d’une limitation de la façon dont les attributs fonctionnent.

Les filtres suivants prennent en charge les dépendances de constructeur fournies à partir de l’injection de dépendances :

Les filtres précédents peuvent être appliqués à une méthode de contrôleur ou d’action :

Des enregistreurs d’événements sont disponibles à partir de l’injection de dépendances. Toutefois, évitez la création et l’utilisation de filtres à des fins purement d’enregistrement. L’enregistrement d’infrastructure intégrée fournit généralement ce qui est nécessaire pour l’enregistrement. Enregistrement ajouté aux filtres :

  • Doit se concentrer sur les problèmes ou le comportement du domaine métier spécifique au filtre.
  • Ne doit pas enregistrer des actions ou d’autres événements d’infrastructure. Les filtres intégrés enregistrent des actions et des événements d’infrastructure.

ServiceFilterAttribute

Les types d’implémentation du filtre de services sont enregistrés dans ConfigureServices. Un ServiceFilterAttribute récupère une instance du filtre à partir de l’injection de dépendances.

Le code suivant affiche le AddHeaderResultServiceFilter :

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

Dans le code suivant, AddHeaderResultServiceFilter est ajouté au conteneur d’injection de dépendance :

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddControllersWithViews(options =>
   {
       options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
           "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    });
}

Dans le code suivant, l’attribut ServiceFilter récupère une instance du filtre AddHeaderResultServiceFilter à partir de l’injection de dépendances :

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

Lors de l’utilisation de ServiceFilterAttribute, la définition de ServiceFilterAttribute.IsReusable :

  • Conseille que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne garantit pas :

    • Qu’une seule instance du filtre sera créée.
    • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.
  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L'objet ServiceFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir de l’injection de dépendances.

TypeFilterAttribute

TypeFilterAttribute est similaire à ServiceFilterAttribute, mais son type n’est pas résolu directement à partir du conteneur d’injection de dépendances. Il instancie le type en utilisant Microsoft.Extensions.DependencyInjection.ObjectFactory.

Étant donné que les types TypeFilterAttribute ne sont pas résolus directement à partir du conteneur d’injection de dépendances :

  • Les types qui sont référencés avec TypeFilterAttribute ne doivent pas d’abord être inscrits auprès du conteneur d’injection de dépendances. Leurs dépendances ne sont pas remplies par le conteneur d’injection de dépendances.
  • TypeFilterAttribute peut éventuellement accepter des arguments de constructeur pour le type.

Lors de l’utilisation de TypeFilterAttribute, la définition de TypeFilterAttribute.IsReusable :

  • Indique que l’instance de filtre peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée. Le runtime ASP.NET Core ne fournit aucune garantie qu’une seule instance du filtre sera créée.

  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

L’exemple suivant indique comment passer des arguments vers un type en utilisant TypeFilterAttribute :

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

Filtres d’autorisations

Les filtres d’autorisations :

  • Sont les premiers filtres à être exécutés dans le pipeline de filtres.
  • Contrôlent l’accès aux méthodes d’action.
  • Ont une méthode avant, mais pas de méthode après.

Les filtres d’autorisations personnalisés nécessitent une infrastructure d’autorisation personnalisée. Préférez la configuration des stratégies d’autorisation ou l’écriture d’une stratégie d’autorisation personnalisée à l’écriture d’un filtre personnalisé. Le filtre d'autorisations intégré :

  • Appelle le système d’autorisation.
  • N’autoriser les requêtes.

Ne levez pas d’exceptions dans les filtres d’autorisations :

  • L’exception ne sera pas gérée.
  • Les filtres d’exceptions ne gèreront pas l’exception.

Songez à émettre une stimulation quand un filtre d’autorisations se produit.

Découvrez plus d’informations sur l’autorisation.

Filtres de ressources

Filtres de ressources :

Les filtres de ressources sont utiles pour court-circuiter la majeure partie du pipeline. Par exemple, un filtre de mise en cache peut éviter le reste du pipeline dans une correspondance dans le cache.

Exemples de filtre de ressources :

Filtres d'actions

Les filtres d’actions ne s’appliquent pas à Razor Pages. Razor Pages prend en charge IPageFilter et IAsyncPageFilter. Pour plus d’informations, consultez méthodes de filtres pour Razor Pages.

Filtres d’actions :

Le code suivant montre un exemple de filtre d’actions :

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext fournit les propriétés suivantes :

  • ActionArguments : permet de lire les entrées vers une méthode d’action.
  • Controller : permet de manipuler l’instance de contrôleur.
  • Result : la définition de Result court-circuite l’exécution de la méthode d’action et les filtres d’actions suivants.

Levée d’une exception dans une méthode d’action :

  • Empêche l’exécution des filtres suivants.
  • Contrairement au paramètre Result, est traité comme un échec plutôt que comme un résultat positif.

ActionExecutedContext fournit Controller et Result, en plus des propriétés suivantes :

  • Canceled : sa valeur est true si l’exécution de l’action a été court-circuitée par un autre filtre.

  • Exception : sa valeur est non Null si l’action ou un filtre d’actions précédemment exécuté a lancé une exception. Paramètre de cette propriété sur null :

    • Gère effectivement une exception.
    • Result est exécuté comme s’il avait été retourné à partir de la méthode d’action.

Pour un IAsyncActionFilter, un appel à ActionExecutionDelegate :

  • Exécute tous les filtres d’actions suivants et la méthode d’action.
  • Retourne ActionExecutedContext.

Pour court-circuiter, attribuez Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result à une instance de résultat et n’appelez pas le next (le ActionExecutionDelegate).

L’infrastructure fournit un ActionFilterAttribute abstrait que vous pouvez placer dans une sous-classe.

Le filtre d’actions OnActionExecuting peut être utilisé pour :

  • Valider l’état du modèle.
  • Renvoyer une erreur si l’état n’est pas valide.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }

Remarque

Les contrôleurs annotés avec l’attribut [ApiController] valident automatiquement l’état du modèle et retournent une réponse 400. Pour plus d’informations, consultez Réponses HTTP 400 automatiques. La méthode OnActionExecuted s’exécute après la méthode d’action :

  • Et peut voir et manipuler les résultats de l’action via la propriété Result.

  • Canceled est défini sur true si l’exécution de l’action a été court-circuitée par un autre filtre.

  • Exception est défini sur une valeur non Null si l’action ou un filtre d’actions suivant a levé une exception. Le fait de définir Exception avec la valeur null :

    • Gère effectivement une exception.
    • ActionExecutedContext.Result est exécuté comme s’il avait été retourné normalement à partir de la méthode d’action.
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

Filtres d’exceptions

Les filtres d’exceptions :

L’échantillon de filtre d’exceptions suivant utilise un affichage personnalisé des erreurs pour afficher des détails sur les exceptions qui se produisent pendant que l’application est en développement :

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IWebHostEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

Le code suivant teste le filtre d’exceptions :

[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
    [AddHeader("Failing Controller", 
               "Won't appear when exception is handled")]
    public IActionResult Index()
    {
        throw new Exception("Testing custom exception filter.");
    }
}

Les filtres d’exceptions :

  • N’ont pas d’événements avant et après.
  • Implémentent OnException ou OnExceptionAsync.
  • Gèrent les exceptions non prises en charge qui se produisent dans Razor Pages ou dans la création des contrôleurs, la liaison de données, les filtres d’actions ou les méthodes d’action.
  • N’interceptent pas les exceptions qui se produisent dans l’exécution des filtres de ressources, des filtres de résultats ou des résultats MVC.

Pour gérer une exception, définissez la propriété ExceptionHandled sur true ou attribuez la propriété Result. Ceci arrête la propagation de l’exception. Un filtre d’exceptions ne peut pas changer une exception en « réussite ». Seul un filtre d’actions peut le faire.

Les filtres d’exceptions :

  • Conviennent pour intercepter des exceptions qui se produisent dans des actions.
  • N’offrent pas la même souplesse que le middleware (intergiciel) de gestion des erreurs.

Privilégiez le middleware pour la gestion des exceptions. Utilisez les filtres d’exceptions uniquement lorsque la gestion des erreurs diffère selon la méthode d’action appelée. Par exemple, votre application peut avoir des méthodes d’action à la fois pour des points de terminaison d’API et pour des affichages/HTML. Les points de terminaison d’API peuvent retourner des informations d’erreur au format JSON, tandis que les actions basées sur une vue peuvent retourner une page d’erreur au format HTML.

Filtres de résultats

Filtres de résultats :

IResultFilter et IAsyncResultFilter

Le code suivant indique un filtre de résultats qui ajoute un en-tête HTTP :

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

Le type de résultat à exécuter dépend de l’action. Une action retournant une vue inclut tous les traitements Razor dans le cadre de l’exécution de ViewResult. Une méthode d’API peut effectuer une sérialisation dans le cadre de l’exécution du résultat. En savoir plus sur les résultats d’actions.

Les filtres de résultats ne sont exécutés que quand une action ou un filtre d’actions produit un résultat d’action. Les filtres de résultats ne sont pas exécutés quand :

  • Un filtre d’autorisations ou un filtre de ressources court-circuite le pipeline.
  • Un filtre d’exception gère une exception en générant un résultat d’action.

La méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting peut court-circuiter l’exécution du résultat d’action et les filtres de résultats suivants en définissant Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel sur true. Écrivez dans l’objet de réponse quand vous court-circuitez pour éviter de générer une réponse vide. La levée d’une exception dans IResultFilter.OnResultExecuting :

  • Empêche l’exécution du résultat d’action et des filtres suivants.
  • Est traitée comme une erreur et non comme une réussite.

Lors de l’exécution de la méthode Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, la réponse a probablement déjà été envoyée au client. Si la réponse a déjà été envoyée au client, elle ne peut plus être modifiée.

ResultExecutedContext.Canceled est défini sur true si l’exécution du résultat d’action a été court-circuitée par un autre filtre.

ResultExecutedContext.Exception est défini sur une valeur non Null si le résultat d’action ou un filtre de résultats suivant a levé une exception. La définition de Exception sur la valeur Null gère effectivement une exception et évite à l’exception d’être à nouveau levée plus loin dans le pipeline. Il n’existe aucun moyen fiable pour écrire des données dans une réponse lors de la gestion d’une exception dans un filtre de résultats. Si les en-têtes ont été vidés pour le client lorsqu’un résultat d’action lance une exception, il n’existe aucun mécanisme fiable pour envoyer un code d’échec.

Pour un IAsyncResultFilter, un appel à await next sur le ResultExecutionDelegate exécute tous les filtres de résultats suivants et le résultat de l’action. Pour court-circuiter, définissez ResultExecutingContext.Cancel sur true et n’appelez pas ResultExecutionDelegate :

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

L’infrastructure fournit un ResultFilterAttribute abstrait que vous pouvez placer dans une sous-classe. La classe AddHeaderAttribute ci-dessus est un exemple d’un attribut de filtre de résultats.

IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter

Les interfaces IAlwaysRunResultFilter et IAsyncAlwaysRunResultFilter déclarent une implémentation IResultFilter qui s’exécute pour tous les résultats d’action. Cela inclut les résultats d’action générés par :

  • Les filtres d’autorisations et les filtres de ressources qui court-circuitent.
  • Les filtres d’exceptions.

Par exemple, le filtre suivant exécute et définit toujours un résultat d’action (ObjectResult) avec un code d’état 422 Entité non traitée en cas d’échec de la négociation de contenu :

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == (int) HttpStatusCode.UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = (int) HttpStatusCode.UnsupportedMediaType,
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

L'objet IFilterFactory implémente l'objet IFilterMetadata. Par conséquent, une instance de IFilterFactory peut être utilisée comme instance de IFilterMetadata n’importe où dans le pipeline de filtres. Quan se prépare à appeler le filtre, il tente de le caster en IFilterFactory. Si ce cast réussit, la méthode CreateInstance est appelée pour créer l’instance IFilterMetadata qui sera appelée. La conception est flexible, car il n’est pas nécessaire de définir explicitement le pipeline de filtres exact quand l’application démarre.

IFilterFactory.IsReusable:

  • indique que l’instance de filtre créée par la fabrique peut être réutilisée en dehors de l’étendue de la requête dans laquelle elle a été créée.
  • Ne doit pas être utilisé avec un filtre qui dépend de services avec une durée de vie autre que singleton.

Le runtime ASP.NET Core ne garantit pas :

  • Qu’une seule instance du filtre sera créée.
  • Le filtre ne sera pas demandé à nouveau à partir du conteneur d’injection de dépendance à un stade ultérieur.

Avertissement

Configurez IFilterFactory.IsReusable pour retourner true uniquement si la source des filtres n’est pas ambiguë, si les filtres sont sans état et si les filtres peuvent être utilisés en toute sécurité sur plusieurs requêtes HTTP. Par exemple, ne retournez pas de filtres de DI qui sont inscrits comme délimités ou temporaires si IFilterFactory.IsReusable retourne true. Une autre approche pour la création de filtres est d’implémenter IFilterFactory à l’aide des implémentations d’attribut personnalisé :

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

Le filtre est appliqué dans le code suivant :

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

Testez le code précédent en exécutant l’exemple de téléchargement :

  • Appeler les outils de développement F12.
  • Accédez à https://localhost:5001/Sample/HeaderWithFactory.

Les outils de développement F12 affichent les en-têtes de réponse suivants ajoutés par l’exemple de code :

  • auteur :Rick Anderson
  • globaladdheader :Result filter added to MvcOptions.Filters
  • interne :My header

Le code précédent crée l’en-tête de réponse interne :My header.

IFilterFactory implémenté sur un attribut

Les filtres qui implémentent IFilterFactory sont utiles pour les filtres qui :

  • Ne nécessitent pas le passage de paramètres.
  • Disposent de dépendances de constructeur qui doivent être remplies par l’injection de dépendances.

L'objet TypeFilterAttribute implémente l'objet IFilterFactory. IFilterFactory expose la méthode CreateInstance pour la création d’une instance IFilterMetadata. CreateInstance charge le type spécifié à partir du conteneur de services (injection de dépendances).

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute()
                         :base(typeof(SampleActionFilterImpl))
    { 
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
           _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
        }
    }
}

Le code suivant illustre trois approches pour appliquer [SampleActionFilter] :

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content("From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content("From TypeFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content("From ServiceFilterTest");
}

Dans le code précédent, la décoration de la méthode avec [SampleActionFilter] est la meilleure approche pour appliquer SampleActionFilter.

Utilisation d’un intergiciel dans le pipeline de filtres

Les filtres de ressources fonctionnent comme un intergiciel dans la mesure où ils entourent l’exécution de tout ce qui vient plus tard dans le pipeline. Les filtres diffèrent cependant d’un intergiciel dans la mesure où ils font partie du runtime, ce qui signifie qu’ils ont accès au contexte et aux constructions.

Pour utiliser un intergiciel comme filtre, créez un type avec une méthode Configure qui spécifie l’intergiciel que vous voulez injecter dans le pipeline de filtres. Voici un exemple qui utilise l’intergiciel de localisation pour établir la culture actuelle d’une requête :

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(
                                       culture: "en-US", 
                                       uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() {
                Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

Utilisez le MiddlewareFilterAttribute pour exécuter l’intergiciel :

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content(
          $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

Les filtres d’intergiciels s’exécutent à la même étape du pipeline de filtres en tant que filtres de ressources, avant la liaison de modèle et après le reste du pipeline.

Sécurité des threads

Lors de la transmission d’une instance d’un filtre dans Add, au lieu de Type, le filtre est un singleton et n’est pas thread-safe.

Actions suivantes