簡體   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