On this page
Dependency Injection
ASP.NET Core has built-in dependency injection (DI). Services are registered at startup and injected into controllers, middleware, and other services.
Service Lifetimes
| Lifetime | Behavior |
|---|---|
Singleton |
One instance for the app lifetime |
Scoped |
One instance per HTTP request |
Transient |
New instance every time requested |
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
Define and Inject Services
public interface IGreetingService
{
string Greet(string name);
}
public class GreetingService : IGreetingService
{
public string Greet(string name) => $"Hello, {name}!";
}
[ApiController]
[Route("api/[controller]")]
public class HelloController : ControllerBase
{
private readonly IGreetingService _greeting;
public HelloController(IGreetingService greeting)
{
_greeting = greeting;
}
[HttpGet("{name}")]
public IActionResult Get(string name) =>
Ok(_greeting.Greet(name));
}
Register in Program.cs:
builder.Services.AddScoped<IGreetingService, GreetingService>();
Factory and Options Pattern
builder.Services.Configure<SmtpSettings>(
builder.Configuration.GetSection("Smtp"));
public class SmtpSettings
{
public string Host { get; set; } = string.Empty;
public int Port { get; set; }
}
public class EmailService
{
private readonly SmtpSettings _settings;
public EmailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
Manual Resolution (Avoid in App Code)
using var scope = app.Services.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IUserRepository>();
Prefer constructor injection. Use IServiceScopeFactory when you need scoped services outside of a request (e.g., background jobs).
Best Practices
- Program to interfaces, not concrete classes.
- Keep services focused — single responsibility.
- Do not inject
Scopedservices intoSingletonservices directly.
Registering Open Generics Services
builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
One registration covers IRepository<User>, IRepository<Order>, etc.
Keyed Services (.NET 8+)
builder.Services.AddKeyedScoped<IPaymentGateway, StripeGateway>("stripe");
builder.Services.AddKeyedScoped<IPaymentGateway, PayPalGateway>("paypal");
public class CheckoutService
{
public CheckoutService(
[FromKeyedServices("stripe")] IPaymentGateway gateway) { }
}
Validating Service Registration at Startup
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true;
});
Catches misconfigured lifetimes (e.g., scoped into singleton) at startup instead of runtime.
Anti-Patterns to Avoid
| Anti-pattern | Problem |
|---|---|
Service locator (GetService everywhere) |
Hides dependencies |
| Singleton holding scoped state | Stale or shared user data |
| Too many constructor parameters | Class doing too much — split it |
| Circular dependencies | Redesign or use Lazy<T> / events |
Testing with DI
var services = new ServiceCollection();
services.AddScoped<IUserRepository, FakeUserRepository>();
services.AddScoped<UserService>();
var provider = services.BuildServiceProvider();
var service = provider.GetRequiredService<UserService>();
Register fakes in tests to isolate the class under test.