如何在 ASP.NET Core 8 中获取键控服务字典

Jay*_*Jay 3 c# dependency-injection asp.net-core asp.net-core-8

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

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

Ste*_*ven 6

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

// License: This code is published under the MIT license.
// Source: https://stackoverflow.com/questions/77559201/
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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

services.AllowResolvingKeyedServicesAsDictionary();
Run Code Online (Sandbox Code Playgroud)

这允许您将密钥注册解析为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 { }
Run Code Online (Sandbox Code Playgroud)

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

IService registrations with string keys:
a: Service1
b: Service2
c: Service3
d: Service4
IService registrations with int keys:
5: Service5
Run Code Online (Sandbox Code Playgroud)