Skip to content

Commit

Permalink
Decorators now support being injected with an Func<Scope, T> decorate…
Browse files Browse the repository at this point in the history
…e factory that allows resolving the decoratee for the supplied scope. Fixes #532.
  • Loading branch information
dotnetjunkie committed Apr 14, 2018
1 parent a83ae9e commit 47639a5
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 55 deletions.
42 changes: 42 additions & 0 deletions src/SimpleInjector.Tests.Unit/DecoratorTests.cs
Expand Up @@ -2473,6 +2473,32 @@ public void RegisterDecorator_WithConstructedGenericServiceType_ThrowsExpectedMe
action);
}

[TestMethod]
public void MethodUnderTest_Scenario_Behaviorx()
{
// Arrange
var container = ContainerFactory.New();
container.Options.DefaultScopedLifestyle = new AmbientlessScopedLifestyle();

container.Register<ICommandHandler<int>, NullCommandHandler<int>>(Lifestyle.Scoped);
container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ScopedCommandHandlerProxy<>),
Lifestyle.Singleton);

var proxy = (ScopedCommandHandlerProxy<int>)container.GetInstance<ICommandHandler<int>>();
var factory = proxy.DecorateeFactory;

// Act
var scope1 = new Scope(container);
var handler1 = proxy.DecorateeFactory(scope1);
var handler2 = proxy.DecorateeFactory(scope1);
var handler3 = proxy.DecorateeFactory(new Scope(container));

// Assert
Assert.IsInstanceOfType(handler1, typeof(NullCommandHandler<int>));
Assert.AreSame(handler1, handler2, "Handler is expected to be Scoped but was transient.");
Assert.AreNotSame(handler2, handler3, "Handler is expected to be Scoped but was singleton.");
}

private static KnownRelationship GetValidRelationship()
{
// Arrange
Expand All @@ -2488,6 +2514,22 @@ public void Intercept(IInvocation invocation)
{
}
}

public sealed class ScopedCommandHandlerProxy<T> : ICommandHandler<T>
{
public readonly Func<Scope, ICommandHandler<T>> DecorateeFactory;

public ScopedCommandHandlerProxy(Func<Scope, ICommandHandler<T>> decorateeFactory)
{
this.DecorateeFactory = decorateeFactory;
}
}

private sealed class AmbientlessScopedLifestyle : ScopedLifestyle
{
public AmbientlessScopedLifestyle() : base("Scoped") { }
protected internal override Func<Scope> CreateCurrentScopeProvider(Container c) => () => null;
}
}

public static class ContainerTestExtensions
Expand Down
Expand Up @@ -41,7 +41,6 @@ public void Analyze_ContainerWithOneMismatchCausedByDecorator_ReturnsExpectedWar
container.Register<ILogger, ConsoleLogger>(Lifestyle.Singleton);
container.RegisterDecorator<ILogger, LoggerDecorator>(Lifestyle.Transient);

// RealUserService depends on IUserRepository
container.Register<ServiceWithDependency<ILogger>>(Lifestyle.Singleton);

container.Verify(VerificationOption.VerifyOnly);
Expand All @@ -56,6 +55,33 @@ public void Analyze_ContainerWithOneMismatchCausedByDecorator_ReturnsExpectedWar
result.Description);
}

[TestMethod]
public void Analyze_ContainerWithOneMismatchCausedByDecoratorWrappedInScopedDecoratorProxy_ReturnsExpectedWarning()
{
// Arrange
var container = new Container();
container.Options.SuppressLifestyleMismatchVerification = true;

container.Register<ILogger, NullLogger>(Lifestyle.Transient);
container.RegisterDecorator<ILogger, LoggerDecorator>(Lifestyle.Singleton);

// ScopedLoggerDecoratorProxy depends on Func<Scope, ILogger>
container.RegisterDecorator<ILogger, ScopedLoggerDecoratorProxy>(Lifestyle.Singleton);

container.Register<ServiceWithDependency<ILogger>>(Lifestyle.Transient);

container.Verify(VerificationOption.VerifyOnly);

// Act
var result = Analyzer.Analyze(container).OfType<LifestyleMismatchDiagnosticResult>().Single();

// Assert
Assert.AreEqual(
"LoggerDecorator (Singleton) depends on ILogger implemented by " +
"NullLogger (Transient).",
result.Description);
}

[TestMethod]
public void Analyze_ContainerWithOneMismatch_ReturnsSeverityWarning()
{
Expand Down
10 changes: 10 additions & 0 deletions src/SimpleInjector.Tests.Unit/TestClasses.cs
Expand Up @@ -264,6 +264,16 @@ public LoggerDecorator(ILogger logger)
this.Logger = logger;
}
}

public sealed class ScopedLoggerDecoratorProxy : ILogger
{
public readonly Func<Scope, ILogger> DecorateeFactory;

public ScopedLoggerDecoratorProxy(Func<Scope, ILogger> decorateeFactory)
{
this.DecorateeFactory = decorateeFactory;
}
}

public sealed class NullValidator<T> : IValidate<T>
{
Expand Down
6 changes: 3 additions & 3 deletions src/SimpleInjector/Container.Verification.cs
Expand Up @@ -195,7 +195,7 @@ private void VerifyThatAllRootObjectsCanBeCreated()
where !producer.InstanceSuccessfullyCreated || !producer.VerifiersAreSuccessfullyCalled
select producer;

VerifyInstanceCreation(producersToVerify.ToArray());
this.VerifyInstanceCreation(producersToVerify.ToArray());
}

private IEnumerable<InstanceProducer> GetProducersThatNeedExplicitVerification()
Expand Down Expand Up @@ -228,7 +228,7 @@ private static void VerifyInstanceProducersOfContainerControlledCollection(Expre
}
}

private static void VerifyInstanceCreation(InstanceProducer[] producersToVerify)
private void VerifyInstanceCreation(InstanceProducer[] producersToVerify)
{
foreach (var producer in producersToVerify)
{
Expand All @@ -241,7 +241,7 @@ private static void VerifyInstanceCreation(InstanceProducer[] producersToVerify)

if (!producer.VerifiersAreSuccessfullyCalled)
{
producer.DoExtraVerfication();
producer.DoExtraVerfication(this.VerificationScope);
}
}
}
Expand Down
Expand Up @@ -163,6 +163,7 @@ private Registration CreateRegistrationForUncontrolledCollection(Expression deco

private OverriddenParameter[] CreateOverriddenParameters(Expression decorateeExpression)
{
// TODO: Check this
ParameterInfo decorateeParameter =
GetDecorateeParameter(this.registeredServiceType, this.decoratorConstructor);

Expand Down Expand Up @@ -275,7 +276,7 @@ private void ThrowWhenDecoratorNeedsAFunc()

private bool DecoratorNeedsADecorateeFactory() => (
from parameter in this.decoratorConstructor.GetParameters()
where IsDecorateeFactoryDependencyParameter(parameter, this.registeredServiceType)
where DecoratorHelpers.IsScopelessDecorateeFactoryDependencyType(parameter.ParameterType, this.registeredServiceType)
select parameter)
.Any();

Expand Down
95 changes: 71 additions & 24 deletions src/SimpleInjector/Decorators/DecoratorExpressionInterceptor.cs
Expand Up @@ -37,6 +37,9 @@ namespace SimpleInjector.Decorators
/// </summary>
internal abstract class DecoratorExpressionInterceptor
{
private static readonly MethodInfo ResolveWithinThreadResolveScopeMethod =
typeof(DecoratorExpressionInterceptor).GetMethod(nameof(ResolveWithinThreadResolveScope));

private static readonly Func<Container, object, ThreadLocal<Dictionary<InstanceProducer, ServiceTypeDecoratorInfo>>> ThreadLocalDictionaryFactory =
(container, key) => new ThreadLocal<Dictionary<InstanceProducer, ServiceTypeDecoratorInfo>>(
() => new Dictionary<InstanceProducer, ServiceTypeDecoratorInfo>());
Expand Down Expand Up @@ -67,6 +70,25 @@ protected DecoratorExpressionInterceptor(DecoratorExpressionInterceptorData data
get;
}

// NOTE: This method must be public for it to be callable through reflection when running in a sandbox.
public static TService ResolveWithinThreadResolveScope<TService>(
Scope scope, Func<TService> instanceCreator)
{
var container = scope.Container;

Scope originalScope = container.CurrentThreadResolveScope;

try
{
container.CurrentThreadResolveScope = scope;
return instanceCreator();
}
finally
{
container.CurrentThreadResolveScope = originalScope;
}
}

// Store a ServiceTypeDecoratorInfo object per closed service type. We have a dictionary per
// thread for thread-safety. We need a dictionary per thread, since the ExpressionBuilt event can
// get raised by multiple threads at the same time (especially for types resolved using
Expand Down Expand Up @@ -142,20 +164,7 @@ protected DecoratorExpressionInterceptor(DecoratorExpressionInterceptorData data
decoratorConstructor.DeclaringType, this.Container,
overriddenParameters);
}

protected static bool IsDecorateeParameter(ParameterInfo parameter, Type registeredServiceType)
{
return IsDecorateeDependencyParameter(parameter, registeredServiceType) ||
IsDecorateeFactoryDependencyParameter(parameter, registeredServiceType);
}

protected static bool IsDecorateeFactoryDependencyParameter(ParameterInfo parameter, Type serviceType)
{
return parameter.ParameterType.IsGenericType() &&
parameter.ParameterType.GetGenericTypeDefinition() == typeof(Func<>) &&
parameter.ParameterType == typeof(Func<>).MakeGenericType(serviceType);
}


protected DecoratorPredicateContext CreatePredicateContext(ExpressionBuiltEventArgs e)
{
return this.CreatePredicateContext(e.InstanceProducer, e.ReplacedRegistration,
Expand All @@ -179,18 +188,19 @@ protected DecoratorPredicateContext CreatePredicateContext(ExpressionBuiltEventA
{
return
BuildExpressionForDecorateeDependencyParameter(parameter, serviceType, expression) ??
this.BuildExpressionForDecorateeFactoryDependencyParameter(parameter, serviceType, expression);
this.BuildExpressionForDecorateeFactoryDependencyParameter(parameter, serviceType, expression) ??
this.BuildExpressionForScopedDecorateeFactoryDependencyParameter(parameter, serviceType, expression);
}

protected static ParameterInfo GetDecorateeParameter(Type serviceType,
ConstructorInfo decoratorConstructor)
protected static ParameterInfo GetDecorateeParameter(
Type serviceType, ConstructorInfo decoratorConstructor)
{
// Although we partly check for duplicate arguments during registration phase, we must do it here
// as well, because some registrations are allowed while not all closed-generic implementations
// can be resolved.
var parameters = (
from parameter in decoratorConstructor.GetParameters()
where DecoratorHelpers.IsDecorateeParameter(parameter.ParameterType, serviceType)
where DecoratorHelpers.IsDecorateeParameter(parameter, serviceType)
select parameter)
.ToArray();

Expand Down Expand Up @@ -219,7 +229,7 @@ protected InstanceProducer CreateDecorateeFactoryProducer(ParameterInfo paramete
{
// Func<T> dependencies for the decoratee must be explicitly added to the InstanceProducer as
// verifier. This allows those dependencies to be verified when calling Container.Verify().
Action verifier = GetVerifierFromDecorateeExpression(decorateeExpression);
Action<Scope> verifier = GetVerifierFromDecorateeExpression(decorateeExpression);

producer.AddVerifier(verifier);
}
Expand All @@ -235,7 +245,7 @@ protected InstanceProducer CreateDecorateeFactoryProducer(ParameterInfo paramete

var currentProducer = info.GetCurrentInstanceProducer();

if (IsDecorateeFactoryDependencyParameter(decorateeParameter, serviceType))
if (DecoratorHelpers.IsDecorateeFactoryDependencyType(decorateeParameter.ParameterType, serviceType))
{
// Adding a verifier makes sure the graph for the decoratee gets created,
// which allows testing whether constructing the graph fails.
Expand Down Expand Up @@ -269,11 +279,20 @@ protected InstanceProducer CreateDecorateeFactoryProducer(ParameterInfo paramete
select new OverriddenParameter(parameter, contextExpression, currentProducer);
}

private static Action GetVerifierFromDecorateeExpression(Expression decorateeExpression)
private static Action<Scope> GetVerifierFromDecorateeExpression(Expression decorateeExpression)
{
Func<object> instanceCreator = (Func<object>)((ConstantExpression)decorateeExpression).Value;
var value = ((ConstantExpression)decorateeExpression).Value;

if (value is Func<object> instanceCreator)
{
return _ => instanceCreator();
}
else
{
var scopedInstanceCreator = (Func<Scope, object>)value;

return () => instanceCreator();
return scope => scopedInstanceCreator(scope);
}
}

// The constructor parameter in which the decorated instance should be injected.
Expand All @@ -297,7 +316,7 @@ private static bool IsDecorateeDependencyParameter(ParameterInfo parameter, Type
private Expression BuildExpressionForDecorateeFactoryDependencyParameter(
ParameterInfo parameter, Type serviceType, Expression expression)
{
if (IsDecorateeFactoryDependencyParameter(parameter, serviceType))
if (DecoratorHelpers.IsScopelessDecorateeFactoryDependencyType(parameter.ParameterType, serviceType))
{
// We can't call CompilationHelpers.CompileExpression here, because it has a generic type and
// we don't know the type at runtime here. We need to do some refactoring to CompilationHelpers
Expand All @@ -312,5 +331,33 @@ private static bool IsDecorateeDependencyParameter(ParameterInfo parameter, Type

return null;
}

// The constructor parameter in which the factory for creating decorated instances should be injected.
private Expression BuildExpressionForScopedDecorateeFactoryDependencyParameter(
ParameterInfo parameter, Type serviceType, Expression expression)
{
if (DecoratorHelpers.IsScopeDecorateeFactoryDependencyParameter(parameter.ParameterType, serviceType))
{
expression = CompilationHelpers.OptimizeScopedRegistrationsInObjectGraph(this.Container, expression);

var instanceCreator =
Expression.Lambda(Expression.Convert(expression, serviceType)).Compile();

var scopeParameter = Expression.Parameter(typeof(Scope), "scope");

// Create an Func<Scope, [ServiceType>
var scopedInstanceCreator = Expression.Lambda(
Expression.Call(
ResolveWithinThreadResolveScopeMethod.MakeGenericMethod(serviceType),
scopeParameter,
Expression.Constant(instanceCreator)),
scopeParameter)
.Compile();

return Expression.Constant(scopedInstanceCreator);
}

return null;
}
}
}
40 changes: 23 additions & 17 deletions src/SimpleInjector/Decorators/DecoratorHelpers.cs
Expand Up @@ -164,7 +164,7 @@ internal static Type GetDecoratingBaseType(Type serviceType, ConstructorInfo dec
var decoratorInterfaces =
from abstraction in Types.GetBaseTypeCandidates(serviceType, decoratorConstructor.DeclaringType)
where decoratorConstructor.GetParameters()
.Any(parameter => IsDecorateeParameter(parameter.ParameterType, abstraction))
.Any(parameter => IsDecorateeParameter(parameter, abstraction))
select abstraction;

return decoratorInterfaces.FirstOrDefault();
Expand All @@ -182,7 +182,7 @@ where decoratorConstructor.GetParameters()

var validServiceTypeArguments =
from parameter in decoratorConstructor.GetParameters()
where IsDecorateeParameter(parameter.ParameterType, decoratorType)
where IsDecorateeParameter(parameter, decoratorType)
select parameter;

return validServiceTypeArguments.Count();
Expand Down Expand Up @@ -214,31 +214,37 @@ internal static bool DecoratesBaseTypes(Type serviceType, ConstructorInfo decora

return (
from baseType in decoratorConstructor.DeclaringType.GetBaseTypesAndInterfaces()
where IsDecorateeDependencyParameter(baseType, decoratingBaseType)
where IsDecorateeDependencyType(baseType, decoratingBaseType)
select baseType)
.ToArray();
}

internal static bool IsDecorateeParameter(Type parameterType, Type decoratingType) =>
IsDecorateeDependencyParameter(parameterType, decoratingType) ||
IsDecorateeFactoryDependencyParameter(parameterType, decoratingType);
internal static bool IsDecorateeParameter(ParameterInfo parameter, Type decoratingType) =>
IsDecorateeDependencyType(parameter.ParameterType, decoratingType)
|| IsDecorateeFactoryDependencyType(parameter.ParameterType, decoratingType);

// Checks if the given parameterType can function as the decorated instance of the given service type.
internal static bool IsDecorateeFactoryDependencyParameter(Type parameterType, Type serviceType)
internal static bool IsDecorateeDependencyType(Type dependencyType, Type serviceType)
{
if (!parameterType.IsGenericType() || parameterType.GetGenericTypeDefinition() != typeof(Func<>))
{
return false;
}
return dependencyType == serviceType;
}

Type funcArgumentType = parameterType.GetGenericArguments()[0];
internal static bool IsDecorateeFactoryDependencyType(Type dependencyType, Type decoratingType) =>
IsScopelessDecorateeFactoryDependencyType(dependencyType, decoratingType)
|| IsScopeDecorateeFactoryDependencyParameter(dependencyType, decoratingType);

return IsDecorateeDependencyParameter(funcArgumentType, serviceType);
internal static bool IsScopelessDecorateeFactoryDependencyType(Type dependencyType, Type decoratingType)
{
return dependencyType.IsGenericType()
&& dependencyType.GetGenericTypeDefinition() == typeof(Func<>)
&& dependencyType == typeof(Func<>).MakeGenericType(decoratingType);
}

// Checks if the given parameterType can function as the decorated instance of the given service type.
private static bool IsDecorateeDependencyParameter(Type parameterType, Type serviceType) =>
parameterType == serviceType;
internal static bool IsScopeDecorateeFactoryDependencyParameter(Type parameterType, Type decoratingType)
{
return parameterType.IsGenericType()
&& parameterType.GetGenericTypeDefinition() == typeof(Func<,>)
&& parameterType == typeof(Func<,>).MakeGenericType(typeof(Scope), decoratingType);
}

private sealed class ContainerControlledCollectionRegistration : Registration
{
Expand Down

0 comments on commit 47639a5

Please sign in to comment.