繁体   English   中英

ASP.NET Core DI 基于请求类型

[英]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 从服务提供商解析HelloHey ,并将其传递给每个 class 的构造函数。 这样,您就可以控制将哪个实现传递给哪个 class。

(顺便说一句,服务提供者还没有构建。您提供的 function 将稍后执行,传递给它的服务提供者将是从服务集合中构建的那个。)

这样做的一个缺点是,现在您的 DI 注册显式调用了您的构造函数。 这可能会很麻烦,因为如果构造函数发生变化(也许您注入其他依赖项),那么您将不得不编辑此代码。 这不是很好,但并不少见。


B 计划将按照微软的建议使用另一个容器。

自动法

首先,添加Autofac.Extensions.DependencyInjection NuGet package。 这引用了 Autofac 并且还提供了将 Autofac 容器添加到服务集合所需的扩展。

我已经安排这个以关注依赖项在 Autofac 中注册的方式。 它类似于IServiceCollectionIServiceProvider 您创建一个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())使用两个函数:

  • 第一个 function 确定给定参数是否是我们要解析的参数。 所以在每种情况下,我们都说,“如果要解析的参数是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.

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