Introduction
If you're preparing for a .NET developer interview, dependency injection questions will come up — not as a formality, but because DI sits at the center of how ASP.NET Core applications are wired together, tested, and scaled. This guide collects 30 ASP.NET Core dependency injection interview questions, organized from junior fundamentals through senior architectural judgment calls.
Quick answer: ASP.NET Core ships with a built-in DI container that creates your objects and controls how long they live. Every service you register gets one of three lifetimes — transient (new every time), scoped (one per scope, which is one per HTTP request in a typical web app), or singleton (one for the whole application). The mistake interviewers probe most often is mixing those lifetimes, for example injecting a scoped service into a singleton.
Dependency injection (DI) is a technique where an object receives the things it depends on — other objects, configuration, services — from the outside, rather than creating them itself. In ASP.NET Core, this is handled by a built-in container, formally called the service provider, which manages object creation and lifetime so your classes don't have to.
The questions below aren't just definitions. Several walk through how the container resolves dependencies internally, what happens when lifetimes clash, and why the built-in container sometimes isn't enough. Treat this as both a crammer before an interview and a reference you can come back to when you hit a confusing runtime error in a real project.
How This Guide Is Organized
Questions are grouped into three tiers. Junior-level questions cover definitions and basic registration syntax — the kind of thing you should answer without hesitation. Mid-level questions dig into lifetime interactions, scopes, and common runtime errors. Senior-level questions move into container internals, testability, and architectural tradeoffs — the kind of thing that separates someone who's used DI from someone who understands why it works the way it does.
Prerequisites
The examples assume a few things, so check them before you try the code yourself:
-
A recent .NET SDK. The minimal hosting model (
WebApplication.CreateBuilder) needs .NET 6 or later, and keyed services (question 22) need .NET 8 or later. The examples target .NET 8 or later. -
Basic C# knowledge: classes, interfaces, constructors, and
async/await. -
For the database example in question 7, the Entity Framework Core SQL Server
provider package (
Microsoft.EntityFrameworkCore.SqlServer) and ausing Microsoft.EntityFrameworkCore;directive.
Junior-Level Questions
1. What is dependency injection, and why does ASP.NET Core use it by default?
Dependency injection is a pattern where a class declares what it needs (its dependencies) through its constructor, and something external supplies those dependencies at runtime. ASP.NET Core builds this in natively because the framework's own internals — middleware, controllers, Razor pages, minimal API handlers — rely on it to get logging, configuration, database contexts, and other services without hardcoding how they're constructed.
2. What is Inversion of Control (IoC), and how does it relate to DI?
Inversion of Control is the broader principle: instead of a class controlling the creation of its own dependencies, that control is handed over to an external framework or container. Dependency injection is one specific technique for achieving IoC. The container inverts the usual flow — your code asks for an interface, and the container decides what concrete implementation to hand back.
3. What are the three types of dependency injection?
The three commonly recognized forms are constructor injection, where dependencies arrive as constructor parameters; method injection, where a dependency is passed into a specific method rather than the whole class; and property injection, where a dependency is set on a public property after construction.
ASP.NET Core's built-in container supports constructor injection as the
primary, idiomatic approach. Method injection shows up in specific framework
features rather than as a general container capability: for example,
[FromServices] on action method parameters,
service parameters on minimal API handlers, and scoped services passed to a
middleware's InvokeAsync method. Property
injection isn't supported by the built-in container itself. Some framework
features, such as Blazor's
[Inject] attribute, do populate properties,
but for your own classes it's generally not recommended because it hides
required dependencies instead of making them explicit.
4. What is the IServiceCollection, and what is it used for?
IServiceCollection is the registry where you
declare which services your application needs and how the container should
construct them. In a typical Program.cs, you
call methods like
builder.Services.AddScoped<IOrderService, OrderService>()
to add entries to this collection before the application builds its service
provider.
5. What is IServiceProvider?
IServiceProvider is the actual container that
resolves instances at runtime. Once
IServiceCollection registrations are
finalized (typically when
builder.Build() runs), ASP.NET Core builds an
IServiceProvider from them. The framework
calls that provider internally whenever it needs to construct a controller, a
middleware, or any class with injected dependencies.
6. What's the difference between AddTransient, AddScoped, and AddSingleton?
These three extension methods register a service with a different lifetime, which controls how long an instance lives and how often a new one is created:
builder.Services.AddTransient<IEmailValidator, EmailValidator>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
A transient service gets a brand-new instance every single time it's requested, anywhere in the dependency graph. A scoped service gets one instance per scope. In a typical web application, a scope corresponds to one HTTP request (in Blazor Server, it's one circuit instead). A singleton service gets exactly one instance for the entire lifetime of the application, created once and reused for every request after that. By default a singleton is created the first time something asks for it, unless you hand the container a ready-made instance.
| Lifetime | New instance created | Typical use | Watch out for |
|---|---|---|---|
| Transient | Every time the service is requested | Lightweight, stateless helpers | Disposable transients are held by the container until their scope (or the app) ends |
| Scoped | Once per scope (one per HTTP request in a typical web app) | Database contexts, per-request work | Don't inject into a singleton |
| Singleton | Once for the whole application | Caches, shared read-mostly state | Must be thread-safe |
One practical detail worth remembering:
AddDbContext registers your database context
as scoped by default, which is why scoped is the lifetime you'll meet most
often in web apps.
7. How do you register a service in Program.cs in a minimal hosting model app?
You call the registration methods on
builder.Services before
builder.Build() is invoked:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
After Build() runs, the registrations are
compiled into the service provider and can't be changed. In recent .NET
versions, trying to add a registration to
builder.Services afterward throws an
InvalidOperationException. Either way, the
running application never sees a registration added after the build.
8. Can you inject a service directly into a controller constructor? Show an example.
Yes — this is the most common usage pattern. The container inspects the constructor parameters and resolves each one from its own registrations.
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderRepository _orderRepository;
public OrdersController(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id)
{
var order = await _orderRepository.GetByIdAsync(id);
return order is null ? NotFound() : Ok(order);
}
}
This assumes IOrderRepository is registered
as in question 7, and that Program.cs calls
AddControllers() and
MapControllers(). The controller never calls
new OrderRepository() directly, which means
the concrete implementation can be swapped or mocked without touching the
controller code.
9. What happens if you request a service that was never registered?
The container throws an
InvalidOperationException at resolution time.
The message looks like
Unable to resolve service for type 'IOrderRepository' while attempting to
activate 'OrdersController'. For a controller, this surfaces on the first request that needs it, not at
startup, so a missing registration can sit unnoticed until that endpoint is
hit. The exception fires when the dependent class is constructed, not when the
unused dependency would first be called.
10. What's the difference between AddScoped and AddSingleton in a console application versus a web application?
In a web application, "scope" maps naturally to one HTTP request, so scoped
services behave intuitively — one instance per request. In a console
application or background worker, nothing creates scopes for you. If you
resolve a scoped service straight from the root provider, it lives as long as
the root provider does, so its lifetime is effectively promoted to singleton.
When scope validation is on (the default in the Development environment for
hosted apps), the container throws instead with a message like
Cannot resolve scoped service from root provider. The fix is to create a scope yourself with
IServiceScopeFactory.CreateScope() and
resolve from that scope's provider. This commonly surprises developers moving
from web projects to worker services.
Mid-Level Questions
11. What is a captive dependency, and why is it dangerous?
A captive dependency happens when a longer-lived service holds a reference to a shorter-lived one — most commonly, a singleton that captures a scoped or transient service through constructor injection. Because the singleton is created once and never recreated, it holds onto that first instance for the lifetime of the application, even though scoped services are meant to be created fresh per request. The diagram below shows the shape of the problem. In practice this leads to stale data, memory growth, and shared use of objects that were never designed for it. A database context is the classic example: it isn't thread-safe, so concurrent requests using one captured instance can fail unpredictably.
AppDbContext keeps that one instance, so every request shares it instead of getting its own.12. Does the built-in ASP.NET Core container detect captive dependencies automatically?
Partially, and only under specific conditions. Validation is controlled by
ValidateScopes and
ValidateOnBuild. Both are enabled
automatically in the Development environment when you use
CreateBuilder, and both are off by default in
production. With validation on, the container throws if a scoped service is
injected into a singleton or resolved from the root provider. The message
looks like
Cannot consume scoped service 'X' from singleton 'Y'.
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true;
});
Setting ValidateOnBuild to true makes the
container check registrations when the service provider is built, so captive
dependency mistakes show up at startup rather than on the first request that
hits the broken code path. It isn't a complete safety net: it can't see inside
factory delegates, and it won't flag a transient service injected into a
singleton. Forcing both options on in production also adds a bit of startup
work, so many teams leave them on only in development and test environments.
13. If a singleton needs data from a scoped service, how do you handle it correctly?
Instead of injecting the scoped service directly into the singleton's
constructor, inject IServiceScopeFactory (or
IServiceProvider) and create a new scope
manually whenever the scoped dependency is actually needed. This is the
approach Microsoft's documentation describes for hosted services such as
BackgroundService.
public class ReportingBackgroundService : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public ReportingBackgroundService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await using (var scope = _scopeFactory.CreateAsyncScope())
{
var orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
await orderRepository.ArchiveOldOrdersAsync(stoppingToken);
}
await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken);
}
}
}
Register this worker with
builder.Services.AddHostedService<ReportingBackgroundService>(). The sample assumes your
IOrderRepository has an
ArchiveOldOrdersAsync method. Each loop
iteration gets its own scope, so the scoped repository — and anything it
depends on, like a database context — is created fresh and disposed properly
instead of being captured for the application's entire lifetime.
Two details are worth knowing.
CreateAsyncScope (.NET 6 and later) disposes
services that implement
IAsyncDisposable correctly, which a plain
CreateScope can't do for a service that
implements only that interface. And in .NET 6 and later, an unhandled
exception inside ExecuteAsync stops the host
by default, so real workers usually wrap the loop body in a
try/catch
that logs the error.
14. What's the difference between GetService and GetRequiredService?
GetService<T>() returns
null if the requested type isn't registered,
while GetRequiredService<T>() throws an
InvalidOperationException immediately if the
service can't be resolved. In most application code,
GetRequiredService is preferable because a
missing registration usually represents a configuration bug you want to fail
loudly, not a condition you want to silently handle with a null check. Both
generic versions are extension methods, so you need
using Microsoft.Extensions.DependencyInjection;
to call them.
15. What is the service locator anti-pattern, and how does it relate to DI?
The service locator pattern is where a class holds a reference to the
container itself (IServiceProvider) and pulls
dependencies out of it on demand, instead of declaring them as constructor
parameters. It's considered an anti-pattern because it hides a class's real
dependencies — you can't tell what a class needs just by looking at its
constructor, and unit testing becomes harder because you have to mock an
entire container instead of a few explicit interfaces. Microsoft's own
guidance also says to avoid it.
The scope-factory pattern shown above is a narrow, documented exception, not a
license to inject
IServiceProvider everywhere. Keep it confined
to the one class that genuinely has to create scopes, such as a background
worker.
16. Can a transient service be injected into a singleton? What happens?
Yes, and this is legal but often misunderstood. The transient service will be created once, at the moment the singleton is constructed, and then held for the singleton's entire lifetime — effectively behaving like a singleton itself from that point on, even though it was registered as transient. This doesn't throw an exception the way a scoped-into-singleton mismatch does under validation, which makes it a sneakier bug because nothing alerts you to the problem. The exception is a transient that itself depends on a scoped service, which validation does catch.
17. What's the difference between registering a service with an interface versus registering a concrete type directly?
Registering with an interface, like
AddScoped<IOrderRepository, OrderRepository>(), lets consumers depend on the abstraction and lets you swap implementations
(for testing, for different environments, or for versioning) without touching
consumer code. Registering a concrete type directly, like
AddScoped<OrderRepository>(), is
sometimes done for simple internal helper classes that don't need an
abstraction layer, but it ties every consumer to that specific implementation.
18. How does ASP.NET Core resolve a class with multiple constructors?
The container only considers public constructors. Among those, it picks the
one with the most parameters where every parameter type can be resolved from
the container. If two constructors both qualify and neither can be ranked
above the other, the container throws an
InvalidOperationException. Parameters the
container can't supply are allowed only when they have default values.
In practice, having multiple constructors on a DI-managed class is rare and often a design smell; most teams standardize on a single constructor per injectable class to avoid ambiguity.
19. What is the difference between AddScoped and AddScoped with a factory delegate?
A plain
AddScoped<TInterface, TImplementation>()
lets the container construct the implementation automatically using its own
constructor resolution. A factory overload gives you manual control over
construction. For example,
AddScoped<IPriceCalculator>(sp => new
PriceCalculator(sp.GetRequiredService<ITaxRates>(), "USD"))
passes in a currency string the container couldn't supply on its own. (IPriceCalculator, PriceCalculator, and
ITaxRates are made-up types for
illustration.) It's useful when a constructor needs a primitive value, a
configuration setting, or conditional logic that the container can't infer.
20. Why might injecting too many dependencies into one constructor be a problem an interviewer wants you to recognize?
A constructor with eight or ten injected dependencies is usually a sign the class is doing too much — violating the single responsibility principle rather than demonstrating thorough DI usage. An interviewer might present a bloated constructor and ask how you'd refactor it; a reasonable answer is to split the class into smaller, more focused services, or introduce a facade service that internally composes the smaller ones so the original consumer's constructor stays clean.
Senior-Level / Architecture Questions
21. How does the ASP.NET Core DI container build the object graph internally?
When you request a service, the container inspects the target type's constructor, resolves each parameter recursively (resolving that parameter's own dependencies first), and then invokes the constructor with the resolved instances. This happens depth-first: the deepest dependency in the graph is constructed first, and the chain unwinds back up to the originally requested type. For performance, the container can compile this resolution logic into cached delegates after the first resolution of a given type, so repeated resolutions of the same service avoid re-walking the dependency graph through reflection every time — though the exact caching behavior is an internal implementation detail that isn't part of the public contract and shouldn't be relied on directly.
22. What are keyed services, and when would you use them?
Keyed services, available in .NET 8 and later, let you register multiple
implementations of the same interface distinguished by a key, rather than
needing separate interfaces or named wrapper classes for each variant. The key
doesn't have to be a string; it can be any object whose type implements
Equals correctly.
builder.Services.AddKeyedScoped<INotificationSender, EmailNotificationSender>("email");
builder.Services.AddKeyedScoped<INotificationSender, SmsNotificationSender>("sms");
public class AlertService
{
private readonly INotificationSender _emailSender;
public AlertService([FromKeyedServices("email")] INotificationSender emailSender)
{
_emailSender = emailSender;
}
}
This is useful when you have several implementations of the same abstraction that are chosen at runtime — for example, different payment gateways or notification channels — without resorting to a factory pattern just to pick one.
Keys aren't always the right tool. If you register the same interface more
than once without keys, a single request for that interface gets the last
registration, while IEnumerable<T> gets
all of them in registration order. That's the usual pattern when every
implementation should run, such as a set of validators.
23. When would you reach for a third-party container like Autofac instead of the built-in one?
The built-in container intentionally stays minimal: it covers constructor
injection and the three standard lifetimes well, but it lacks some features
that larger applications sometimes want, such as property injection,
module-based registration for organizing large registration sets, assembly
scanning with more granular conventions, or interceptors for cross-cutting
concerns like logging and caching applied automatically around method calls.
Autofac is a widely used third-party choice in .NET when a team needs those
capabilities, and it can be plugged in through
IServiceProviderFactory<T> so ASP.NET
Core's hosting model uses it instead of the default container. Scrutor is
another option worth knowing — it doesn't replace the built-in container, but
extends IServiceCollection with assembly
scanning and decoration support, which covers a good chunk of why teams reach
for a bigger container in the first place.
24. How would you design a class to make it easily testable using DI?
Depend on interfaces rather than concrete classes for anything that touches external state — databases, HTTP calls, file systems, the clock. Inject those interfaces through the constructor so a unit test can substitute a mock or fake implementation without needing a real database connection or network access.
public interface IClock
{
DateTime UtcNow { get; }
}
public class OrderExpiryService
{
private readonly IClock _clock;
private readonly IOrderRepository _orderRepository;
public OrderExpiryService(IClock clock, IOrderRepository orderRepository)
{
_clock = clock;
_orderRepository = orderRepository;
}
public async Task<bool> IsExpiredAsync(int orderId)
{
var order = await _orderRepository.GetByIdAsync(orderId);
return order is not null && order.CreatedAt.AddDays(30) < _clock.UtcNow;
}
}
Wrapping DateTime.UtcNow behind an
IClock interface is a small example, but it
illustrates the bigger principle: anything that makes a test nondeterministic
or slow is a candidate for abstraction and injection, so tests can control it
precisely. On .NET 8 and later, the built-in
TimeProvider class does this job for time, so
you may not need to write your own IClock.
25. What's the performance cost of using Transient lifetime heavily versus Singleton?
Transient services are reconstructed on every resolution, which means more allocations and more work for the garbage collector if the class or its dependencies are expensive to build — though for small, stateless classes this overhead is usually negligible in practice. Singleton services avoid that repeated construction cost entirely, but they come with their own tradeoff: they must be thread-safe, since a single instance is shared across every concurrent request, and any internal mutable state needs careful synchronization. The general guidance is to default to scoped or transient for anything that isn't explicitly designed to be shared and thread-safe, and reserve singleton for things like configuration objects, caches, or clients genuinely meant to be reused.
26. How would you unit test a class that depends on IServiceProvider directly?
This can be a trick question, because depending on
IServiceProvider directly is usually a sign
of the service locator anti-pattern discussed in question 15. The honest
answer in an interview is to point out that it's hard to test cleanly. You'd
need a real IServiceProvider built from a
test ServiceCollection, or a mock that sets
up GetService(Type), because
GetRequiredService is an extension method
that calls it. The better fix is refactoring the class to depend on the
specific interfaces it actually needs, which also makes the class's
responsibilities more obvious to anyone reading it later.
27. How does ASP.NET Core handle disposal of services with different lifetimes?
The container tracks any service it creates that implements
IDisposable (or
IAsyncDisposable) within the scope that
created it. Scoped and transient services resolved within a request are
disposed automatically when that request's scope ends. Singleton services are
disposed when the application's root service provider is disposed, which
typically happens on application shutdown. That includes singletons created by
a factory delegate.
Three subtleties are worth knowing. First, the container doesn't dispose
instances you create yourself and pass in, such as
AddSingleton(myInstance); that cleanup is
your job. Second, you shouldn't dispose services you got from the container,
because it owns their lifetime. Third, a transient service resolved from the
root container (not from a request scope) can end up living — and not being
disposed — for the application's full lifetime, which is another variation of
the captive dependency problem.
28. What is the difference between IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>?
All three give your classes strongly typed configuration through DI. You bind a section of configuration to a plain class once at startup, then inject one of the three wrappers:
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));
public class MailService
{
private readonly SmtpSettings _settings;
public MailService(IOptions<SmtpSettings> options)
{
_settings = options.Value;
}
}
Here SmtpSettings is a plain class whose
properties match the Smtp section of your
configuration. The difference between the three wrappers is how they treat
configuration changes. IOptions<T> is
registered as a singleton and won't reflect configuration changes after
startup. IOptionsSnapshot<T> is scoped
and recomputed per request, picking up changes from reloadable configuration
sources, which also means it can't be injected into a singleton.
IOptionsMonitor<T> is a singleton that
always exposes the current value and supports change notifications through a
callback, which makes it the usual choice for long-running services like
background workers that need to react to configuration changes without
restarting.
29. Can you have circular dependencies with constructor injection in ASP.NET Core, and what happens?
A circular dependency occurs when class A depends on class B, and class B
depends back on class A (directly or through a longer chain). The built-in
container does not support resolving circular dependencies through constructor
injection and will throw an exception describing the cycle when it attempts to
build the object graph. The fix is almost always a design fix rather than a
container workaround: introduce a third abstraction that both classes depend
on, use an event or mediator pattern to decouple the interaction, or break the
constructor-time link with a factory delegate that you register yourself. Note
that the built-in container doesn't resolve
Lazy<T> or
Func<T> automatically the way some
third-party containers do, so you have to register those yourself.
30. How would you explain, to a mid-level developer on your team, why mixing lifetimes incorrectly caused a production bug?
A strong senior-level answer here isn't just technical, it's about
communication and debugging method. A good response walks through a concrete
scenario: for instance, a singleton
ICacheService that accidentally captured a
scoped AppDbContext through a constructor
parameter. Every request then shared one context, which isn't thread-safe, so
under load you'd see intermittent errors such as Entity Framework Core's "A
second operation was started on this context instance before a previous
operation completed," along with stale data that was hard to reproduce
locally. The explanation should cover how you'd diagnose it — turning on
ValidateScopes and
ValidateOnBuild, reviewing which services are
singletons, and tracing which of their dependencies are scoped — and then how
you'd fix it, typically by injecting
IServiceScopeFactory and creating a scope per
operation instead of capturing the scoped dependency directly.
Common DI Pitfalls Interviewers Love to Probe
A few patterns show up repeatedly across interviews, independent of the
specific question wording. Captive dependencies, covered above, are a frequent
deep-dive topic because they combine lifetime knowledge with real debugging
experience. Over-injection — constructors with too many parameters — tests
whether a candidate recognizes design smells rather than just DI syntax. The
service locator anti-pattern tests whether a candidate understands why DI
exists in the first place, not just how to call
AddScoped. And disposal timing, particularly
around transient services resolved from the root container, tests whether a
candidate understands the container's internal tracking behavior rather than
just its public API surface.
It's also common for interviewers to present a broken code snippet — often a
singleton injecting a scoped DbContext, or a
background service injecting a scoped repository directly — and ask you to
spot the bug before asking you to fix it. Practicing reading code for these
patterns, not just reciting definitions, pays off more than memorizing answers
verbatim.
Middleware is another place this bites. A middleware class is created once for
the application, so injecting a scoped service into its constructor forces
that service to behave like a singleton, and the container throws. Take scoped
services as parameters of the
InvokeAsync method instead, or use
factory-based middleware.
Knowing the exact error messages helps you recognize these bugs quickly. The wording can vary slightly between .NET versions:
-
Unable to resolve service for type ...: the service was never registered, or a dependency of it wasn't (questions 7 and 9). -
Cannot consume scoped service ... from singleton ...: a captive dependency caught by scope validation (questions 11 and 12). -
Cannot resolve scoped service ... from root provider: a scoped service was requested outside any scope (question 10). -
A circular dependency was detected for the service of type ...: two or more classes depend on each other (question 29).
Best Practices for Answering DI Questions in Interviews
When a question asks you to compare lifetimes, always ground your answer in a concrete scenario rather than reciting the definitions in isolation — interviewers notice the difference between someone who memorized a table and someone who's actually hit a scope mismatch in production. If you're asked about third-party containers like Autofac and you haven't used one in production, say so plainly and explain what you understand about when and why teams reach for them instead of overstating familiarity you don't have. For architecture-level questions, it helps to talk through tradeoffs out loud — naming the downside of your proposed solution, not just its benefit — since that's usually what senior interviews are actually screening for.
Key Takeaways
- Transient means a new instance every time, scoped means one per scope (one per HTTP request in a typical web app), and singleton means one per application.
-
A longer-lived service must never hold a shorter-lived one. Use
IServiceScopeFactoryto create a scope when a singleton or background worker needs scoped services. - Scope validation catches scoped-into-singleton mistakes, but it's on by default only in Development and won't flag a transient injected into a singleton.
-
Prefer constructor injection with explicit interfaces. Treat injecting
IServiceProvideras the exception, not the habit. -
The built-in container doesn't do property injection,
Lazy<T>, or decoration out of the box. Know when a third-party container or Scrutor fills that gap.
Further Reading
These Microsoft Learn pages are the primary sources for the behavior described above:
- Dependency injection in ASP.NET Core
- .NET dependency injection overview, including constructor selection, scope validation, and keyed services
- Dependency injection guidelines
- Options pattern in ASP.NET Core
Conclusion
ASP.NET Core dependency injection interview questions rarely stop at "what's
the difference between Transient, Scoped, and Singleton" — the deeper
questions test whether you understand captive dependencies, container
internals, and the tradeoffs behind the built-in container's deliberately
minimal design. You should now be able to explain not just how to register a
service, but why a singleton capturing a scoped dependency breaks in
production, and how to fix it with a scope factory instead of a workaround.
Practice by reading real code for lifetime mismatches rather than only
memorizing definitions, since that's closer to what interviewers commonly
probe for. A good next step is to spin up a small ASP.NET Core project,
deliberately introduce a captive dependency, enable
ValidateOnBuild, and watch the container
catch it — that hands-on exercise sticks better than any list of questions,
including this one.
