注意:此答案使用HttpClient 和HttpClientFactory 作为示例,但很容易适用于任何其他类型的事物。特别是对于HttpClient,首选来自Microsoft.Extensions.Http 的using the new IHttpClientFactory。
内置的依赖注入容器不支持命名依赖注册,有no plans to add this at the moment。
其中一个原因是,使用依赖注入,没有类型安全的方法可以指定您想要哪种命名实例。您当然可以为构造函数使用参数属性(或用于属性注入的属性上的属性),但这将是一种不同的复杂性,可能不值得;而且它肯定不会由类型系统支持,这是依赖注入工作方式的重要组成部分。
通常,命名依赖项表明您没有正确设计依赖项。如果您有两个相同类型的不同依赖项,那么这应该意味着它们可以互换使用。如果不是这种情况,并且其中一个有效而另一个无效,则表明您可能违反了Liskov substitution principle。
此外,如果您查看那些支持命名依赖项的依赖注入容器,您会注意到检索这些依赖项的唯一方法不是使用依赖注入,而是使用service locator pattern与 DI 促进的 inversion of control 完全相反。
Simple Injector,较大的依赖注入容器之一,explains their absence of named dependencies like this:
通过键解析实例是 Simple Injector 有意遗漏的一个功能,因为它总是会导致应用程序往往对 DI 容器本身有大量依赖项的设计。要解析键控实例,您可能需要直接调用 Container 实例,这会导致 Service Locator anti-pattern。
这并不意味着通过键解析实例永远不会有用。通过键解析实例通常是特定工厂的工作,而不是 Container。这种方法使设计更加简洁,使您不必依赖于 DI 库,并支持 DI 容器作者根本没有考虑的许多场景。
话虽如此,有时您真的想要这样的东西,并且拥有大量子类型和单独的注册根本不可行。在这种情况下,有一些适当的方法可以解决这个问题。
我能想到的一种特殊情况是 ASP.NET Core 在其框架代码中具有与此类似的内容:身份验证框架的命名配置选项。让我试着快速解释一下这个概念(请耐心等待):
ASP.NET Core 中的身份验证堆栈支持注册多个相同类型的身份验证提供程序,例如,您可能最终拥有多个您的应用程序可能使用的OpenID Connect providers。但是,尽管它们都共享相同的协议技术实现,但需要有一种方法让它们独立工作并单独配置实例。
这可以通过给每个“身份验证方案”一个唯一的名称来解决。添加方案时,基本上是注册一个新名称并告诉注册它应该使用哪种处理程序类型。此外,您可以使用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));
然后您将在某处注入HttpClientFactory 并使用其GetClient 方法来检索命名客户端。
显然,如果您考虑一下这个实现以及我之前写的内容,那么这看起来与服务定位器模式非常相似。在某种程度上,在这种情况下它确实是一个,尽管它建立在现有的依赖注入容器之上。这会让它变得更好吗?可能不是,但这是用现有容器实现您的要求的一种方式,所以这很重要。顺便说一句,为了全面防御,在上面的身份验证选项案例中,选项工厂是一个 real 工厂,因此它构造实际对象并且不使用现有的预注册实例,所以它在技术上是 不是那里的服务位置模式。
显然,另一种选择是完全忽略我上面写的内容,并在 ASP.NET Core 中使用不同的依赖注入容器。比如Autofac支持命名依赖,可以easily replace the default container for ASP.NET Core。