如何在ABP中实现基类属性注入,无需派生类传递依赖?
问题背景
在ASP.NET Boilerplate(ABP)模板中,传统的AppServiceBase类需要在构造函数中注入TenantManager和UserManager,派生类必须在自己的构造函数中接收这两个依赖并传递给基类,代码示例如下:
public abstract class BaseClass { public TenantManager TenantManager { get; set; } public UserManager UserManager { get; set; } public BaseClass(TenantManager tenantManager, UserManager userManager) { TenantManager = tenantManager; UserManager = userManager; } } public class DerivedClass : BaseClass { public DerivedClass(TenantManager tenantManager, UserManager userManager) : base(tenantManager, userManager) { } public void SomeMethod() { // 使用基类属性 TenantManager.Something(); UserManager.Something(); } }
而在原生ASP.NET Core DI中,我们可以直接通过HttpContext获取服务,派生类无需在构造函数中传递这两个通用依赖:
public abstract class BaseClass { public TenantManager Service1 => HttpContext.RequestServices.GetService<TenantManager>(); public UserManager Service2 => HttpContext.RequestServices.GetService<UserManager>(); public BaseClass() { } } public class DerivedClass : BaseClass { public DerivedClass(ISpecificService service) { // 只注入自己需要的服务,TenantManager和UserManager仍可直接使用 } public void SomeMethod() { Service1.Something(); Service2.Something(); } }
你尝试过直接在基类构造函数中创建新的依赖注入作用域来解析服务,但遇到了对象被提前释放的问题:
public abstract class BaseClass { public TenantManager TenantManager { get; set; } public UserManager UserManager { get; set; } public BaseClass() { var t = IocManager.Instance.CreateScope(); TenantManager = t.Resolve<TenantManager>(); UserManager = t.Resolve<UserManager>(); } }
这是因为你手动创建的作用域在构造函数执行完毕后就会被回收,导致里面的服务实例也跟着被释放,不符合ABP中服务生命周期的管理规则。
正确的实现方案
方案1:利用Lazy延迟加载 + ABP内置的ServiceProvider
ABP的ApplicationService基类已经内置了ServiceProvider属性(绑定当前请求的作用域),结合Lazy<T>可以延迟解析服务,确保在请求上下文内获取实例,避免提前释放:
public abstract class AppServiceBase : ApplicationService { private readonly Lazy<TenantManager> _lazyTenantManager; private readonly Lazy<UserManager> _lazyUserManager; // 派生类可直接使用的属性 protected TenantManager TenantManager => _lazyTenantManager.Value; protected UserManager UserManager => _lazyUserManager.Value; protected AppServiceBase() { // 延迟加载,第一次访问属性时才从当前作用域解析服务 _lazyTenantManager = new Lazy<TenantManager>(() => ServiceProvider.GetRequiredService<TenantManager>()); _lazyUserManager = new Lazy<UserManager>(() => ServiceProvider.GetRequiredService<UserManager>()); } }
派生类只需要注入自己专属的服务,完全不用管TenantManager和UserManager:
public class DerivedAppService : AppServiceBase { private readonly ISpecificService _specificService; public DerivedAppService(ISpecificService specificService) { _specificService = specificService; } public void SomeMethod() { // 直接使用基类的属性,完全无需传递依赖 TenantManager.DoSomething(); UserManager.DoSomething(); } }
方案2:使用ABP的属性注入
ABP原生支持属性注入,只需给属性标记[Inject]特性,ABP会自动在构造函数执行后注入对应的服务实例:
public abstract class AppServiceBase : ApplicationService { [Inject] protected TenantManager TenantManager { get; set; } [Inject] protected UserManager UserManager { get; set; } }
派生类的写法同样简洁:
public class DerivedAppService : AppServiceBase { private readonly ISpecificService _specificService; public DerivedAppService(ISpecificService specificService) { _specificService = specificService; } public void SomeMethod() { TenantManager.DoSomething(); UserManager.DoSomething(); } }
⚠️ 注意:属性注入的服务是在构造函数执行之后才被注入的,所以如果在派生类的构造函数中直接使用TenantManager或UserManager,会得到null,仅能在方法中使用。
为什么你的原有方案会失败?
你手动创建的IocManager.Instance.CreateScope()是一个独立的临时作用域,这个作用域在构造函数执行结束后没有被持有,会被自动回收,导致里面解析的TenantManager和UserManager实例也被释放。而我们需要的是绑定当前请求的作用域,也就是ABP为每个请求自动创建的作用域,通过ServiceProvider获取的服务都是这个作用域内的实例,会在请求结束后才被释放,符合生命周期管理规则。
内容的提问来源于stack exchange,提问作者user2058413

