ASP.Net CORE API : Utiliser les filters pour structurer votre code

En tant que développeur, nous aimons tous écrire du code propre et structuré. Nous mettons donc un point d’honneur à avoir des noms de méthodes clairs, et surtout que ces dernières ne fassent rien d’autre que ce que leur nom dit.

Il en va de même pour nos contrôleurs API. La méthode Get() ne doit s’occuper que de retourner l’objet demandé.

public ActionResult<IEnumerable<string>> Get()
{
    try
    {
        _logger.LogInformation("Start calling /Demo/Get");
        var stopWatch = new Stopwatch();

        var result = new[] { "value1", "value2" };

        stopWatch.Stop();
        _logger.LogInformation($"End calling /Demo/Get. Request duration : {stopWatch.Elapsed:mm\\:ss\\.fff}");

        return result;
    }
    catch (Exception e)
    {
        _logger.LogError(e, "Errors occured while calling /Demo/Get");
        return null;
    }           
}

Dans l’exemple ci-dessus, la méthode Get() retourne bien un résultat, avec une gestion des exceptions et de logging. Si j’ajoute une méthode Post(), j’ajouterai également cette gestion des exceptions et de logging.

Ce qui m’embête dans l’exemple ci-dessus, c’est que la logique de la méthode est mélangée à la gestion des exceptions et de logging. Si nous voulons être propres, la méthode Get() devrait donc s’appeler GetWithLoggingAndManageException(). Cependant, ce n’est pas un verbe HTTP connu, et votre API ne pourra plus se prétendre RESTful .

Heureusement, les fondateurs d’ASP.Net CORE ont pensé aux développeurs rigoureux que nous sommes, et ont intégré la notion de filtres. Les filtres permettent d’exécuter du code avant et après chaque requête effectuée vers votre API. Les filtres peuvent être de type :

  • Authorization : Ce type de filtre exécute du code avant la requête pour vérifier si la demande est autorisée, sinon il annule la requête.
  • Resource : Ce type de filtre est utilisé pour effectuer du caching ou pour court-circuiter la requête
  • Action : Un filtre de type Action permet d’effectuer du code juste avant et juste après la requête.
  • Exception : Ce type de filtre est dédié à la gestion des exceptions levées lors du traitement de la requête
  • Result : Ce filtre est exécuté uniquement lorsque la requête s’est déroulée correctement. Il peut être utile si le résultat retourné doit être formaté pour un affichage dans une vue.

L’ordre d’exécution des filtres est présenté dans le schéma suivant, repris de la documentation Microsoft.

Pour notre exemple, nous aurons donc besoin d’un ActionFilter pour le logging, et d’un ExceptionFilter pour la gestion d’exceptions. Il est toutefois déconseillé de créer un ActionFilter pour faire du logging et d’utiliser plutôt les fonctionnalités de logging intégrées à Asp.Net CORE, mais pour notre exemple, cela convient bien.

Pour créer un ActionFilter, il suffit de créer une classe qui implémente l’interface IActionFilter.

public class LoggingFilterAttribute : IActionFilter
{
    private readonly ILogger _logger;
    private readonly Stopwatch _stopWatch = new Stopwatch();

    public LoggingFilterAttribute(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<LoggingFilterAttribute>();
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {           
        _logger.LogInformation($"Start calling {context.ActionDescriptor.DisplayName}");
        _stopWatch.Start();
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        _stopWatch.Stop();
        _logger.LogInformation($"End calling {context.ActionDescriptor.DisplayName}. Request duration : {_stopWatch.Elapsed:mm\\:ss\\.fff}");
    }
}

Le code situé dans la méthode OnActionExecuting sera exécuté avant l’exécution du contenu de la méthode Get(), et le code OnActionExecuted sera exécuté après l’exécution de la méthode Get().

L’ExceptionFilter est créé en implémentant la classe ExceptionFilterAttribute, cette dernière possédant la méthode OnException qui sera appelée en cas d’exception.

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly ILogger _logger;

    public CustomExceptionFilterAttribute(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<CustomExceptionFilterAttribute>();
    }

    public override void OnException(ExceptionContext context)
    {
        _logger.LogError(context.Exception, $"Errors occured while calling {context.ActionDescriptor.DisplayName}");
        context.Result = new StatusCodeResult(500);
    }
}

Étant donné que nos filtres ont des dépendances injectées, nous devons enregistrer ceux-ci dans le containeur. Le fichier Startup.cs sera modifié en ce sens.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new AddFilterWothoutDIAttribute()); // Ajout d'un filtre qui n'a pas de DI
    });
    // Ajout de filtres avec DI
    services.AddScoped<LoggingFilterAttribute>();
    services.AddScoped<CustomExceptionFilterAttribute>();
}

La dernière étape est d’ajouter les filtres créés à notre méthode Get(). Dans le cas où vous avez de l’injection de dépendances dans vos filtres, vous devrez utiliser le ServiceFilter afin que vos dépendances soient bien résolues.

[HttpGet]
[AddFilterWothoutDI]
[ServiceFilter(typeof(LoggingFilterAttribute))]
[ServiceFilter(typeof(CustomExceptionFilterAttribute))]
public ActionResult<IEnumerable<string>> Get()
{
    return new[] { "value1", "value2" };
}

Le contenu de notre méthode Get() est maintenant beaucoup plus lisible.

You may also like...

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *