Skip to content

Commit ec5c223

Browse files
authored
Implement IKeyedServiceProvider interface on ServiceProviderEngineScope (#89509)
* Implement IKeyedServiceProvider interface * Add more tests
1 parent 849ed0a commit ec5c223

File tree

4 files changed

+159
-3
lines changed

4 files changed

+159
-3
lines changed

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

+118
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,114 @@ public void ResolveKeyedServiceTransientTypeWithAnyKey()
324324
Assert.NotSame(first, second);
325325
}
326326

327+
[Fact]
328+
public void ResolveKeyedSingletonFromInjectedServiceProvider()
329+
{
330+
var serviceCollection = new ServiceCollection();
331+
serviceCollection.AddKeyedSingleton<IService, Service>("key");
332+
serviceCollection.AddSingleton<ServiceProviderAccessor>();
333+
334+
var provider = CreateServiceProvider(serviceCollection);
335+
var accessor = provider.GetRequiredService<ServiceProviderAccessor>();
336+
337+
Assert.Null(accessor.ServiceProvider.GetService<IService>());
338+
339+
var service1 = accessor.ServiceProvider.GetKeyedService<IService>("key");
340+
var service2 = accessor.ServiceProvider.GetKeyedService<IService>("key");
341+
342+
Assert.Same(service1, service2);
343+
}
344+
345+
[Fact]
346+
public void ResolveKeyedTransientFromInjectedServiceProvider()
347+
{
348+
var serviceCollection = new ServiceCollection();
349+
serviceCollection.AddKeyedTransient<IService, Service>("key");
350+
serviceCollection.AddSingleton<ServiceProviderAccessor>();
351+
352+
var provider = CreateServiceProvider(serviceCollection);
353+
var accessor = provider.GetRequiredService<ServiceProviderAccessor>();
354+
355+
Assert.Null(accessor.ServiceProvider.GetService<IService>());
356+
357+
var service1 = accessor.ServiceProvider.GetKeyedService<IService>("key");
358+
var service2 = accessor.ServiceProvider.GetKeyedService<IService>("key");
359+
360+
Assert.NotSame(service1, service2);
361+
}
362+
363+
[Fact]
364+
public void ResolveKeyedSingletonFromScopeServiceProvider()
365+
{
366+
var serviceCollection = new ServiceCollection();
367+
serviceCollection.AddKeyedSingleton<IService, Service>("key");
368+
369+
var provider = CreateServiceProvider(serviceCollection);
370+
var scopeA = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
371+
var scopeB = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
372+
373+
Assert.Null(scopeA.ServiceProvider.GetService<IService>());
374+
Assert.Null(scopeB.ServiceProvider.GetService<IService>());
375+
376+
var serviceA1 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
377+
var serviceA2 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
378+
379+
var serviceB1 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
380+
var serviceB2 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
381+
382+
Assert.Same(serviceA1, serviceA2);
383+
Assert.Same(serviceB1, serviceB2);
384+
Assert.Same(serviceA1, serviceB1);
385+
}
386+
387+
[Fact]
388+
public void ResolveKeyedScopedFromScopeServiceProvider()
389+
{
390+
var serviceCollection = new ServiceCollection();
391+
serviceCollection.AddKeyedScoped<IService, Service>("key");
392+
393+
var provider = CreateServiceProvider(serviceCollection);
394+
var scopeA = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
395+
var scopeB = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
396+
397+
Assert.Null(scopeA.ServiceProvider.GetService<IService>());
398+
Assert.Null(scopeB.ServiceProvider.GetService<IService>());
399+
400+
var serviceA1 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
401+
var serviceA2 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
402+
403+
var serviceB1 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
404+
var serviceB2 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
405+
406+
Assert.Same(serviceA1, serviceA2);
407+
Assert.Same(serviceB1, serviceB2);
408+
Assert.NotSame(serviceA1, serviceB1);
409+
}
410+
411+
[Fact]
412+
public void ResolveKeyedTransientFromScopeServiceProvider()
413+
{
414+
var serviceCollection = new ServiceCollection();
415+
serviceCollection.AddKeyedTransient<IService, Service>("key");
416+
417+
var provider = CreateServiceProvider(serviceCollection);
418+
var scopeA = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
419+
var scopeB = provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
420+
421+
Assert.Null(scopeA.ServiceProvider.GetService<IService>());
422+
Assert.Null(scopeB.ServiceProvider.GetService<IService>());
423+
424+
var serviceA1 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
425+
var serviceA2 = scopeA.ServiceProvider.GetKeyedService<IService>("key");
426+
427+
var serviceB1 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
428+
var serviceB2 = scopeB.ServiceProvider.GetKeyedService<IService>("key");
429+
430+
Assert.NotSame(serviceA1, serviceA2);
431+
Assert.NotSame(serviceB1, serviceB2);
432+
Assert.NotSame(serviceA1, serviceB1);
433+
}
434+
327435
internal interface IService { }
328436

329437
internal class Service : IService
@@ -358,5 +466,15 @@ internal class ServiceWithIntKey : IService
358466

359467
public ServiceWithIntKey([ServiceKey] int id) => _id = id;
360468
}
469+
470+
internal class ServiceProviderAccessor
471+
{
472+
public ServiceProviderAccessor(IServiceProvider serviceProvider)
473+
{
474+
ServiceProvider = serviceProvider;
475+
}
476+
477+
public IServiceProvider ServiceProvider { get; }
478+
}
361479
}
362480
}

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs

+21-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
1212
{
1313
[DebuggerDisplay("{DebuggerToString(),nq}")]
1414
[DebuggerTypeProxy(typeof(ServiceProviderEngineScopeDebugView))]
15-
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAsyncDisposable, IServiceScopeFactory
15+
internal sealed class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
1616
{
1717
// For testing and debugging only
1818
internal IList<object> Disposables => _disposables ?? (IList<object>)Array.Empty<object>();
@@ -50,6 +50,26 @@ public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
5050
return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
5151
}
5252

53+
public object? GetKeyedService(Type serviceType, object? serviceKey)
54+
{
55+
if (_disposed)
56+
{
57+
ThrowHelper.ThrowObjectDisposedException();
58+
}
59+
60+
return RootProvider.GetKeyedService(serviceType, serviceKey, this);
61+
}
62+
63+
public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
64+
{
65+
if (_disposed)
66+
{
67+
ThrowHelper.ThrowObjectDisposedException();
68+
}
69+
70+
return RootProvider.GetRequiredKeyedService(serviceType, serviceKey, this);
71+
}
72+
5373
public IServiceProvider ServiceProvider => this;
5474

5575
public IServiceScope CreateScope() => RootProvider.CreateScope();

src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,17 @@ internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, Serv
9898
public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
9999

100100
public object? GetKeyedService(Type serviceType, object? serviceKey)
101-
=> GetService(new ServiceIdentifier(serviceKey, serviceType), Root);
101+
=> GetKeyedService(serviceType, serviceKey, Root);
102+
103+
internal object? GetKeyedService(Type serviceType, object? serviceKey, ServiceProviderEngineScope serviceProviderEngineScope)
104+
=> GetService(new ServiceIdentifier(serviceKey, serviceType), serviceProviderEngineScope);
102105

103106
public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
107+
=> GetRequiredKeyedService(serviceType, serviceKey, Root);
108+
109+
internal object GetRequiredKeyedService(Type serviceType, object? serviceKey, ServiceProviderEngineScope serviceProviderEngineScope)
104110
{
105-
object? service = GetKeyedService(serviceType, serviceKey);
111+
object? service = GetKeyedService(serviceType, serviceKey, serviceProviderEngineScope);
106112
if (service == null)
107113
{
108114
throw new InvalidOperationException(SR.Format(SR.NoServiceRegistered, serviceType));

src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs

+12
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Collections.Generic;
56
using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
67
using Xunit;
8+
using Xunit.Abstractions;
79

810
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
911
{
@@ -29,5 +31,15 @@ public void RootEngineScopeDisposeTest()
2931

3032
Assert.Throws<ObjectDisposedException>(() => sp.GetRequiredService<IServiceProvider>());
3133
}
34+
35+
[Fact]
36+
public void ServiceProviderEngineScope_ImplementsAllServiceProviderInterfaces()
37+
{
38+
var engineScopeInterfaces = typeof(ServiceProviderEngineScope).GetInterfaces();
39+
foreach (var serviceProviderInterface in typeof(ServiceProvider).GetInterfaces())
40+
{
41+
Assert.Contains(serviceProviderInterface, engineScopeInterfaces);
42+
}
43+
}
3244
}
3345
}

0 commit comments

Comments
 (0)