使用接口继承时如何避免Autofac中的嵌套装饰器?
我有一个基于.NET 7.0的应用,使用Autofac作为DI容器,包含两个接口:IReadonlyRepository<T> 和 IRepository<T>,其中IRepository<T>继承自IReadonlyRepository<T>。Repository<T>类实现了这两个接口,同时有一个LoggingRepository<T>装饰器,用于包装IRepository<T>,记录仓库方法调用并委托给被包装的实例。
LoggingRepository 代码
class LoggingRepository<T> : IRepository<T> { private readonly IRepository<T> _repository; public LoggingRepository(IRepository<T> repository) { _repository = repository; } public IEnumerable<T> List() { Console.WriteLine($"Calling {_repository.GetType().Name}<{typeof(T).Name}>.List()"); return _repository.List(); } }
当前DI注册代码
builder.RegisterGeneric(typeof(Repository<>)) .As(typeof(IRepository<>)) .As(typeof(IReadonlyRepository<>)) .InstancePerLifetimeScope(); builder.RegisterGenericDecorator(typeof(LoggingRepository<>), typeof(IRepository<>)); builder.RegisterGenericDecorator(typeof(LoggingRepository<>), typeof(IReadonlyRepository<>));
遇到的问题
当请求IReadonlyRepository<T>实例时,会出现LoggingRepository<T>嵌套装饰的情况:一个LoggingRepository<T>装饰另一个LoggingRepository<T>,再包装Repository<T>。预期是仅用单个LoggingRepository<T>装饰Repository<T>。
解决方案
核心原因
IRepository<T>继承自IReadonlyRepository<T>,且同时给两个接口注册了装饰器,Autofac会对每个接口的服务链分别应用装饰器,导致嵌套。正确做法是让两个接口复用同一套装饰后的服务,或限制装饰器仅作用于原始实现。
方案一:复用装饰后的服务(推荐)
通过命名注册区分原始仓库和装饰后的服务,让IReadonlyRepository<T>直接指向已被装饰的IRepository<T>实例:
// 1. 命名注册原始仓库,避免被直接解析 builder.RegisterGeneric(typeof(Repository<>)) .Named("OriginalRepository", typeof(IRepository<>)) .InstancePerLifetimeScope(); // 2. 给IRepository<T>注册装饰器,基于命名的原始服务 builder.RegisterGenericDecorator( typeof(LoggingRepository<>), typeof(IRepository<>), fromKey: "OriginalRepository"); // 3. 让IReadonlyRepository<T>解析为已装饰的IRepository<T>实例 builder.RegisterGeneric(typeof(IRepository<>)) .As(typeof(IReadonlyRepository<>)) .InstancePerLifetimeScope();
方案二:条件注册限制装饰器作用范围
使用条件注册,让装饰器仅应用于原始的Repository<T>类型,避免重复装饰:
builder.RegisterGeneric(typeof(Repository<>)) .As(typeof(IRepository<>)) .As(typeof(IReadonlyRepository<>)) .InstancePerLifetimeScope(); // 仅当被装饰的实例是Repository<>时,才应用LoggingRepository<> builder.RegisterGenericDecorator( typeof(LoggingRepository<>), typeof(IRepository<>), context => context.ImplementationType == typeof(Repository<>)); // 让IReadonlyRepository<T>复用已装饰的IRepository<T>服务 builder.RegisterGeneric(typeof(IRepository<>)) .As(typeof(IReadonlyRepository<>)) .InstancePerLifetimeScope();
说明
两种方案的核心都是让IReadonlyRepository<T>获取的是已经被LoggingRepository<T>装饰过的IRepository<T>实例,而非对IReadonlyRepository<T>单独应用装饰器,从而避免嵌套。
内容的提问来源于stack exchange,提问作者snowfrogdev

