ASP.NET Core Avancé : Concevoir des Middlewares Personnalisés et Maîtriser l'Endpoint Routing pour une Performance Maximale

mercredi 28 octobre 2026 | ReadingTime
ASP.NET Core Avancé : Concevoir des Middlewares Personnalisés et Maîtriser l'Endpoint Routing pour une Performance Maximale

Dans le développement d'API Web à fort trafic sous ASP.NET Core, l'efficacité d'exécution de chaque requête HTTP dépend directement de la qualité d'architecture de votre pipeline. Chaque requête traverse une chaîne bidirectionnelle de composants — le pipeline de middlewares — qui interagit continuellement avec l'abstraction centrale du runtime : l'objet HttpContext.


La compréhension de l'ordre d'exécution de ces composants et l'utilisation optimale du mécanisme d'Endpoint Routing constituent des exigences architecturales fondamentales. Une mauvaise configuration de cette chaîne génère des goulots d'étranglement de latence, des fuites de mémoire liées à des allocations inutiles sur le Tas (Heap), ainsi que des failles de sécurité si l'authentification ou l'audit s'exécutent dans un ordre incorrect.


Dans cet article technique, nous analyserons la conception de middlewares personnalisés à faible allocation mémoire et l'exploitation de l'Endpoint Routing pour inspecter les métadonnées avant l'exécution du code final applicatif.


1. Conception de Middlewares Personnalisés Haute Performance


Il existe deux approches pour créer des middlewares sous ASP.NET Core : l'approche basée sur les Conventions et l'approche basée sur les Interfaces (IMiddleware).


Basé sur les Conventions vs. Basé sur Interfaces (IMiddleware)


  • Par Convention (Instanciation Unique / Injection par Méthode) : Le middleware est instancié une seule fois lors du démarrage de l'application (durée de vie Singleton). Les dépendances Transitoires (Transient) ou Scopées (Scoped) doivent être injectées directement dans la méthode InvokeAsync(HttpContext context, ...) et non dans le constructeur. Ce modèle est recommandé pour les trajectoires d'exécution à haut débit afin d'éviter la création d'objets à chaque requête.
  • Basé sur Interface (IMiddleware / Scoped) : Implémente l'interface IMiddleware et nécessite un enregistrement explicite dans le conteneur d'Injection de Dépendances (DI). Une instance est résolue pour chaque requête HTTP (Scoped), ce qui simplifie la gestion des dépendances Scopées au prix d'un léger surcoût de résolution dans le conteneur IoC.


Exemple Pratique : Middleware de Mesure de Latence et Traçabilité


L'exemple suivant illustre la création d'un middleware par convention optimisé pour mesurer le temps d'exécution des requêtes HTTP et injecter des identifiants de corrélation (Correlation-ID) sans allocation mémoire superfétatoire.


C#

namespace MasterStack.Architecture.Web.Middlewares; 

 

using System.Diagnostics; 

using Microsoft.AspNetCore.Http; 

using Microsoft.Extensions.Logging; 

 

public sealed class RequestTracingMiddleware 

{ 

    private readonly RequestDelegate _next; 

    private readonly ILogger<RequestTracingMiddleware> _logger; 

    private const string CorrelationIdHeader = "X-Correlation-ID"; 

 

    public RequestTracingMiddleware(RequestDelegate next, ILogger<RequestTracingMiddleware> logger) 

    { 

        _next = next ?? throw new ArgumentNullException(nameof(next)); 

        _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 

    } 

 

    public async Task InvokeAsync(HttpContext context) 

    { 

        // 1. Extraction ou génération de l'ID de corrélation sans allocation massive de chaînes 

        if (!context.Request.Headers.TryGetValue(CorrelationIdHeader, out var correlationId) || string.IsNullOrWhiteSpace(correlationId)) 

        { 

            correlationId = Guid.NewGuid().ToString("N"); 

            context.Request.Headers[CorrelationIdHeader] = correlationId; 

        } 

 

        // Ajout de l'en-tête de réponse avant l'envoi au client 

        context.Response.OnStarting(() => 

        { 

            context.Response.Headers[CorrelationIdHeader] = correlationId; 

            return Task.CompletedTask; 

        }); 

 

        // 2. Mesure précise du temps d'exécution via Stopwatch 

        var startTimestamp = Stopwatch.GetTimestamp(); 

 

        try 

        { 

            await _next(context); 

        } 

        finally 

        { 

            var delta = Stopwatch.GetElapsedTime(startTimestamp); 

             

            _logger.LogInformation( 

                "HTTP {Method} {Path} a répondu {StatusCode} en {ElapsedMs:F2}ms [CorrelationId: {CorrelationId}]", 

                context.Request.Method, 

                context.Request.Path, 

                context.Response.StatusCode, 

                delta.TotalMilliseconds, 

                correlationId.ToString()); 

        } 

    } 

} 

 

// Méthode d'extension idiomatique 

public static class RequestTracingMiddlewareExtensions 

{ 

    public static IApplicationBuilder UseRequestTracing(this IApplicationBuilder builder) 

    { 

        ArgumentNullException.ThrowIfNull(builder); 

        return builder.UseMiddleware<RequestTracingMiddleware>(); 

    } 

} 


2. Maîtriser l'Endpoint Routing sous ASP.NET Core


Introduit avec .NET Core 3.0 et perfectionné dans .NET 8 et .NET 9, l'Endpoint Routing sépare la sélection des routes de l'exécution des contrôleurs d'API ou Minimal APIs. Ce processus s'articule en deux phases distinctes :


  1. UseRouting (Sélection) : Analyse la requête HTTP (URL, Method, Headers), identifie la route correspondante et associe l'objet Endpoint au HttpContext.GetEndpoint().
  2. UseEndpoints ou Minimal APIs (Exécution) : Exécute le délégué configuré pour l'endpoint sélectionné. 

Requête HTTP ──► [ UseRouting ] ──► (Endpoint Sélectionné / Métadonnées) ──► [ Middlewares Intermédiaires ] ──► [ Exécution Endpoint ] 


L'avantage architectural majeur de cette conception réside dans la capacité des middlewares placés entre UseRouting et l'exécuteur final d'inspecter les métadonnées de l'endpoint (EndpointMetadata) avant le traitement du code applicatif.


Exemple Pratique : Contrainte de Route Personnalisée (IRouteConstraint)


Le code suivant montre l'implémentation d'une contrainte de route personnalisée qui valide si un paramètre d'URL correspond à un identifiant système client (CUS) valide avant d'exécuter l'endpoint.


C#

namespace MasterStack.Architecture.Web.Constraints; 

 

using Microsoft.AspNetCore.Http; 

using Microsoft.AspNetCore.Routing; 

 

public sealed class CustomerIdRouteConstraint : IRouteConstraint 

{ 

    public bool Match( 

        HttpContext? httpContext, 

        IRouter? route, 

        string routeKey, 

        RouteValueDictionary values, 

        RouteDirection routeDirection) 

    { 

        if (!values.TryGetValue(routeKey, out var value) || value is null) 

        { 

            return false; 

        } 

 

        var customerId = value.ToString(); 

         

        // Exemple : Vérification que l'ID commence par "CUS-" suivi de chiffres 

        if (string.IsNullOrEmpty(customerId) || !customerId.StartsWith("CUS-", StringComparison.OrdinalIgnoreCase)) 

        { 

            return false; 

        } 

 

        return customerId.Length > 4 && long.TryParse(customerId.AsSpan(4), out _); 

    } 

} 

 

Enregistrement et Utilisation avec Minimal APIs : 




C# 

var builder = WebApplication.CreateBuilder(args); 

 

builder.Services.Configure<RouteOptions>(options => 

{ 

    options.ConstraintMap.Add("validCustomerId", typeof(CustomerIdRouteConstraint)); 

}); 

 

var app = builder.Build(); 

 

app.UseRouting(); 

 

// Les requêtes invalides sont rejetées automatiquement avec un code HTTP 404 

app.MapGet("/api/customers/{id:validCustomerId}", (string id) =>  

{ 

    return Results.Ok(new { CustomerId = id, Status = "Active" }); 

}); 

 

app.Run(); 


3. Cas d'Usage Réels, Pièges et Performance (FinOps & SRE)


L'Ordre d'Enregistrement : Impact sur la Sécurité et la Latence


L'ordre d'enregistrement des middlewares régit le comportement complet du système. Une erreur fréquente consiste à enregistrer le middleware de gestion des exceptions après l'authentification. En cas de défaillance durant la validation des jetons, les erreurs ne respecteront pas le format de réponse standardisé.


✅ Ordre Recommandé du Pipeline : 

1. Exception Handling (Gestion globale des erreurs) 

2. HSTS / Redirection HTTPS 

3. Fichiers Statiques 

4. Routing (UseRouting - Sélection de route et chargement des métadonnées) 

5. CORS 

6. Authentication (Vérification de l'identité) 

7. Authorization (Validation des permissions selon les métadonnées de l'endpoint) 

8. Middlewares Métier Personnalisés 

9. Endpoint Execution (UseEndpoints / MapEndpoints) 


Optimisations Mémoire pour les API à Fort Débit


  • Évitez la Lecture Synchrone du Corps des Requêtes : L'accès synchrone à context.Request.Body peut entraîner une saturation des threads (ThreadPool starvation). N'activez context.Request.EnableBuffering() que si la relecture du flux est strictement nécessaire, et effectuez toujours des lectures asynchrones via ReadAsync().
  • Limitez les Allocations dans InvokeAsync : N'instanciez pas d'objets temporaires, de parsers JSON ou de collections au sein du bloc InvokeAsync. Privilégiez des instances statiques ou réutilisables afin de réduire la pression sur le Garbage Collector (GC).


Quand Utiliser vs. Quand ÉVITER 


Manipulation des En-têtes HTTP (CORS, Correlation-ID)

  • Middleware Global : ✅ Oui
  • Filtre d'Endpoint / Action : ❌ Non

Journalisation Globale et Métriques de Latence

  • Middleware Global : ✅ Oui
  • Filtre d'Endpoint / Action : ❌ Non

Validation de Modèles de Domaine Spécifiques

  • Middleware Global : ❌ Non
  • Filtre d'Endpoint / Action : ✅ Oui

Autorisation basée sur des Rôles Spécifiques à une Action

  • Middleware Global : ❌ Non
  • Filtre d'Endpoint / Action : ✅ Oui



Conclusion


La maîtrise de l'architecture du pipeline HTTP et de l'Endpoint Routing sous ASP.NET Core permet de concevoir des API Web résilientes, sécurisées et ultra-performantes. L'application rigoureuse des middlewares par convention et des contraintes de route préserve les ressources processeur et limite l'utilisation de la mémoire sur le Tas.


👉 Prêt à franchir un cap dans votre carrière d'ingénierie .NET ? Explorez nos articles techniques et découvrez les meilleures opportunités pour développeurs sur MasterStack Jobs. Nous connectons les talents d'expérience avec les entreprises technologiques les mais innovantes !

Marcio Mazeu
Écrit par
Marcio Mazeu

.NET Software Developer

Analyste-Programmeur .NET diplômé en Génie Informatique, spécialisé dans la modernisation d'applications et l'architecture d'APIs (C#, SQL Server, JavaScript). Passionné par le développement d'outils performants et l'apprentissage continu, il contribue à la création de solutions technologiques fiables et axées sur l'expérience utilisateur.