From 7e3187569a0bffe61ce6edb33241a6a4d2aa870c Mon Sep 17 00:00:00 2001 From: dotnetjunkie Date: Sun, 15 Apr 2018 11:19:54 +0200 Subject: [PATCH] ScopedLifestyle.Flowing added to simplify #532. --- .../DecoratorTests.cs | 8 +-- .../ScopedLifestyleTests.cs | 13 ++--- src/SimpleInjector/Container.Verification.cs | 8 ++- ...ncontrolledServicesDecoratorInterceptor.cs | 1 - .../Internals/CompilationHelpers.cs | 5 +- .../Lifestyles/FlowingScopedLifestyle.cs | 49 +++++++++++++++++++ .../Lifestyles/ScopedScopeLifestyle.cs | 12 ++--- src/SimpleInjector/ScopedLifestyle.cs | 8 +++ 8 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs diff --git a/src/SimpleInjector.Tests.Unit/DecoratorTests.cs b/src/SimpleInjector.Tests.Unit/DecoratorTests.cs index e0f4b4e23..24bfc1232 100644 --- a/src/SimpleInjector.Tests.Unit/DecoratorTests.cs +++ b/src/SimpleInjector.Tests.Unit/DecoratorTests.cs @@ -2478,7 +2478,7 @@ public void InjectedScopeDecorateeFactory_WhenSuppliedWithAScopeInstance_Creates { // Arrange var container = ContainerFactory.New(); - container.Options.DefaultScopedLifestyle = new AmbientlessScopedLifestyle(); + container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing; container.Register, NullCommandHandler>(Lifestyle.Scoped); container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ScopedCommandHandlerProxy<>), @@ -2524,12 +2524,6 @@ public ScopedCommandHandlerProxy(Func> decorateeFactor this.DecorateeFactory = decorateeFactory; } } - - private sealed class AmbientlessScopedLifestyle : ScopedLifestyle - { - public AmbientlessScopedLifestyle() : base("Scoped") { } - protected internal override Func CreateCurrentScopeProvider(Container c) => () => null; - } } public static class ContainerTestExtensions diff --git a/src/SimpleInjector.Tests.Unit/ScopedLifestyleTests.cs b/src/SimpleInjector.Tests.Unit/ScopedLifestyleTests.cs index 5f7f29098..6ecf9cc85 100644 --- a/src/SimpleInjector.Tests.Unit/ScopedLifestyleTests.cs +++ b/src/SimpleInjector.Tests.Unit/ScopedLifestyleTests.cs @@ -749,7 +749,7 @@ public void Verify_ExecutedWithinActiveScope_CreatesScopedInstancesInItsOwnScope plugin = null; // Act - // When calling verify, we expect DisposablePlugin to be created again. + // When calling verify, we expect DisposablePlugin to be created again, because verify gets its own scope container.Verify(); // Assert @@ -976,7 +976,7 @@ public void GetInstance_ResolvingScopedDependencyDirectlyFromScope_ResolvesTheIn var container = ContainerFactory.New(); // We need a 'dummy' scoped lifestyle to be able to use Lifestyle.Scoped - container.Options.DefaultScopedLifestyle = new AmbientlessScopedLifestyle(); + container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing; container.Register(Lifestyle.Scoped); @@ -999,12 +999,13 @@ public void GetInstance_LambdaThatCallsBackIntoContainerExecutedFromScopeResolve // Arrange var container = ContainerFactory.New(); - container.Options.DefaultScopedLifestyle = new AmbientlessScopedLifestyle(); + container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing; // Calling back into the container to get a scoped instance, from within an instanceCreator lambda, // should work, in case the the root object is resolved from a scope. container.Register(() => container.GetInstance()); container.Register(Lifestyle.Scoped); + container.Register>(); var scope = new Scope(container); @@ -1187,11 +1188,5 @@ public FakeScopedLifestyle(Scope scope) protected internal override Func CreateCurrentScopeProvider(Container c) => () => this.scope; } - - private sealed class AmbientlessScopedLifestyle : ScopedLifestyle - { - public AmbientlessScopedLifestyle() : base("Scoped") { } - protected internal override Func CreateCurrentScopeProvider(Container c) => () => null; - } } } \ No newline at end of file diff --git a/src/SimpleInjector/Container.Verification.cs b/src/SimpleInjector/Container.Verification.cs index b94356815..9a96e7d7e 100644 --- a/src/SimpleInjector/Container.Verification.cs +++ b/src/SimpleInjector/Container.Verification.cs @@ -50,12 +50,11 @@ public partial class Container internal Scope VerificationScope { get; private set; } // Allows to resolve directly from a scope instead of relying on an ambient context. - // TODO: Optimize performance for the common scenario where the resolveScope is never used. internal Scope CurrentThreadResolveScope { get { - return this.resolveScope.Value; + return this.usingCurrentThreadResolveScope ? this.resolveScope.Value : null; } set @@ -120,6 +119,11 @@ internal Scope GetVerificationOrResolveScopeForCurrentThread() => ? this.resolveScope.Value : null; + internal void UseCurrentThreadResolveScope() + { + this.usingCurrentThreadResolveScope = true; + } + private void VerifyInternal(bool suppressLifestyleMismatchVerification) { // Prevent multiple threads from starting verification at the same time. This could crash, because diff --git a/src/SimpleInjector/Decorators/ContainerUncontrolledServicesDecoratorInterceptor.cs b/src/SimpleInjector/Decorators/ContainerUncontrolledServicesDecoratorInterceptor.cs index 48766384e..c73adbdb7 100644 --- a/src/SimpleInjector/Decorators/ContainerUncontrolledServicesDecoratorInterceptor.cs +++ b/src/SimpleInjector/Decorators/ContainerUncontrolledServicesDecoratorInterceptor.cs @@ -163,7 +163,6 @@ private Registration CreateRegistrationForUncontrolledCollection(Expression deco private OverriddenParameter[] CreateOverriddenParameters(Expression decorateeExpression) { - // TODO: Check this ParameterInfo decorateeParameter = GetDecorateeParameter(this.registeredServiceType, this.decoratorConstructor); diff --git a/src/SimpleInjector/Internals/CompilationHelpers.cs b/src/SimpleInjector/Internals/CompilationHelpers.cs index 0a11fe080..5c8fa9bc8 100644 --- a/src/SimpleInjector/Internals/CompilationHelpers.cs +++ b/src/SimpleInjector/Internals/CompilationHelpers.cs @@ -98,7 +98,6 @@ internal static Delegate CompileExpression(Type resultType, Container container, return compiledLambda ?? CompileLambda(resultType, expression); } - // TODO: If we make this method a no-op, which unit tests do break? Does the new ambient-less scoping test break? [MethodImpl(MethodImplOptions.NoInlining)] internal static Expression OptimizeScopedRegistrationsInObjectGraph(Container container, Expression expression) @@ -142,8 +141,8 @@ static partial void TryCompileInDynamicAssembly(Type resultType, Expression expr // Func factory = () => // { // var scope1 = new LazyScope(scopeFactory1, container); - // var value1 = new LazyScopedRegistration(reg1); - // var value2 = new LazyScopedRegistration(reg2); + // var value1 = new LazyScopedRegistration(reg1); + // var value2 = new LazyScopedRegistration(reg2); // // return new HomeController( // value1.GetInstance(scope1.Value), // Hits ThreadLocal, hits dictionary diff --git a/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs b/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs new file mode 100644 index 000000000..ef462bfe9 --- /dev/null +++ b/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs @@ -0,0 +1,49 @@ +#region Copyright Simple Injector Contributors +/* The Simple Injector is an easy-to-use Inversion of Control library for .NET + * + * Copyright (c) 2018 Simple Injector Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO + * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#endregion + +namespace SimpleInjector.Lifestyles +{ + using System; + + /// + /// This lifestyle can be used to implement ambient context-less scoping in Simple Injector. This lifestyle + /// can be set as DefaultScopedLifestyle and later used via Lifestyle.Scoped to register scoped instances, + /// while instances are resolved via Scope.GetInstance. + /// + internal sealed class FlowingScopedLifestyle : ScopedLifestyle + { + public FlowingScopedLifestyle() : base("Scoped") + { + } + + protected internal override Func CreateCurrentScopeProvider(Container container) + { + // Notify the container that we're using the thread-resolve scope. + container.UseCurrentThreadResolveScope(); + + return () => container.GetVerificationOrResolveScopeForCurrentThread(); + } + + protected override Scope GetCurrentScopeCore(Container container) => + container.GetVerificationOrResolveScopeForCurrentThread(); + } +} \ No newline at end of file diff --git a/src/SimpleInjector/Lifestyles/ScopedScopeLifestyle.cs b/src/SimpleInjector/Lifestyles/ScopedScopeLifestyle.cs index a7fa6ae46..e35c21825 100644 --- a/src/SimpleInjector/Lifestyles/ScopedScopeLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/ScopedScopeLifestyle.cs @@ -47,17 +47,11 @@ private Scope GetScopeFromDefaultScopedLifestyle(Container container) return lifestyle.GetCurrentScope(container) ?? ThrowThereIsNoActiveScopeException(); } - if (container.GetVerificationOrResolveScopeForCurrentThread() == null) - { - ThrowResolveFromScopeOrRegisterDefaultScopedLifestyleException(); - } - - // We return null instead of one of the scope returned from the previous method call, - // since the CreateCurrentScopeProvider contract states that we should return null. - return null; + return container.GetVerificationOrResolveScopeForCurrentThread() + ?? ThrowResolveFromScopeOrRegisterDefaultScopedLifestyleException(); } - private static void ThrowResolveFromScopeOrRegisterDefaultScopedLifestyleException() + private static Scope ThrowResolveFromScopeOrRegisterDefaultScopedLifestyleException() { throw new InvalidOperationException( "To be able to resolve and inject Scope instances, you need to either resolve " + diff --git a/src/SimpleInjector/ScopedLifestyle.cs b/src/SimpleInjector/ScopedLifestyle.cs index 51eb97906..e216943c6 100644 --- a/src/SimpleInjector/ScopedLifestyle.cs +++ b/src/SimpleInjector/ScopedLifestyle.cs @@ -35,6 +35,14 @@ namespace SimpleInjector /// public abstract class ScopedLifestyle : Lifestyle { + /// + /// Gets the scoped lifestyle that allows Scoped registrations to be resolved direclty from the + /// by calling . This allows multiple + /// scopes to be active and overlap within the same logical context, such as a single thread, or an + /// asynchronous context. + /// + public static readonly ScopedLifestyle Flowing = new FlowingScopedLifestyle(); + /// Initializes a new instance of the class. /// The user friendly name of this lifestyle. /// Thrown when is null (Nothing in VB)