diff --git a/src/SimpleInjector.Tests.Unit/Lifestyles/AsyncScopedLifestyleTests.cs b/src/SimpleInjector.Tests.Unit/Lifestyles/AsyncScopedLifestyleTests.cs index 5b087e194..89ad5578a 100644 --- a/src/SimpleInjector.Tests.Unit/Lifestyles/AsyncScopedLifestyleTests.cs +++ b/src/SimpleInjector.Tests.Unit/Lifestyles/AsyncScopedLifestyleTests.cs @@ -1162,6 +1162,22 @@ public void Dispose_ObjectRegisteredForDisposalUsingRequestedCurrentLifetimeScop Assert.IsTrue(instanceToDispose.HasBeenDisposed); } + [TestMethod] + public void GetCurrentScope_WithCurrentScopeSet_ReturnsTheSetScope() + { + var lifestyle = new AsyncScopedLifestyle(); + var container = new Container(); + var expectedScope = new Scope(container); + + // Act + lifestyle.SetCurrentScope(expectedScope); + + // Assert + var actualScope = lifestyle.GetCurrentScope(container); + + Assert.AreSame(expectedScope, actualScope); + } + private static async Task Inner(Container container, ICommand command) { DisposableCommand cmd1, cmd2; diff --git a/src/SimpleInjector/Lifestyles/AsyncScopedLifestyle.cs b/src/SimpleInjector/Lifestyles/AsyncScopedLifestyle.cs index 8f5071be6..6f36be7ce 100644 --- a/src/SimpleInjector/Lifestyles/AsyncScopedLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/AsyncScopedLifestyle.cs @@ -90,6 +90,10 @@ public static Scope BeginScope(Container container) /// A instance or null when there is no scope active in this context. protected override Scope? GetCurrentScopeCore(Container container) => GetScopeManager(container).CurrentScope; + + /// + protected override void SetCurrentScopeCore(Scope scope) => + GetScopeManager(scope.Container!).SetCurrentScope(scope); private static ScopeManager GetScopeManager(Container c) => c.ContainerScope.GetOrSetItem(managerKey, CreateManager); diff --git a/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs b/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs index 31bf634a0..2e3083cb2 100644 --- a/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/FlowingScopedLifestyle.cs @@ -28,5 +28,8 @@ public FlowingScopedLifestyle() : base("Scoped") protected override Scope? GetCurrentScopeCore(Container container) => container.GetVerificationOrResolveScopeForCurrentThread(); + + protected override void SetCurrentScopeCore(Scope scope) => + scope.Container!.CurrentThreadResolveScope = scope; } } \ No newline at end of file diff --git a/src/SimpleInjector/Lifestyles/LifestyleSelectorScopedHybridLifestyle.cs b/src/SimpleInjector/Lifestyles/LifestyleSelectorScopedHybridLifestyle.cs index 3325c4979..abee62d18 100644 --- a/src/SimpleInjector/Lifestyles/LifestyleSelectorScopedHybridLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/LifestyleSelectorScopedHybridLifestyle.cs @@ -52,6 +52,9 @@ internal override int DependencyLength(Container container) => protected override Scope? GetCurrentScopeCore(Container container) => this.CurrentLifestyle(container).GetCurrentScope(container); + protected override void SetCurrentScopeCore(Scope scope) => + this.CurrentLifestyle(scope.Container!).SetCurrentScope(scope); + private ScopedLifestyle CurrentLifestyle(Container container) => this.selector(container) ? this.trueLifestyle : this.falseLifestyle; diff --git a/src/SimpleInjector/Lifestyles/ScopeManager.cs b/src/SimpleInjector/Lifestyles/ScopeManager.cs index db0204128..2a567a3d4 100644 --- a/src/SimpleInjector/Lifestyles/ScopeManager.cs +++ b/src/SimpleInjector/Lifestyles/ScopeManager.cs @@ -30,6 +30,8 @@ private Scope? CurrentScopeInternal set { this.scopeReplacer(value); } } + internal void SetCurrentScope(Scope scope) => this.CurrentScopeInternal = scope; + internal Scope BeginScope() => this.CurrentScopeInternal = new Scope(this.container, this, this.GetCurrentScopeWithAutoCleanup()); diff --git a/src/SimpleInjector/Lifestyles/ScopedProxyLifestyle.cs b/src/SimpleInjector/Lifestyles/ScopedProxyLifestyle.cs index 0e20d4c4c..93d6e27bd 100644 --- a/src/SimpleInjector/Lifestyles/ScopedProxyLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/ScopedProxyLifestyle.cs @@ -5,6 +5,8 @@ namespace SimpleInjector.Lifestyles { using System; + // This lifestyle is exposed by the Lifestyle.Scoped property and forwards any calls to the lifestyle + // configured in Container.Options.DefaultScopedLifestyle. internal sealed class ScopedProxyLifestyle : ScopedLifestyle { public ScopedProxyLifestyle() : base("Scoped") @@ -30,6 +32,9 @@ protected internal override Registration CreateRegistrationCore( protected override Scope? GetCurrentScopeCore(Container container) => GetDefaultScopedLifestyle(container).GetCurrentScope(container); + protected override void SetCurrentScopeCore(Scope scope) => + GetDefaultScopedLifestyle(scope.Container!).SetCurrentScope(scope); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] private static ScopedLifestyle GetDefaultScopedLifestyle(Container container) => container.Options.DefaultScopedLifestyle ?? ThrowDefaultScopeLifestyleIsNotSet(); diff --git a/src/SimpleInjector/Lifestyles/ThreadScopedLifestyle.cs b/src/SimpleInjector/Lifestyles/ThreadScopedLifestyle.cs index fd2114b19..de3b136c5 100644 --- a/src/SimpleInjector/Lifestyles/ThreadScopedLifestyle.cs +++ b/src/SimpleInjector/Lifestyles/ThreadScopedLifestyle.cs @@ -109,6 +109,10 @@ public static Scope BeginScope(Container container) protected override Scope? GetCurrentScopeCore(Container container) => GetScopeManager(container).CurrentScope; + /// + protected override void SetCurrentScopeCore(Scope scope) => + GetScopeManager(scope.Container!).SetCurrentScope(scope); + private static ScopeManager GetScopeManager(Container container) => container.ContainerScope.GetOrSetItem(ManagerKey, CreateManager); diff --git a/src/SimpleInjector/ScopedLifestyle.cs b/src/SimpleInjector/ScopedLifestyle.cs index b2db281bc..58ca47d85 100644 --- a/src/SimpleInjector/ScopedLifestyle.cs +++ b/src/SimpleInjector/ScopedLifestyle.cs @@ -92,6 +92,35 @@ public void RegisterForDisposal(Container container, IDisposable disposable) return this.GetCurrentScopeInternal(container); } + /// + /// Sets the given as current scope in the given context. + /// + /// The current scope. + /// Thrown when is a null reference. + /// Thrown when the is not related to + /// a . + /// Thrown when there is already an active scope in the + /// current context. + /// Thrown when the implementation does not support setting + /// the current scope. + public void SetCurrentScope(Scope scope) + { + Requires.IsNotNull(scope, nameof(scope)); + + if (scope.Container is null) + { + throw new ArgumentException("The scope has no related Container.", nameof(scope)); + } + + if (this.GetCurrentScope(scope.Container) != null) + { + throw new InvalidOperationException( + $"This method can only be called in case {nameof(GetCurrentScope)} returns null."); + } + + this.SetCurrentScopeCore(scope); + } + /// /// Creates a delegate that upon invocation return the current for this /// lifestyle and the given , or null when the delegate is executed outside @@ -140,6 +169,17 @@ protected internal override Registration CreateRegistrationCore(Type concreteTyp return currentScopeProvider.Invoke(); } + /// + /// Sets the given as current scope in the given context. + /// + /// The scope instance to set. + protected virtual void SetCurrentScopeCore(Scope scope) + { + throw new NotSupportedException( + $"Setting the current scope is not supported by the {this.Name} lifestyle " + + $"({this.GetType().ToFriendlyName()}."); + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] private Scope GetCurrentScopeOrThrow(Container container) {