-
Notifications
You must be signed in to change notification settings - Fork 313
Validate scope into singleton sevice injection #430
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,9 +7,29 @@ namespace Microsoft.Extensions.DependencyInjection | |
{ | ||
public static class ServiceCollectionContainerBuilderExtensions | ||
{ | ||
/// <summary> | ||
/// Creates an <see cref="IServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/>. | ||
/// </summary> | ||
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param> | ||
/// <returns>The<see cref="IServiceProvider"/>.</returns> | ||
|
||
public static IServiceProvider BuildServiceProvider(this IServiceCollection services) | ||
{ | ||
return new ServiceProvider(services); | ||
return BuildServiceProvider(services, validateScopes: false); | ||
} | ||
|
||
/// <summary> | ||
/// Creates an <see cref="IServiceProvider"/> containing services from the provided <see cref="IServiceCollection"/> | ||
/// optionaly enabling scope validation. | ||
/// </summary> | ||
/// <param name="services">The <see cref="IServiceCollection"/> containing service descriptors.</param> | ||
/// <param name="validateScopes"> | ||
/// <c>true</c> to perform check verifying that scoped services never gets resolved from root provider; otherwise <c>false</c>. | ||
/// </param> | ||
/// <returns>The<see cref="IServiceProvider"/>.</returns> | ||
public static IServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Time for doc comments? On one hand, I kind of hope no one ever uses this (except for may us). On the other hand, without context, I would have no idea what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the other method + type too since they're all public. |
||
{ | ||
return new ServiceProvider(services, validateScopes); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup | ||
{ | ||
internal class CallSiteValidator: CallSiteVisitor<CallSiteValidator.CallSiteValidatorState, Type> | ||
{ | ||
// Keys are services being resolved via GetService, values - first scoped service in their call site tree | ||
private readonly Dictionary<Type, Type> _scopedServices = new Dictionary<Type, Type>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment to indicate the key is the service being resolved via |
||
|
||
public void ValidateCallSite(Type serviceType, IServiceCallSite callSite) | ||
{ | ||
var scoped = VisitCallSite(callSite, default(CallSiteValidatorState)); | ||
if (scoped != null) | ||
{ | ||
_scopedServices.Add(serviceType, scoped); | ||
} | ||
} | ||
|
||
public void ValidateResolution(Type serviceType, ServiceProvider serviceProvider) | ||
{ | ||
Type scopedService; | ||
if (ReferenceEquals(serviceProvider, serviceProvider.Root) | ||
&& _scopedServices.TryGetValue(serviceType, out scopedService)) | ||
{ | ||
if (serviceType == scopedService) | ||
{ | ||
throw new InvalidOperationException( | ||
Resources.FormatDirectScopedResolvedFromRootException(serviceType, | ||
nameof(ServiceLifetime.Scoped).ToLowerInvariant())); | ||
} | ||
|
||
throw new InvalidOperationException( | ||
Resources.FormatScopedResolvedFromRootException( | ||
serviceType, | ||
scopedService, | ||
nameof(ServiceLifetime.Scoped).ToLowerInvariant())); | ||
} | ||
} | ||
|
||
protected override Type VisitTransient(TransientCallSite transientCallSite, CallSiteValidatorState state) | ||
{ | ||
return VisitCallSite(transientCallSite.Service, state); | ||
} | ||
|
||
protected override Type VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state) | ||
{ | ||
Type result = null; | ||
foreach (var parameterCallSite in constructorCallSite.ParameterCallSites) | ||
{ | ||
var scoped = VisitCallSite(parameterCallSite, state); | ||
if (result == null) | ||
{ | ||
result = scoped; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
protected override Type VisitClosedIEnumerable(ClosedIEnumerableCallSite closedIEnumerableCallSite, | ||
CallSiteValidatorState state) | ||
{ | ||
Type result = null; | ||
foreach (var serviceCallSite in closedIEnumerableCallSite.ServiceCallSites) | ||
{ | ||
var scoped = VisitCallSite(serviceCallSite, state); | ||
if (result == null) | ||
{ | ||
result = scoped; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
protected override Type VisitSingleton(SingletonCallSite singletonCallSite, CallSiteValidatorState state) | ||
{ | ||
state.Singleton = singletonCallSite; | ||
return VisitCallSite(singletonCallSite.ServiceCallSite, state); | ||
} | ||
|
||
protected override Type VisitScoped(ScopedCallSite scopedCallSite, CallSiteValidatorState state) | ||
{ | ||
// We are fine with having ServiceScopeService requested by singletons | ||
if (scopedCallSite.ServiceCallSite is ServiceScopeService) | ||
{ | ||
return null; | ||
} | ||
if (state.Singleton != null) | ||
{ | ||
throw new InvalidOperationException(Resources.FormatScopedInSingletonException( | ||
scopedCallSite.Key.ServiceType, | ||
state.Singleton.Key.ServiceType, | ||
nameof(ServiceLifetime.Scoped).ToLowerInvariant(), | ||
nameof(ServiceLifetime.Singleton).ToLowerInvariant() | ||
)); | ||
} | ||
return scopedCallSite.Key.ServiceType; | ||
} | ||
|
||
protected override Type VisitConstant(ConstantCallSite constantCallSite, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitInstanceService(InstanceService instanceCallSite, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitServiceProviderService(ServiceProviderService serviceProviderService, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitEmptyIEnumerable(EmptyIEnumerableCallSite emptyIEnumerableCallSite, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitServiceScopeService(ServiceScopeService serviceScopeService, CallSiteValidatorState state) => null; | ||
|
||
protected override Type VisitFactoryService(FactoryService factoryService, CallSiteValidatorState state) => null; | ||
|
||
internal struct CallSiteValidatorState | ||
{ | ||
public SingletonCallSite Singleton { get; set; } | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might as well point out
false
is the default behavior.