2

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

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

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

这是我声明我的 SQL Server 提供程序的方式...

[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);

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

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

4

1 回答 1

2

如果我正确理解您的用例,您可能有多种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 Containers 在这方面更成熟,功能更丰富。

于 2020-11-18T21:17:43.963 回答