ASP.NET Core DI中注册具有多个接口的服务
ASP.NET Core的一个关键特性是它使用依赖注入(DI)。该框架围绕“符合容器”抽象设计,允许框架本身使用简单容器,同时还允许插入功能更丰富的第三方容器。
针对多个服务注册实现的一般模式是常见的。大多数第三方DI容器都内置了这个概念。例如:Autofac;
但是在Core 的DI容器中它是不支持的,比如:
public interface IBar {} public interface IFoo {} public class Foo : IFoo, IBar {} [Fact] public void WhenRegisteredAsSeparateSingleton_InstancesAreNotTheSame() { var services = new ServiceCollection(); services.AddSingleton<IFoo, Foo>(); services.AddSingleton<IBar, Foo>(); var provider = services.BuildServiceProvider(); var foo1 = provider.GetService<IFoo>(); // An instance of Foo var foo2 = provider.GetService<IBar>(); // An instance of Foo Assert.Same(foo1, foo2); // FAILS }
注册的Foo
是两个单例IFoo
和IBar
,但结果可能不是你所期望的。我们实际上有两个Foo
“Singleton” 实例,每个实例注册一次。
这个问题是由David Fowler在2年前提出的,但它已经解决了,有两种不太优雅的解决方案。
1.提供服务实例(仅限Singleton)
[Fact] public void WhenRegisteredAsInstance_InstancesAreTheSame() { var foo = new Foo(); // The singleton instance var services = new ServiceCollection(); services.AddSingleton<IFoo>(foo); services.AddSingleton<IBar>(foo); var provider = services.BuildServiceProvider(); var foo1 = provider.GetService<IFoo>(); var foo2 = provider.GetService<IBar>(); Assert.Same(foo1, foo); // PASSES; Assert.Same(foo2, foo); // PASSES; }
这里要注意: 必须在配置时对Foo进行实例化,并且必须知道并提供Foo的依赖项。在单例情况下,这可能比较适合,但它不是很灵活。
此外,如果你想Foo
成为每个请求范围的单个实例(Scoped),那么这种方法就不行了,需要第二种方法
2.使用工厂方法实现转发
[Fact] public void WhenRegisteredAsForwardedSingleton_InstancesAreTheSame() { var services = new ServiceCollection(); services.AddSingleton<Foo>(); // We must explicitly register Foo services.AddSingleton<IFoo>(x => x.GetRequiredService<Foo>()); // Forward requests to Foo services.AddSingleton<IBar>(x => x.GetRequiredService<Foo>()); // Forward requests to Foo var provider = services.BuildServiceProvider(); var foo1 = provider.GetService<Foo>(); // An instance of Foo var foo2 = provider.GetService<IFoo>(); // An instance of Foo var foo3 = provider.GetService<IBar>(); // An instance of Foo Assert.Same(foo1, foo2); // PASSES Assert.Same(foo1, foo3); // PASSES }
为了将接口请求“转发”到具体类型,必须做两件事:
- 使用明确注册具体类型
services.AddSingleton<Foo>()
- 通过提供工厂函数将接口请求委托给具体类型:
services.AddSingleton<IFoo>(x => x.GetRequiredService<Foo>())