Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ public partial interface IKeyedServiceProvider : System.IServiceProvider
object? GetKeyedService(System.Type serviceType, object? serviceKey);
object GetRequiredKeyedService(System.Type serviceType, object? serviceKey);
}
public partial interface IServiceKeysProvider
{
System.Collections.Generic.IEnumerable<object?> GetServiceKeys(System.Type serviceType);
}
public partial interface ISupportRequiredService
{
object GetRequiredService(System.Type serviceType);
Expand Down Expand Up @@ -230,6 +234,7 @@ public static partial class ServiceProviderKeyedServiceExtensions
public static object? GetKeyedService(this System.IServiceProvider provider, System.Type serviceType, object? serviceKey) { throw null; }
public static object GetRequiredKeyedService(this System.IServiceProvider provider, System.Type serviceType, object? serviceKey) { throw null; }
public static T GetRequiredKeyedService<T>(this System.IServiceProvider provider, object? serviceKey) where T : notnull { throw null; }
public static System.Collections.Generic.IEnumerable<object?>? GetServiceKeys<T>(this System.IServiceProvider provider) { throw null; }
}
public static partial class ServiceProviderServiceExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Provides known service keys for a service type.
/// </summary>
public interface IServiceKeysProvider
{
/// <summary>
/// Gets known service keys for the specified <paramref name="serviceType"/>.
/// </summary>
/// <param name="serviceType">The type of service to get known keys for.</param>
/// <returns>An enumeration of known service keys for <paramref name="serviceType"/>.</returns>
IEnumerable<object?> GetServiceKeys(Type serviceType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,23 @@ public static IEnumerable<T> GetKeyedServices<T>(this IServiceProvider provider,
Type? genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
return (IEnumerable<object>)provider.GetRequiredKeyedService(genericEnumerable, serviceKey);
}

/// <summary>
/// Get all known service keys for service type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
/// </summary>
/// <typeparam name="T">The type of service object to get known keys for.</typeparam>
/// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service keys from.</param>
/// <returns>An enumeration of known service keys for <typeparamref name="T"/> or <see langword="null"/> if not supported.</returns>
public static IEnumerable<object?>? GetServiceKeys<T>(this IServiceProvider provider)
{
ArgumentNullException.ThrowIfNull(provider);

if (provider is IServiceKeysProvider serviceKeysProvider)
{
return serviceKeysProvider.GetServiceKeys(typeof(T));
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,22 @@ public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration()
Assert.Equal(new[] { service5, service6 }, unkeyedServices);
}

[Fact]
public void ResolveServiceKeys()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IService, Service>();
serviceCollection.AddKeyedSingleton<IService>("first-service", new Service());
serviceCollection.AddKeyedSingleton<IService>("service", new Service());
serviceCollection.AddKeyedSingleton<IService>("service", new Service());
serviceCollection.AddKeyedSingleton<IService>(null, new Service());
serviceCollection.AddKeyedTransient<IService>(KeyedService.AnyKey, (sp, key) => new Service());

var provider = CreateServiceProvider(serviceCollection);

Assert.Equal(new object?[] { "first-service", "service", null }, provider.GetServiceKeys<IService>());
}

[Fact]
public void ResolveKeyedServicesAnyKeyConsistency()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,42 @@ public void Add(ServiceIdentifier serviceIdentifier, ServiceCallSite serviceCall
_callSiteCache[new ServiceCacheKey(serviceIdentifier, DefaultSlot)] = serviceCallSite;
}

internal IEnumerable<object?> GetServiceKeys(Type serviceType)
{
ArgumentNullException.ThrowIfNull(serviceType);

List<object?>? serviceKeys = null;
HashSet<object?>? serviceKeySet = null;

for (int i = 0; i < _descriptors.Length; i++)
{
ServiceDescriptor descriptor = _descriptors[i];
if (!descriptor.IsKeyedService || descriptor.ServiceKey == KeyedService.AnyKey)
{
continue;
}

if (!ShouldCreateExact(descriptor.ServiceType, serviceType) &&
!ShouldCreateOpenGeneric(descriptor.ServiceType, serviceType))
{
continue;
}

object? serviceKey = descriptor.ServiceKey;
if (serviceKeySet?.Contains(serviceKey) == true)
{
continue;
}

serviceKeySet ??= new HashSet<object?>();
serviceKeys ??= new List<object?>();
serviceKeySet.Add(serviceKey);
serviceKeys.Add(serviceKey);
}

return serviceKeys is null ? Array.Empty<object?>() : serviceKeys;
}

public bool IsService(Type serviceType) => IsService(new ServiceIdentifier(null, serviceType));

public bool IsKeyedService(Type serviceType, object? key) => IsService(new ServiceIdentifier(key, serviceType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
[DebuggerDisplay("{DebuggerToString(),nq}")]
[DebuggerTypeProxy(typeof(ServiceProviderEngineScopeDebugView))]
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IServiceKeysProvider, IAsyncDisposable, IServiceScopeFactory
{
// For testing and debugging only
internal IList<object> Disposables => _disposables ?? (IList<object>)Array.Empty<object>();
Expand Down Expand Up @@ -71,6 +71,16 @@ public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
return RootProvider.GetRequiredKeyedService(serviceType, serviceKey, this);
}

public IEnumerable<object?> GetServiceKeys(Type serviceType)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}

return RootProvider.GetServiceKeys(serviceType);
}

public IServiceProvider ServiceProvider => this;

public IServiceScope CreateScope() => RootProvider.CreateScope();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
[DebuggerDisplay("{DebuggerToString(),nq}")]
[DebuggerTypeProxy(typeof(ServiceProviderDebugView))]
public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IDisposable, IAsyncDisposable
public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IServiceKeysProvider, IDisposable, IAsyncDisposable
{
private readonly CallSiteValidator? _callSiteValidator;

Expand Down Expand Up @@ -167,6 +167,18 @@ internal object GetRequiredKeyedService(Type serviceType, object? serviceKey, Se
return service;
}

/// <summary>
/// Gets all known service keys for the specified type.
/// </summary>
/// <param name="serviceType">The type of service to get known keys for.</param>
/// <returns>An enumeration of known service keys for the specified <paramref name="serviceType"/>.</returns>
public IEnumerable<object?> GetServiceKeys(Type serviceType)
{
ArgumentNullException.ThrowIfNull(serviceType);

return CallSiteFactory.GetServiceKeys(serviceType);
}

internal bool IsDisposed() => _disposed;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,19 @@ public void NonGeneric_GetServices_Returns_EmptyArray_WhenNoServicesAvailable()
Assert.IsType<IFoo[]>(services);
}

[Fact]
public void GetServiceKeys_ReturnsNull_WhenProviderDoesNotSupportServiceKeys()
{
// Arrange
var serviceProvider = new RequiredServiceSupportingProvider();

// Act
var serviceKeys = serviceProvider.GetServiceKeys<IFoo>();

// Assert
Assert.Null(serviceKeys);
}

[Fact]
public void GetServices_WithBuildServiceProvider_Returns_EmptyList_WhenNoServicesAvailable()
{
Expand Down
Loading