DI Anti-Patterns
A class takes one constructor parameter, `IServiceProvider`, and compiles without complaint. Nothing in its signature admits it needs an email sender, so when that registration goes missing the failure lands on a customer's order, not your build.
- ▸A constructor that compiles clean can still hide every collaborator the class uses; one
IServiceProviderparameter is the tell. - ▸Put a per-request service inside a forever one and it freezes at request one, with no error until validation runs.
- ▸One
BuildServiceProvidercall in startup quietly mints a second copy of every singleton you registered.
OrderService declares one constructor parameter: IServiceProvider provider. Inside its PlaceOrder method it calls provider.GetRequiredService<IEmailSender>() and uses the result. The code compiles, runs in your tests, and reads as ordinary dependency injection. It is not.
Pulling a dependency out of the provider by hand, instead of declaring it in the constructor, is the service locator pattern. The cost hides in plain sight: read the constructor and the class looks like it needs nothing but a provider. The one collaborator it actually depends on, IEmailSender, appears only deep inside a method body. A caller reading the public signature cannot see it. The compiler, which would refuse to build a constructor whose IEmailSender argument has no registration, never gets the chance, because there is no such argument to check.
So the failure moves. Forget to register IEmailSender and constructor injection fails loudly at startup, naming the gap. Service locator sails through the build and throws InvalidOperationException the first time a real order reaches that line, in production, on a Tuesday. The fix is to delete the provider parameter and put IEmailSender sender back in the constructor, where the class can be read and checked.