16 questions found
What is the Options pattern's relationship to DI, and how does IOptions<T> itself get resolved through the container?
Advanced
IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T> are themselves services resolved through the DI container (registered automatically when you call services.AddOptions() or implicitly via Configure<T>()), demonstrating that the Options pattern is built entirely on top of the standard DI infrastructure rather than being a separate mechanism -- IOptions<T> is registered as Singleton internally, IOptionsSnapshot<T> as Scoped, explaining their respective behavior around configuration change visibility.
// Configure<T> implicitly registers the necessary IOptions<T> infrastructure
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
// Under the hood, this makes IOptions<SmtpSettings>, IOptionsSnapshot<SmtpSettings>,
// and IOptionsMonitor<SmtpSettings> all resolvable via the same DI container
Real-world example
Understanding that IOptionsSnapshot<T> is registered as Scoped explains precisely why it can't be safely injected into a Singleton service -- it's the exact same captive dependency problem as any other Scoped service being consumed by a Singleton.
Common follow-ups: Why does IOptionsMonitor<T> avoid the captive dependency problem despite being usable in Singletons?;How would you build a custom pattern following these same DI principles?
Configuration & Options;Background Services
How do third-party DI containers like Autofac integrate with ASP.NET Core's built-in DI abstractions?
Intermediate
ASP.NET Core's DI system is built around abstractions (IServiceCollection for registration, IServiceProvider for resolution) that third-party containers can replace entirely via a custom IServiceProviderFactory, letting you register services using the built-in IServiceCollection (for compatibility with framework and library registrations) while gaining access to advanced features the built-in container lacks, like multiple constructor resolution, property injection, or more sophisticated modules/scanning -- Autofac is the most common choice for teams needing these advanced capabilities.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => {
containerBuilder.RegisterModule<MyAutofacModule>();
});
Real-world example
A large enterprise application with complex conditional registration requirements (based on assembly scanning and convention-based module discovery) adopts Autofac specifically for its more powerful registration API, while still leveraging built-in framework services registered via standard IServiceCollection calls.
Common follow-ups: What features does Autofac provide that the built-in container lacks?;What's the performance trade-off of using a more feature-rich third-party container?
Dependency Injection;.NET CLI
SDK & Project Structure (csproj)
How would you unit test a service that depends on multiple injected interfaces without spinning up the full DI container?
Advanced
Unit tests typically construct the service under test directly, passing mock or fake implementations of its dependencies (created via a mocking library like Moq or NSubstitute, or hand-written test doubles) directly to the constructor -- bypassing the DI container entirely, since unit tests should test the class's logic in isolation, not the wiring itself (that's what integration tests using WebApplicationFactory are for).
[Fact]
public async Task ProcessOrder_SendsConfirmationEmail() {
var mockEmailSender = new Mock<IEmailSender>();
var sut = new OrderService(mockEmailSender.Object); // direct construction, no DI container
await sut.ProcessOrderAsync(new Order());
mockEmailSender.Verify(e => e.SendAsync(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
Real-world example
A test suite for OrderService directly constructs it with a mocked IEmailSender and IPaymentGateway, verifying business logic behavior in milliseconds without any DI container overhead or database/network dependencies.
Common follow-ups: When would an integration test actually need to resolve services through the real DI container?;How do you test a service with many dependencies without excessive mock setup boilerplate?
Testing in .NET (xUnit
Integration & Unit Testing);Dependency Injection
What does GetService<T>() return compared to GetRequiredService<T>() when a service isn't registered?
Beginner
GetService<T>() returns null if the requested service type isn't registered in the container, requiring the caller to handle the null case explicitly. GetRequiredService<T>() instead throws an InvalidOperationException immediately with a clear message identifying the missing service, which is generally preferred in application code since a missing required dependency usually indicates a configuration bug that should fail loudly and immediately rather than propagate a confusing null reference later.
var logger = serviceProvider.GetService<ILogger<MyClass>>(); // returns null if not registered
if (logger == null) { /* handle missing case */ }
var requiredLogger = serviceProvider.GetRequiredService<ILogger<MyClass>>(); // throws clearly if missing
Real-world example
A team standardizes on GetRequiredService throughout their startup code specifically so a missing service registration causes an immediate, clear startup failure instead of a null reference exception deep inside unrelated business logic hours later.
Common follow-ups: When is GetService's null-returning behavior actually useful over GetRequiredService?;How does constructor injection avoid needing either of these calls directly?
Dependency Injection;Diagnostics & Performance
How does ASP.NET Core's DI container support injecting a logger via ILogger<T>, and how is the generic type parameter used?
Intermediate
ILogger<T> is automatically resolvable for any type T without explicit registration, since the logging infrastructure registers a generic ILogger<> open generic factory -- the T parameter becomes the logger's 'category name' (typically the full type name), letting log output and filtering be scoped precisely to the class that generated each log entry.
public class OrderService {
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger) => _logger = logger;
public void Process() {
_logger.LogInformation("Processing order"); // logged with category 'OrderService'
}
}
Real-world example
A production log filter configured to show only Warning+ level messages from OrderService (but Debug level from PaymentService) relies entirely on each class injecting its own correctly-typed ILogger<T>, which automatically tags every log entry with the right category.
Common follow-ups: How does the logging configuration system use these category names for filtering?;Why is ILogger<T> registered without explicit AddScoped/AddSingleton calls?
Logging;Dependency Injection
What is dependency injection, and why does it make a codebase easier to test and maintain?
Intermediate
Dependency injection (DI) is a design pattern where a class receives the dependencies it needs from outside (typically via its constructor) rather than creating them itself -- this loosens coupling (a class depends on an interface, not a concrete implementation), makes unit testing far easier (a test can inject a mock/fake implementation instead of the real dependency), and centralizes how dependencies are wired together, so swapping an implementation later doesn't require touching every class that uses it.
// Tightly coupled: OrderService creates its own dependency directly
public class OrderService {
private PaymentService _paymentService = new(); // hardcoded, hard to test or swap
}
// Loosely coupled via constructor injection
public class OrderService {
private readonly IPaymentService _paymentService;
public OrderService(IPaymentService paymentService) => _paymentService = paymentService;
}
// ASP.NET Core registers the mapping once, centrally
builder.Services.AddScoped<IPaymentService, PaymentService>();
Real-world example
A team writing unit tests for OrderService injects a fake IPaymentService that always returns success, testing OrderService's own logic in complete isolation without needing a real payment gateway connection during every test run.
Common follow-ups: What's the difference between constructor, property, and method injection?;How does ASP.NET Core's built-in DI container decide an object's lifetime (transient, scoped, singleton)?
Dependency Injection;Design Patterns in C#