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 Scoped services into Singleton services 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.