[英]ASP.NET Core DI based on requesting type
如何在 ASP.NET Core 中配置依賴注入以根據注入的類型返回某個實例?
假設我有一個簡單的界面,
public interface IHello
{
string SayHello();
}
還有兩種不同的實現:
public class Hello : IHello
{
public string SayHello() => "Hello...";
}
public class Hey : IHello
{
public string SayHello() => "HEY!";
}
最后,我有一些類都依賴於IHello
的實例:
public class Class1
{
public Class1(IHello hello)
{
}
}
public class Class2
{
public Class2(IHello hello)
{
}
}
現在,在ConfigureServices
我會做這樣的事情:
services.AddSingleton<IHello, Hello>();
根據IHello
配置任何 class 以始終獲得相同的Hello
實例。
但是:我真正想要的是Class1
始終獲得相同的 singleton 實例Hey
而所有其他類應該只獲得Hello
的實例。 在ConfigureServices
中它可能看起來像這樣(顯然不起作用):
services.AddSingleton<IHello, Hello>();
services.AddSingleton<IHello, Hey, Class1>(); // Doesn't work, but would be neat if it did...
這是一個簡單的方法。 它缺乏一定的優雅,但它會做你需要的:
public static void Register(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<Hello>();
serviceCollection.AddSingleton<Hey>();
serviceCollection.AddSingleton<ClassThatDependsOnIHello1>(serviceProvider =>
new ClassThatDependsOnIHello1(serviceProvider.GetService<Hello>()));
serviceCollection.AddSingleton<ClassThatDependsOnIHello2>(serviceProvider =>
new ClassThatDependsOnIHello2(serviceProvider.GetService<Hey>()));
}
有兩個類依賴於IHello
。 他們每個人的注冊包括一個 function。 function 從服務提供商解析Hello
或Hey
,並將其傳遞給每個 class 的構造函數。 這樣,您就可以控制將哪個實現傳遞給哪個 class。
(順便說一句,服務提供者還沒有構建。您提供的 function 將稍后執行,傳遞給它的服務提供者將是從服務集合中構建的那個。)
這樣做的一個缺點是,現在您的 DI 注冊顯式調用了您的構造函數。 這可能會很麻煩,因為如果構造函數發生變化(也許您注入其他依賴項),那么您將不得不編輯此代碼。 這不是很好,但並不少見。
B 計划將按照微軟的建議使用另一個容器。
首先,添加Autofac.Extensions.DependencyInjection NuGet package。 這引用了 Autofac 並且還提供了將 Autofac 容器添加到服務集合所需的擴展。
我已經安排這個以關注依賴項在 Autofac 中注冊的方式。 它類似於IServiceCollection
和IServiceProvider
。 您創建一個ContainerBuilder
,注冊依賴項,然后從中構建一個Container
:
static void RegisterDependencies(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<Hello>().Named<IHello>("Hello");
containerBuilder.RegisterType<Hey>().Named<IHello>("Hey");
containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
(parameter, context) => context.ResolveNamed<IHello>("Hello")
));
containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
(parameter, context) => context.ResolveNamed<IHello>("Hey")
));
}
這也不是很漂亮,但它回避了調用構造函數的問題。
首先,它注冊兩個IHello
的實現並給它們命名。
然后它注冊了兩個依賴於IHello
的類。 WithParameter(new ResolvedParameter())
使用兩個函數:
IHello
,那么使用下一個 function 來解析它。”IHello
。 我對這有多復雜並不感到興奮,但這確實意味着如果這些類注入了其他依賴項,它們將被正常解析。 您可以解析ClassThatDependsOnIHello1
而無需實際調用其構造函數。
您也可以不使用名稱:
static void RegisterDependencies(this ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<Hello>();
containerBuilder.RegisterType<Hey>();
containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
(parameter, context) => context.Resolve<Hello>()
));
containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(IHello),
(parameter, context) => context.Resolve<Hey>()
));
containerBuilder.RegisterType<SomethingElse>().As<ISomethingElse>();
}
我們可以使用一種簡化創建ResolvedParameter
的方法來清理它,因為那太可怕了。
public static ResolvedParameter CreateResolvedParameter<TDependency, TImplementation>()
where TDependency : class
where TImplementation : TDependency
{
return new ResolvedParameter((parameter, context) => parameter.ParameterType == typeof(TDependency),
(parameter, context) => context.Resolve<TImplementation>());
}
現在之前的注冊變成了:
containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
CreateResolvedParameter<IHello, Hello>());
containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
CreateResolvedParameter<IHello, Hey>());
更好的!
這留下了如何將其與應用程序集成的詳細信息,並且會因應用程序而異。 這是Autofac 的文檔,它提供了更多詳細信息。
出於測試目的,您可以這樣做:
public static IServiceProvider CreateServiceProvider()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterDependencies();
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
我喜歡為這類事情編寫單元測試。 此方法將從 Autofac 容器創建一個IServiceProvider
,然后您可以測試從容器中解析的內容,以確保它們按預期得到解析。
如果您更喜歡另一個容器,請查看它是否具有類似的集成以將其與 Microsoft 的容器一起使用。 你可能會找到一個你更喜歡的。
這是一個使用Castle.Windsor.MsDependencyInjection的類似示例。
public static class WindsorRegistrations
{
public static IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
var container = new WindsorContainer();
container.RegisterDependencies();
return WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection);
}
public static void RegisterDependencies(this IWindsorContainer container)
{
container.Register(
Component.For<Hello>(),
Component.For<Hey>(),
Component.For<ClassThatDependsOnIHello1>()
.DependsOn(Dependency.OnComponent<IHello, Hello>()),
Component.For<ClassThatDependsOnIHello2>()
.DependsOn(Dependency.OnComponent<IHello, Hey>())
);
}
}
我喜歡這個有兩點:
ClassThatDependsOnIHello2
時,使用Hey
滿足對IHello
的依賴。 簡單的。WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection)
- 允許您使用IServiceCollection
注冊依賴項並將它們包含在服務提供程序中。 因此,如果您的現有代碼向IServiceCollection
注冊了大量依賴項,您仍然可以使用它。 您可以向 Windsor 注冊其他依賴項。 然后CreateServiceProvider
將它們混合在一起。 (也許 Autofac 也有辦法做到這一點。我不知道。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.