Skip to content
Merged
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 @@ -32,8 +32,10 @@ public void Dispose() { }
[System.AttributeUsageAttribute(System.AttributeTargets.Parameter)]
public partial class FromKeyedServicesAttribute : System.Attribute
{
public FromKeyedServicesAttribute(object key) { }
public object Key { get { throw null; } }
public FromKeyedServicesAttribute() { }
public FromKeyedServicesAttribute(object? key) { }
public object? Key { get { throw null; } }
public Microsoft.Extensions.DependencyInjection.ServiceKeyLookupMode LookupMode { get { throw null; } }
}
public partial interface IServiceCollection : System.Collections.Generic.ICollection<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IEnumerable<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IList<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.IEnumerable
{
Expand Down Expand Up @@ -206,6 +208,12 @@ public partial class ServiceKeyAttribute : System.Attribute
{
public ServiceKeyAttribute() { }
}
public enum ServiceKeyLookupMode
{
InheritKey = 0,
NullKey = 1,
ExplicitKey = 2,
}
public enum ServiceLifetime
{
Singleton = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,42 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Indicates that the parameter should be bound using the keyed service registered with the specified key.
/// </summary>
/// <seealso cref="ServiceKeyAttribute"/>
/// <seealso cref="ServiceKeyLookupMode"/>
[AttributeUsage(AttributeTargets.Parameter)]
public class FromKeyedServicesAttribute : Attribute
{
/// <summary>
/// Creates a new <see cref="FromKeyedServicesAttribute"/> instance.
/// </summary>
/// <param name="key">The key of the keyed service to bind to.</param>
public FromKeyedServicesAttribute(object key) => Key = key;
public FromKeyedServicesAttribute(object? key)
{
Key = key;
LookupMode = key == null ? ServiceKeyLookupMode.NullKey : ServiceKeyLookupMode.ExplicitKey;
}

/// <summary>
/// Creates a new <see cref="FromKeyedServicesAttribute"/> instance with <see cref="LookupMode"/> set to <see cref="ServiceKeyLookupMode.InheritKey"/>.
/// </summary>
public FromKeyedServicesAttribute()
{
Key = null;
LookupMode = ServiceKeyLookupMode.InheritKey;
}

/// <summary>
/// The key of the keyed service to bind to.
/// </summary>
public object Key { get; }
/// <remarks>A <see langword="null"/> value with indicates there is not a key and just the parameter type is used to resolve the service.
/// This is useful for DI implementations that require an explict way to declare that the parameter should be resolved for unkeyed services.
/// A <see langword="null"/> value is also used along with <see cref="LookupMode"/> set to <see cref="ServiceKeyLookupMode.InheritKey"/> to indicate that the key should be inherited from the parent scope.
/// </remarks>
public object? Key { get; }

/// <summary>
/// Gets the mode used to look up the service key.
/// </summary>
public ServiceKeyLookupMode LookupMode { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// Specifies the parameter to inject the key that was used for registration or resolution.
/// </summary>
/// <seealso cref="FromKeyedServicesAttribute"/>
[AttributeUsage(AttributeTargets.Parameter)]
public class ServiceKeyAttribute : Attribute
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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
{
/// <summary>
/// Specifies how to look up the service key for a parameter.
/// </summary>
public enum ServiceKeyLookupMode
{
/// <summary>
/// The key is inherited from the parent service.
/// </summary>
InheritKey,

/// <summary>
/// A <see langword="null" /> key indicates that the parameter should be resolved from unkeyed services.
/// This is useful for DI implementations that require an explicit way to declare that the parameter should be resolved from unkeyed services.
/// </summary>
NullKey,

/// <summary>
/// The key is explicitly specified.
/// </summary>
ExplicitKey
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ public void ResolveKeyedServiceSingletonFactoryWithAnyKey()

Assert.Null(provider.GetService<IService>());

for (int i=0; i<3; i++)
for (int i = 0; i < 3; i++)
{
var key = "service" + i;
var s1 = provider.GetKeyedService<IService>(key);
Expand Down Expand Up @@ -823,23 +823,23 @@ public ServiceProviderAccessor(IServiceProvider serviceProvider)
public IServiceProvider ServiceProvider { get; }
}

[Fact]
public void SimpleServiceKeyedResolution()
{
// Arrange
var services = new ServiceCollection();
services.AddKeyedTransient<ISimpleService, SimpleService>("simple");
services.AddKeyedTransient<ISimpleService, AnotherSimpleService>("another");
services.AddTransient<SimpleParentWithDynamicKeyedService>();
var provider = CreateServiceProvider(services);
var sut = provider.GetService<SimpleParentWithDynamicKeyedService>();

// Act
var result = sut!.GetService("simple");

// Assert
Assert.True(result.GetType() == typeof(SimpleService));
}
[Fact]
public void SimpleServiceKeyedResolution()
{
// Arrange
var services = new ServiceCollection();
services.AddKeyedTransient<ISimpleService, SimpleService>("simple");
services.AddKeyedTransient<ISimpleService, AnotherSimpleService>("another");
services.AddTransient<SimpleParentWithDynamicKeyedService>();
var provider = CreateServiceProvider(services);
var sut = provider.GetService<SimpleParentWithDynamicKeyedService>();

// Act
var result = sut!.GetService("simple");

// Assert
Assert.True(result.GetType() == typeof(SimpleService));
}

public class SimpleParentWithDynamicKeyedService
{
Expand All @@ -863,5 +863,89 @@ public class NonKeyedServiceProvider : IServiceProvider
{
public object GetService(Type serviceType) => throw new NotImplementedException();
}

#if NET10_0_OR_GREATER
[Fact]
public void ResolveKeyedServiceWithFromServiceKeyAttribute()
{
ServiceCollection services = new();
services.AddKeyedSingleton<ServiceUsingFromServiceKeyAttribute>("key");
services.AddKeyedSingleton<ServiceCreatedWithServiceKeyAttribute>("key");

IServiceProvider provider = CreateServiceProvider(services);

ServiceUsingFromServiceKeyAttribute service = provider.GetRequiredKeyedService<ServiceUsingFromServiceKeyAttribute>("key");
Assert.Equal("key", service.OtherService.MyKey);
}

[Fact]
public void ResolveKeyedServiceWithFromServiceKeyAttribute_NotFound()
{
ServiceCollection services = new();
services.AddKeyedSingleton<ServiceUsingFromServiceKeyAttribute>("key1");
services.AddKeyedSingleton<ServiceCreatedWithServiceKeyAttribute>("key2");

IServiceProvider provider = CreateServiceProvider(services);

Assert.Throws<InvalidOperationException>(() => provider.GetKeyedService<ServiceUsingFromServiceKeyAttribute>("key1"));
}

[Fact]
public void ResolveKeyedServiceWithFromServiceKeyAttribute_NotFound_WithUnkeyed()
{
ServiceCollection services = new();
services.AddKeyedSingleton<ServiceUsingFromServiceKeyAttribute>("key1");
services.AddSingleton<ServiceCreatedWithServiceKeyAttribute>();

IServiceProvider provider = CreateServiceProvider(services);

Assert.Throws<InvalidOperationException>(() => provider.GetKeyedService<ServiceUsingFromServiceKeyAttribute>("key1"));
}

private class ServiceUsingFromServiceKeyAttribute : IService
{
public ServiceCreatedWithServiceKeyAttribute OtherService { get; }

public ServiceUsingFromServiceKeyAttribute([FromKeyedServices] ServiceCreatedWithServiceKeyAttribute otherService)
{
OtherService = otherService;
}
}

private class ServiceCreatedWithServiceKeyAttribute : IService
{
public string MyKey { get; }

public ServiceCreatedWithServiceKeyAttribute([ServiceKey] string myKey)
{
MyKey = myKey;
}
}

[Fact]
public void ResolveUnkeyedServiceWithFromServiceKeyAttributeWithNullKey()
{
ServiceCollection services = new();
services.AddSingleton<UnkeyedServiceWithFromServiceKeyAttributeWithNullKey>();
services.AddSingleton<Service>();

IServiceProvider provider = CreateServiceProvider(services);

UnkeyedServiceWithFromServiceKeyAttributeWithNullKey service =
provider.GetRequiredService<UnkeyedServiceWithFromServiceKeyAttributeWithNullKey>();

Assert.NotNull(service.OtherService);
}

private class UnkeyedServiceWithFromServiceKeyAttributeWithNullKey : IService
{
public Service OtherService { get; }

public UnkeyedServiceWithFromServiceKeyAttributeWithNullKey([FromKeyedServices(null)] Service otherService)
{
OtherService = otherService;
}
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -629,12 +629,22 @@ private ConstructorCallSite CreateConstructorCallSite(
break;
}

if (attribute is FromKeyedServicesAttribute keyed)
if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute)
{
var parameterSvcId = new ServiceIdentifier(keyed.Key, parameterType);
callSite = GetCallSite(parameterSvcId, callSiteChain);
isKeyedParameter = true;
break;
object? serviceKey = fromKeyedServicesAttribute.LookupMode switch
{
ServiceKeyLookupMode.InheritKey => serviceIdentifier.ServiceKey,
ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key,
ServiceKeyLookupMode.NullKey => null,
_ => null
};

if (serviceKey is not null)
{
callSite = GetCallSite(new ServiceIdentifier(serviceKey, parameterType), callSiteChain);
isKeyedParameter = true;
break;
}
}
}

Expand Down
Loading