Performance ASP.NET Core : L'art de l'optimisation
La performance d'une application web peut faire la différence entre le succès et l'échec. Voici 20 techniques avancées pour optimiser vos applications ASP.NET Core.
1. Object Pooling
Réutilisez les objets coûteux à créer :
// Configuration du pooling
services.AddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
{
var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
var policy = new StringBuilderPooledObjectPolicy();
return provider.Create(policy);
});
// Utilisation dans un controller
public class HomeController : Controller
{
private readonly ObjectPool<StringBuilder> _stringBuilderPool;
public HomeController(ObjectPool<StringBuilder> stringBuilderPool)
{
_stringBuilderPool = stringBuilderPool;
}
public IActionResult GenerateReport()
{
var sb = _stringBuilderPool.Get();
try
{
sb.AppendLine("Report Header");
sb.AppendLine($"Generated: {DateTime.Now}");
return Content(sb.ToString());
}
finally
{
_stringBuilderPool.Return(sb);
}
}
}
2. Memory Caching Avancé
public class CachedUserService
{
private readonly IMemoryCache _cache;
private readonly IUserRepository _userRepository;
public async Task<User> GetUserAsync(int userId)
{
return await _cache.GetOrCreateAsync($"user_{userId}", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
entry.SlidingExpiration = TimeSpan.FromMinutes(5);
entry.Priority = CacheItemPriority.High;
// Invalidation conditionnelle
entry.RegisterPostEvictionCallback((key, value, reason, state) =>
{
if (reason == EvictionReason.Expired)
{
// Log ou notification
Console.WriteLine($"Cache expired for {key}");
}
});
return await _userRepository.GetByIdAsync(userId);
});
}
}
3. Response Compression Intelligente
// Startup.cs
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"application/json",
"text/json",
"application/javascript",
"text/javascript"
});
options.EnableForHttps = true;
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
4. Database Connection Pooling
// Optimisation des connexions Entity Framework
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.CommandTimeout(30);
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null);
});
}, ServiceLifetime.Scoped);
// Configuration du pool de connexions
services.AddDbContextPool<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString);
}, poolSize: 128);
5. Streaming et IAsyncEnumerable
[HttpGet("large-dataset")]
public async IAsyncEnumerable<DataItem> GetLargeDatasetStream(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var batchSize = 1000;
var offset = 0;
while (!cancellationToken.IsCancellationRequested)
{
var batch = await _repository.GetBatchAsync(offset, batchSize, cancellationToken);
if (!batch.Any())
break;
foreach (var item in batch)
{
yield return item;
}
offset += batchSize;
}
}
6. Custom Middleware pour Performance
public class PerformanceMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<PerformanceMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
}
finally
{
stopwatch.Stop();
var responseTime = stopwatch.ElapsedMilliseconds;
context.Response.Headers.Add("X-Response-Time", $"{responseTime}ms");
if (responseTime > 1000) // Log slow requests
{
_logger.LogWarning(
"Slow request: {Method} {Path} took {ResponseTime}ms",
context.Request.Method,
context.Request.Path,
responseTime);
}
}
}
}
7. Optimisation des JSON APIs
// Configuration JSON optimisée
services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.WriteIndented = false; // Production
});
// Source generators pour JSON
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
internal partial class UserJsonContext : JsonSerializerContext
{
}
// Usage avec des performances optimales
public async Task<IActionResult> GetUsersOptimized()
{
var users = await _userService.GetAllAsync();
return Results.Json(users, UserJsonContext.Default.ListUser);
}
Techniques de Monitoring
8. Application Insights et Métriques Custom
public class MetricsService
{
private readonly TelemetryClient _telemetryClient;
public void TrackCustomMetric(string name, double value, IDictionary<string, string> properties = null)
{
_telemetryClient.TrackMetric(name, value, properties);
}
public void TrackDependency(string dependencyName, string commandName,
DateTimeOffset startTime, TimeSpan duration, bool success)
{
_telemetryClient.TrackDependency(dependencyName, commandName,
startTime, duration, success);
}
}
Optimisations Avancées
9. Précompilation des Expressions
public static class PrecompiledExpressions
{
private static readonly Func<User, bool> IsActiveUser =
CompileExpression<User, bool>(u => u.IsActive && u.LastLoginDate > DateTime.Now.AddDays(-30));
private static Func<T, TResult> CompileExpression<T, TResult>(Expression<Func<T, TResult>> expression)
{
return expression.Compile();
}
public static bool CheckIfUserIsActive(User user)
{
return IsActiveUser(user);
}
}
10. Output Caching (ASP.NET Core 7+)
// Configuration
services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromMinutes(10)));
options.AddPolicy("ApiCache", builder =>
builder.SetVaryByQuery("page", "pageSize")
.Expire(TimeSpan.FromMinutes(5)));
});
// Usage dans un controller
[OutputCache(PolicyName = "ApiCache")]
public async Task<IActionResult> GetProducts(int page = 1, int pageSize = 20)
{
var products = await _productService.GetPagedAsync(page, pageSize);
return Ok(products);
}
Bonnes Pratiques de Performance
- Profilez avant d'optimiser - Utilisez des outils comme dotTrace ou PerfView
- Optimisez les goulots d'étranglement - Ne pas optimiser prématurément
- Surveillez la mémoire - Évitez les fuites mémoire
- Utilisez des patterns asynchrones - async/await partout où c'est pertinent
- Optimisez vos requêtes - Indexation et requêtes efficaces
Conclusion
L'optimisation des performances dans ASP.NET Core nécessite une approche holistique combinant techniques de développement, architecture et monitoring. Ces 20 techniques vous donneront une base solide pour créer des applications haute performance.