[英]ASP.NET Core DI based on requesting type
How do I configure dependency injection in ASP.NET Core to return a certain instance depending on the type it's being injected into?如何在 ASP.NET Core 中配置依赖注入以根据注入的类型返回某个实例?
Let's say I have a simple interface,假设我有一个简单的界面,
public interface IHello
{
string SayHello();
}
And two different implementations:还有两种不同的实现:
public class Hello : IHello
{
public string SayHello() => "Hello...";
}
public class Hey : IHello
{
public string SayHello() => "HEY!";
}
And finally I have a few classes that all depend on an instance of IHello
:最后,我有一些类都依赖于IHello
的实例:
public class Class1
{
public Class1(IHello hello)
{
}
}
public class Class2
{
public Class2(IHello hello)
{
}
}
Now, in ConfigureServices
I would do something like this:现在,在ConfigureServices
我会做这样的事情:
services.AddSingleton<IHello, Hello>();
to configure any class depending on IHello
to always get the same instance of Hello
.根据IHello
配置任何 class 以始终获得相同的Hello
实例。
BUT: What I really want is for Class1
to always get the same singleton instance of Hey
and all other classes should just get an instance of Hello
.但是:我真正想要的是Class1
始终获得相同的 singleton 实例Hey
而所有其他类应该只获得Hello
的实例。 It could look like this in ConfigureServices
(doesn't work, obviously):在ConfigureServices
中它可能看起来像这样(显然不起作用):
services.AddSingleton<IHello, Hello>();
services.AddSingleton<IHello, Hey, Class1>(); // Doesn't work, but would be neat if it did...
Here's a simple approach.这是一个简单的方法。 It lacks a certain elegance, but it will do what you need:它缺乏一定的优雅,但它会做你需要的:
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>()));
}
There are two classes that depend on IHello
.有两个类依赖于IHello
。 The registration for each of them includes a function.他们每个人的注册包括一个 function。 That function resolves either Hello
or Hey
from the service provider and passes it to the constructor of each respective class. function 从服务提供商解析Hello
或Hey
,并将其传递给每个 class 的构造函数。 That way you get control over which implementation gets passed to which class.这样,您就可以控制将哪个实现传递给哪个 class。
(It's beside the point that the service provider hasn't been built yet. The function you're providing will be executed later, and the service provider passed to it will be the one that has been built from the service collection.) (顺便说一句,服务提供者还没有构建。您提供的 function 将稍后执行,传递给它的服务提供者将是从服务集合中构建的那个。)
A downside to this is that now your DI registration explicitly calls your constructors.这样做的一个缺点是,现在您的 DI 注册显式调用了您的构造函数。 That can be a nuisance because if the constructors change (maybe you inject other dependencies) then you'll have to edit this code.这可能会很麻烦,因为如果构造函数发生变化(也许您注入其他依赖项),那么您将不得不编辑此代码。 That's not great, but it's not uncommon.这不是很好,但并不少见。
Plan B would be to do as Microsoft suggests and use another container. B 计划将按照微软的建议使用另一个容器。
First, add the Autofac.Extensions.DependencyInjection NuGet package.首先,添加Autofac.Extensions.DependencyInjection NuGet package。 This references Autofac and also provides the extensions needed to add an Autofac container to a service collection.这引用了 Autofac 并且还提供了将 Autofac 容器添加到服务集合所需的扩展。
I've arranged this to focus on the way dependencies get registered with Autofac.我已经安排这个以关注依赖项在 Autofac 中注册的方式。 It's similar to IServiceCollection
and IServiceProvider
.它类似于IServiceCollection
和IServiceProvider
。 You create a ContainerBuilder
, register dependencies, and then build a Container
from it:您创建一个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")
));
}
That's not really pretty either, but it sidesteps the problem of calling the constructors.这也不是很漂亮,但它回避了调用构造函数的问题。
First it registers two implementations of IHello
and gives them names.首先,它注册两个IHello
的实现并给它们命名。
Then it registers the two classes that depend on IHello
.然后它注册了两个依赖于IHello
的类。 WithParameter(new ResolvedParameter())
uses two functions: WithParameter(new ResolvedParameter())
使用两个函数:
IHello
, then resolve it using the next function."所以在每种情况下,我们都说,“如果要解析的参数是IHello
,那么使用下一个 function 来解析它。”IHello
by specifying which named registration to use.然后它通过指定要使用的命名注册来解析IHello
。 I'm not excited by how complicated that is, but it does mean that if those classes have other dependencies injected, they'll be resolved normally.我对这有多复杂并不感到兴奋,但这确实意味着如果这些类注入了其他依赖项,它们将被正常解析。 You can resolve ClassThatDependsOnIHello1
without actually calling its constructor.您可以解析ClassThatDependsOnIHello1
而无需实际调用其构造函数。
You can also do it without the names:您也可以不使用名称:
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>();
}
We can clean that up some with an method that simplifies creating that ResolvedParameter
because that's so hideous.我们可以使用一种简化创建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>());
}
Now the previous registration becomes:现在之前的注册变成了:
containerBuilder.RegisterType<ClassThatDependsOnIHello1>().WithParameter(
CreateResolvedParameter<IHello, Hello>());
containerBuilder.RegisterType<ClassThatDependsOnIHello2>().WithParameter(
CreateResolvedParameter<IHello, Hey>());
Better!更好的!
That leaves the details of how you integrate it with your application, and that varies with your application.这留下了如何将其与应用程序集成的详细信息,并且会因应用程序而异。 Here's Autofac's documentation which provides more detail.这是Autofac 的文档,它提供了更多详细信息。
For testing purposes you can do this:出于测试目的,您可以这样做:
public static IServiceProvider CreateServiceProvider()
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterDependencies();
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
I like to write unit tests for this sort of thing.我喜欢为这类事情编写单元测试。 This method will create an IServiceProvider
from the Autofac container, and then you can test resolving things from the container to make sure they get resolved as expected.此方法将从 Autofac 容器创建一个IServiceProvider
,然后您可以测试从容器中解析的内容,以确保它们按预期得到解析。
If you prefer another container, see if it has similar integrations to use it with Microsoft's container.如果您更喜欢另一个容器,请查看它是否具有类似的集成以将其与 Microsoft 的容器一起使用。 You might find one you like better.你可能会找到一个你更喜欢的。
Here's a similar example using Castle.Windsor.MsDependencyInjection .这是一个使用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>())
);
}
}
There are two things I like about this:我喜欢这个有两点:
ClassThatDependsOnIHello2
, fulfill the dependency on IHello
with Hey
.解析ClassThatDependsOnIHello2
时,使用Hey
满足对IHello
的依赖。 Simple.简单的。WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection)
- allows you to register dependencies with the IServiceCollection
and include them in the service provider.这 - WindsorRegistrationHelper.CreateServiceProvider(container, serviceCollection)
- 允许您使用IServiceCollection
注册依赖项并将它们包含在服务提供程序中。 So if you have existing code that registers lots of dependencies with IServiceCollection
you can still use it.因此,如果您的现有代码向IServiceCollection
注册了大量依赖项,您仍然可以使用它。 You can register other dependencies with Windsor.您可以向 Windsor 注册其他依赖项。 Then CreateServiceProvider
mixes them all together.然后CreateServiceProvider
将它们混合在一起。 (Maybe Autofac has a way to do that too. I don't know.) (也许 Autofac 也有办法做到这一点。我不知道。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.