Skip to content

Commit 138cb59

Browse files
Add Keyed Services Support to Dependency Injection (#87183)
1 parent 33e0669 commit 138cb59

35 files changed

+3353
-184
lines changed

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs

Lines changed: 105 additions & 0 deletions
Large diffs are not rendered by default.

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,53 @@ private static bool TryCreateParameterMap(ParameterInfo[] constructorParameters,
476476
return true;
477477
}
478478

479+
private static object? GetService(IServiceProvider serviceProvider, ParameterInfo parameterInfo)
480+
{
481+
// Handle keyed service
482+
if (TryGetServiceKey(parameterInfo, out object? key))
483+
{
484+
if (serviceProvider is IKeyedServiceProvider keyedServiceProvider)
485+
{
486+
return keyedServiceProvider.GetKeyedService(parameterInfo.ParameterType, key);
487+
}
488+
throw new InvalidOperationException(SR.KeyedServicesNotSupported);
489+
}
490+
// Try non keyed service
491+
return serviceProvider.GetService(parameterInfo.ParameterType);
492+
}
493+
494+
private static bool IsService(IServiceProviderIsService serviceProviderIsService, ParameterInfo parameterInfo)
495+
{
496+
// Handle keyed service
497+
if (TryGetServiceKey(parameterInfo, out object? key))
498+
{
499+
if (serviceProviderIsService is IServiceProviderIsKeyedService serviceProviderIsKeyedService)
500+
{
501+
return serviceProviderIsKeyedService.IsKeyedService(parameterInfo.ParameterType, key);
502+
}
503+
throw new InvalidOperationException(SR.KeyedServicesNotSupported);
504+
}
505+
// Try non keyed service
506+
return serviceProviderIsService.IsService(parameterInfo.ParameterType);
507+
}
508+
509+
private static bool TryGetServiceKey(ParameterInfo parameterInfo, out object? key)
510+
{
511+
if (parameterInfo.CustomAttributes != null)
512+
{
513+
foreach (var attribute in parameterInfo.GetCustomAttributes(true))
514+
{
515+
if (attribute is FromKeyedServicesAttribute keyed)
516+
{
517+
key = keyed.Key;
518+
return true;
519+
}
520+
}
521+
}
522+
key = null;
523+
return false;
524+
}
525+
479526
private readonly struct ConstructorMatcher
480527
{
481528
private readonly ConstructorInfo _constructor;
@@ -517,7 +564,7 @@ public int Match(object[] givenParameters, IServiceProviderIsService serviceProv
517564
for (int i = 0; i < _parameters.Length; i++)
518565
{
519566
if (_parameterValues[i] == null &&
520-
!serviceProviderIsService.IsService(_parameters[i].ParameterType))
567+
!IsService(serviceProviderIsService, _parameters[i]))
521568
{
522569
if (ParameterDefaultValue.TryGetDefaultValue(_parameters[i], out object? defaultValue))
523570
{
@@ -539,7 +586,7 @@ public object CreateInstance(IServiceProvider provider)
539586
{
540587
if (_parameterValues[index] == null)
541588
{
542-
object? value = provider.GetService(_parameters[index].ParameterType);
589+
object? value = GetService(provider, _parameters[index]);
543590
if (value == null)
544591
{
545592
if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out object? defaultValue))

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.Keyed.cs

Lines changed: 413 additions & 0 deletions
Large diffs are not rendered by default.

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
1010
/// <summary>
1111
/// Extension methods for adding and removing services to an <see cref="IServiceCollection" />.
1212
/// </summary>
13-
public static class ServiceCollectionDescriptorExtensions
13+
public static partial class ServiceCollectionDescriptorExtensions
1414
{
1515
/// <summary>
1616
/// Adds the specified <paramref name="descriptor"/> to the <paramref name="collection"/>.
@@ -66,7 +66,8 @@ public static void TryAdd(
6666
int count = collection.Count;
6767
for (int i = 0; i < count; i++)
6868
{
69-
if (collection[i].ServiceType == descriptor.ServiceType)
69+
if (collection[i].ServiceType == descriptor.ServiceType
70+
&& collection[i].ServiceKey == descriptor.ServiceKey)
7071
{
7172
// Already added
7273
return;
@@ -411,7 +412,7 @@ public static void TryAddSingleton<TService>(this IServiceCollection collection,
411412
ThrowHelper.ThrowIfNull(collection);
412413
ThrowHelper.ThrowIfNull(instance);
413414

414-
var descriptor = ServiceDescriptor.Singleton(typeof(TService), instance);
415+
var descriptor = ServiceDescriptor.Singleton(serviceType: typeof(TService), implementationInstance: instance);
415416
TryAdd(collection, descriptor);
416417
}
417418

@@ -472,7 +473,8 @@ public static void TryAddEnumerable(
472473
{
473474
ServiceDescriptor service = services[i];
474475
if (service.ServiceType == descriptor.ServiceType &&
475-
service.GetImplementationType() == implementationType)
476+
service.GetImplementationType() == implementationType &&
477+
service.ServiceKey == descriptor.ServiceKey)
476478
{
477479
// Already added
478480
return;
@@ -530,7 +532,7 @@ public static IServiceCollection Replace(
530532
int count = collection.Count;
531533
for (int i = 0; i < count; i++)
532534
{
533-
if (collection[i].ServiceType == descriptor.ServiceType)
535+
if (collection[i].ServiceType == descriptor.ServiceType && collection[i].ServiceKey == descriptor.ServiceKey)
534536
{
535537
collection.RemoveAt(i);
536538
break;
@@ -564,7 +566,7 @@ public static IServiceCollection RemoveAll(this IServiceCollection collection, T
564566
for (int i = collection.Count - 1; i >= 0; i--)
565567
{
566568
ServiceDescriptor? descriptor = collection[i];
567-
if (descriptor.ServiceType == serviceType)
569+
if (descriptor.ServiceType == serviceType && descriptor.ServiceKey == null)
568570
{
569571
collection.RemoveAt(i);
570572
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.DependencyInjection
7+
{
8+
[AttributeUsage(AttributeTargets.Parameter)]
9+
public class FromKeyedServicesAttribute : Attribute
10+
{
11+
public FromKeyedServicesAttribute(object key) => Key = key;
12+
13+
public object Key { get; }
14+
}
15+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.DependencyInjection
7+
{
8+
public interface IKeyedServiceProvider : IServiceProvider
9+
{
10+
/// <summary>
11+
/// Gets the service object of the specified type.
12+
/// </summary>
13+
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
14+
/// <param name="serviceKey">An object that specifies the key of service object to get.</param>
15+
/// <returns> A service object of type serviceType. -or- null if there is no service object of type serviceType.</returns>
16+
object? GetKeyedService(Type serviceType, object? serviceKey);
17+
18+
/// <summary>
19+
/// Gets service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/> implementing
20+
/// this interface.
21+
/// </summary>
22+
/// <param name="serviceType">An object that specifies the type of service object to get.</param>
23+
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
24+
/// <returns>A service object of type <paramref name="serviceType"/>.
25+
/// Throws an exception if the <see cref="IServiceProvider"/> cannot create the object.</returns>
26+
object GetRequiredKeyedService(Type serviceType, object? serviceKey);
27+
}
28+
29+
public static class KeyedService
30+
{
31+
public static object AnyKey { get; } = new AnyKeyObj();
32+
33+
private sealed class AnyKeyObj
34+
{
35+
public override string? ToString() => "*";
36+
}
37+
}
38+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.DependencyInjection
7+
{
8+
public interface IServiceProviderIsKeyedService : IServiceProviderIsService
9+
{
10+
/// <summary>
11+
/// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
12+
/// </summary>
13+
/// <param name="serviceType">An object that specifies the type of service object to test.</param>
14+
/// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
15+
/// <returns>true if the specified service is a available, false if it is not.</returns>
16+
bool IsKeyedService(Type serviceType, object? serviceKey);
17+
}
18+
}

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,13 @@
167167
<value>Multiple constructors accepting all given argument types have been found in type '{0}'. There should only be one applicable constructor.</value>
168168
<comment>{0} = instance type</comment>
169169
</data>
170+
<data name="KeyedServicesNotSupported" xml:space="preserve">
171+
<value>This service provider doesn't support keyed services.</value>
172+
</data>
173+
<data name="KeyedDescriptorMisuse" xml:space="preserve">
174+
<value>This service descriptor is keyed. Your service provider may not support keyed services.</value>
175+
</data>
176+
<data name="NonKeyedDescriptorMisuse" xml:space="preserve">
177+
<value>This service descriptor is not keyed.</value>
178+
</data>
170179
</root>

0 commit comments

Comments
 (0)