diff --git a/doc/AG0043.md b/doc/AG0043.md index e17b151..0e1861e 100644 --- a/doc/AG0043.md +++ b/doc/AG0043.md @@ -1,15 +1,14 @@ # AG0043: BuildServiceProvider should not be used in production code ## Problem Description - Using `BuildServiceProvider()` in production code can lead to memory leaks and other issues because it creates a new container. This is especially problematic when called repeatedly, such as in request processing scenarios. ## Rule Details - This rule raises an issue when `BuildServiceProvider()` is called on `IServiceCollection` instances. -### Noncompliant Code Example +### Noncompliant Code Examples +#### Traditional ASP.NET Core ```csharp public void ConfigureServices(IServiceCollection services) { @@ -19,8 +18,27 @@ public void ConfigureServices(IServiceCollection services) } ``` -### Compliant Solution +#### Minimal API +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddTransient(); + +var app = builder.Build(); + +app.MapGet("/", () => +{ + var serviceProvider = app.Services.BuildServiceProvider(); // Noncompliant + var myService = serviceProvider.GetService(); + return myService.GetMessage(); +}); + +app.Run(); +``` + +### Compliant Solutions +#### Traditional ASP.NET Core ```csharp public class Startup { @@ -29,7 +47,6 @@ public class Startup services.AddTransient(); // Let ASP.NET Core build the service provider } - public void Configure(IApplicationBuilder app, IMyService myService) { // Services are injected by the framework @@ -37,36 +54,52 @@ public class Startup } ``` -## Why is this an Issue? +#### Minimal API +```csharp +var builder = WebApplication.CreateBuilder(args); -1. **Memory Leaks**: Each call to `BuildServiceProvider()` creates a new dependency injection container, which holds references to all registered services. If called repeatedly (e.g., during request processing), this leads to memory leaks. +builder.Services.AddTransient(); -2. **Performance Impact**: Creating new service providers is computationally expensive and can impact application performance. +var app = builder.Build(); +app.MapGet("/", (IMyService myService) => myService.GetMessage()); + +app.Run(); + +// Service interfaces and implementations +public interface IMyService +{ + string GetMessage(); +} + +public class MyService : IMyService +{ + public string GetMessage() => "Hello from MyService!"; +} +``` + +## Why is this an Issue? +1. **Memory Leaks**: Each call to `BuildServiceProvider()` creates a new dependency injection container, which holds references to all registered services. If called repeatedly (e.g., during request processing), this leads to memory leaks. +2. **Performance Impact**: Creating new service providers is computationally expensive and can impact application performance. 3. **Singleton Duplication**: Multiple service providers can result in multiple instances of services that should be singletons. ## Exceptions - `BuildServiceProvider()` may be acceptable in the following scenarios: - - Unit tests where a full DI container is needed - Development-time configuration validation - Tools and utilities that run outside the normal application lifecycle ## How to Fix It - 1. In ASP.NET Core applications: - Let the framework handle service provider creation - Use constructor injection to obtain dependencies - Use `IServiceScope` for creating scoped service providers when needed - - HttpContext and other services have method for `RequestServices.GetService` to get scoped services - + - `HttpContext` and other services have methods like `RequestServices.GetService()` to get scoped services 2. For configuration validation: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddTransient(); - // Only in development if (Environment.IsDevelopment()) {