Skip to content

Commit

Permalink
Add Keyed Services Support to Dependency Injection (#87183)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminpetit authored Jul 13, 2023
1 parent 33e0669 commit 138cb59
Show file tree
Hide file tree
Showing 35 changed files with 3,353 additions and 184 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,53 @@ private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters,
return true;
}

private static object? GetService(IServiceProvider serviceProvider, ParameterInfo parameterInfo)
{
// Handle keyed service
if (TryGetServiceKey(parameterInfo, out object? key))
{
if (serviceProvider is IKeyedServiceProvider keyedServiceProvider)
{
return keyedServiceProvider.GetKeyedService(parameterInfo.ParameterType, key);
}
throw new InvalidOperationException(SR.KeyedServicesNotSupported);
}
// Try non keyed service
return serviceProvider.GetService(parameterInfo.ParameterType);
}

private static bool IsService(IServiceProviderIsService serviceProviderIsService, ParameterInfo parameterInfo)
{
// Handle keyed service
if (TryGetServiceKey(parameterInfo, out object? key))
{
if (serviceProviderIsService is IServiceProviderIsKeyedService serviceProviderIsKeyedService)
{
return serviceProviderIsKeyedService.IsKeyedService(parameterInfo.ParameterType, key);
}
throw new InvalidOperationException(SR.KeyedServicesNotSupported);
}
// Try non keyed service
return serviceProviderIsService.IsService(parameterInfo.ParameterType);
}

private static bool TryGetServiceKey(ParameterInfo parameterInfo, out object? key)
{
if (parameterInfo.CustomAttributes != null)
{
foreach (var attribute in parameterInfo.GetCustomAttributes(true))
{
if (attribute is FromKeyedServicesAttribute keyed)
{
key = keyed.Key;
return true;
}
}
}
key = null;
return false;
}

private readonly struct ConstructorMatcher
{
private readonly ConstructorInfo _constructor;
Expand Down Expand Up @@ -517,7 +564,7 @@ public int Match(object[] givenParameters, IServiceProviderIsService serviceProv
for (int i = 0; i < _parameters.Length; i++)
{
if (_parameterValues[i] == null &&
!serviceProviderIsService.IsService(_parameters[i].ParameterType))
!IsService(serviceProviderIsService, _parameters[i]))
{
if (ParameterDefaultValue.TryGetDefaultValue(_parameters[i], out object? defaultValue))
{
Expand All @@ -539,7 +586,7 @@ public object CreateInstance(IServiceProvider provider)
{
if (_parameterValues[index] == null)
{
object? value = provider.GetService(_parameters[index].ParameterType);
object? value = GetService(provider, _parameters[index]);
if (value == null)
{
if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out object? defaultValue))
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
/// <summary>
/// Extension methods for adding and removing services to an <see cref="IServiceCollection" />.
/// </summary>
public static class ServiceCollectionDescriptorExtensions
public static partial class ServiceCollectionDescriptorExtensions
{
/// <summary>
/// Adds the specified <paramref name="descriptor"/> to the <paramref name="collection"/>.
Expand Down Expand Up @@ -66,7 +66,8 @@ public static void TryAdd(
int count = collection.Count;
for (int i = 0; i < count; i++)
{
if (collection[i].ServiceType == descriptor.ServiceType)
if (collection[i].ServiceType == descriptor.ServiceType
&& collection[i].ServiceKey == descriptor.ServiceKey)
{
// Already added
return;
Expand Down Expand Up @@ -411,7 +412,7 @@ public static void TryAddSingleton<TService>(this IServiceCollection collection,
ThrowHelper.ThrowIfNull(collection);
ThrowHelper.ThrowIfNull(instance);

var descriptor = ServiceDescriptor.Singleton(typeof(TService), instance);
var descriptor = ServiceDescriptor.Singleton(serviceType: typeof(TService), implementationInstance: instance);
TryAdd(collection, descriptor);
}

Expand Down Expand Up @@ -472,7 +473,8 @@ public static void TryAddEnumerable(
{
ServiceDescriptor service = services[i];
if (service.ServiceType == descriptor.ServiceType &&
service.GetImplementationType() == implementationType)
service.GetImplementationType() == implementationType &&
service.ServiceKey == descriptor.ServiceKey)
{
// Already added
return;
Expand Down Expand Up @@ -530,7 +532,7 @@ public static IServiceCollection Replace(
int count = collection.Count;
for (int i = 0; i < count; i++)
{
if (collection[i].ServiceType == descriptor.ServiceType)
if (collection[i].ServiceType == descriptor.ServiceType && collection[i].ServiceKey == descriptor.ServiceKey)
{
collection.RemoveAt(i);
break;
Expand Down Expand Up @@ -564,7 +566,7 @@ public static IServiceCollection RemoveAll(this IServiceCollection collection, T
for (int i = collection.Count - 1; i >= 0; i--)
{
ServiceDescriptor? descriptor = collection[i];
if (descriptor.ServiceType == serviceType)
if (descriptor.ServiceType == serviceType && descriptor.ServiceKey == null)
{
collection.RemoveAt(i);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Extensions.DependencyInjection
{
[AttributeUsage(AttributeTargets.Parameter)]
public class FromKeyedServicesAttribute : Attribute
{
public FromKeyedServicesAttribute(object key) => Key = key;

public object Key { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Extensions.DependencyInjection
{
public interface IKeyedServiceProvider : IServiceProvider
{
/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <param name="serviceKey">An object that specifies the key of service object to get.</param>
/// <returns> A service object of type serviceType. -or- null if there is no service object of type serviceType.</returns>
object? GetKeyedService(Type serviceType, object? serviceKey);

/// <summary>
/// Gets service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/> implementing
/// this interface.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <returns>A service object of type <paramref name="serviceType"/>.
/// Throws an exception if the <see cref="IServiceProvider"/> cannot create the object.</returns>
object GetRequiredKeyedService(Type serviceType, object? serviceKey);
}

public static class KeyedService
{
public static object AnyKey { get; } = new AnyKeyObj();

private sealed class AnyKeyObj
{
public override string? ToString() => "*";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceProviderIsKeyedService : IServiceProviderIsService
{
/// <summary>
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
/// </summary>
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
/// <returns>true if the specified service is a available, false if it is not.</returns>
bool IsKeyedService(Type serviceType, object? serviceKey);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,13 @@
<value>Multiple constructors accepting all given argument types have been found in type '{0}'. There should only be one applicable constructor.</value>
<comment>{0} = instance type</comment>
</data>
<data name="KeyedServicesNotSupported" xml:space="preserve">
<value>This service provider doesn't support keyed services.</value>
</data>
<data name="KeyedDescriptorMisuse" xml:space="preserve">
<value>This service descriptor is keyed. Your service provider may not support keyed services.</value>
</data>
<data name="NonKeyedDescriptorMisuse" xml:space="preserve">
<value>This service descriptor is not keyed.</value>
</data>
</root>
Loading

0 comments on commit 138cb59

Please sign in to comment.