diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs index a0dc73d58f820b..0c1a553008e9c3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs @@ -100,6 +100,64 @@ public void ResolveKeyedServices() Assert.Equal(new[] { service2, service3, service4 }, services); } + [Fact] + public void ResolveKeyedServicesAnyKey() + { + var service1 = new Service(); + var service2 = new Service(); + var service3 = new Service(); + var service4 = new Service(); + var service5 = new Service(); + var service6 = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedSingleton("first-service", service1); + serviceCollection.AddKeyedSingleton("service", service2); + serviceCollection.AddKeyedSingleton("service", service3); + serviceCollection.AddKeyedSingleton("service", service4); + serviceCollection.AddKeyedSingleton(null, service5); + serviceCollection.AddSingleton(service6); + + var provider = CreateServiceProvider(serviceCollection); + + // Return all services registered with a non null key + var allServices = provider.GetKeyedServices(KeyedService.AnyKey).ToList(); + Assert.Equal(4, allServices.Count); + Assert.Equal(new[] { service1, service2, service3, service4 }, allServices); + + // Check again (caching) + var allServices2 = provider.GetKeyedServices(KeyedService.AnyKey).ToList(); + Assert.Equal(allServices, allServices2); + } + + [Fact] + public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration() + { + var service1 = new Service(); + var service2 = new Service(); + var service3 = new Service(); + var service4 = new Service(); + var service5 = new Service(); + var service6 = new Service(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddKeyedTransient(KeyedService.AnyKey, (sp, key) => new Service()); + serviceCollection.AddKeyedSingleton("first-service", service1); + serviceCollection.AddKeyedSingleton("service", service2); + serviceCollection.AddKeyedSingleton("service", service3); + serviceCollection.AddKeyedSingleton("service", service4); + serviceCollection.AddKeyedSingleton(null, service5); + serviceCollection.AddSingleton(service6); + + var provider = CreateServiceProvider(serviceCollection); + + _ = provider.GetKeyedService("something-else"); + _ = provider.GetKeyedService("something-else-again"); + + // Return all services registered with a non null key, but not the one "created" with KeyedService.AnyKey + var allServices = provider.GetKeyedServices(KeyedService.AnyKey).ToList(); + Assert.Equal(5, allServices.Count); + Assert.Equal(new[] { service1, service2, service3, service4 }, allServices.Skip(1)); + } + [Fact] public void ResolveKeyedGenericServices() { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj index ce7ac08140311d..cc0bebcb2c129b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj @@ -7,6 +7,8 @@ $(NoWarn);CP0001 true + true + 1 Default implementation of dependency injection for Microsoft.Extensions.DependencyInjection. diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 29f229c17fff8d..f9b902dde19f3e 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -283,7 +283,10 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica ServiceCallSite[] callSites; // If item type is not generic we can safely use descriptor cache + // Special case for KeyedService.AnyKey, we don't want to check the cache because a KeyedService.AnyKey registration + // will "hide" all the other service registration if (!itemType.IsConstructedGenericType && + !KeyedService.AnyKey.Equals(cacheKey.ServiceKey) && _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem descriptors)) { callSites = new ServiceCallSite[descriptors.Count]; @@ -694,7 +697,11 @@ private static bool KeysMatch(object? key1, object? key2) return true; if (key1 != null && key2 != null) - return key1.Equals(KeyedService.AnyKey) || key1.Equals(key2); + { + return key1.Equals(key2) + || key1.Equals(KeyedService.AnyKey) + || key2.Equals(KeyedService.AnyKey); + } return false; }