@@ -13,6 +13,84 @@ public abstract partial class KeyedDependencyInjectionSpecificationTests
1313 {
1414 protected abstract IServiceProvider CreateServiceProvider ( IServiceCollection collection ) ;
1515
16+ [ Fact ]
17+ public void CombinationalRegistration ( )
18+ {
19+ Service service1 = new ( ) ;
20+ Service service2 = new ( ) ;
21+ Service keyedService1 = new ( ) ;
22+ Service keyedService2 = new ( ) ;
23+ Service anykeyService1 = new ( ) ;
24+ Service anykeyService2 = new ( ) ;
25+ Service nullkeyService1 = new ( ) ;
26+ Service nullkeyService2 = new ( ) ;
27+
28+ ServiceCollection serviceCollection = new ( ) ;
29+ serviceCollection . AddSingleton < IService > ( service1 ) ;
30+ serviceCollection . AddSingleton < IService > ( service2 ) ;
31+ serviceCollection . AddKeyedSingleton < IService > ( null , nullkeyService1 ) ;
32+ serviceCollection . AddKeyedSingleton < IService > ( null , nullkeyService2 ) ;
33+ serviceCollection . AddKeyedSingleton < IService > ( KeyedService . AnyKey , anykeyService1 ) ;
34+ serviceCollection . AddKeyedSingleton < IService > ( KeyedService . AnyKey , anykeyService2 ) ;
35+ serviceCollection . AddKeyedSingleton < IService > ( "keyedService" , keyedService1 ) ;
36+ serviceCollection . AddKeyedSingleton < IService > ( "keyedService" , keyedService2 ) ;
37+
38+ IServiceProvider provider = CreateServiceProvider ( serviceCollection ) ;
39+
40+ /*
41+ * Table for what results are included:
42+ *
43+ * Query | Keyed? | Unkeyed? | AnyKey? | null key?
44+ * -------------------------------------------------------------------
45+ * GetServices(Type) | no | yes | no | yes
46+ * GetService(Type) | no | yes | no | yes
47+ *
48+ * GetKeyedServices(null) | no | yes | no | yes
49+ * GetKeyedService(null) | no | yes | no | yes
50+ *
51+ * GetKeyedServices(AnyKey) | yes | no | no | no
52+ * GetKeyedService(AnyKey) | throw | throw | throw | throw
53+ *
54+ * GetKeyedServices(key) | yes | no | no | no
55+ * GetKeyedService(key) | yes | no | yes | no
56+ *
57+ * Summary:
58+ * - A null key is the same as unkeyed. This allows the KeyServices APIs to support both keyed and unkeyed.
59+ * - AnyKey is a special case of Keyed.
60+ * - AnyKey registrations are not returned with GetKeyedServices(AnyKey) and GetKeyedService(AnyKey) always throws.
61+ * - For IEnumerable, the ordering of the results are in registration order.
62+ * - For a singleton resolve, the last match wins.
63+ */
64+
65+ // Unkeyed (which is really keyed by Type).
66+ Assert . Equal (
67+ new [ ] { service1 , service2 , nullkeyService1 , nullkeyService2 } ,
68+ provider . GetServices < IService > ( ) ) ;
69+
70+ Assert . Equal ( nullkeyService2 , provider . GetService < IService > ( ) ) ;
71+
72+ // Null key.
73+ Assert . Equal (
74+ new [ ] { service1 , service2 , nullkeyService1 , nullkeyService2 } ,
75+ provider . GetKeyedServices < IService > ( null ) ) ;
76+
77+ Assert . Equal ( nullkeyService2 , provider . GetKeyedService < IService > ( null ) ) ;
78+
79+ // AnyKey.
80+ Assert . Equal (
81+ new [ ] { keyedService1 , keyedService2 } ,
82+ provider . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
83+
84+ Assert . Throws < InvalidOperationException > ( ( ) => provider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
85+
86+ // Keyed.
87+ Assert . Equal (
88+ new [ ] { keyedService1 , keyedService2 } ,
89+ provider . GetKeyedServices < IService > ( "keyedService" ) ) ;
90+
91+ Assert . Equal ( keyedService2 , provider . GetKeyedService < IService > ( "keyedService" ) ) ;
92+ }
93+
1694 [ Fact ]
1795 public void ResolveKeyedService ( )
1896 {
@@ -158,10 +236,75 @@ public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration()
158236 _ = provider . GetKeyedService < IService > ( "something-else" ) ;
159237 _ = provider . GetKeyedService < IService > ( "something-else-again" ) ;
160238
161- // Return all services registered with a non null key, but not the one "created" with KeyedService.AnyKey
239+ // Return all services registered with a non null key, but not the one "created" with KeyedService.AnyKey,
240+ // nor the KeyedService.AnyKey registration
162241 var allServices = provider . GetKeyedServices < IService > ( KeyedService . AnyKey ) . ToList ( ) ;
163- Assert . Equal ( 5 , allServices . Count ) ;
164- Assert . Equal ( new [ ] { service1 , service2 , service3 , service4 } , allServices . Skip ( 1 ) ) ;
242+ Assert . Equal ( 4 , allServices . Count ) ;
243+ Assert . Equal ( new [ ] { service1 , service2 , service3 , service4 } , allServices ) ;
244+
245+ var someKeyedServices = provider . GetKeyedServices < IService > ( "service" ) . ToList ( ) ;
246+ Assert . Equal ( new [ ] { service2 , service3 , service4 } , someKeyedServices ) ;
247+
248+ var unkeyedServices = provider . GetServices < IService > ( ) . ToList ( ) ;
249+ Assert . Equal ( new [ ] { service5 , service6 } , unkeyedServices ) ;
250+ }
251+
252+ [ Fact ]
253+ public void ResolveKeyedServicesAnyKeyConsistency ( )
254+ {
255+ var serviceCollection = new ServiceCollection ( ) ;
256+ var service = new Service ( "first-service" ) ;
257+ serviceCollection . AddKeyedSingleton < IService > ( "first-service" , service ) ;
258+
259+ var provider1 = CreateServiceProvider ( serviceCollection ) ;
260+ Assert . Throws < InvalidOperationException > ( ( ) => provider1 . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
261+ // We don't return KeyedService.AnyKey registration when listing services
262+ Assert . Equal ( new [ ] { service } , provider1 . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
263+
264+ var provider2 = CreateServiceProvider ( serviceCollection ) ;
265+ Assert . Equal ( new [ ] { service } , provider2 . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
266+ Assert . Throws < InvalidOperationException > ( ( ) => provider2 . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
267+ }
268+
269+ [ Fact ]
270+ public void ResolveKeyedServicesAnyKeyConsistencyWithAnyKeyRegistration ( )
271+ {
272+ var serviceCollection = new ServiceCollection ( ) ;
273+ var service = new Service ( "first-service" ) ;
274+ var any = new Service ( "any" ) ;
275+ serviceCollection . AddKeyedSingleton < IService > ( "first-service" , service ) ;
276+ serviceCollection . AddKeyedSingleton < IService > ( KeyedService . AnyKey , ( sp , key ) => any ) ;
277+
278+ var provider1 = CreateServiceProvider ( serviceCollection ) ;
279+ Assert . Equal ( new [ ] { service } , provider1 . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
280+
281+ // Check twice in different order to check caching
282+ var provider2 = CreateServiceProvider ( serviceCollection ) ;
283+ Assert . Equal ( new [ ] { service } , provider2 . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
284+ Assert . Same ( any , provider2 . GetKeyedService < IService > ( new object ( ) ) ) ;
285+
286+ Assert . Throws < InvalidOperationException > ( ( ) => provider2 . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
287+ }
288+
289+ [ Fact ]
290+ public void ResolveKeyedServicesAnyKeyOrdering ( )
291+ {
292+ var serviceCollection = new ServiceCollection ( ) ;
293+ var service1 = new Service ( ) ;
294+ var service2 = new Service ( ) ;
295+ var service3 = new Service ( ) ;
296+
297+ serviceCollection . AddKeyedSingleton < IService > ( "A-service" , service1 ) ;
298+ serviceCollection . AddKeyedSingleton < IService > ( "B-service" , service2 ) ;
299+ serviceCollection . AddKeyedSingleton < IService > ( "A-service" , service3 ) ;
300+
301+ var provider = CreateServiceProvider ( serviceCollection ) ;
302+
303+ // The order should be in registration order, and not grouped by key for example.
304+ // Although this isn't necessarily a requirement, it is the current behavior.
305+ Assert . Equal (
306+ new [ ] { service1 , service2 , service3 } ,
307+ provider . GetKeyedServices < IService > ( KeyedService . AnyKey ) ) ;
165308 }
166309
167310 [ Fact ]
@@ -250,7 +393,7 @@ public void ResolveKeyedServicesSingletonInstanceWithAnyKey()
250393 var provider = CreateServiceProvider ( serviceCollection ) ;
251394
252395 var services = provider . GetKeyedServices < IFakeOpenGenericService < PocoClass > > ( "some-key" ) . ToList ( ) ;
253- Assert . Equal ( new [ ] { service1 , service2 } , services ) ;
396+ Assert . Equal ( new [ ] { service2 } , services ) ;
254397 }
255398
256399 [ Fact ]
@@ -504,6 +647,9 @@ public void ResolveKeyedSingletonFromScopeServiceProvider()
504647 Assert . Null ( scopeA . ServiceProvider . GetService < IService > ( ) ) ;
505648 Assert . Null ( scopeB . ServiceProvider . GetService < IService > ( ) ) ;
506649
650+ Assert . Throws < InvalidOperationException > ( ( ) => scopeA . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
651+ Assert . Throws < InvalidOperationException > ( ( ) => scopeB . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
652+
507653 var serviceA1 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
508654 var serviceA2 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
509655
@@ -528,6 +674,9 @@ public void ResolveKeyedScopedFromScopeServiceProvider()
528674 Assert . Null ( scopeA . ServiceProvider . GetService < IService > ( ) ) ;
529675 Assert . Null ( scopeB . ServiceProvider . GetService < IService > ( ) ) ;
530676
677+ Assert . Throws < InvalidOperationException > ( ( ) => scopeA . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
678+ Assert . Throws < InvalidOperationException > ( ( ) => scopeB . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
679+
531680 var serviceA1 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
532681 var serviceA2 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
533682
0 commit comments