草庐IT

c# - ASP.NET Core 2 中多个相同类型实例的依赖注入(inject)

coder 2024-05-21 原文

在ASP.NET Core 2 Web Api中,我想使用依赖注入(inject)来注入(inject)httpClientA HttpClient 的实例至 ControllerA ,和一个实例 httpClientBHttpClientControllerB .

DI 注册代码如下所示:

HttpClient httpClientA = new HttpClient();
httpClientA.BaseAddress = endPointA;
services.AddSingleton<HttpClient>(httpClientA);

HttpClient httpClientB = new HttpClient();
httpClientB.BaseAddress = endPointB;
services.AddSingleton<HttpClient>(httpClientB);

我知道我可以子类化 HttpClient为每个 Controller 制作一个独特的类型,但这不能很好地扩展。

什么是更好的方法?

更新
特别是关于 HttpClient 微软似乎在做一些事情

https://github.com/aspnet/HttpClientFactory/blob/dev/samples/HttpClientFactorySample/Program.cs#L32 - 感谢@mountain-traveller (Dylan) 指出这一点。

最佳答案

Note: This answer uses HttpClient and a HttpClientFactory as an example but easily applies to any other kind of thing. For HttpClient in particular, using the new IHttpClientFactory from Microsoft.Extensions.Http is preferred.



内置依赖注入(inject)容器不支持命名依赖注册,还有no plans to add this at the moment .
这样做的一个原因是,对于依赖注入(inject),没有类型安全的方法来指定您想要哪种命名实例。您肯定可以使用类似构造函数的参数属性(或属性注入(inject)的属性)之类的东西,但这将是一种不同的复杂性,可能不值得;它当然不会得到类型系统的支持,这是依赖注入(inject)工作方式的重要部分。
通常,命名依赖项表明您没有正确设计依赖项。如果您有两个相同类型的不同依赖项,那么这应该意味着它们可以互换使用。如果情况并非如此,并且其中一个有效而另一个无效,则表明您可能违反了 Liskov substitution principle .
此外,如果您查看那些确实支持命名依赖项的依赖项注入(inject)容器,您会注意到检索这些依赖项的唯一方法不是使用依赖项注入(inject),而是 service locator pattern相反,这与 inversion of control 正好相反DI 促进。
Simple Injector,较大的依赖注入(inject)容器之一,explains their absence of named dependencies like this :

Resolving instances by a key is a feature that is deliberately left out of Simple Injector, because it invariably leads to a design where the application tends to have numerous dependencies on the DI container itself. To resolve a keyed instance you will likely need to call directly into the Container instance and this leads to the Service Locator anti-pattern.

This doesn’t mean that resolving instances by a key is never useful. Resolving instances by a key is normally a job for a specific factory rather than the Container. This approach makes the design much cleaner, saves you from having to take numerous dependencies on the DI library and enables many scenarios that the DI container authors simply didn’t consider.



尽管如此,有时您真的想要这样的东西,并且拥有大量的子类型和单独的注册根本不可行。在这种情况下,有适当的方法来解决这个问题。
我能想到的一种特殊情况是 ASP.NET Core 在其框架代码中具有与此类似的内容:身份验证框架的命名配置选项。让我尝试快速解释这个概念(请耐心等待):
ASP.NET Core 中的身份验证堆栈支持注册多个相同类型的身份验证提供程序,例如您可能最终拥有多个 OpenID Connect providers您的应用程序可能会使用。但是,尽管它们都共享相同的协议(protocol)技术实现,但需要有一种方法让它们独立工作并单独配置实例。
这是通过给每个“身份验证方案”一个唯一名称来解决的。添加方案时,基本上是注册一个新名称并告诉注册它应该使用哪种处理程序类型。此外,您可以使用 IConfigureNamedOptions<T> 配置每个方案。当你实现它时,基本上会传递一个未配置的选项对象,然后配置 - 如果名称匹配。因此对于每种身份验证类型 T ,最终会有IConfigureNamedOptions<T>的多次注册可以为方案配置单独的选项对象。
在某些时候,特定方案的身份验证处理程序运行并需要实际配置的选项对象。为此,它取决于 IOptionsFactory<T>谁的default implementation使您能够创建一个具体的选项对象,然后由所有这些对象进行配置 IConfigureNamedOptions<T>处理程序。
您可以利用选项工厂的确切逻辑来实现一种“命名依赖”。翻译成您的特定示例,例如可能如下所示:
// container type to hold the client and give it a name
public class NamedHttpClient
{
    public string Name { get; private set; }
    public HttpClient Client { get; private set; }

    public NamedHttpClient (string name, HttpClient client)
    {
        Name = name;
        Client = client;
    }
}

// factory to retrieve the named clients
public class HttpClientFactory
{
    private readonly IDictionary<string, HttpClient> _clients;

    public HttpClientFactory(IEnumerable<NamedHttpClient> clients)
    {
        _clients = clients.ToDictionary(n => n.Name, n => n.Client);
    }

    public HttpClient GetClient(string name)
    {
        if (_clients.TryGet(name, out var client))
            return client;

        // handle error
        throw new ArgumentException(nameof(name));
    }
}


// register those named clients
services.AddSingleton<NamedHttpClient>(new NamedHttpClient("A", httpClientA));
services.AddSingleton<NamedHttpClient>(new NamedHttpClient("B", httpClientB));
然后,您将注入(inject) HttpClientFactory某处并使用它的 GetClient检索命名客户端的方法。
显然,如果你考虑一下这个实现和我之前写的内容,那么这看起来非常类似于服务定位器模式。从某种意义上说,在这种情况下它确实是一个,尽管它构建在现有的依赖注入(inject)容器之上。这是否使它变得更好?可能不是,但这是一种使用现有容器实现您的需求的方法,所以这很重要。顺便说一句,为了全面防御,在上面的身份验证选项案例中,选项工厂是一个真正的工厂,因此它构建实际对象并且不使用现有的预注册实例,因此从技术上讲,它不是那里的服务位置模式。

显然,另一种选择是完全忽略我上面写的内容,并在 ASP.NET Core 中使用不同的依赖注入(inject)容器。例如,Autofac支持命名依赖,它可以 easily replace the default container for ASP.NET Core .

关于c# - ASP.NET Core 2 中多个相同类型实例的依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46476112/

有关c# - ASP.NET Core 2 中多个相同类型实例的依赖注入(inject)的更多相关文章

  1. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  2. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  5. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  6. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  7. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  8. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

  9. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  10. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

随机推荐