Skip to content
This repository was archived by the owner on Nov 2, 2018. It is now read-only.

Commit 0b98f06

Browse files
committed
Validate scope into singleton sevice injection
1 parent 0b13fed commit 0b98f06

16 files changed

+199
-10
lines changed

src/Microsoft.Extensions.DependencyInjection/Properties/Resources.Designer.cs

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Extensions.DependencyInjection/Resources.resx

+3
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,7 @@
141141
<value>A suitable constructor for type '{0}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.</value>
142142
<comment>{0} = service type</comment>
143143
</data>
144+
<data name="ScopedInSingletonException" xml:space="preserve">
145+
<value>Cannot consume scoped service '{0}' from singleton '{1}'.</value>
146+
</data>
144147
</root>

src/Microsoft.Extensions.DependencyInjection/ServiceCollectionContainerBuilderExtensions.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ public static class ServiceCollectionContainerBuilderExtensions
99
{
1010
public static IServiceProvider BuildServiceProvider(this IServiceCollection services)
1111
{
12-
return new ServiceProvider(services);
12+
return BuildServiceProvider(services, false);
13+
}
14+
15+
public static IServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
16+
{
17+
return new ServiceProvider(services, validateScopes);
1318
}
1419
}
1520
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
7+
{
8+
internal class CallSiteValidator: CallSiteVisitor<CallSiteValidatorState, object>
9+
{
10+
public void Validate(IServiceCallSite callSite)
11+
{
12+
VisitCallSite(callSite, default(CallSiteValidatorState));
13+
}
14+
15+
protected override object VisitTransient(TransientCallSite transientCallSite, CallSiteValidatorState state)
16+
{
17+
VisitCallSite(transientCallSite.Service, state);
18+
return null;
19+
}
20+
21+
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
22+
{
23+
foreach (var parameterCallSite in constructorCallSite.ParameterCallSites)
24+
{
25+
VisitCallSite(parameterCallSite, state);
26+
}
27+
return null;
28+
}
29+
30+
protected override object VisitSingleton(SingletonCallSite singletonCallSite, CallSiteValidatorState state)
31+
{
32+
state.Singleton = singletonCallSite;
33+
VisitCallSite(singletonCallSite.ServiceCallSite, state);
34+
return null;
35+
}
36+
37+
protected override object VisitScoped(ScopedCallSite scopedCallSite, CallSiteValidatorState state)
38+
{
39+
// We are fine with having ServiceScopeService requested by singletons
40+
if (scopedCallSite.ServiceCallSite is ServiceScopeService)
41+
{
42+
return null;
43+
}
44+
if (state.Singleton != null)
45+
{
46+
throw new InvalidOperationException(Resources.FormatScopedInSingletonException(scopedCallSite.Key.Type, state.Singleton.Key.Type));
47+
}
48+
VisitCallSite(scopedCallSite.ServiceCallSite, state);
49+
return null;
50+
}
51+
52+
protected override object VisitConstant(ConstantCallSite constantCallSite, CallSiteValidatorState state) => null;
53+
54+
protected override object VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteValidatorState state) => null;
55+
56+
protected override object VisitInstanceService(InstanceService instanceCallSite, CallSiteValidatorState state) => null;
57+
58+
protected override object VisitServiceProviderService(ServiceProviderService serviceProviderService, CallSiteValidatorState state) => null;
59+
60+
protected override object VisitEmptyIEnumerable(EmptyIEnumerableCallSite emptyIEnumerableCallSite, CallSiteValidatorState state) => null;
61+
62+
protected override object VisitServiceScopeService(ServiceScopeService serviceScopeService, CallSiteValidatorState state) => null;
63+
64+
protected override object VisitClosedIEnumerable(ClosedIEnumerableCallSite closedIEnumerableCallSite, CallSiteValidatorState state) => null;
65+
66+
protected override object VisitFactoryService(FactoryService factoryService, CallSiteValidatorState state) => null;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
5+
{
6+
internal struct CallSiteValidatorState
7+
{
8+
public SingletonCallSite Singleton;
9+
}
10+
}

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/ClosedIEnumerableService.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public ServiceLifetime Lifetime
2525
get { return ServiceLifetime.Transient; }
2626
}
2727

28+
public Type Type => _itemType;
29+
2830
public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
2931
{
3032
var list = new List<IServiceCallSite>();
@@ -36,6 +38,5 @@ public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> call
3638
}
3739
return new ClosedIEnumerableCallSite(_itemType, list.ToArray());
3840
}
39-
4041
}
4142
}

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/FactoryService.cs

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public ServiceLifetime Lifetime
2222
get { return Descriptor.Lifetime; }
2323
}
2424

25+
public Type Type => Descriptor.ServiceType;
26+
2527
public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
2628
{
2729
return this;

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/IService.cs

+2
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ internal interface IService
1313
ServiceLifetime Lifetime { get; }
1414

1515
IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain);
16+
17+
Type Type { get; }
1618
}
1719
}

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/InstanceService.cs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public ServiceLifetime Lifetime
2525
get { return Descriptor.Lifetime; }
2626
}
2727

28+
public Type Type => Descriptor.ServiceType;
29+
2830
public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
2931
{
3032
return this;

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/Service.cs

+2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> call
117117
}
118118
}
119119

120+
public Type Type => _descriptor.ServiceType;
121+
120122
private bool IsSuperset(IEnumerable<Type> left, IEnumerable<Type> right)
121123
{
122124
return new HashSet<Type>(left).IsSupersetOf(right);

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/ServiceProviderService.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ internal class ServiceProviderService : IService, IServiceCallSite
1212

1313
public ServiceLifetime Lifetime
1414
{
15-
get { return ServiceLifetime.Scoped; }
15+
get { return ServiceLifetime.Transient; }
1616
}
1717

18+
public Type Type => typeof(IServiceProvider);
19+
1820
public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
1921
{
2022
return this;

src/Microsoft.Extensions.DependencyInjection/ServiceLookup/ServiceScopeService.cs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public ServiceLifetime Lifetime
1515
get { return ServiceLifetime.Scoped; }
1616
}
1717

18+
public Type Type => typeof(IServiceScopeFactory);
19+
1820
public IServiceCallSite CreateCallSite(ServiceProvider provider, ISet<Type> callSiteChain)
1921
{
2022
return this;

src/Microsoft.Extensions.DependencyInjection/ServiceProvider.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Microsoft.Extensions.DependencyInjection
1717
/// </summary>
1818
internal class ServiceProvider : IServiceProvider, IDisposable
1919
{
20+
private readonly bool _validateScopes;
2021
private readonly ServiceTable _table;
2122
private bool _disposeCalled;
2223
private List<IDisposable> _transientDisposables;
@@ -28,10 +29,13 @@ internal class ServiceProvider : IServiceProvider, IDisposable
2829

2930
// CallSiteRuntimeResolver is stateless so can be shared between all instances
3031
private static readonly CallSiteRuntimeResolver _callSiteRuntimeResolver = new CallSiteRuntimeResolver();
32+
private static readonly CallSiteValidator _callSiteValidator = new CallSiteValidator();
3133

32-
public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors)
34+
public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, bool validateScopes)
3335
{
3436
Root = this;
37+
38+
_validateScopes = validateScopes;
3539
_table = new ServiceTable(serviceDescriptors);
3640

3741
_table.Add(typeof(IServiceProvider), new ServiceProviderService());
@@ -44,6 +48,7 @@ internal ServiceProvider(ServiceProvider parent)
4448
{
4549
Root = parent.Root;
4650
_table = parent._table;
51+
_validateScopes = parent._validateScopes;
4752
}
4853

4954
/// <summary>
@@ -62,6 +67,10 @@ private static Func<ServiceProvider, object> CreateServiceAccessor(Type serviceT
6267
var callSite = serviceProvider.GetServiceCallSite(serviceType, new HashSet<Type>());
6368
if (callSite != null)
6469
{
70+
if (serviceProvider._validateScopes)
71+
{
72+
_callSiteValidator.Validate(callSite);
73+
}
6574
return RealizeService(serviceProvider._table, serviceType, callSite);
6675
}
6776

test/Microsoft.Extensions.DependencyInjection.Tests/CallSiteTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public static IEnumerable<object[]> TestServiceDescriptors(ServiceLifetime lifet
8282
public void BuiltExpressionWillReturnResolvedServiceWhenAppropriate(
8383
ServiceDescriptor[] desciptors, Type serviceType, Func<object, object, bool> compare)
8484
{
85-
var provider = new ServiceProvider(desciptors);
85+
var provider = new ServiceProvider(desciptors, false);
8686

8787
var callSite = provider.GetServiceCallSite(serviceType, new HashSet<Type>());
8888
var collectionCallSite = provider.GetServiceCallSite(typeof(IEnumerable<>).MakeGenericType(serviceType), new HashSet<Type>());
@@ -111,7 +111,7 @@ public void BuiltExpressionCanResolveNestedScopedService()
111111
descriptors.AddScoped<ServiceB>();
112112
descriptors.AddScoped<ServiceC>();
113113

114-
var provider = new ServiceProvider(descriptors);
114+
var provider = new ServiceProvider(descriptors, false);
115115
var callSite = provider.GetServiceCallSite(typeof(ServiceC), new HashSet<Type>());
116116
var compiledCallSite = CompileCallSite(callSite);
117117

@@ -129,7 +129,7 @@ public void BuiltExpressionRethrowsOriginalExceptionFromConstructor()
129129
descriptors.AddTransient<ClassWithThrowingCtor>();
130130
descriptors.AddTransient<IFakeService, FakeService>();
131131

132-
var provider = new ServiceProvider(descriptors);
132+
var provider = new ServiceProvider(descriptors, false);
133133

134134
var callSite1 = provider.GetServiceCallSite(typeof(ClassWithThrowingEmptyCtor), new HashSet<Type>());
135135
var compiledCallSite1 = CompileCallSite(callSite1);

test/Microsoft.Extensions.DependencyInjection.Tests/ServiceLookup/ServiceTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void CreateCallSite_Throws_IfTypeHasNoPublicConstructors()
2222
"Ensure the type is concrete and services are registered for all parameters of a public constructor.";
2323
var descriptor = new ServiceDescriptor(type, type, ServiceLifetime.Transient);
2424
var service = new Service(descriptor);
25-
var serviceProvider = new ServiceProvider(new[] { descriptor });
25+
var serviceProvider = new ServiceProvider(new[] { descriptor }, false);
2626

2727
// Act and Assert
2828
var ex = Assert.Throws<InvalidOperationException>(() => service.CreateCallSite(serviceProvider, new HashSet<Type>()));
@@ -38,7 +38,7 @@ public void CreateCallSite_CreatesInstanceCallSite_IfTypeHasDefaultOrPublicParam
3838
// Arrange
3939
var descriptor = new ServiceDescriptor(type, type, ServiceLifetime.Transient);
4040
var service = new Service(descriptor);
41-
var serviceProvider = new ServiceProvider(new[] { descriptor });
41+
var serviceProvider = new ServiceProvider(new[] { descriptor }, false);
4242

4343
// Act
4444
var callSite = service.CreateCallSite(serviceProvider, new HashSet<Type>());
@@ -99,7 +99,7 @@ public void CreateCallSite_UsesNullaryConstructorIfServicesCannotBeInjectedIntoO
9999
var type = typeof(TypeWithParameterizedAndNullaryConstructor);
100100
var descriptor = new ServiceDescriptor(type, type, ServiceLifetime.Transient);
101101
var service = new Service(descriptor);
102-
var serviceProvider = new ServiceProvider(new[] { descriptor });
102+
var serviceProvider = new ServiceProvider(new[] { descriptor }, false);
103103

104104
// Act
105105
var callSite = service.CreateCallSite(serviceProvider, new HashSet<Type>());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace Microsoft.Extensions.DependencyInjection.Tests
5+
{
6+
public class ServiceProviderValidationTests
7+
{
8+
[Fact]
9+
public void GetService_Throws_WhenScopedIsInjectedIntoSingleton()
10+
{
11+
// Arrange
12+
var serviceCollection = new ServiceCollection();
13+
serviceCollection.AddSingleton<IFoo, Foo>();
14+
serviceCollection.AddScoped<IBar, Bar>();
15+
var serviceProvider = serviceCollection.BuildServiceProvider(true);
16+
17+
// Act + Assert
18+
var exception = Assert.Throws<InvalidOperationException>(() => serviceProvider.GetService(typeof(IFoo)));
19+
Assert.Equal($"Cannot consume scoped service '{typeof(IBar)}' from singleton '{typeof(IFoo)}'.", exception.Message);
20+
}
21+
22+
public void GetService_DoesNotThrow_WhenScopeFactoryIsInjectedIntoSingleton()
23+
{
24+
// Arrange
25+
var serviceCollection = new ServiceCollection();
26+
serviceCollection.AddSingleton<IBoo, Boo>();
27+
var serviceProvider = serviceCollection.BuildServiceProvider(true);
28+
29+
// Act + Assert
30+
var result = serviceProvider.GetService(typeof(IBoo));
31+
Assert.NotNull(result);
32+
}
33+
34+
private interface IFoo
35+
{
36+
}
37+
38+
private class Foo : IFoo
39+
{
40+
public Foo(IBar bar)
41+
{
42+
}
43+
}
44+
45+
private interface IBar
46+
{
47+
}
48+
49+
private class Bar : IBar
50+
{
51+
}
52+
53+
private interface IBoo
54+
{
55+
}
56+
57+
private class Boo : IBoo
58+
{
59+
public Boo(IServiceScopeFactory scopeFactory)
60+
{
61+
}
62+
}
63+
64+
}
65+
}

0 commit comments

Comments
 (0)