如何在Autofac中无需新增接口实现构造函数按名称依赖注入
如何在Autofac中无需子接口区分,为不同Client注入对应的Provider实现?
首先,你的第二版实现思路是正确的,但存在几处小笔误需要修正,修正后就能正常工作。另外,我还会分享几种更灵活的实现方式,你可以根据实际场景选择。
一、你的第二版实现的修正与正确性验证
先看你代码里的两处小问题:
Client2的构造函数定义错误,应该是public Client2([KeyFilter("DatabaseProvider")] IProvider provider),而不是public Client(...)- Provider的命名有点反直觉(比如
WebProvider对应Provider2),虽然不影响功能,但调整命名会更清晰(比如把Provider改为Provider1,对应DatabaseProvider;Provider2对应WebProvider)
修正后的代码如下:
// Provider类调整命名(可选,只是更清晰) public sealed class Provider1 : ProviderBase, IProvider { } public sealed class Provider2 : ProviderBase, IProvider { } // Client构造函数修正 public sealed class Client1 : ClientBase, IClient { public Client1([KeyFilter("WebProvider")] IProvider provider) : base(provider) { } } public sealed class Client2 : ClientBase, IClient { public Client2([KeyFilter("DatabaseProvider")] IProvider provider) : base(provider) { } } // 注册代码 private void Build() { var builder = new ContainerBuilder(); // 注册带Key的Provider builder.RegisterType<Provider1>().Keyed<IProvider>("DatabaseProvider"); builder.RegisterType<Provider2>().Keyed<IProvider>("WebProvider"); // 注册Client并启用属性筛选 builder.RegisterType<Client1>().Named<IClient>("Web").WithAttributeFiltering(); builder.RegisterType<Client2>().Named<IClient>("Database").WithAttributeFiltering(); Container = builder.Build(); }
修正后,这个实现是完全可行的:WithAttributeFiltering()会让Autofac识别构造函数上的KeyFilter属性,自动注入对应Key的IProvider实例,完美达成你“无需子接口区分依赖”的需求。
二、其他更合适的实现方式
如果你不想在业务类中添加Autofac相关的属性(保持代码纯净),或者需要更直观的依赖配置,以下几种方式会更合适:
方式1:Lambda注册直接指定依赖
这种方式把所有依赖关系集中在注册代码中,不用修改Client类的构造函数,可读性极强:
private void Build() { var builder = new ContainerBuilder(); // 先注册命名的Provider实例 builder.RegisterType<Provider1>().Named<IProvider>("DatabaseProvider"); builder.RegisterType<Provider2>().Named<IProvider>("WebProvider"); // 注册Client时直接解析对应命名的Provider builder.Register(c => new Client1(c.ResolveNamed<IProvider>("WebProvider"))) .Named<IClient>("Web"); builder.Register(c => new Client2(c.ResolveNamed<IProvider>("DatabaseProvider"))) .Named<IClient>("Database"); Container = builder.Build(); }
方式2:使用WithParameter匹配注入
和Lambda方式类似,但更贴合Autofac的注册语法,适合需要批量配置的场景:
private void Build() { var builder = new ContainerBuilder(); builder.RegisterType<Provider1>().Named<IProvider>("DatabaseProvider"); builder.RegisterType<Provider2>().Named<IProvider>("WebProvider"); // 为Client1指定注入WebProvider builder.RegisterType<Client1>() .Named<IClient>("Web") .WithParameter( (param, ctx) => param.ParameterType == typeof(IProvider), (param, ctx) => ctx.ResolveNamed<IProvider>("WebProvider") ); // 为Client2指定注入DatabaseProvider builder.RegisterType<Client2>() .Named<IClient>("Database") .WithParameter( (param, ctx) => param.ParameterType == typeof(IProvider), (param, ctx) => ctx.ResolveNamed<IProvider>("DatabaseProvider") ); Container = builder.Build(); }
方式3:使用元数据(Metadata)筛选
如果需要基于多个维度筛选依赖(比如除了类型还有其他属性),元数据方式会更灵活:
// 定义元数据键常量(可选,只是规范) public static class ProviderMetadata { public const string Category = "Category"; } private void Build() { var builder = new ContainerBuilder(); // 注册Provider时添加元数据 builder.RegisterType<Provider1>().As<IProvider>().WithMetadata(ProviderMetadata.Category, "Database"); builder.RegisterType<Provider2>().As<IProvider>().WithMetadata(ProviderMetadata.Category, "Web"); // 注册Client并启用属性筛选 builder.RegisterType<Client1>().Named<IClient>("Web").WithAttributeFiltering(); builder.RegisterType<Client2>().Named<IClient>("Database").WithAttributeFiltering(); Container = builder.Build(); } // Client类用MetadataFilter标记需要的元数据 public sealed class Client1 : ClientBase, IClient { public Client1([MetadataFilter(ProviderMetadata.Category, "Web")] IProvider provider) : base(provider) { } } public sealed class Client2 : ClientBase, IClient { public Client2([MetadataFilter(ProviderMetadata.Category, "Database")] IProvider provider) : base(provider) { } }
总结
- 你的第二版实现修正笔误后完全可行,适合愿意在构造函数中添加DI框架属性的场景;
- Lambda注册和
WithParameter方式更适合保持业务类的纯净,让依赖配置集中在一处; - 元数据方式则适合复杂的依赖筛选需求。
你可以根据团队的编码规范和项目需求选择最适合的方案。
内容的提问来源于stack exchange,提问作者Lord_Pinhead
相关产品推荐
相关产品推荐

