[英]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.