Resolving a service
“How does a controller get its LoanService? What does it mean to resolve a service?”
Who asks the container for an object, when they ask, and what goes wrong when a class asks for itself.
See it
GET /api/loans/{id}. An object must handle it: a .In plain words
Resolving means asking the container for a ready-made object. When a request comes in, the framework needs a . It asks the container for what the controller needs. The container sees a loan service is needed, which needs three more things. It builds them in order and hands everything back. Your classes only list what they need in their constructor. They never build it, and never go looking for it.
- Resolving is asking the DI container, , for an instance of a registered type.
- For each request, MVC creates the controller and resolves its constructor parameters from the request's , building the whole dependency graph recursively.
- Constructor injection is the normal way.
- throws when a type is not registered; returns null.
- Injecting and pulling dependencies inside methods is the service locator anti-pattern: it hides dependencies and moves the failure from startup to runtime.
In the lending API
Four files from the lending API: the normal way, the bug, and a test. Press Next line to walk through them.
Lending.sln · where these 4 files sit
- src/
- Api/Controllers/Servicing/
- Application/Servicing/Loans/
- Api/Controllers/Servicing/
- tests/Api.IntegrationTests/
GET /api/loans/{id}. An object must handle it: a ./api/loans/... come to this controller. The framework creates a new one for every request.What they ask next
Interviewers check that you can explain every line. Tap a question to open it.
Explain this lineWhy does the controller ask for , not ?
So it depends on the contract, not the class. A test can pass in a fake , and the team can swap the real class by changing one registration. See .
Who exactly resolves the controller, and when?
MVC's controller activator, once per request. It creates the controller and asks the request's provider, , for each constructor parameter. That provider belongs to the request's . The controller itself is not registered in the container.
So does the startup check cover the controller's needs?
No. checks only registered services. A missing shows up on the first request to that controller, as a 500. Calling registers controllers, so the startup check covers them too. A test that calls each endpoint also catches it.
Why not… or ?
when the service must exist, which is almost always. It fails at that line with the type's name. returns null, so the failure turns up later as a . Use only for something truly optional.
What is the service locator anti-pattern?
A class takes and fetches its own dependencies inside its methods. Its constructor hides what it needs, a test must build a container to run it, and a missing registration fails mid-request instead of at startup.
Is asking the provider by hand ever right?
Yes, at the edges of the app, not inside business classes. For example:
Program.csafterBuild(), a factory, or a background job that opens a new for each piece of work with .Why would anyone write it?
Usually to get round an error: a long constructor, a circular dependency, or a lifetime error. It hides the problem instead of fixing it.
What if two services need each other?
The container cannot build either one first, so it throws: "A circular dependency was detected for the service of type ...". Fix the design: move the shared part into a third class that both depend on.
A constructor with eight parameters: is that a problem?
It is a sign the class does too much. Split it into smaller classes. A service locator would hide the eight needs, not remove them.
Does resolving the same type twice give the same object?
It depends on the lifetime it was registered with. A singleton: always the same. Scoped: the same within one request. Transient: a new one each time. covers this.
Can a single action get a service without the constructor?
Yes: mark the action's parameter with
[FromServices]. The service is resolved only when that action runs. Useful when one action out of many needs something expensive.
Traps
- Don't say
The controller creates its LoanService.SayThe container builds and passes it into the controller's constructor. The controller only says it needs an . - Don't say
Injecting is still dependency injection.SayThat is a service locator. The class still fetches its own dependencies; they are just hidden from its constructor. - Don't say
throws when the service is not registered.Say returns null. throws .
Canva grades whether a candidate finds and fixes problems in AI-generated code. One to look for here: generated code that takes and calls inside a method, for example to get round a lifetime error. It compiles, and passes every test until a registration is missing. Ask of every class: does its constructor list everything it needs?