diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs new file mode 100644 index 0000000000..e253d86614 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/WindsorScopedServiceProviderCustomWindsorContainerTests.cs @@ -0,0 +1,31 @@ +// 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.Tests +{ + using System; + + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Specification; + + public class WindsorScopedServiceProviderCustomWindsorContainerTests : DependencyInjectionSpecificationTests + { + protected override IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection) + { + var factory = new WindsorServiceProviderFactory(new WindsorContainer()); + var container = factory.CreateBuilder(serviceCollection); + return factory.CreateServiceProvider(container); + } + } +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj b/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj index dd78ff03d9..1af0d01e46 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Castle.Windsor.Extensions.DependencyInjection.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 9.0 diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs index 38e23c1759..38d46d34d7 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs @@ -39,7 +39,7 @@ public static ComponentRegistration ScopedToNetServiceScope( public static ComponentRegistration LifestyleNetTransient(this ComponentRegistration registration) where TService : class { return registration - .Attribute(ExtensionContainerScope.TransientMarker).Eq(Boolean.TrueString) + .Attribute(ExtensionContainerScopeBase.TransientMarker).Eq(Boolean.TrueString) .LifeStyle.ScopedToNetServiceScope(); //.NET core expects new instances but release on scope dispose } diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs index 5869163f2f..95f29a07d3 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs @@ -14,19 +14,16 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { - internal class ExtensionContainerRootScope : ExtensionContainerScope + internal class ExtensionContainerRootScope : ExtensionContainerScopeBase { - internal static ExtensionContainerRootScope RootScope {get; private set;} - private ExtensionContainerRootScope() : base(null) - { - - } + public static ExtensionContainerRootScope BeginRootScope() { var scope = new ExtensionContainerRootScope(); - ExtensionContainerScope.current.Value = scope; - RootScope = scope; + ExtensionContainerScopeCache.Current = scope; return scope; } + + internal override ExtensionContainerScopeBase RootScope => this; } -} \ No newline at end of file +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs index d51a45b628..25139efec9 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs @@ -15,7 +15,7 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { using System; - + using Castle.MicroKernel.Context; using Castle.MicroKernel.Lifestyle.Scoped; @@ -23,16 +23,11 @@ internal class ExtensionContainerRootScopeAccessor : IScopeAccessor { public ILifetimeScope GetScope(CreationContext context) { - if (ExtensionContainerRootScope.RootScope == null) - { - throw new InvalidOperationException("No root scope"); - } - - return ExtensionContainerRootScope.RootScope; + return ExtensionContainerScopeCache.Current.RootScope ?? throw new InvalidOperationException("No root scope available"); } public void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs index 807fdcbe5c..2ff5a5c497 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs @@ -1,4 +1,4 @@ -// Copyright 2004-2020 Castle Project - http://www.castleproject.org/ +// 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. @@ -14,92 +14,32 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { - using System; - using System.Threading; - - using Castle.Core; - using Castle.MicroKernel; - using Castle.MicroKernel.Lifestyle.Scoped; - - internal class ExtensionContainerScope : ILifetimeScope, IDisposable + internal class ExtensionContainerScope : ExtensionContainerScopeBase { - public static ExtensionContainerScope Current => current.Value; - public static string TransientMarker = "Transient"; - protected static readonly AsyncLocal current = new AsyncLocal(); - private readonly ExtensionContainerScope parent; - private readonly IScopeCache scopeCache; - - protected ExtensionContainerScope(ExtensionContainerScope parent) - { - scopeCache = new ScopeCache(); - if(parent == null) - { - this.parent = ExtensionContainerRootScope.RootScope; - } - else - { - this.parent = parent; - } - } + private readonly ExtensionContainerScopeBase parent; - public static ExtensionContainerScope BeginScope(ExtensionContainerScope parent) + protected ExtensionContainerScope() { - var scope = new ExtensionContainerScope(parent); - current.Value = scope; - return scope; + parent = ExtensionContainerScopeCache.Current; } + internal override ExtensionContainerScopeBase RootScope { get; set; } - public void Dispose() - { - var disposableCache = scopeCache as IDisposable; - if (disposableCache != null) - { - disposableCache.Dispose(); - } - - current.Value = parent; - } - public Burden GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance) + internal static ExtensionContainerScopeBase BeginScope() { - lock (scopeCache) - { - - // Add transient's burden to scope so it gets released - if (model.Configuration.Attributes.Get(TransientMarker) == bool.TrueString) - { - var transientBurden = createInstance((_) => {}); - scopeCache[transientBurden] = transientBurden; - return transientBurden; - } - - var scopedBurden = scopeCache[model]; - if (scopedBurden != null) - { - return scopedBurden; - } - scopedBurden = createInstance((_) => {}); - scopeCache[model] = scopedBurden; - return scopedBurden; - } + var scope = new ExtensionContainerScope { RootScope = ExtensionContainerScopeCache.Current.RootScope }; + ExtensionContainerScopeCache.Current = scope; + return scope; } - /// - /// Forces a specific for 'using' block. In .NET scope is tied to an instance of not a thread or async context - /// - internal class ForcedScope : IDisposable + public override void Dispose() { - private readonly ExtensionContainerScope previousScope; - public ForcedScope(ExtensionContainerScope scope) - { - previousScope = ExtensionContainerScope.Current; - ExtensionContainerScope.current.Value = scope; - } - public void Dispose() + if (ExtensionContainerScopeCache.current.Value == this) { - ExtensionContainerScope.current.Value = previousScope; + ExtensionContainerScopeCache.current.Value = parent; } + base.Dispose(); } } } \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs index cf6621fdab..9042944d75 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs @@ -14,8 +14,6 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { - using System; - using Castle.MicroKernel.Context; using Castle.MicroKernel.Lifestyle.Scoped; @@ -23,11 +21,7 @@ internal class ExtensionContainerScopeAccessor : IScopeAccessor { public ILifetimeScope GetScope(CreationContext context) { - if(ExtensionContainerScope.Current == null) - { - throw new InvalidOperationException("No scope available"); - } - return ExtensionContainerScope.Current; + return ExtensionContainerScopeCache.Current; } public void Dispose() diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs new file mode 100644 index 0000000000..edb784dfcb --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs @@ -0,0 +1,66 @@ +// 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 +{ + using System; + + using Castle.Core; + using Castle.MicroKernel; + using Castle.MicroKernel.Lifestyle.Scoped; + + internal abstract class ExtensionContainerScopeBase : ILifetimeScope + { + public static readonly string TransientMarker = "Transient"; + private readonly IScopeCache scopeCache; + + protected ExtensionContainerScopeBase() + { + scopeCache = new ScopeCache(); + } + + internal virtual ExtensionContainerScopeBase RootScope { get; set; } + + public virtual void Dispose() + { + if (scopeCache is IDisposable disposableCache) + { + disposableCache.Dispose(); + } + } + + public Burden GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance) + { + lock (scopeCache) + { + // Add transient's burden to scope so it gets released + if (model.Configuration.Attributes.Get(TransientMarker) == bool.TrueString) + { + var transientBurden = createInstance(_ => {}); + scopeCache[transientBurden] = transientBurden; + return transientBurden; + } + + var scopedBurden = scopeCache[model]; + if (scopedBurden != null) + { + return scopedBurden; + } + scopedBurden = createInstance((_) => {}); + scopeCache[model] = scopedBurden; + return scopedBurden; + } + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeCache.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeCache.cs new file mode 100644 index 0000000000..d89f9dd1f2 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeCache.cs @@ -0,0 +1,31 @@ +// 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 +{ + using System; + using System.Threading; + + internal static class ExtensionContainerScopeCache + { + internal static readonly AsyncLocal current = new AsyncLocal(); + /// Current scope for the thread. Initial scope will be set when calling BeginRootScope from a ExtensionContainerRootScope instance. + /// Thrown when there is no scope available. + internal static ExtensionContainerScopeBase Current + { + get => current.Value ?? throw new InvalidOperationException("No scope available"); + set => current.Value = value; + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs new file mode 100644 index 0000000000..c9d41dfa66 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs @@ -0,0 +1,38 @@ +// 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 +{ + using System; + + /// + /// Forces a specific for 'using' block. In .NET scope is tied to an instance of not a thread or async context + /// + internal class ForcedScope : IDisposable + { + private readonly ExtensionContainerScopeBase scope; + private readonly ExtensionContainerScopeBase previousScope; + internal ForcedScope(ExtensionContainerScopeBase scope) + { + previousScope = ExtensionContainerScopeCache.Current; + this.scope = scope; + ExtensionContainerScopeCache.Current = scope; + } + public void Dispose() + { + if(ExtensionContainerScopeCache.Current != scope) return; + ExtensionContainerScopeCache.Current = previousScope; + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ServiceScope.cs similarity index 67% rename from src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs rename to src/Castle.Windsor.Extensions.DependencyInjection/Scope/ServiceScope.cs index e8a0d24d43..c033687851 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ServiceScope.cs @@ -12,34 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Castle.Windsor.Extensions.DependencyInjection +namespace Castle.Windsor.Extensions.DependencyInjection.Scope { using System; - + using Microsoft.Extensions.DependencyInjection; internal class ServiceScope : IServiceScope { private readonly IDisposable scope; - private readonly IServiceProvider serviceProvider; public ServiceScope(IDisposable windsorScope, IServiceProvider serviceProvider) { - if(windsorScope == null) - { - throw new ArgumentNullException(nameof(scope)); - } - - if(serviceProvider == null) - { - throw new ArgumentNullException(nameof(serviceProvider)); - } - - this.scope = windsorScope; - this.serviceProvider = serviceProvider; + scope = windsorScope ?? throw new ArgumentNullException(nameof(scope)); + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } - public IServiceProvider ServiceProvider => serviceProvider; + public IServiceProvider ServiceProvider { get; } public void Dispose() { diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs similarity index 84% rename from src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs rename to src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs index 0bfe472860..0686ea95a6 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs @@ -13,19 +13,18 @@ // limitations under the License. -namespace Castle.Windsor.Extensions.DependencyInjection +namespace Castle.Windsor.Extensions.DependencyInjection.Scope { using System; - + using Castle.Windsor; - using Castle.Windsor.Extensions.DependencyInjection.Scope; - + using Microsoft.Extensions.DependencyInjection; internal class WindsorScopeFactory : IServiceScopeFactory { private readonly IWindsorContainer scopeFactoryContainer; - + public WindsorScopeFactory(IWindsorContainer container) { scopeFactoryContainer = container; @@ -33,12 +32,12 @@ public WindsorScopeFactory(IWindsorContainer container) public IServiceScope CreateScope() { - var scope = ExtensionContainerScope.BeginScope(ExtensionContainerScope.Current); - + var scope = ExtensionContainerScope.BeginScope(); + //since WindsorServiceProvider is scoped, this gives us new instance var provider = scopeFactoryContainer.Resolve(); return new ServiceScope(scope, provider); } } -} \ No newline at end of file +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs index cbbc331b33..4e61f2f765 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs @@ -26,20 +26,20 @@ namespace Castle.Windsor.Extensions.DependencyInjection internal class WindsorScopedServiceProvider : IServiceProvider, ISupportRequiredService, IDisposable { - private readonly ExtensionContainerScope scope; - private bool disposing = false; + private readonly ExtensionContainerScopeBase scope; + private bool disposing; private readonly IWindsorContainer container; public WindsorScopedServiceProvider(IWindsorContainer container) { this.container = container; - this.scope = ExtensionContainerScope.Current; + scope = ExtensionContainerScopeCache.Current; } public object GetService(Type serviceType) { - using(var fs = new ExtensionContainerScope.ForcedScope(scope)) + using(_ = new ForcedScope(scope)) { return ResolveInstanceOrNull(serviceType, true); } @@ -47,7 +47,7 @@ public object GetService(Type serviceType) public object GetRequiredService(Type serviceType) { - using(var fs = new ExtensionContainerScope.ForcedScope(scope)) + using(_ = new ForcedScope(scope)) { return ResolveInstanceOrNull(serviceType, false); } @@ -55,20 +55,12 @@ public object GetRequiredService(Type serviceType) public void Dispose() { - if(scope is ExtensionContainerRootScope) - { - if(!disposing) - { - disposing = true; - var disposableScope = scope as IDisposable; - if(disposableScope != null) - { - disposableScope.Dispose(); - } - container.Dispose(); - } - - } + if (!(scope is ExtensionContainerRootScope)) return; + if (disposing) return; + disposing = true; + var disposableScope = scope as IDisposable; + disposableScope?.Dispose(); + container.Dispose(); } private object ResolveInstanceOrNull(Type serviceType, bool isOptional) { diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactory.cs index 75bb7a0a36..b312ab974b 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactory.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactory.cs @@ -21,14 +21,14 @@ public sealed class WindsorServiceProviderFactory : WindsorServiceProviderFactor public WindsorServiceProviderFactory() { - CreateRootContainer(); CreateRootScope(); + CreateRootContainer(); } public WindsorServiceProviderFactory(IWindsorContainer container) { - SetRootContainer(container); CreateRootScope(); + SetRootContainer(container); } } -} \ No newline at end of file +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs index 890e9e52e5..21363c4fd9 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs @@ -46,7 +46,7 @@ protected virtual void CreateRootScope() { rootScope = ExtensionContainerRootScope.BeginRootScope(); } - + protected virtual void CreateRootContainer() { SetRootContainer(new WindsorContainer()); @@ -82,7 +82,7 @@ protected virtual IWindsorContainer BuildContainer(IServiceCollection serviceCol RegisterContainer(rootContainer); RegisterProviders(rootContainer); RegisterFactories(rootContainer); - + AddSubResolvers(); RegisterServiceCollection(serviceCollection, windsorContainer); @@ -111,6 +111,7 @@ protected virtual void RegisterFactories(IWindsorContainer container) container.Register(Component .For() .ImplementedBy() + .DependsOn(Dependency.OnValue(rootScope)) .LifestyleSingleton(), Component .For>() @@ -133,4 +134,4 @@ protected virtual void AddSubResolvers() rootContainer.Kernel.Resolver.AddSubResolver(new LoggerDependencyResolver(rootContainer.Kernel)); } } -} \ No newline at end of file +}