Improving on steven answer above https://stackoverflow.com/a/77559901/1875674
Made KeyedServiceCache
non-generic with generic methods to resolve keys from IServiceCollection
, that way the in registration of KeyedServiceCache
you can inject the IServiceCollection
directly with having to register it
Added a ConcurrentDictionary<Type, IEnumerable<object>>
for caching
Added filter for KeyedImplementationFactory
and KeyedImplementationInstance
as well as KeyedImplementationType
public class KeyedServiceCache
{
private readonly IServiceCollection _serviceCollection;
private readonly ConcurrentDictionary<Type, IEnumerable<object>> _keyCache = new ConcurrentDictionary<Type, IEnumerable<object>>();
public KeyedServiceCache(IServiceCollection serviceCollection)
{
_serviceCollection = serviceCollection;
}
public IEnumerable<TKey> GetKeys<TKey>(Type serviceType)
{
return _keyCache.GetOrAdd(serviceType, _ =>
_serviceCollection
.Where(sd => sd.IsKeyedService
&& sd.ServiceKey?.GetType() == typeof(TKey)
&& FilterImplementationType(sd, serviceType))
.Select(s => s.ServiceKey)
.Distinct()
).OfType<TKey>();
}
public IEnumerable<TKey> GetKeys<TKey,TService>()
{
return GetKeys<TKey>(typeof(TService));
}
private static bool FilterImplementationType(ServiceDescriptor serviceDescriptor,Type serviceType)
{
if (serviceDescriptor.KeyedImplementationType != null &&
serviceDescriptor.KeyedImplementationType == serviceType)
return true;
if (serviceDescriptor.KeyedImplementationFactory != null &&
serviceDescriptor.KeyedImplementationFactory.Method.ReturnType == serviceType)
return true;
return serviceDescriptor.KeyedImplementationInstance != null &&
serviceDescriptor.KeyedImplementationInstance.GetType() == serviceType;
}
}
Made KeyServiceDictionary<TKey, TService>
fully Lazy Loaded, will now resolve the Keys separate to the, resolving of services
Added support to resolve KeyServiceDictionary<TKey, IEnumerable<TService>>
Solved the issue of Captive Dependency by injecting IServiceProviderIsKeyedService
into KeyServiceDictionary<TKey, TService>
allowing filtering by scope using IsKeyedService
method
public class KeyServiceDictionary<TKey, TService> : IReadOnlyDictionary<TKey, TService>
{
private readonly KeyedServiceCache _keyedServiceCache;
private readonly IServiceProvider _serviceProvider;
private readonly bool _isServiceEnumerable;
private readonly Type _serviceType;
private readonly IServiceProviderIsKeyedService _serviceProviderIsKeyedService;
//caching
private readonly Dictionary<TKey, TService> _resolvedServices = new Dictionary<TKey, TService>();
public KeyServiceDictionary(KeyedServiceCache keyedServiceCache, IServiceProvider serviceProvider, IServiceProviderIsKeyedService serviceProviderIsKeyedService)
{
_isServiceEnumerable = typeof(TService).IsGenericType &&
typeof(TService).GetGenericTypeDefinition() == typeof(IEnumerable<>);
_serviceType = _isServiceEnumerable? typeof(TService).GetGenericArguments()[0]: typeof(TService);
_keyedServiceCache = keyedServiceCache;
_serviceProvider = serviceProvider;
_serviceProviderIsKeyedService = serviceProviderIsKeyedService;
}
public IEnumerator<KeyValuePair<TKey, TService>> GetEnumerator()
{
return new Enumerator(Keys.GetEnumerator(), this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count => Keys.Count();
public bool ContainsKey(TKey key)
{
return Keys.Any(x => x.Equals(key));
}
public bool TryGetValue(TKey key, out TService value)
{
value = _serviceProvider.GetKeyedService<TService>(key);
return value != null;
}
public TService this[TKey key]
{
get
{
if (!_serviceProviderIsKeyedService.IsKeyedService(_serviceType, key))
throw new KeyNotFoundException($"Could not find Key:{key} in Dictionary");
if (_resolvedServices.TryGetValue(key, out var service))
return service;
try
{
service = _isServiceEnumerable
? (TService)_serviceProvider.GetKeyedServices(_serviceType, key)
: _serviceProvider.GetRequiredKeyedService<TService>(key);
}
catch (InvalidOperationException e)
{
throw new KeyNotFoundException($"Could not find Key:{key} in Dictionary", e);
}
_resolvedServices.Add(key, service);
return service;
}
}
public IEnumerable<TKey> Keys =>
(_isServiceEnumerable
? _keyedServiceCache.GetKeys<TKey>(_serviceType)
: _keyedServiceCache.GetKeys<TKey, TService>()).Where(k =>
_serviceProviderIsKeyedService.IsKeyedService(_serviceType, _serviceType));
public IEnumerable<TService> Values => Keys.Select(key => this[key]);
private class Enumerator : IEnumerator<KeyValuePair<TKey, TService>>
{
private readonly IEnumerator<TKey> _keys;
private readonly IReadOnlyDictionary<TKey, TService> _parentDictionary;
private KeyValuePair<TKey, TService> _currentKeyPair;
public Enumerator(IEnumerator<TKey> keys, IReadOnlyDictionary<TKey,TService> parentDictionary)
{
_keys = keys;
_parentDictionary = parentDictionary;
}
public bool MoveNext()
{
return _keys.MoveNext();
}
public void Reset()
{
_keys.Reset();
}
private KeyValuePair<TKey, TService> GetCurrent()
{
var service = _parentDictionary[_keys.Current];
_currentKeyPair = new KeyValuePair<TKey, TService>(_keys.Current, service);
return _currentKeyPair;
}
public object Current => GetCurrent();
KeyValuePair<TKey, TService> IEnumerator<KeyValuePair<TKey, TService>>.Current => GetCurrent();
public void Dispose()
{
}
}
}
public static class ServiceExtensions
{
public static void WithKeyServiceDictionarySupport(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton(s => new KeyedServiceCache(serviceCollection));
serviceCollection.AddTransient(typeof(IReadOnlyDictionary<,>), typeof(KeyServiceDictionary<,>));
}
}
Up to date Gist