在ASP.NET Core 8中,我们可以 Select 使用密钥注册服务.通常,在构造函数中注入IEnumerable<ICustom>
将返回ICustom
注册的所有服务.
但是,我如何解析服务的词典以及它们的密钥呢?例如,IDictionary<string, ICustom>
,这样我就可以同时访问该服务及其密钥?
在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