@@ -13,6 +13,84 @@ public abstract partial class KeyedDependencyInjectionSpecificationTests
13
13
{
14
14
protected abstract IServiceProvider CreateServiceProvider ( IServiceCollection collection ) ;
15
15
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
+
16
94
[ Fact ]
17
95
public void ResolveKeyedService ( )
18
96
{
@@ -158,10 +236,75 @@ public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration()
158
236
_ = provider . GetKeyedService < IService > ( "something-else" ) ;
159
237
_ = provider . GetKeyedService < IService > ( "something-else-again" ) ;
160
238
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
162
241
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 ) ) ;
165
308
}
166
309
167
310
[ Fact ]
@@ -250,7 +393,7 @@ public void ResolveKeyedServicesSingletonInstanceWithAnyKey()
250
393
var provider = CreateServiceProvider ( serviceCollection ) ;
251
394
252
395
var services = provider . GetKeyedServices < IFakeOpenGenericService < PocoClass > > ( "some-key" ) . ToList ( ) ;
253
- Assert . Equal ( new [ ] { service1 , service2 } , services ) ;
396
+ Assert . Equal ( new [ ] { service2 } , services ) ;
254
397
}
255
398
256
399
[ Fact ]
@@ -504,6 +647,9 @@ public void ResolveKeyedSingletonFromScopeServiceProvider()
504
647
Assert . Null ( scopeA . ServiceProvider . GetService < IService > ( ) ) ;
505
648
Assert . Null ( scopeB . ServiceProvider . GetService < IService > ( ) ) ;
506
649
650
+ Assert . Throws < InvalidOperationException > ( ( ) => scopeA . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
651
+ Assert . Throws < InvalidOperationException > ( ( ) => scopeB . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
652
+
507
653
var serviceA1 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
508
654
var serviceA2 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
509
655
@@ -528,6 +674,9 @@ public void ResolveKeyedScopedFromScopeServiceProvider()
528
674
Assert . Null ( scopeA . ServiceProvider . GetService < IService > ( ) ) ;
529
675
Assert . Null ( scopeB . ServiceProvider . GetService < IService > ( ) ) ;
530
676
677
+ Assert . Throws < InvalidOperationException > ( ( ) => scopeA . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
678
+ Assert . Throws < InvalidOperationException > ( ( ) => scopeB . ServiceProvider . GetKeyedService < IService > ( KeyedService . AnyKey ) ) ;
679
+
531
680
var serviceA1 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
532
681
var serviceA2 = scopeA . ServiceProvider . GetKeyedService < IService > ( "key" ) ;
533
682
0 commit comments