【问题标题】:ASP.NET Core DI based on requesting type基于请求类型的 ASP.NET Core DI
【发布时间】:2020-07-20 03:44:51
【问题描述】:

如何在 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 配置任何类以始终获得Hello 的相同实例。

但是:我真正想要的是 Class1 始终获得 Hey 的相同单例实例,而所有其他类应该只获得 Hello 的实例。它可能看起来像ConfigureServices 中的这个(显然不起作用):

services.AddSingleton<IHello, Hello>();
services.AddSingleton<IHello, Hey, Class1>(); // Doesn't work, but would be neat if it did...

【问题讨论】:

  • 这个问题是针对依赖注入和服务如何工作的?如果您总是希望 Class1 使用 Hey 的单例实例,为什么不为 Hey 实现单例模式并让 Class1 使用它呢?为什么要通过 DI 获得它?
  • 我认为对于这种情况,也许最好研究一下 autofaq? autofac.readthedocs.io/en/latest/register/…
  • 这是old 但可能仍然相关...您可能只想为您的类注册工厂并自己将参数解析为您想要的任何实现...
  • @SeanSkelly 我想我的问题是针对 ASP.NET DI 如何工作的,对我来说,这似乎应该是可能的,但我很困惑它不是。应该可以将不同的实现注入 Class1,将其硬编码为 Hey 不是一种选择。
  • @AnkitVijay 确实,Autofac 似乎具有此功能。我会考虑更换 DI,但我宁愿不必这样做。

标签: c# .net asp.net-core dependency-injection


【解决方案1】:

这是一个简单的方法。它缺乏某种优雅,但它会满足您的需求:

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。他们每个人的注册都包括一个功能。该函数从服务提供者解析HelloHey,并将其传递给每个相应类的构造函数。这样您就可以控制将哪个实现传递给哪个类。

(题外话,服务提供者还没有建立。你提供的功能将在稍后执行,传递给它的服务提供者将是从服务集合中构建的服务提供者。 )

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


B 计划是像Microsoft suggests 一样使用另一个容器。

Autofac

首先,添加Autofac.Extensions.DependencyInjection NuGet 包。这引用了 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()) 使用两个函数:

  • 第一个函数确定给定参数是否是我们要解析的参数。因此,在每种情况下,我们都会说,“如果要解析的参数是 IHello,则使用下一个函数解析它。”
  • 然后它通过指定要使用的命名注册来解析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's documentation,它提供了更多详细信息。

出于测试目的,您可以这样做:

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 也有办法做到这一点。我不知道。)

【讨论】:

  • 谢谢。在我的情况下,对类构造函数的硬引用将是一个大问题,因为要考虑更多的依赖项。
  • 我正在研究答案的第二部分。
  • 嗨,Scott,您介意我更新您的答案,在 Windsor 示例下方包含一个 Simple Injector 示例吗?
  • 拜托,去吧。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-15
  • 2020-06-16
  • 1970-01-01
  • 1970-01-01
  • 2020-06-09
  • 1970-01-01
相关资源
最近更新 更多