在ASP.NET Core 8中,我们可以 Select 使用密钥注册服务.通常,在构造函数中注入IEnumerable<ICustom>将返回ICustom注册的所有服务.

但是,我如何解析服务的词典以及它们的密钥呢?例如,IDictionary<string, ICustom>,这样我就可以同时访问该服务及其密钥?

推荐答案

OOTB没有包含任何内容来获取键控服务的字典,但您可以使用以下扩展方法添加此行为:

// License: This code is published under the MIT license.
public static class KeyedServiceExtensions
{
    public static void AllowResolvingKeyedServicesAsDictionary(
        this IServiceCollection sc)
    {
        // KeyedServiceCache caches all the keys of a given type for a
        // specific service type. By making it a singleton we only have
        // determine the keys once, which makes resolving the dict very fast.
        sc.AddSingleton(typeof(KeyedServiceCache<,>));
        
        // KeyedServiceCache depends on the IServiceCollection to get
        // the list of keys. That's why we register that here as well, as it
        // is not registered by default in MS.DI.
        sc.AddSingleton(sc);
        
        // Last we make the registration for the dictionary itself, which maps
        // to our custom type below. This registration must be  transient, as
        // the containing services could have any lifetime and this registration
        // should by itself not cause Captive Dependencies.
        sc.AddTransient(typeof(IDictionary<,>), typeof(KeyedServiceDictionary<,>));
        
        // For completeness, let's also allow IReadOnlyDictionary to be resolved.
        sc.AddTransient(
            typeof(IReadOnlyDictionary<,>), typeof(KeyedServiceDictionary<,>));
    }

    // We inherit from ReadOnlyDictionary, to disallow consumers from changing
    // the wrapped dependencies while reusing all its functionality. This way
    // we don't have to implement IDictionary<T,V> ourselves; too much work.
    private sealed class KeyedServiceDictionary<TKey, TService>(
        KeyedServiceCache<TKey, TService> keys, IServiceProvider provider)
        : ReadOnlyDictionary<TKey, TService>(Create(keys, provider))
        where TKey : notnull
        where TService : notnull
    {
        private static Dictionary<TKey, TService> Create(
            KeyedServiceCache<TKey, TService> keys, IServiceProvider provider)
        {
            var dict = new Dictionary<TKey, TService>(capacity: keys.Keys.Length);

            foreach (TKey key in keys.Keys)
            {
                dict[key] = provider.GetRequiredKeyedService<TService>(key);
            }

            return dict;
        }
    }

    private sealed class KeyedServiceCache<TKey, TService>(IServiceCollection sc)
        where TKey : notnull
        where TService : notnull
    {
        // Once this class is resolved, all registrations are guaranteed to be
        // made, so we can, at that point, safely iterate the collection to get
        // the keys for the service type.
        public TKey[] Keys { get; } = (
            from service in sc
            where service.ServiceKey != null
            where service.ServiceKey!.GetType() == typeof(TKey)
            where service.ServiceType == typeof(TService)
            select (TKey)service.ServiceKey!)
            .ToArray();
    }
}

将此代码添加到项目后,您可以按如下方式使用它:

services.AllowResolvingKeyedServicesAsDictionary();

这使您可以将键控注册解析为IDictionary<TKey, TService>.例如,看看这个完全正常工作的控制台应用程序示例:

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AllowResolvingKeyedServicesAsDictionary();

services.AddKeyedTransient<IService, Service1>("a");
services.AddKeyedTransient<IService, Service2>("b");
services.AddKeyedTransient<IService, Service3>("c");
services.AddKeyedTransient<IService, Service4>("d");
services.AddKeyedTransient<IService, Service5>(5);

var provider = services.BuildServiceProvider(validateScopes: true);

using (var scope = provider.CreateAsyncScope())
{
    var stringDict = scope.ServiceProvider
        .GetRequiredService<IDictionary<string, IService>>();

    Console.WriteLine("IService registrations with string keys:");
    foreach (var pair in stringDict)
    {
        Console.WriteLine($"{pair.Key}: {pair.Value.GetType().Name}");
    }

    var intDict = scope.ServiceProvider
        .GetRequiredService<IReadOnlyDictionary<int, IService>>();

    Console.WriteLine("IService registrations with int keys:");
    foreach (var pair in intDict)
    {
        Console.WriteLine($"{pair.Key}: {pair.Value.GetType().Name}");
    }
}

public class IService { }

public class Service1 : IService { }
public class Service2 : IService { }
public class Service3 : IService { }
public class Service4 : IService { }
public class Service5 : IService { }

运行时,应用程序将产生以下输出:

IService registrations with string keys:
a: Service1
b: Service2
c: Service3
d: Service4
IService registrations with int keys:
5: Service5

Csharp相关问答推荐

自定义JsonEditor,用于将SON序列化为抽象类

如何创建ASP.NET Core主机并在同一进程中运行请求

为什么这个Reflection. Emit代码会导致一个DDL ViolationException?

通过条件列表删除/更新EF Core 7中的实体的有效方法

在C#中,DirectoryEntry返回空AuditRules集合,即使审计规则确实存在

具有单一导航属性的EF核心一对多关系

与C#中的Zip列表并行

如何使用新的Microsoft.IdentityModel.JsonWebToken创建JwtSecurityToken?

UWP中的任务和界面

如何解决提交按钮后 Select 选项错误空参照异常

当前的文化决定了错误的文化

为什么无法将对象转换为泛型类型

.NET SDK包中的官方C#编译器在哪里?

在扩展方法中,IEnumerable<;T>;不会转换为IEumerable<;T&>

{ or ; expected error如何解决此问题

如何在发布NuGet包之前设置命名空间?

是否可以从IQueryable T中获取一个IdentyEntry T>

避免在特定区域中设置Visual Studio代码的自动格式

如何对列表<;列表>;使用集合表达式?

如何处理ASP.NET Core中包含两个构造函数的控制器?