diff --git a/.gitignore b/.gitignore index 17b24b2466..253f845d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ _ReSharper*/ *.csproj.ReSharper .cr +.idea # Build output build/ diff --git a/Castle.Windsor.sln b/Castle.Windsor.sln index b2e7f6ee36..202d7ae4de 100644 --- a/Castle.Windsor.sln +++ b/Castle.Windsor.sln @@ -64,6 +64,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildScripts", "BuildScript EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Castle.Windsor.Extensions.Hosting", "src\Castle.Windsor.Extensions.Hosting\Castle.Windsor.Extensions.Hosting.csproj", "{4F123B9A-F9B3-4F81-A0EC-8C8DEF03298A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp", "src\Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp\Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp.csproj", "{418B8D35-3225-45B6-AAE0-B1C4B3303CFE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +144,10 @@ Global {4F123B9A-F9B3-4F81-A0EC-8C8DEF03298A}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F123B9A-F9B3-4F81-A0EC-8C8DEF03298A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F123B9A-F9B3-4F81-A0EC-8C8DEF03298A}.Release|Any CPU.Build.0 = Release|Any CPU + {418B8D35-3225-45B6-AAE0-B1C4B3303CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {418B8D35-3225-45B6-AAE0-B1C4B3303CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {418B8D35-3225-45B6-AAE0-B1C4B3303CFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {418B8D35-3225-45B6-AAE0-B1C4B3303CFE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -164,6 +170,7 @@ Global {7C2F5733-0E06-4201-A15A-1ED1F3308DB4} = {7935AFF5-BF6D-4D59-8D66-058B6557F70F} {DD7F7887-F27C-4C52-AA41-0F386D4D9996} = {7E507A42-984B-470D-8A0C-648B9AF8E1DC} {4F123B9A-F9B3-4F81-A0EC-8C8DEF03298A} = {7935AFF5-BF6D-4D59-8D66-058B6557F70F} + {418B8D35-3225-45B6-AAE0-B1C4B3303CFE} = {7935AFF5-BF6D-4D59-8D66-058B6557F70F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CC2A1EB6-49EC-4064-AD8B-29439E8A45E4} diff --git a/Castle.Windsor.sln.DotSettings b/Castle.Windsor.sln.DotSettings index 2a21d2fed7..6b04a377f5 100644 --- a/Castle.Windsor.sln.DotSettings +++ b/Castle.Windsor.sln.DotSettings @@ -267,6 +267,7 @@ limitations under the License. True True True + True True True True diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp.csproj b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp.csproj new file mode 100644 index 0000000000..18b78daac6 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.1;net6.0 + + false + + Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp + Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp + + + + + + + diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/DecoratedUserService.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/DecoratedUserService.cs new file mode 100644 index 0000000000..3c308c5f7b --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/DecoratedUserService.cs @@ -0,0 +1,26 @@ +// 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.AspNetCoreApp.Components +{ + public class DecoratedUserService : UserService + { + public IUserService UserService { get; } + + public DecoratedUserService(IUserService userService) + { + UserService = userService; + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/IUserService.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/IUserService.cs new file mode 100644 index 0000000000..cf42352e15 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/IUserService.cs @@ -0,0 +1,21 @@ +// 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.AspNetCoreApp.Components +{ + public interface IUserService + { + + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/UserService.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/UserService.cs new file mode 100644 index 0000000000..b20841e0ea --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Components/UserService.cs @@ -0,0 +1,21 @@ +// 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.AspNetCoreApp.Components +{ + public class UserService : IUserService + { + + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Program.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Program.cs new file mode 100644 index 0000000000..16fc933249 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp/Program.cs @@ -0,0 +1,85 @@ +// Copyright 2004-2023 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.AspNetCoreApp +{ + using Microsoft.AspNetCore.Builder; +#if NETCOREAPP3_1 + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; +#endif + using Microsoft.Extensions.Hosting; + + public class Program + { +#if NET6_0 + public static void Main(string[] args) + { + // create web application builder + WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + + // configure container + WindsorContainer container = new WindsorContainer(); + WindsorServiceProviderFactory factory = new WindsorServiceProviderFactory(container); + IHostBuilder _ = builder.Host.UseServiceProviderFactory(factory); + + // configure pipeline + WebApplication app = builder.Build(); + app.MapGet("/", () => "Hello world"); + + // start + app.Run(); + } +#elif NETCOREAPP3_1 + public static void Main(string[] args) + { + // create host builder + IHost app = + CreateHostBuilder(args) + .Build(); + + // start + app.Run(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) + { + WindsorContainer container = new WindsorContainer(); + WindsorServiceProviderFactory factory = new WindsorServiceProviderFactory(container); + return Host + .CreateDefaultBuilder(args) + .UseServiceProviderFactory(factory) + .ConfigureWebHostDefaults(hostBuilder => hostBuilder.UseStartup()); + } + + public class Startup + { + public void ConfigureServices(IServiceCollection serviceCollection) + { + } + + public void Configure(IApplicationBuilder app) + { + app.Map( + string.Empty, + appBuilder => + { + appBuilder.Run(async (context) => await context.Response.WriteAsync("Hello world")); + }); + } + } +#endif + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/AspNetCoreScopeTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/AspNetCoreScopeTests.cs new file mode 100644 index 0000000000..0543a5051b --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/AspNetCoreScopeTests.cs @@ -0,0 +1,39 @@ +// Copyright 2004-2023 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.Net; + using System.Net.Http; + using System.Threading.Tasks; + + using Castle.Windsor.Extensions.DependencyInjection.Tests.AspNetCoreApp; + + using Microsoft.AspNetCore.Mvc.Testing; + + using Xunit; + + public class AspNetCoreScopeTests + { + [Fact] + public async Task TestScopeIsAvailable() + { + WebApplicationFactory factory = new WebApplicationFactory(); + HttpClient client = factory.CreateClient(); + HttpResponseMessage response = await client.GetAsync("/"); + Assert.NotNull(response); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj index 6ef705103a..1b052eca3b 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/Castle.Windsor.Extensions.DependencyInjection.Tests.csproj @@ -20,17 +20,22 @@ - - + + + - + + + + + 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..754ea0381f --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs @@ -0,0 +1,56 @@ +using Castle.MicroKernel.Registration; +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 { + public class ResolveFromThreadpoolUnsafe { + [Fact] + public async Task Can_Resolve_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); + + /* + ThreadPool.UnsafeQueueUserWorkItem(state => { + // resolving using castle (without scopes) works + var actualUserService = container.Resolve(nameof(UserService)); + Assert.NotNull(actualUserService); + }, null); + */ + + 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); + } + } +} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/SkippableDependencyInjectionSpecificationTests.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/SkippableDependencyInjectionSpecificationTests.cs index 12537699c6..8304da2931 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/SkippableDependencyInjectionSpecificationTests.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/SkippableDependencyInjectionSpecificationTests.cs @@ -19,12 +19,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Diagnostics; -using System.Linq; - +// ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection.Specification { + using System; + using System.Linq; + + using StackTrace = System.Diagnostics.StackTrace; + public abstract class SkippableDependencyInjectionSpecificationTests : DependencyInjectionSpecificationTests { public string[] SkippedTests => new[] { "SingletonServiceCanBeResolvedFromScope" }; diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Extensions/WindsorExtensions.cs index 38d46d34d7..38e23c1759 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(ExtensionContainerScopeBase.TransientMarker).Eq(Boolean.TrueString) + .Attribute(ExtensionContainerScope.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 95f29a07d3..d2358270b5 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs @@ -14,16 +14,17 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { - internal class ExtensionContainerRootScope : ExtensionContainerScopeBase + internal class ExtensionContainerRootScope : ExtensionContainerScope { - + private ExtensionContainerRootScope() : base(null, null) + { + } + public static ExtensionContainerRootScope BeginRootScope() { var scope = new ExtensionContainerRootScope(); - ExtensionContainerScopeCache.Current = scope; + current.Value = 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..e15b5455e0 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs @@ -23,7 +23,17 @@ internal class ExtensionContainerRootScopeAccessor : IScopeAccessor { public ILifetimeScope GetScope(CreationContext context) { - return ExtensionContainerScopeCache.Current.RootScope ?? throw new InvalidOperationException("No root scope available"); + if (ExtensionContainerScope.Current == null) + { + throw new InvalidOperationException("No root scope"); + } + + if (ExtensionContainerScope.Current.RootScope == null) + { + throw new InvalidOperationException("No root scope"); + } + + return ExtensionContainerScope.Current.RootScope; } 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..ead6cec9ed 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,32 +14,93 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { - internal class ExtensionContainerScope : ExtensionContainerScopeBase + using System; + using System.Threading; + + using Castle.Core; + using Castle.MicroKernel; + using Castle.MicroKernel.Lifestyle.Scoped; + + internal class ExtensionContainerScope : ILifetimeScope, IDisposable { - private readonly ExtensionContainerScopeBase parent; + public static ExtensionContainerScope Current => current.Value; + public static string TransientMarker = "Transient"; + protected static readonly AsyncLocal current = new AsyncLocal(); + private readonly ExtensionContainerScope parent; + private readonly ExtensionContainerRootScope rootScope; + private readonly IScopeCache scopeCache; - protected ExtensionContainerScope() + protected ExtensionContainerScope( + ExtensionContainerScope parent, + ExtensionContainerRootScope rootScope) { - parent = ExtensionContainerScopeCache.Current; + scopeCache = new ScopeCache(); + this.parent = parent ?? rootScope; + this.rootScope = rootScope; } - internal override ExtensionContainerScopeBase RootScope { get; set; } - + public ExtensionContainerRootScope RootScope + => this as ExtensionContainerRootScope ?? rootScope; - internal static ExtensionContainerScopeBase BeginScope() + public static ExtensionContainerScope BeginScope(ExtensionContainerScope parent, ExtensionContainerRootScope rootScope) { - var scope = new ExtensionContainerScope { RootScope = ExtensionContainerScopeCache.Current.RootScope }; - ExtensionContainerScopeCache.Current = scope; + if (rootScope == null) + throw new ArgumentNullException(nameof(rootScope)); + + var scope = new ExtensionContainerScope(parent, rootScope); + current.Value = scope; return scope; } - public override void Dispose() + public void Dispose() + { + var disposableCache = scopeCache as IDisposable; + if (disposableCache != null) + { + disposableCache.Dispose(); + } + + current.Value = parent; + } + + public Burden GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance) { - if (ExtensionContainerScopeCache.current.Value == this) + 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; + } + } + + /// + /// 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 ExtensionContainerScope previousScope; + public ForcedScope(ExtensionContainerScope scope) + { + previousScope = ExtensionContainerScope.Current; + ExtensionContainerScope.current.Value = scope; + } + public void Dispose() { - ExtensionContainerScopeCache.current.Value = parent; + ExtensionContainerScope.current.Value = previousScope; } - 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 9042944d75..cf6621fdab 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeAccessor.cs @@ -14,6 +14,8 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope { + using System; + using Castle.MicroKernel.Context; using Castle.MicroKernel.Lifestyle.Scoped; @@ -21,7 +23,11 @@ internal class ExtensionContainerScopeAccessor : IScopeAccessor { public ILifetimeScope GetScope(CreationContext context) { - return ExtensionContainerScopeCache.Current; + if(ExtensionContainerScope.Current == null) + { + throw new InvalidOperationException("No scope available"); + } + return ExtensionContainerScope.Current; } public void Dispose() diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs deleted file mode 100644 index edb784dfcb..0000000000 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeBase.cs +++ /dev/null @@ -1,66 +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 -{ - 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 deleted file mode 100644 index d89f9dd1f2..0000000000 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScopeCache.cs +++ /dev/null @@ -1,31 +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 -{ - 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 deleted file mode 100644 index c9d41dfa66..0000000000 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs +++ /dev/null @@ -1,38 +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 -{ - 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/Scope/ServiceScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs similarity index 67% rename from src/Castle.Windsor.Extensions.DependencyInjection/Scope/ServiceScope.cs rename to src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs index c033687851..e8a0d24d43 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ServiceScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/ServiceScope.cs @@ -12,23 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Castle.Windsor.Extensions.DependencyInjection.Scope +namespace Castle.Windsor.Extensions.DependencyInjection { using System; - + using Microsoft.Extensions.DependencyInjection; internal class ServiceScope : IServiceScope { private readonly IDisposable scope; + private readonly IServiceProvider serviceProvider; public ServiceScope(IDisposable windsorScope, IServiceProvider serviceProvider) { - scope = windsorScope ?? throw new ArgumentNullException(nameof(scope)); - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + if(windsorScope == null) + { + throw new ArgumentNullException(nameof(scope)); + } + + if(serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + this.scope = windsorScope; + this.serviceProvider = serviceProvider; } - public IServiceProvider ServiceProvider { get; } + public IServiceProvider ServiceProvider => serviceProvider; public void Dispose() { diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs similarity index 72% rename from src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs rename to src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs index 0686ea95a6..4264ff28fb 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopeFactory.cs @@ -13,26 +13,35 @@ // limitations under the License. -namespace Castle.Windsor.Extensions.DependencyInjection.Scope +namespace Castle.Windsor.Extensions.DependencyInjection { 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) + private readonly ExtensionContainerRootScope rootScope; + + public WindsorScopeFactory( + IWindsorContainer container, + ExtensionContainerRootScope rootScope) { scopeFactoryContainer = container; + this.rootScope = rootScope; } public IServiceScope CreateScope() { - var scope = ExtensionContainerScope.BeginScope(); + var scope = + ExtensionContainerScope + .BeginScope( + ExtensionContainerScope.Current, + 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..cbbc331b33 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 ExtensionContainerScopeBase scope; - private bool disposing; + private readonly ExtensionContainerScope scope; + private bool disposing = false; private readonly IWindsorContainer container; public WindsorScopedServiceProvider(IWindsorContainer container) { this.container = container; - scope = ExtensionContainerScopeCache.Current; + this.scope = ExtensionContainerScope.Current; } public object GetService(Type serviceType) { - using(_ = new ForcedScope(scope)) + using(var fs = new ExtensionContainerScope.ForcedScope(scope)) { return ResolveInstanceOrNull(serviceType, true); } @@ -47,7 +47,7 @@ public object GetService(Type serviceType) public object GetRequiredService(Type serviceType) { - using(_ = new ForcedScope(scope)) + using(var fs = new ExtensionContainerScope.ForcedScope(scope)) { return ResolveInstanceOrNull(serviceType, false); } @@ -55,12 +55,20 @@ public object GetRequiredService(Type serviceType) public void Dispose() { - if (!(scope is ExtensionContainerRootScope)) return; - if (disposing) return; - disposing = true; - var disposableScope = scope as IDisposable; - disposableScope?.Dispose(); - container.Dispose(); + if(scope is ExtensionContainerRootScope) + { + if(!disposing) + { + disposing = true; + var disposableScope = scope as IDisposable; + if(disposableScope != null) + { + disposableScope.Dispose(); + } + container.Dispose(); + } + + } } private object ResolveInstanceOrNull(Type serviceType, bool isOptional) {