Skip to content

Dependency Injection

Controller-based queries support full dependency injection through their constructors, allowing you to inject services, repositories, loggers, and other dependencies from the service collection.

The most common pattern is to inject dependencies through the controller’s constructor:

[Route("api/accounts")]
public class Accounts : Controller
{
readonly IAccountService _accountService;
readonly ILogger<Accounts> _logger;
readonly IMongoCollection<DebitAccount> _collection;
public Accounts(
IAccountService accountService,
ILogger<Accounts> logger,
IMongoCollection<DebitAccount> collection)
{
_accountService = accountService;
_logger = logger;
_collection = collection;
}
[HttpGet]
public IEnumerable<DebitAccount> AllAccounts()
{
_logger.LogInformation("Retrieving all accounts");
return _accountService.GetAllAccounts();
}
}

MongoDB collections are commonly injected:

public class Accounts : Controller
{
readonly IMongoCollection<DebitAccount> _collection;
public Accounts(IMongoCollection<DebitAccount> collection)
{
_collection = collection;
}
[HttpGet]
public async Task<IEnumerable<DebitAccount>> GetAccountsAsync()
{
var result = await _collection.FindAsync(_ => true);
return result.ToList();
}
}

For Entity Framework Core scenarios:

public class Accounts : Controller
{
readonly ApplicationDbContext _dbContext;
public Accounts(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public async Task<IEnumerable<DebitAccount>> GetAccountsAsync()
{
return await _dbContext.Accounts.ToListAsync();
}
}

Inject business logic services:

public class Accounts : Controller
{
readonly IAccountService _accountService;
readonly ICustomerService _customerService;
public Accounts(IAccountService accountService, ICustomerService customerService)
{
_accountService = accountService;
_customerService = customerService;
}
[HttpGet("{id}/details")]
public async Task<AccountDetails> GetAccountDetails(AccountId id)
{
var account = await _accountService.GetAccountAsync(id);
var customer = await _customerService.GetCustomerAsync(account.Owner);
return new AccountDetails(account, customer);
}
}

Structured logging with dependency injection:

public class Accounts : Controller
{
readonly IMongoCollection<DebitAccount> _collection;
readonly ILogger<Accounts> _logger;
public Accounts(IMongoCollection<DebitAccount> collection, ILogger<Accounts> logger)
{
_collection = collection;
_logger = logger;
}
[HttpGet("search")]
public async Task<IEnumerable<DebitAccount>> SearchAccounts([FromQuery] string term)
{
_logger.LogInformation("Searching accounts with term: {SearchTerm}", term);
var filter = Builders<DebitAccount>.Filter.Regex(
a => a.Name,
new BsonRegularExpression(term, "i"));
var result = await _collection.FindAsync(filter);
var accounts = result.ToList();
_logger.LogInformation("Found {AccountCount} accounts", accounts.Count);
return accounts;
}
}

Inject configuration objects:

public class Accounts : Controller
{
readonly IMongoCollection<DebitAccount> _collection;
readonly AccountQueryOptions _options;
public Accounts(
IMongoCollection<DebitAccount> collection,
IOptions<AccountQueryOptions> options)
{
_collection = collection;
_options = options.Value;
}
[HttpGet]
public async Task<IEnumerable<DebitAccount>> GetAccounts()
{
var result = await _collection.FindAsync(_ => true);
return result.Limit(_options.DefaultPageSize).ToList();
}
}

Make sure your dependencies are registered in the service collection:

// In Program.cs or Startup.cs
builder.Services.AddScoped<IAccountService, AccountService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();
builder.Services.Configure<AccountQueryOptions>(
builder.Configuration.GetSection("AccountQueries"));

Controllers can have many dependencies injected:

public class Accounts : Controller
{
readonly IAccountService _accountService;
readonly ICustomerService _customerService;
readonly ICachingService _cache;
readonly ILogger<Accounts> _logger;
readonly IMapper _mapper;
readonly AccountQueryOptions _options;
public Accounts(
IAccountService accountService,
ICustomerService customerService,
ICachingService cache,
ILogger<Accounts> logger,
IMapper mapper,
IOptions<AccountQueryOptions> options)
{
_accountService = accountService;
_customerService = customerService;
_cache = cache;
_logger = logger;
_mapper = mapper;
_options = options.Value;
}
[HttpGet("{id}")]
public async Task<AccountDetails> GetAccount(AccountId id)
{
var cacheKey = $"account-{id}";
var cached = await _cache.GetAsync<AccountDetails>(cacheKey);
if (cached is not null)
{
_logger.LogInformation("Returning cached account {AccountId}", id);
return cached;
}
_logger.LogInformation("Loading account {AccountId} from database", id);
var account = await _accountService.GetAccountAsync(id);
var customer = await _customerService.GetCustomerAsync(account.Owner);
var result = _mapper.Map<AccountDetails>((account, customer));
await _cache.SetAsync(cacheKey, result, _options.CacheExpiry);
return result;
}
}

You can inject generic types:

public class GenericQueries<T> : Controller where T : class
{
readonly IRepository<T> _repository;
readonly ILogger<GenericQueries<T>> _logger;
public GenericQueries(IRepository<T> repository, ILogger<GenericQueries<T>> logger)
{
_repository = repository;
_logger = logger;
}
[HttpGet]
public async Task<IEnumerable<T>> GetAll()
{
_logger.LogInformation("Getting all {EntityType}", typeof(T).Name);
return await _repository.GetAllAsync();
}
}
  1. Use readonly fields - Store injected dependencies as readonly fields
  2. Prefer constructor injection over method injection or service locator patterns
  3. Keep constructors clean - Don’t perform logic in constructors, just store dependencies
  4. Use appropriate lifetimes - Register services with appropriate lifetimes (Singleton, Scoped, Transient)
  5. Validate dependencies - Ensure all required dependencies are registered in the DI container
  6. Use IOptions<T> for configuration objects rather than injecting raw configuration

Don’t use IServiceProvider directly in your controllers:

// ❌ Don't do this - service locator anti-pattern
public class BadAccounts : Controller
{
readonly IServiceProvider _serviceProvider;
public BadAccounts(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpGet]
public IEnumerable<DebitAccount> GetAccounts()
{
var service = _serviceProvider.GetRequiredService<IAccountService>();
return service.GetAllAccounts();
}
}
// ✅ Do this instead - constructor injection
public class GoodAccounts : Controller
{
readonly IAccountService _accountService;
public GoodAccounts(IAccountService accountService)
{
_accountService = accountService;
}
[HttpGet]
public IEnumerable<DebitAccount> GetAccounts()
{
return _accountService.GetAllAccounts();
}
}