| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // 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 ServiceKeyAttribute : Attribute | ||
steveharter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // 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; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection | ||
| { | ||
| /// <summary> | ||
| /// Extension methods for getting services from an <see cref="IServiceProvider" />. | ||
| /// </summary> | ||
| public static class ServiceProviderKeyedServiceExtensions | ||
| { | ||
| /// <summary> | ||
| /// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of service object to get.</typeparam> | ||
| /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param> | ||
| /// <param name="serviceKey">An object that specifies the key of service object to get.</param> | ||
| /// <returns>A service object of type <typeparamref name="T"/> or null if there is no such service.</returns> | ||
| public static T? GetKeyedService<T>(this IServiceProvider provider, object? serviceKey) | ||
| { | ||
| ThrowHelper.ThrowIfNull(provider); | ||
|
|
||
| if (provider is IKeyedServiceProvider keyedServiceProvider) | ||
| { | ||
| return (T?)keyedServiceProvider.GetKeyedService(typeof(T), serviceKey); | ||
| } | ||
|
|
||
| throw new InvalidOperationException(SR.KeyedServicesNotSupported); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>. | ||
| /// </summary> | ||
| /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param> | ||
| /// <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 <paramref name="serviceType"/>.</returns> | ||
| /// <exception cref="System.InvalidOperationException">There is no service of type <paramref name="serviceType"/>.</exception> | ||
| public static object GetRequiredKeyedService(this IServiceProvider provider, Type serviceType, object? serviceKey) | ||
| { | ||
| ThrowHelper.ThrowIfNull(provider); | ||
| ThrowHelper.ThrowIfNull(serviceType); | ||
|
|
||
| if (provider is IKeyedServiceProvider requiredServiceSupportingProvider) | ||
| { | ||
| return requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey); | ||
| } | ||
|
|
||
| throw new InvalidOperationException(SR.KeyedServicesNotSupported); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of service object to get.</typeparam> | ||
| /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param> | ||
| /// <param name="serviceKey">An object that specifies the key of service object to get.</param> | ||
| /// <returns>A service object of type <typeparamref name="T"/>.</returns> | ||
| /// <exception cref="System.InvalidOperationException">There is no service of type <typeparamref name="T"/>.</exception> | ||
| public static T GetRequiredKeyedService<T>(this IServiceProvider provider, object? serviceKey) where T : notnull | ||
| { | ||
| ThrowHelper.ThrowIfNull(provider); | ||
|
|
||
| return (T)provider.GetRequiredKeyedService(typeof(T), serviceKey); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get an enumeration of services of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>. | ||
| /// </summary> | ||
| /// <typeparam name="T">The type of service object to get.</typeparam> | ||
| /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param> | ||
| /// <param name="serviceKey">An object that specifies the key of service object to get.</param> | ||
| /// <returns>An enumeration of services of type <typeparamref name="T"/>.</returns> | ||
| public static IEnumerable<T> GetKeyedServices<T>(this IServiceProvider provider, object? serviceKey) | ||
| { | ||
| ThrowHelper.ThrowIfNull(provider); | ||
|
|
||
| return provider.GetRequiredKeyedService<IEnumerable<T>>(serviceKey); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get an enumeration of services of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>. | ||
| /// </summary> | ||
| /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param> | ||
| /// <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>An enumeration of services of type <paramref name="serviceType"/>.</returns> | ||
| [RequiresDynamicCode("The native code for an IEnumerable<serviceType> might not be available at runtime.")] | ||
| public static IEnumerable<object?> GetKeyedServices(this IServiceProvider provider, Type serviceType, object? serviceKey) | ||
| { | ||
| ThrowHelper.ThrowIfNull(provider); | ||
| ThrowHelper.ThrowIfNull(serviceType); | ||
|
|
||
| Type? genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType); | ||
| return (IEnumerable<object>)provider.GetRequiredKeyedService(genericEnumerable, serviceKey); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,362 @@ | ||
| // 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 Xunit; | ||
| using Microsoft.Extensions.DependencyInjection.Specification.Fakes; | ||
| using System.Linq; | ||
| using System.Security.Cryptography; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification | ||
| { | ||
| public abstract partial class KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| protected abstract IServiceProvider CreateServiceProvider(IServiceCollection collection); | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedService() | ||
| { | ||
| var service1 = new Service(); | ||
steveharter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var service2 = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>("service1", service1); | ||
| serviceCollection.AddKeyedSingleton<IService>("service2", service2); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| Assert.Same(service1, provider.GetKeyedService<IService>("service1")); | ||
| Assert.Same(service2, provider.GetKeyedService<IService>("service2")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveNullKeyedService() | ||
| { | ||
| var service1 = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>(null, service1); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| var nonKeyed = provider.GetService<IService>(); | ||
| var nullKey = provider.GetKeyedService<IService>(null); | ||
|
|
||
| Assert.Same(service1, nonKeyed); | ||
| Assert.Same(service1, nullKey); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveNonKeyedService() | ||
| { | ||
| var service1 = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddSingleton<IService>(service1); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| var nonKeyed = provider.GetService<IService>(); | ||
| var nullKey = provider.GetKeyedService<IService>(null); | ||
|
|
||
| Assert.Same(service1, nonKeyed); | ||
| Assert.Same(service1, nullKey); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedOpenGenericService() | ||
| { | ||
| var collection = new ServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeOpenGenericService<>), "my-service", typeof(FakeOpenGenericService<>)); | ||
| collection.AddSingleton<IFakeSingletonService, FakeService>(); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var genericService = provider.GetKeyedService<IFakeOpenGenericService<IFakeSingletonService>>("my-service"); | ||
| var singletonService = provider.GetService<IFakeSingletonService>(); | ||
|
|
||
| // Assert | ||
| Assert.Same(singletonService, genericService.Value); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServices() | ||
| { | ||
| var service1 = new Service(); | ||
| var service2 = new Service(); | ||
| var service3 = new Service(); | ||
| var service4 = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>("first-service", service1); | ||
| serviceCollection.AddKeyedSingleton<IService>("service", service2); | ||
| serviceCollection.AddKeyedSingleton<IService>("service", service3); | ||
| serviceCollection.AddKeyedSingleton<IService>("service", service4); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| var firstSvc = provider.GetKeyedServices<IService>("first-service").ToList(); | ||
| Assert.Single(firstSvc); | ||
| Assert.Same(service1, firstSvc[0]); | ||
|
|
||
| var services = provider.GetKeyedServices<IService>("service").ToList(); | ||
| Assert.Equal(new[] { service2, service3, service4 }, services); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedGenericServices() | ||
| { | ||
| var service1 = new FakeService(); | ||
| var service2 = new FakeService(); | ||
| var service3 = new FakeService(); | ||
| var service4 = new FakeService(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("first-service", service1); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service2); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service3); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service4); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| var firstSvc = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("first-service").ToList(); | ||
| Assert.Single(firstSvc); | ||
| Assert.Same(service1, firstSvc[0]); | ||
|
|
||
| var services = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("service").ToList(); | ||
| Assert.Equal(new[] { service2, service3, service4 }, services); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonInstance() | ||
| { | ||
| var service = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>("service1", service); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| Assert.Same(service, provider.GetKeyedService<IService>("service1")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonInstanceWithKeyInjection() | ||
| { | ||
| var serviceKey = "this-is-my-service"; | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>(serviceKey); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| var svc = provider.GetKeyedService<IService>(serviceKey); | ||
| Assert.NotNull(svc); | ||
| Assert.Equal(serviceKey, svc.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonInstanceWithAnyKey() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>(KeyedService.AnyKey); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
|
|
||
| var serviceKey1 = "some-key"; | ||
| var svc1 = provider.GetKeyedService<IService>(serviceKey1); | ||
| Assert.NotNull(svc1); | ||
| Assert.Equal(serviceKey1, svc1.ToString()); | ||
|
|
||
| var serviceKey2 = "some-other-key"; | ||
| var svc2 = provider.GetKeyedService<IService>(serviceKey2); | ||
| Assert.NotNull(svc2); | ||
| Assert.Equal(serviceKey2, svc2.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServicesSingletonInstanceWithAnyKey() | ||
| { | ||
| var service1 = new FakeService(); | ||
| var service2 = new FakeService(); | ||
|
|
||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>(KeyedService.AnyKey, service1); | ||
| serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("some-key", service2); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| var services = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("some-key").ToList(); | ||
| Assert.Equal(new[] { service1, service2 }, services); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonInstanceWithKeyedParameter() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>("service1"); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>("service2"); | ||
| serviceCollection.AddSingleton<OtherService>(); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| var svc = provider.GetService<OtherService>(); | ||
| Assert.NotNull(svc); | ||
| Assert.Equal("service1", svc.Service1.ToString()); | ||
| Assert.Equal("service2", svc.Service2.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void CreateServiceWithKeyedParameter() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddSingleton<IService, Service>(); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>("service1"); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>("service2"); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<OtherService>()); | ||
| var svc = ActivatorUtilities.CreateInstance<OtherService>(provider); | ||
| Assert.NotNull(svc); | ||
| Assert.Equal("service1", svc.Service1.ToString()); | ||
| Assert.Equal("service2", svc.Service2.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonFactory() | ||
| { | ||
| var service = new Service(); | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>("service1", (sp, key) => service); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| Assert.Same(service, provider.GetKeyedService<IService>("service1")); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonFactoryWithAnyKey() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService>(KeyedService.AnyKey, (sp, key) => new Service((string)key)); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
|
|
||
| for (int i=0; i<3; i++) | ||
| { | ||
| var key = "service" + i; | ||
| var s1 = provider.GetKeyedService<IService>(key); | ||
| var s2 = provider.GetKeyedService<IService>(key); | ||
| Assert.Same(s1, s2); | ||
| Assert.Equal(key, s1.ToString()); | ||
| } | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonFactoryWithAnyKeyIgnoreWrongType() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedTransient<IService, ServiceWithIntKey>(KeyedService.AnyKey); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| Assert.NotNull(provider.GetKeyedService<IService>(87)); | ||
| Assert.ThrowsAny<InvalidOperationException>(() => provider.GetKeyedService<IService>(new object())); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceSingletonType() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedSingleton<IService, Service>("service1"); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| Assert.Equal(typeof(Service), provider.GetKeyedService<IService>("service1")!.GetType()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceTransientFactory() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedTransient<IService>("service1", (sp, key) => new Service(key as string)); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| var first = provider.GetKeyedService<IService>("service1"); | ||
| var second = provider.GetKeyedService<IService>("service1"); | ||
| Assert.NotSame(first, second); | ||
| Assert.Equal("service1", first.ToString()); | ||
| Assert.Equal("service1", second.ToString()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceTransientType() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedTransient<IService, Service>("service1"); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| var first = provider.GetKeyedService<IService>("service1"); | ||
| var second = provider.GetKeyedService<IService>("service1"); | ||
| Assert.NotSame(first, second); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveKeyedServiceTransientTypeWithAnyKey() | ||
| { | ||
| var serviceCollection = new ServiceCollection(); | ||
| serviceCollection.AddKeyedTransient<IService, Service>(KeyedService.AnyKey); | ||
|
|
||
| var provider = CreateServiceProvider(serviceCollection); | ||
|
|
||
| Assert.Null(provider.GetService<IService>()); | ||
| var first = provider.GetKeyedService<IService>("service1"); | ||
| var second = provider.GetKeyedService<IService>("service1"); | ||
| Assert.NotSame(first, second); | ||
| } | ||
|
|
||
| internal interface IService { } | ||
|
|
||
| internal class Service : IService | ||
| { | ||
| private readonly string _id; | ||
|
|
||
| public Service() => _id = Guid.NewGuid().ToString(); | ||
|
|
||
| public Service([ServiceKey] string id) => _id = id; | ||
|
|
||
| public override string? ToString() => _id; | ||
| } | ||
|
|
||
| internal class OtherService | ||
| { | ||
| public OtherService( | ||
| [FromKeyedServices("service1")] IService service1, | ||
| [FromKeyedServices("service2")] IService service2) | ||
| { | ||
| Service1 = service1; | ||
| Service2 = service2; | ||
| } | ||
|
|
||
| public IService Service1 { get; } | ||
|
|
||
| public IService Service2 { get; } | ||
| } | ||
|
|
||
| internal class ServiceWithIntKey : IService | ||
| { | ||
| private readonly int _id; | ||
|
|
||
| public ServiceWithIntKey([ServiceKey] int id) => _id = id; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // 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; | ||
| using System.Text; | ||
| using Microsoft.Extensions.DependencyInjection.Specification.Fakes; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Specification | ||
| { | ||
| public abstract partial class KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| public virtual bool SupportsIServiceProviderIsKeyedService => true; | ||
|
|
||
| [Fact] | ||
| public void ExplicitServiceRegistrationWithIsKeyedService() | ||
| { | ||
| if (!SupportsIServiceProviderIsKeyedService) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Arrange | ||
| var key = new object(); | ||
| var collection = new TestServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeService), key, typeof(FakeService)); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(serviceProviderIsService); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeService), key)); | ||
| Assert.False(serviceProviderIsService.IsKeyedService(typeof(FakeService), new object())); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void OpenGenericsWithIsKeyedService() | ||
| { | ||
| if (!SupportsIServiceProviderIsKeyedService) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Arrange | ||
| var key = new object(); | ||
| var collection = new TestServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeOpenGenericService<>), key, typeof(FakeOpenGenericService<>)); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(serviceProviderIsService); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<IFakeService>), key)); | ||
| Assert.False(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<>), new object())); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ClosedGenericsWithIsKeyedService() | ||
| { | ||
| if (!SupportsIServiceProviderIsKeyedService) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Arrange | ||
| var key = new object(); | ||
| var collection = new TestServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeOpenGenericService<IFakeService>), key, typeof(FakeOpenGenericService<IFakeService>)); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(serviceProviderIsService); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<IFakeService>), key)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void IEnumerableWithIsKeyedServiceAlwaysReturnsTrue() | ||
| { | ||
| if (!SupportsIServiceProviderIsKeyedService) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Arrange | ||
| var key = new object(); | ||
| var collection = new TestServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeService), key, typeof(FakeService)); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(serviceProviderIsService); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<IFakeService>), key)); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<FakeService>), key)); | ||
| Assert.False(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<>), new object())); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void NonKeyedServiceWithIsKeyedService() | ||
| { | ||
| if (!SupportsIServiceProviderIsKeyedService) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Arrange | ||
| var collection = new TestServiceCollection(); | ||
| collection.AddKeyedTransient(typeof(IFakeService), null, typeof(FakeService)); | ||
| var provider = CreateServiceProvider(collection); | ||
|
|
||
| // Act | ||
| var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>(); | ||
|
|
||
| // Assert | ||
| Assert.NotNull(serviceProviderIsService); | ||
| Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeService), null)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // 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.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.ServiceLookup | ||
| { | ||
| internal static class ServiceDescriptorExtensions | ||
| { | ||
| public static bool HasImplementationInstance(this ServiceDescriptor serviceDescriptor) => GetImplementationInstance(serviceDescriptor) != null; | ||
|
|
||
| public static bool HasImplementationFactory(this ServiceDescriptor serviceDescriptor) => GetImplementationFactory(serviceDescriptor) != null; | ||
|
|
||
| public static bool HasImplementationType(this ServiceDescriptor serviceDescriptor) => GetImplementationType(serviceDescriptor) != null; | ||
|
|
||
| public static object? GetImplementationInstance(this ServiceDescriptor serviceDescriptor) | ||
| { | ||
| return serviceDescriptor.IsKeyedService | ||
| ? serviceDescriptor.KeyedImplementationInstance | ||
| : serviceDescriptor.ImplementationInstance; | ||
| } | ||
|
|
||
| public static object? GetImplementationFactory(this ServiceDescriptor serviceDescriptor) | ||
| { | ||
| return serviceDescriptor.IsKeyedService | ||
| ? serviceDescriptor.KeyedImplementationFactory | ||
| : serviceDescriptor.ImplementationFactory; | ||
| } | ||
|
|
||
| [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] | ||
| public static Type? GetImplementationType(this ServiceDescriptor serviceDescriptor) | ||
| { | ||
| return serviceDescriptor.IsKeyedService | ||
| ? serviceDescriptor.KeyedImplementationType | ||
| : serviceDescriptor.ImplementationType; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| // 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.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.ServiceLookup | ||
| { | ||
| internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier> | ||
| { | ||
| public object? ServiceKey { get; } | ||
|
|
||
| public Type ServiceType { get; } | ||
|
|
||
| public ServiceIdentifier(Type serviceType) | ||
| { | ||
| ServiceType = serviceType; | ||
| } | ||
|
|
||
| public ServiceIdentifier(object? serviceKey, Type serviceType) | ||
| { | ||
| ServiceKey = serviceKey; | ||
| ServiceType = serviceType; | ||
| } | ||
|
|
||
| public static ServiceIdentifier FromDescriptor(ServiceDescriptor serviceDescriptor) | ||
| => new ServiceIdentifier(serviceDescriptor.ServiceKey, serviceDescriptor.ServiceType); | ||
|
|
||
| public static ServiceIdentifier FromServiceType(Type type) => new ServiceIdentifier(null, type); | ||
|
|
||
| public bool Equals(ServiceIdentifier other) | ||
| { | ||
| if (ServiceKey == null && other.ServiceKey == null) | ||
| { | ||
| return ServiceType.Equals(other.ServiceType); | ||
| } | ||
| else if (ServiceKey != null && other.ServiceKey != null) | ||
| { | ||
| return ServiceType.Equals(other.ServiceType) && ServiceKey.Equals(other.ServiceKey); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| public override bool Equals([NotNullWhen(true)] object? obj) | ||
| { | ||
| return obj is ServiceIdentifier && Equals((ServiceIdentifier)obj); | ||
| } | ||
|
|
||
| public override int GetHashCode() | ||
| { | ||
| if (ServiceKey == null) | ||
| { | ||
| return ServiceType.GetHashCode(); | ||
| } | ||
| unchecked | ||
| { | ||
| return ((ServiceType?.GetHashCode() ?? 23) * 397) ^ ServiceKey.GetHashCode(); | ||
| } | ||
| } | ||
|
|
||
| public bool IsConstructedGenericType => ServiceType.IsConstructedGenericType; | ||
|
|
||
| public ServiceIdentifier GetGenericTypeDefinition() => new ServiceIdentifier(ServiceKey, ServiceType.GetGenericTypeDefinition()); | ||
|
|
||
| public override string? ToString() | ||
| { | ||
| if (ServiceKey == null) | ||
| { | ||
| return ServiceType.ToString(); | ||
| } | ||
|
|
||
| return $"({ServiceKey}, {ServiceType})"; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // 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.Threading.Tasks; | ||
| using Microsoft.Extensions.DependencyInjection.Specification; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Extensions.DependencyInjection.Tests | ||
| { | ||
| public class KeyedServiceProviderDefaultContainerTests : KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.Default); | ||
| } | ||
|
|
||
| public class KeyedServiceProviderDynamicContainerTests : KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(); | ||
| } | ||
|
|
||
| public class KeyedServiceProviderExpressionContainerTests : KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.Expressions); | ||
| } | ||
|
|
||
| public class KeyedServiceProviderILEmitContainerTests : KeyedDependencyInjectionSpecificationTests | ||
| { | ||
| protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.ILEmit); | ||
| } | ||
| } |