简体   繁体   English

ASP.NET Core DI 基于请求类型

[英]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 从服务提供商解析HelloHey ,并将其传递给每个 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 计划将按照微软的建议使用另一个容器。

Autofac自动法

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 .它类似于IServiceCollectionIServiceProvider 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())使用两个函数:

  • The first function determines whether a given parameter is the one we want to resolve.第一个 function 确定给定参数是否是我们要解析的参数。 So in each case we're saying, "If the parameter to resolve is IHello , then resolve it using the next function."所以在每种情况下,我们都说,“如果要解析的参数是IHello ,那么使用下一个 function 来解析它。”
  • It then resolves 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.你可能会找到一个你更喜欢的。


Windsor温莎

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:我喜欢这个有两点:

  • It's so much easier to read!它更容易阅读! When resolving ClassThatDependsOnIHello2 , fulfill the dependency on IHello with Hey .解析ClassThatDependsOnIHello2时,使用Hey满足对IHello的依赖。 Simple.简单的。
  • This - 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM