Skip to content

Commit 75aee17

Browse files
authored
Fix AnyKey query issues with service identity (#114972)
1 parent 5e48524 commit 75aee17

File tree

3 files changed

+423
-81
lines changed

3 files changed

+423
-81
lines changed

src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Linq;
67
using System.Security.Cryptography;
78
using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
@@ -286,6 +287,280 @@ public void ResolveKeyedServicesAnyKeyConsistencyWithAnyKeyRegistration()
286287
Assert.Throws<InvalidOperationException>(() => provider2.GetKeyedService<IService>(KeyedService.AnyKey));
287288
}
288289

290+
[Theory]
291+
[InlineData(true)]
292+
[InlineData(false)]
293+
// Test ordering and slot assignments when DI calls the service's constructor
294+
// across keyed services with different service types and keys.
295+
public void ResolveWithAnyKeyQuery_Constructor(bool anyKeyQueryBeforeSingletonQueries)
296+
{
297+
var serviceCollection = new ServiceCollection();
298+
299+
// Interweave these to check that the slot \ ordering logic is correct.
300+
// Each unique key + its service Type maintains their own slot in a AnyKey query.
301+
serviceCollection.AddKeyedSingleton<TestServiceA>("key1");
302+
serviceCollection.AddKeyedSingleton<TestServiceB>("key1");
303+
serviceCollection.AddKeyedSingleton<TestServiceA>("key2");
304+
serviceCollection.AddKeyedSingleton<TestServiceB>("key2");
305+
serviceCollection.AddKeyedSingleton<TestServiceA>("key3");
306+
serviceCollection.AddKeyedSingleton<TestServiceB>("key3");
307+
308+
var provider = CreateServiceProvider(serviceCollection);
309+
310+
TestServiceA[] allInstancesA = null;
311+
TestServiceB[] allInstancesB = null;
312+
313+
if (anyKeyQueryBeforeSingletonQueries)
314+
{
315+
DoAnyKeyQuery();
316+
}
317+
318+
var serviceA1 = provider.GetKeyedService<TestServiceA>("key1");
319+
var serviceB1 = provider.GetKeyedService<TestServiceB>("key1");
320+
var serviceA2 = provider.GetKeyedService<TestServiceA>("key2");
321+
var serviceB2 = provider.GetKeyedService<TestServiceB>("key2");
322+
var serviceA3 = provider.GetKeyedService<TestServiceA>("key3");
323+
var serviceB3 = provider.GetKeyedService<TestServiceB>("key3");
324+
325+
if (!anyKeyQueryBeforeSingletonQueries)
326+
{
327+
DoAnyKeyQuery();
328+
}
329+
330+
Assert.Equal(
331+
new[] { serviceA1, serviceA2, serviceA3 },
332+
allInstancesA);
333+
334+
Assert.Equal(
335+
new[] { serviceB1, serviceB2, serviceB3 },
336+
allInstancesB);
337+
338+
void DoAnyKeyQuery()
339+
{
340+
IEnumerable<TestServiceA> allA = provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey);
341+
IEnumerable<TestServiceB> allB = provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey);
342+
343+
// Verify caching returns the same IEnumerable<> instance.
344+
Assert.Same(allA, provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey));
345+
Assert.Same(allB, provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey));
346+
347+
allInstancesA = allA.ToArray();
348+
allInstancesB = allB.ToArray();
349+
}
350+
}
351+
352+
[Theory]
353+
[InlineData(true)]
354+
[InlineData(false)]
355+
// Test ordering and slot assignments when DI calls the service's constructor
356+
// across keyed services with different service types with duplicate keys.
357+
public void ResolveWithAnyKeyQuery_Constructor_Duplicates(bool anyKeyQueryBeforeSingletonQueries)
358+
{
359+
var serviceCollection = new ServiceCollection();
360+
361+
// Interweave these to check that the slot \ ordering logic is correct.
362+
// Each unique key + its service Type maintains their own slot in a AnyKey query.
363+
serviceCollection.AddKeyedSingleton<TestServiceA>("key");
364+
serviceCollection.AddKeyedSingleton<TestServiceB>("key");
365+
serviceCollection.AddKeyedSingleton<TestServiceA>("key");
366+
serviceCollection.AddKeyedSingleton<TestServiceB>("key");
367+
serviceCollection.AddKeyedSingleton<TestServiceA>("key");
368+
serviceCollection.AddKeyedSingleton<TestServiceB>("key");
369+
370+
var provider = CreateServiceProvider(serviceCollection);
371+
372+
TestServiceA[] allInstancesA = null;
373+
TestServiceB[] allInstancesB = null;
374+
375+
if (anyKeyQueryBeforeSingletonQueries)
376+
{
377+
DoAnyKeyQuery();
378+
}
379+
380+
var serviceA = provider.GetKeyedService<TestServiceA>("key");
381+
Assert.Same(serviceA, provider.GetKeyedService<TestServiceA>("key"));
382+
383+
var serviceB = provider.GetKeyedService<TestServiceB>("key");
384+
Assert.Same(serviceB, provider.GetKeyedService<TestServiceB>("key"));
385+
386+
if (!anyKeyQueryBeforeSingletonQueries)
387+
{
388+
DoAnyKeyQuery();
389+
}
390+
391+
// An AnyKey query we get back the last registered service for duplicates.
392+
// The first and second services are effectively hidden unless we query all.
393+
Assert.Equal(3, allInstancesA.Length);
394+
Assert.Same(serviceA, allInstancesA[2]);
395+
Assert.NotSame(serviceA, allInstancesA[1]);
396+
Assert.NotSame(serviceA, allInstancesA[0]);
397+
Assert.NotSame(allInstancesA[0], allInstancesA[1]);
398+
399+
Assert.Equal(3, allInstancesB.Length);
400+
Assert.Same(serviceB, allInstancesB[2]);
401+
Assert.NotSame(serviceB, allInstancesB[1]);
402+
Assert.NotSame(serviceB, allInstancesB[0]);
403+
Assert.NotSame(allInstancesB[0], allInstancesB[1]);
404+
405+
void DoAnyKeyQuery()
406+
{
407+
IEnumerable<TestServiceA> allA = provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey);
408+
IEnumerable<TestServiceB> allB = provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey);
409+
410+
// Verify caching returns the same IEnumerable<> instances.
411+
Assert.Same(allA, provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey));
412+
Assert.Same(allB, provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey));
413+
414+
allInstancesA = allA.ToArray();
415+
allInstancesB = allB.ToArray();
416+
}
417+
}
418+
419+
[Theory]
420+
[InlineData(true)]
421+
[InlineData(false)]
422+
// Test ordering and slot assignments when service is provided
423+
// across keyed services with different service types and keys.
424+
public void ResolveWithAnyKeyQuery_InstanceProvided(bool anyKeyQueryBeforeSingletonQueries)
425+
{
426+
var serviceCollection = new ServiceCollection();
427+
428+
TestServiceA serviceA1 = new();
429+
TestServiceA serviceA2 = new();
430+
TestServiceA serviceA3 = new();
431+
TestServiceB serviceB1 = new();
432+
TestServiceB serviceB2 = new();
433+
TestServiceB serviceB3 = new();
434+
435+
// Interweave these to check that the slot \ ordering logic is correct.
436+
// Each unique key + its service Type maintains their own slot in a AnyKey query.
437+
serviceCollection.AddKeyedSingleton<TestServiceA>("key1", serviceA1);
438+
serviceCollection.AddKeyedSingleton<TestServiceB>("key1", serviceB1);
439+
serviceCollection.AddKeyedSingleton<TestServiceA>("key2", serviceA2);
440+
serviceCollection.AddKeyedSingleton<TestServiceB>("key2", serviceB2);
441+
serviceCollection.AddKeyedSingleton<TestServiceA>("key3", serviceA3);
442+
serviceCollection.AddKeyedSingleton<TestServiceB>("key3", serviceB3);
443+
444+
var provider = CreateServiceProvider(serviceCollection);
445+
446+
TestServiceA[] allInstancesA = null;
447+
TestServiceB[] allInstancesB = null;
448+
449+
if (anyKeyQueryBeforeSingletonQueries)
450+
{
451+
DoAnyKeyQuery();
452+
}
453+
454+
var fromServiceA1 = provider.GetKeyedService<TestServiceA>("key1");
455+
var fromServiceA2 = provider.GetKeyedService<TestServiceA>("key2");
456+
var fromServiceA3 = provider.GetKeyedService<TestServiceA>("key3");
457+
Assert.Same(serviceA1, fromServiceA1);
458+
Assert.Same(serviceA2, fromServiceA2);
459+
Assert.Same(serviceA3, fromServiceA3);
460+
461+
var fromServiceB1 = provider.GetKeyedService<TestServiceB>("key1");
462+
var fromServiceB2 = provider.GetKeyedService<TestServiceB>("key2");
463+
var fromServiceB3 = provider.GetKeyedService<TestServiceB>("key3");
464+
Assert.Same(serviceB1, fromServiceB1);
465+
Assert.Same(serviceB2, fromServiceB2);
466+
Assert.Same(serviceB3, fromServiceB3);
467+
468+
if (!anyKeyQueryBeforeSingletonQueries)
469+
{
470+
DoAnyKeyQuery();
471+
}
472+
473+
Assert.Equal(
474+
new[] { serviceA1, serviceA2, serviceA3 },
475+
allInstancesA);
476+
477+
Assert.Equal(
478+
new[] { serviceB1, serviceB2, serviceB3 },
479+
allInstancesB);
480+
481+
void DoAnyKeyQuery()
482+
{
483+
IEnumerable<TestServiceA> allA = provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey);
484+
IEnumerable<TestServiceB> allB = provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey);
485+
486+
// Verify caching returns the same items.
487+
Assert.Equal(allA, provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey));
488+
Assert.Equal(allB, provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey));
489+
490+
allInstancesA = allA.ToArray();
491+
allInstancesB = allB.ToArray();
492+
}
493+
}
494+
495+
[Theory]
496+
[InlineData(true)]
497+
[InlineData(false)]
498+
// Test ordering and slot assignments when service is provided
499+
// across keyed services with different service types with duplicate keys.
500+
public void ResolveWithAnyKeyQuery_InstanceProvided_Duplicates(bool anyKeyQueryBeforeSingletonQueries)
501+
{
502+
var serviceCollection = new ServiceCollection();
503+
504+
TestServiceA serviceA1 = new();
505+
TestServiceA serviceA2 = new();
506+
TestServiceA serviceA3 = new();
507+
TestServiceB serviceB1 = new();
508+
TestServiceB serviceB2 = new();
509+
TestServiceB serviceB3 = new();
510+
511+
// Interweave these to check that the slot \ ordering logic is correct.
512+
// Each unique key + its service Type maintains their own slot in a AnyKey query.
513+
serviceCollection.AddKeyedSingleton<TestServiceA>("key", serviceA1);
514+
serviceCollection.AddKeyedSingleton<TestServiceB>("key", serviceB1);
515+
serviceCollection.AddKeyedSingleton<TestServiceA>("key", serviceA2);
516+
serviceCollection.AddKeyedSingleton<TestServiceB>("key", serviceB2);
517+
serviceCollection.AddKeyedSingleton<TestServiceA>("key", serviceA3);
518+
serviceCollection.AddKeyedSingleton<TestServiceB>("key", serviceB3);
519+
520+
var provider = CreateServiceProvider(serviceCollection);
521+
522+
TestServiceA[] allInstancesA = null;
523+
TestServiceB[] allInstancesB = null;
524+
525+
if (anyKeyQueryBeforeSingletonQueries)
526+
{
527+
DoAnyKeyQuery();
528+
}
529+
530+
// We get back the last registered service for duplicates.
531+
Assert.Same(serviceA3, provider.GetKeyedService<TestServiceA>("key"));
532+
Assert.Same(serviceB3, provider.GetKeyedService<TestServiceB>("key"));
533+
534+
if (!anyKeyQueryBeforeSingletonQueries)
535+
{
536+
DoAnyKeyQuery();
537+
}
538+
539+
Assert.Equal(
540+
new[] { serviceA1, serviceA2, serviceA3 },
541+
allInstancesA);
542+
543+
Assert.Equal(
544+
new[] { serviceB1, serviceB2, serviceB3 },
545+
allInstancesB);
546+
547+
void DoAnyKeyQuery()
548+
{
549+
IEnumerable<TestServiceA> allA = provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey);
550+
IEnumerable<TestServiceB> allB = provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey);
551+
552+
// Verify caching returns the same items.
553+
Assert.Equal(allA, provider.GetKeyedServices<TestServiceA>(KeyedService.AnyKey));
554+
Assert.Equal(allB, provider.GetKeyedServices<TestServiceB>(KeyedService.AnyKey));
555+
556+
allInstancesA = allA.ToArray();
557+
allInstancesB = allB.ToArray();
558+
}
559+
}
560+
561+
private class TestServiceA { }
562+
private class TestServiceB { }
563+
289564
[Fact]
290565
public void ResolveKeyedServicesAnyKeyOrdering()
291566
{

0 commit comments

Comments
 (0)