diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs
new file mode 100644
index 0000000000..ca0a2133a9
--- /dev/null
+++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs
@@ -0,0 +1,752 @@
+using Castle.MicroKernel.Lifestyle;
+using Castle.MicroKernel.Registration;
+using Castle.Windsor.Extensions.DependencyInjection.Extensions;
+using Castle.Windsor.Extensions.DependencyInjection.Tests.Components;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Castle.Windsor.Extensions.DependencyInjection.Tests
+{
+ [CollectionDefinition(nameof(DoNotParallelize), DisableParallelization = true)]
+ public class DoNotParallelize { }
+
+ ///
+ /// These is the original Castle Windsor Dependency Injection behavior.
+ ///
+ public class ResolveFromThreadpoolUnsafe_NetStatic : AbstractResolveFromThreadpoolUnsafe
+ {
+ public ResolveFromThreadpoolUnsafe_NetStatic() : base()
+ {
+ }
+
+ #region "Singleton"
+
+ ///
+ /// This test will Succeed is we use standard Castle Windsor Singleton lifestyle instead of the custom
+ /// NetStatic lifestyle.
+ ///
+ [Fact]
+ public async Task Cannot_Resolve_LifestyleNetStatic_From_WindsorContainer_NoRootScopeAvailable()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifeStyle.NetStatic()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ // The test succeeds if we use standard Castle Windsor Singleton lifestyle instead of the custom NetStatic lifestyle.
+ Assert.NotNull(result);
+ });
+
+ // This test will fail if we use NetStatic lifestyle
+ Assert.NotNull(ex);
+ Assert.IsType(ex);
+ Assert.Equal("No root scope available", ex.Message);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Mapping NetStatic to usual Singleton lifestyle.
+ ///
+ public class ResolveFromThreadpoolUnsafe_Singleton : AbstractResolveFromThreadpoolUnsafe
+ {
+ public ResolveFromThreadpoolUnsafe_Singleton() : base()
+ {
+ }
+
+ #region "Singleton"
+
+ ///
+ /// This test will Succeed is we use standard Castle Windsor Singleton lifestyle instead of the custom
+ /// NetStatic lifestyle.
+ ///
+ [Fact]
+ public async Task Can_Resolve_LifestyleNetStatic_From_WindsorContainer()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifeStyle.Singleton
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ // The test succeeds if we use standard Castle Windsor Singleton lifestyle instead of the custom NetStatic lifestyle.
+ Assert.NotNull(result);
+ });
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ #endregion
+ }
+
+ ///
+ /// relying on static state (WindsorDependencyInjectionOptions) is not good for tests
+ /// that might run in parallel, can lead to false positives / negatives.
+ ///
+ [Collection(nameof(DoNotParallelize))]
+ public abstract class AbstractResolveFromThreadpoolUnsafe
+ {
+ #region Singleton
+
+ /*
+ * Singleton tests should never fail, given you have a container instance you should always
+ * be able to resolve a singleton from it.
+ */
+
+ [Fact]
+ public async Task Can_Resolve_LifestyleSingleton_From_ServiceProvider()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ [Fact]
+ public async Task Can_Resolve_LifestyleSingleton_From_WindsorContainer()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ [Fact]
+ public async Task Can_Resolve_LifestyleNetStatic_From_ServiceProvider()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifeStyle.NetStatic()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ #endregion
+
+ #region Scoped
+
+ /*
+ * Scoped tests might fail if for whatever reason you do not have a current scope
+ * (like when you run from Threadpool.UnsafeQueueUserWorkItem).
+ */
+
+ ///
+ /// This test will fail because the service provider adapter
+ /// does not create a standard Castle Windsor scope
+ ///
+ [Fact]
+ public async Task Cannot_Resolve_LifestyleScoped_From_ServiceProvider()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleScoped()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ // must create a standard Castle Windsor scope (not managed by the adapter)
+ using (var s = container.BeginScope())
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+ });
+
+ Assert.NotNull(ex);
+ Assert.IsType(ex);
+ Assert.StartsWith("Scope was not available. Did you forget to call container.BeginScope()?", ex.Message);
+ }
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ ///
+ /// This test will fail because the service provider adapter
+ /// does not create a standard Castle Windsor scope
+ ///
+ [Fact]
+ public async Task Cannot_Resolve_LifestyleScoped_From_WindsorContainer()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleScoped()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ // must create a standard Castle Windsor scope (not managed by the adapter)
+ using (var s = container.BeginScope())
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+ });
+
+ Assert.NotNull(ex);
+ Assert.IsType(ex);
+ Assert.StartsWith("Scope was not available. Did you forget to call container.BeginScope()?", ex.Message);
+ }
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ ///
+ /// This test succeeds because WindsorScopedServiceProvider captured the root scope on creation
+ /// and forced it to be current before service resolution.
+ /// Scoped is tied to the rootscope = potential memory leak.
+ ///
+ [Fact]
+ public async Task Can_Resolve_LifestyleScopedToNetServiceScope_From_ServiceProvider_MemoryLeak()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifeStyle.ScopedToNetServiceScope()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ [Fact]
+ public async Task Cannot_Resolve_LifestyleScopedToNetServiceScope_From_WindsorContainer()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifeStyle.ScopedToNetServiceScope()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+ });
+
+ Assert.NotNull(ex);
+ Assert.IsType(ex);
+ Assert.StartsWith("No scope available", ex.Message);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ #endregion
+
+ #region Transient
+
+ /*
+ * Transient tests failure is questionable:
+ * - if you have a container you should be able to resolve transient without a scope,
+ * but they might be tracked by the container itself (or the IServiceProvider)
+ * - when windsor container is disposed all transient services are disposed as well
+ * - when a IServiceProvider is disposed all transient services (created by it) are disposed as well
+ * - problem is: we have una instance of a windsor container passed on to multiple instances of IServiceProvider
+ * one solution will be to tie the Transients to a scope, and the scope is tied to service provider
+ * when both of them are disposed, the transient services are disposed as well
+ */
+
+ [Fact]
+ public async Task Can_Resolve_LifestyleTransient_From_ServiceProvider()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleTransient()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ [Fact]
+ public async Task Can_Resolve_LifestyleTransient_From_WindsorContainer()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleTransient()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ ///
+ /// This test succeeds because WindsorScopedServiceProvider captured the root scope on creation
+ /// and forced it to be current before service resolution.
+ /// Transient is tied to the rootscope = potential memory leak.
+ ///
+ [Fact]
+ public async Task Can_Resolve_LifestyleNetTransient_From_ServiceProvider_MemoryLeak()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleNetTransient()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ [Fact]
+ public async Task Cannot_Resolve_LifestyleNetTransient_From_WindsorContainer_NoScopeAvailable()
+ {
+ var serviceProvider = new ServiceCollection();
+ var container = new WindsorContainer();
+ var f = new WindsorServiceProviderFactory(container);
+ f.CreateBuilder(serviceProvider);
+
+ container.Register(
+ Component.For().ImplementedBy().LifestyleNetTransient()
+ );
+
+ IServiceProvider sp = f.CreateServiceProvider(container);
+
+ var actualUserService = sp.GetService();
+ Assert.NotNull(actualUserService);
+
+ TaskCompletionSource tcs = new TaskCompletionSource();
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ try
+ {
+ var actualUserService = container.Resolve();
+ Assert.NotNull(actualUserService);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ tcs.SetResult(actualUserService);
+ }, null);
+
+ // Wait for the work item to complete.
+ var ex = await Catches.ExceptionAsync(async () =>
+ {
+ var task = tcs.Task;
+ IUserService result = await task;
+ Assert.NotNull(result);
+ });
+
+ Assert.NotNull(ex);
+ Assert.IsType(ex);
+ Assert.StartsWith("No scope available", ex.Message);
+
+ (sp as IDisposable)?.Dispose();
+ container.Dispose();
+ }
+
+ #endregion
+
+ /*
+ * Missing tests: we should also test what happens with injected IServiceProvider (what scope do they get?)
+ * Injected IServiceProvider might or might not have a scope (it depends on AsyncLocal value).
+ */
+ }
+
+ public static class Catches
+ {
+ public static Exception Exception(Action action)
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ return null;
+ }
+
+ public async static Task ExceptionAsync(Func func)
+ {
+ try
+ {
+ await func();
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs
deleted file mode 100644
index 95f29a07d3..0000000000
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2004-2020 Castle Project - http://www.castleproject.org/
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-namespace Castle.Windsor.Extensions.DependencyInjection.Scope
-{
- internal class ExtensionContainerRootScope : ExtensionContainerScopeBase
- {
-
- public static ExtensionContainerRootScope BeginRootScope()
- {
- var scope = new ExtensionContainerRootScope();
- ExtensionContainerScopeCache.Current = scope;
- return scope;
- }
-
- internal override ExtensionContainerScopeBase RootScope => this;
- }
-}
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs
index 25139efec9..8ebea69573 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs
@@ -23,7 +23,7 @@ internal class ExtensionContainerRootScopeAccessor : IScopeAccessor
{
public ILifetimeScope GetScope(CreationContext context)
{
- return ExtensionContainerScopeCache.Current.RootScope ?? throw new InvalidOperationException("No root scope available");
+ return ExtensionContainerScopeCache.current.Value?.RootScope ?? throw new InvalidOperationException("No root scope available");
}
public void Dispose()
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs
index 2ff5a5c497..6df162c1f4 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs
@@ -20,15 +20,12 @@ internal class ExtensionContainerScope : ExtensionContainerScopeBase
protected ExtensionContainerScope()
{
- parent = ExtensionContainerScopeCache.Current;
+ parent = ExtensionContainerScopeCache.current.Value;
}
- internal override ExtensionContainerScopeBase RootScope { get; set; }
-
-
- internal static ExtensionContainerScopeBase BeginScope()
+ internal static ExtensionContainerScopeBase BeginScope(ExtensionContainerScopeBase rootScope = null)
{
- var scope = new ExtensionContainerScope { RootScope = ExtensionContainerScopeCache.Current.RootScope };
+ var scope = new ExtensionContainerScope { RootScope = rootScope };
ExtensionContainerScopeCache.Current = scope;
return scope;
}
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs
index c9d41dfa66..b24ccfbd52 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs
@@ -25,13 +25,13 @@ internal class ForcedScope : IDisposable
private readonly ExtensionContainerScopeBase previousScope;
internal ForcedScope(ExtensionContainerScopeBase scope)
{
- previousScope = ExtensionContainerScopeCache.Current;
+ previousScope = ExtensionContainerScopeCache.current.Value;
this.scope = scope;
ExtensionContainerScopeCache.Current = scope;
}
public void Dispose()
{
- if(ExtensionContainerScopeCache.Current != scope) return;
+ if(ExtensionContainerScopeCache.Current != scope || previousScope == null) return;
ExtensionContainerScopeCache.Current = previousScope;
}
}
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
index 0686ea95a6..5545560b33 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs
@@ -24,15 +24,17 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope
internal class WindsorScopeFactory : IServiceScopeFactory
{
private readonly IWindsorContainer scopeFactoryContainer;
-
- public WindsorScopeFactory(IWindsorContainer container)
+ private readonly ExtensionContainerScopeBase rootScope;
+
+ public WindsorScopeFactory(IWindsorContainer container, ExtensionContainerScopeBase rootScope)
{
scopeFactoryContainer = container;
+ this.rootScope = rootScope;
}
public IServiceScope CreateScope()
{
- var scope = ExtensionContainerScope.BeginScope();
+ var scope = ExtensionContainerScope.BeginScope(rootScope);
//since WindsorServiceProvider is scoped, this gives us new instance
var provider = scopeFactoryContainer.Resolve();
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs
index 4e61f2f765..0442753462 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs
@@ -55,7 +55,7 @@ public object GetRequiredService(Type serviceType)
public void Dispose()
{
- if (!(scope is ExtensionContainerRootScope)) return;
+ if (scope != scope.RootScope) return;
if (disposing) return;
disposing = true;
var disposableScope = scope as IDisposable;
diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs
index 21363c4fd9..0a4efca449 100644
--- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs
+++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs
@@ -27,7 +27,7 @@ namespace Castle.Windsor.Extensions.DependencyInjection
public abstract class WindsorServiceProviderFactoryBase : IServiceProviderFactory
{
- internal ExtensionContainerRootScope rootScope;
+ internal ExtensionContainerScopeBase rootScope;
protected IWindsorContainer rootContainer;
public virtual IWindsorContainer Container => rootContainer;
@@ -44,7 +44,8 @@ public virtual IServiceProvider CreateServiceProvider(IWindsorContainer containe
protected virtual void CreateRootScope()
{
- rootScope = ExtensionContainerRootScope.BeginRootScope();
+ rootScope = ExtensionContainerScope.BeginScope();
+ rootScope.RootScope = rootScope;
}
protected virtual void CreateRootContainer()
@@ -111,7 +112,7 @@ protected virtual void RegisterFactories(IWindsorContainer container)
container.Register(Component
.For()
.ImplementedBy()
- .DependsOn(Dependency.OnValue(rootScope))
+ .DependsOn(Dependency.OnValue(rootScope))
.LifestyleSingleton(),
Component
.For>()