Stratégies de gestion des exceptions avec l'intergiciel intégré d'ASP.NET Core

La gestion robuste des erreurs est cruciale pour toute application web afin d'offrir une expérience utilisateur cohérente et de maintenir la stabilité. ASP.NET Core propose un intergiciel (middleware) d'exception puissant et flexible, conçu pour intercepter et traiter les erreurs non gérées au sein de la pipeline de requêtes. Cet article explore diverses approches pour configurer et personnaliser ce mécanisme.

1. Traitement direct des exceptions dans la pipeline

La manière la plus simple d'utiliser l'intergiciel d'exception est de le configurer pour qu'il gère directement les erreurs, par exemple en écrivant les détails de l'exception dans la réponse HTTP. Cette méthode est souvent utilisée pour le débogage ou pour des APIs simples où une réponse textuelle détaillée est acceptable.

app.UseExceptionHandler(contexteConfiguration =>
{
    contexteConfiguration.Run(async contexteHttp =>
    {
        var caracteristiquesException = contexteHttp.Features.Get<IExceptionHandlerPathFeature>();
        var erreurRencontree = caracteristiquesException?.Error;

        if (erreurRencontree != null)
        {
            contexteHttp.Response.ContentType = "text/plain;charset=utf-8";
            contexteHttp.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await contexteHttp.Response.WriteAsync($"Une erreur serveur est survenue : {erreurRencontree.Message}\n\nDétails : {erreurRencontree.StackTrace}");
        }
        else
        {
            contexteHttp.Response.ContentType = "text/plain;charset=utf-8";
            contexteHttp.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await contexteHttp.Response.WriteAsync("Erreur interne non spécifiée.");
        }
    });
});

Une variation de cette approche implique l'utilisation de l'objet ExceptionHandlerOptions pour spécifier le délégué de gestion des exceptions, offrant une clarté structurelle similaire.

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    ExceptionHandler = async contexteHttp =>
    {
        var cheminErreur = contexteHttp.Features.Get<IExceptionHandlerPathFeature>();
        var exceptionDetectee = cheminErreur?.Error;

        if (exceptionDetectee != null)
        {
            contexteHttp.Response.ContentType = "text/plain;charset=utf-8";
            contexteHttp.Response.StatusCode = StatusCodes.Status500InternalServerError;
            await contexteHttp.Response.WriteAsync($"Incident détecté : {exceptionDetectee.Message}");
        }
    }
});

2. Redirection vers un gestionnaire d'erreurs dédié

Pour une gestion des erreurs plus conviviale ou pour afficher une page d'erreur personnalisée, l'intergiciel d'exception peut être configuré pour rediriger le flux de la requête vers un chemin URL spécifique. Ce chemin peut ensuite être géré par un intergiciel subséquent ou un contrôleur, permettant une présentation cohérente des messages d'erreur.

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    // Indique au middleware de rediriger vers /errors en cas d'exception non gérée
    ExceptionHandlingPath = "/errors" 
});

// Définit un intergiciel pour le chemin /errors
app.Map("/errors", appBuilder =>
{
    appBuilder.Run(async contexte =>
    {
        // On peut récupérer les détails de l'erreur via IExceptionHandlerPathFeature si désiré
        var infosErreur = contexte.Features.Get<IExceptionHandlerPathFeature>();
        contexte.Response.StatusCode = StatusCodes.Status500InternalServerError;
        contexte.Response.ContentType = "text/html;charset=utf-8";
        await contexte.Response.WriteAsync("<h1>Oups ! Une erreur est survenue.</h1><p>Nous travaillons à résoudre le problème. Veuillez réessayer plus tard.</p>");
    });
});

3. Implémentatino d'un intergiciel d'exception personnalisé pour une gestion avancée

Pour une gestion des erreurs plus complexe, comme la journalisation détaillée, la personnalisation des réponses JSON en fonction du type d'exception, ou la traduction des messages, la création d'un intergiciel personnalisé est la méthode privilégiée. Cela offre un contrôle total sur le processus de traitmeent des erreurs.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

// Classe modèle pour la réponse d'erreur de l'API
public class ErreurApiResponse
{
    public bool EstReussie { get; set; } = false;
    public string MessageGlobal { get; set; }
    public string CodeSpecific { get; set; }
}

/// <summary>
/// Intergiciel personnalisé pour la gestion globale des exceptions.
/// </summary>
public class GestionnaireExceptionsApiMiddleware
{
    private readonly RequestDelegate _requeteSuivante;
    private readonly ILogger<GestionnaireExceptionsApiMiddleware> _journalisateur;

    public GestionnaireExceptionsApiMiddleware(RequestDelegate requeteSuivante, ILogger<GestionnaireExceptionsApiMiddleware> journalisateur)
    {
        _requeteSuivante = requeteSuivante;
        _journalisateur = journalisateur;
    }

    public async Task InvokeAsync(HttpContext contexteHttp)
    {
        try
        {
            await _requeteSuivante(contexteHttp);
        }
        catch (Exception exceptionInterceptee)
        {
            await TraiterLaReponseErreur(contexteHttp, exceptionInterceptee);
        }
    }

    private async Task TraiterLaReponseErreur(HttpContext contexteHttp, Exception exceptionCourante)
    {
        contexteHttp.Response.ContentType = "application/json";
        var reponseErreur = new ErreurApiResponse { EstReussie = false };
        contexteHttp.Response.StatusCode = StatusCodes.Status500InternalServerError; // Statut par défaut

        switch (exceptionCourante)
        {
            case ApplicationException appEx:
            {
                // Exemple : erreur liée à la logique métier
                reponseErreur.MessageGlobal = appEx.Message;
                reponseErreur.CodeSpecific = "APP_LOGIC_ERROR";
                contexteHttp.Response.StatusCode = StatusCodes.Status400BadRequest;
                _journalisateur.LogWarning(appEx, "Erreur d'application métier : {Msg}", appEx.Message);
                break;
            }
            case KeyNotFoundException keyEx:
            {
                // Exemple : ressource non trouvée
                reponseErreur.MessageGlobal = "L'élément demandé n'a pas été trouvé.";
                reponseErreur.CodeSpecific = "RESOURCE_NOT_FOUND";
                contexteHttp.Response.StatusCode = StatusCodes.Status404NotFound;
                _journalisateur.LogInformation(keyEx, "Clé d'accès introuvable : {Msg}", keyEx.Message);
                break;
            }
            default:
            {
                // Toutes les autres exceptions non spécifiques
                reponseErreur.MessageGlobal = "Une erreur inattendue est survenue sur le serveur.";
                reponseErreur.CodeSpecific = "INTERNAL_SERVER_ERROR";
                _journalisateur.LogError(exceptionCourante, "Exception non gérée : {Msg}", exceptionCourante.Message);
                break;
            }
        }

        var contenuJson = JsonConvert.SerializeObject(reponseErreur);
        await contexteHttp.Response.WriteAsync(contenuJson);
    }
}

Pour intégrer cet intergiciel personnalisé dans la pipeline de requêtes, il suffit d'utiliser la méthode UseMiddleware :

app.UseMiddleware<GestionnaireExceptionsApiMiddleware>();

Il est impératif de souligner que les mécanismes d'intergiciel d'exception et de filtres d'exception d'ASP.NET Core ne traitent que les exceptions qui ne sont pas déjà capturées par un bloc try-catch situé plus bas dans la chaîne d'exécution. Une exception gérée localement ne remontera pas la pipeline et ne sera donc pas traitée par ces intergiciels.

Étiquettes: ASP.NET Core middleware gestion des exceptions C# Traitement des erreurs

Publié le 24 octobre à 23h00