在 .NET Core 中通过键解析动态注册的服务

Resolving dynamically registered services by key in .NET Core

我正在将一个工具迁移到 .net 5 控制台应用程序。我想更改我的 DI 系统,目前是 TinyIoC 的修改版本,以尽可能使用内置 DI。目前,我的工具将加载并自动注册它在其配置文件中找到的任何 dll。先进先出,因此用户提供的我的接口之一的实现将优先于我的默认接口,最后加载。

此外,我需要能够注册给定接口的多个变体,并让我的 DI 系统根据配置在它们之间进行选择。目前,这适用于我添加到 Tiny 的 RegistrationName 属性。当 tiny 自动注册 dll 中的所有内容时,它会在其注册中包含此名称。

因此,例如,我有一个 IProvider interface,其方法包括 IDbConnection GetConnection(string connectionString);。我有几个 SQL 服务器、Postgres 等的默认实现,用户可以在 dll 中提供我在编译我的工具时不知道的其他实现。

这是我声明我的 SQL 服务器提供商的方式...

[RegistrationName("System.Data.SqlClient")]
class SqlClient : IProvider
{

这是我在 qfconfig.json...

中指定提供商的方式
{
    "defaultConnection": "Data Source=localhost;Initial Catalog=Northwind;Integrated Security=True",
    "provider": "System.Data.SqlClient"
} 

以下是我向 Tiny 询问具体实例的方式...

// RegistrationName is passed to the Resolve method.
// Tiny returns the implementation whose RegistrationName matches.
_provider = _tiny.Resolve<IProvider>(config.provider);

所以我想保留这种可能性,但找到一种不那么个人化的方式。

恐怕我在这个问题上走入了森林。我找到的文档和教程都涵盖了更简单的场景,其中有一个接口的注册实现,并且所有内容都在代码中显式注册。有人可以给我指路吗?

如果我正确理解你的用例,你可能有多个 IProvider 实现,但在运行时总是只需要一个,它基于映射到 RegistrationName 属性的配置值.

MS.DI 框架没有内置任何东西来简化这种用例,但由于您只需要在运行时注册一个,您可以通过迭代程序集并找到特定的实现并注册来实现这一点那:

var providers =
    from assembly in assemblies
    from type in assembly.GetExportedTypes()
    where typeof(IProvider).IsAssignableFrom(type)
    where !type.IsAbstract
    let attr = type.GetCustomAttribute<RegistrationNameAttribute>()
    where attr?.Name == config.provider
    select type;

services.AddTransient(typeof(IProvider), providers.Single());

这种方式注册是基于名称的,而解析可以以无密钥的方式完成:

serviceProvider.GetRequiredService<IProvider>();

万一我误解了你的问题,而你需要在运行时执行多个 IProvider 实现,并且需要通过它们的键来解决它们......好吧,这当然是可能的,但你将不得不写更多代码。以下是所有内容的要点,ActivatorUtilities 是你的朋友:

// Find all 'name -> provider' mappings
var providerDefinitions =
    from assembly in assemblies
    from type in assembly.GetExportedTypes()
    where typeof(IProvider).IsAssignableFrom(type)
    where !type.IsAbstract
    let name = type.GetCustomAttribute<RegistrationNameAttribute>()?.Name
    where name != null
    select new { name, type };

// Helper method that builds IProvider factory delegates
Func<IServiceProvider, IProvider> BuildProviderFactory(Type type) =>
    provider => (IProvider)ActivatorUtilities.CreateInstance(provider, type);

// Create a dictionary that maps the name to a provider factory
Dictionary<string, Func<IServiceProvider, IProvider>> providerFactories =
    providerDefinitions.ToDictionary(
        keySelector: i => i.name,
        elementSelector: i => BuildProviderFactory(i.type));

// Possible use
Func<IServiceProvider, IProvider> factory = providerFactories[config.provider];
IProvider provider = factory(serviceProvider);

ActivatorUtilities.CreateInstance 是 MS.DI 的扩展点,它允许创建未注册的 类,同时将它们注入作为提供的 IServiceProvider 实例一部分的依赖项。

ActivatorUtilities.CreateInstance 有很多不幸的微妙缺点,例如无法检查循环依赖性,这可能会导致讨厌的堆栈溢出异常。但这是我们可以用 MS.DI 实现的最好结果。其他DI容器在这方面比较成熟,功能也比较丰富。