控制器依赖注入:构造函数注入VS属性注入及基类派生类场景问询
Great question! Let's break down the tradeoffs between these two DI approaches, specifically tailored to your scenario where a BaseController (using property injection) is inherited by multiple controllers.
First, Let's Cover Constructor Injection: The Preferred Approach
Constructor injection is widely considered the best practice for controller dependencies, and here's why:
- Explicit, Clear Dependencies: Anyone looking at your controller's constructor immediately knows what services it needs to function. No hidden dependencies buried in property getters—this makes your code far more readable and maintainable.
- Guaranteed Non-Null Dependencies: When you inject dependencies via the constructor, they're resolved before the controller is fully initialized. You can add null checks (like
throw new ArgumentNullException) to catch missing dependencies at startup, not halfway through a request when your code tries to useAuditand gets a null. - Follows SOLID Principles: It aligns perfectly with the Dependency Inversion Principle—your controller depends on abstractions (like
IAuditRepository), and the constructor enforces that these abstractions are provided. It also avoids tight coupling to a specific DI container (like your current use ofDependencyResolver.Current). - DI Container Friendly: Modern frameworks like ASP.NET Core's built-in DI container natively support constructor injection, handling all the resolution automatically. You won't need to write manual
GetServicecalls, reducing boilerplate and potential bugs.
How to Refactor Your BaseController to Use Constructor Injection
Here's how you'd rewrite your base controller to follow this pattern:
private readonly IAuditRepository _audit; protected BaseController(IAuditRepository audit) { _audit = audit ?? throw new ArgumentNullException(nameof(audit)); } protected IAuditRepository Audit => _audit;
Then, your derived controllers simply pass the required dependency up to the base class in their own constructors:
public class ProductController : BaseController { private readonly IProductRepository _productRepo; public ProductController(IAuditRepository audit, IProductRepository productRepo) : base(audit) { _productRepo = productRepo ?? throw new ArgumentNullException(nameof(productRepo)); } }
When Might Property Injection Make Sense?
Property injection isn't all bad—it has niche use cases:
- Optional Dependencies: If a service isn't critical to the controller's core functionality (e.g., a logging service that's nice to have but not required), property injection can work. You can set a default value or check for null before using it.
- Legacy Framework Constraints: In older versions of ASP.NET MVC (pre-Core), constructor injection support was limited, so property injection was a common workaround. But this is irrelevant in modern frameworks.
That said, your IAuditRepository looks like a core dependency (you're using it across multiple controllers via the base class), so property injection is not a good fit here.
Problems with Your Current Property Injection Approach
Your current code uses a lazy-loaded property with DependencyResolver.Current.GetService:
private IAuditRepository _audit; protected IAuditRepository Audit { get { return _audit ?? (_audit = DependencyResolver.Current.GetService<IAuditRepository>()); } }
This has a few key issues:
- Hidden Dependency: Someone reading a derived controller's code won't know it depends on
IAuditRepositoryunless they check the base class. - Runtime Null Risk: If
DependencyResolvercan't resolveIAuditRepository(e.g., a registration mistake), you'll get a null reference exception when theAuditproperty is first used—this could happen in production, not just during testing. - Tight Coupling: You're directly tying your controller to
DependencyResolver, making it harder to test (you'd have to mock the resolver instead of just passing a mockIAuditRepository).
Final Recommendation
Stick with constructor injection for your controllers and base controller. It's safer, more maintainable, and aligns with industry best practices. The small amount of extra code in derived controllers is worth it for the clarity and reliability it provides.
内容的提问来源于stack exchange,提问作者rpmansion

