Les middleware personnalisés constituent un élément fondamental de l'architecture ASP.NET Core, permettant d'intercepter et de modifier les requêtes HTTP tout au long du pipeline de traitement. Dans cet article, nous explorerons en détail comment créer et utiliser des middleware personnalisés pour enrichir vos applications ASP.NET Core de manière élégante et efficace.
Comprendre les Middleware dans ASP.NET Core
Un middleware est un composant logiciel qui s'intègre dans le pipeline de traitement HTTP d'une application ASP.NET Core. Il peut examiner, modifier ou court-circuiter les requêtes et les réponses avant qu'elles n'atteignent leur destination finale.
Architecture du Pipeline
Le pipeline de middleware fonctionne selon le principe "first in, first out" (FIFO), où chaque middleware peut :
- Traiter la requête entrante
- Passer la requête au middleware suivant
- Traiter la réponse sortante
Création d'un Middleware Personnalisé
Il existe deux approches principales pour créer un middleware personnalisé :
1. Convention Middleware (méthode simple)
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Logging avant le traitement de la requête
_logger.LogInformation($"Requête entrante: {context.Request.Path}");
// Appel du middleware suivant
await _next(context);
// Logging après le traitement de la requête
_logger.LogInformation($"Réponse : {context.Response.StatusCode}");
}
}
2. Interface IMiddleware (approche orientée DI)
public class PerformanceMiddleware : IMiddleware
{
private readonly ILogger<PerformanceMiddleware> _logger;
private readonly Stopwatch _stopwatch;
public PerformanceMiddleware(ILogger<PerformanceMiddleware> logger)
{
_logger = logger;
_stopwatch = new Stopwatch();
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
_stopwatch.Start();
await next(context);
_stopwatch.Stop();
_logger.LogInformation($"Requête traitée en {_stopwatch.ElapsedMilliseconds}ms");
}
}
Enregistrement et Configuration
Pour utiliser vos middleware, vous devez les enregistrer dans la méthode Configure de Startup.cs :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Middleware conventionnel
app.UseMiddleware<RequestLoggingMiddleware>();
// Middleware IMiddleware
app.UseMiddleware<PerformanceMiddleware>();
// Extension method (recommandé)
app.UseRequestLogging();
}
Méthode d'Extension
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseRequestLogging(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggingMiddleware>();
}
}
Cas d'Usage Pratiques
1. Middleware d'Authentication Personnalisé
public class CustomAuthMiddleware
{
private readonly RequestDelegate _next;
public CustomAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(authHeader))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Token manquant");
return;
}
// Validation du token...
await _next(context);
}
}
2. Middleware de Gestion des Exceptions
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public GlobalExceptionMiddleware(RequestDelegate next,
ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Une erreur s'est produite");
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var response = new
{
Message = "Une erreur interne s'est produite",
ExceptionMessage = ex.Message
};
await context.Response.WriteAsJsonAsync(response);
}
}
}
Bonnes Pratiques
- Suivez le principe de responsabilité unique (SRP)
- Utilisez l'injection de dépendances
- Gérez correctement les exceptions
- Documentez le comportement du middleware
- Testez exhaustivement
Tests Unitaires
public class RequestLoggingMiddlewareTests
{
[Fact]
public async Task InvokeAsync_LogsRequestAndResponse()
{
// Arrange
var loggerMock = new Mock<ILogger<RequestLoggingMiddleware>>();
var contextMock = new DefaultHttpContext();
var nextMock = new RequestDelegate(_ => Task.CompletedTask);
var middleware = new RequestLoggingMiddleware(nextMock, loggerMock.Object);
// Act
await middleware.InvokeAsync(contextMock);
// Assert
loggerMock.Verify(
x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => true),
It.IsAny<Exception>(),
It.Is<Func<It.IsAnyType, Exception, string>>((v, t) => true)),
Times.Exactly(2));
}
}
Considérations de Performance
Pour optimiser les performances de vos middleware :
- Évitez les opérations bloquantes
- Utilisez async/await correctement
- Mettez en cache les résultats quand c'est possible
- Minimisez l'allocation de mémoire
Conclusion
Les middleware personnalisés sont un outil puissant pour étendre les fonctionnalités d'ASP.NET Core. En suivant les bonnes pratiques et en comprenant leur fonctionnement, vous pouvez créer des composants réutilisables qui améliorent la qualité et la maintenabilité de vos applications.
N'oubliez pas de :
- Bien organiser l'ordre d'exécution des middleware
- Documenter leur comportement
- Tester rigoureusement
- Surveiller les performances