diff --git a/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs b/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs index a7806b7e3f2..645cfc7ad3e 100644 --- a/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs +++ b/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs @@ -11,56 +11,86 @@ namespace DotNetNuke.Common.Extensions /// Dependency injection extensions for HttpContext. public static class HttpContextDependencyInjectionExtensions { - /// Sets the service scope for the http context base. - /// The http context base. - /// The service scope. - public static void SetScope(this HttpContextBase httpContext, IServiceScope scope) - { - httpContext.Items[typeof(IServiceScope)] = scope; - } - /// Sets the service scope for the http context. /// The http context. /// The service scope. public static void SetScope(this HttpContext httpContext, IServiceScope scope) + => SetScope(new HttpContextWrapper(httpContext), scope); + + /// Sets the service scope for the http context base. + /// The http context base. + /// The service scope. + public static void SetScope(this HttpContextBase httpContext, IServiceScope scope) { - httpContext.Items[typeof(IServiceScope)] = scope; + if (!httpContext.Items.Contains(typeof(IServiceScope))) + { + httpContext.Items[typeof(IServiceScope)] = scope; + } } /// Clears the service scope for the http context. /// The http context on which to clear the service scope. public static void ClearScope(this HttpContext httpContext) + => ClearScope(new HttpContextWrapper(httpContext)); + + /// Clears the service scope for the http context. + /// The http context on which to clear the service scope. + public static void ClearScope(this HttpContextBase httpContext) { httpContext.Items.Remove(typeof(IServiceScope)); } + /// Gets the http context service scope. + /// The http context from which to get the scope from. + /// A service scope. + public static IServiceScope GetScope(this HttpContext httpContext) + => GetScope(new HttpContextWrapper(httpContext)); + /// Gets the http context base service scope. /// The http context base from which to get the scope from. /// A service scope. public static IServiceScope GetScope(this HttpContextBase httpContext) { - return GetScope(httpContext.Items); - } + if (httpContext.Items.Contains(typeof(IServiceScope))) + { + return httpContext.Items[typeof(IServiceScope)] as IServiceScope; + } - /// Gets the http context service scope. - /// The http context from which to get the scope from. - /// A service scope. - public static IServiceScope GetScope(this HttpContext httpContext) - { - return GetScope(httpContext.Items); - } + var scopeLock = new object(); + const string ScopeLockName = "GetScope_lock"; + if (httpContext.Items.Contains(ScopeLockName)) + { + // another thread is adding the scope, try again + return GetScope(httpContext); + } - /// Gets the service scope from a collection of context items. - /// A dictionary of context items. - /// The found service scope. - internal static IServiceScope GetScope(System.Collections.IDictionary contextItems) - { - if (!contextItems.Contains(typeof(IServiceScope))) + IServiceScope scope = null; + httpContext.Items.Add(ScopeLockName, scopeLock); + lock (httpContext.Items[ScopeLockName]) + { + if (httpContext.Items[ScopeLockName] == scopeLock) + { + if (!httpContext.Items.Contains(typeof(IServiceScope))) + { + scope = Globals.DependencyProvider.CreateScope(); + httpContext.Items[typeof(IServiceScope)] = scope; + } + } + } + + if (scope is not null) { - return null; + httpContext.AddOnRequestCompleted(DisposeScope); + return scope; } - return contextItems[typeof(IServiceScope)] is IServiceScope scope ? scope : null; + return GetScope(httpContext); + } + + private static void DisposeScope(HttpContextBase httpContext) + { + httpContext.GetScope()?.Dispose(); + httpContext.ClearScope(); } } } diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 20c776ff562..00a3d76f362 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -751,6 +751,7 @@ + diff --git a/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs b/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs index 92f02d18291..42f704084a0 100644 --- a/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs +++ b/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs @@ -4,39 +4,13 @@ namespace DotNetNuke.Services.DependencyInjection { - using System; - using System.Collections; - using System.Web; - - using DotNetNuke.Common.Extensions; using Microsoft.Extensions.DependencyInjection; + /// A contract specifying the ability to access an instance. public interface IScopeAccessor - { + { + /// Gets the scope. + /// The scope, or if there is no scope to get. IServiceScope GetScope(); } - - public class ScopeAccessor : IScopeAccessor - { - private static Func fallbackGetContextItems = () => HttpContext.Current?.Items; - - private Func getContextItems; - - /// Initializes a new instance of the class. - public ScopeAccessor() - { - this.getContextItems = fallbackGetContextItems; - } - - /// - public IServiceScope GetScope() - { - return HttpContextDependencyInjectionExtensions.GetScope(this.getContextItems()); - } - - internal void SetContextItemsFunc(Func getContextItems) - { - this.getContextItems = getContextItems ?? fallbackGetContextItems; - } - } } diff --git a/DNN Platform/Library/Services/DependencyInjection/ScopeAccessor.cs b/DNN Platform/Library/Services/DependencyInjection/ScopeAccessor.cs new file mode 100644 index 00000000000..272d548f465 --- /dev/null +++ b/DNN Platform/Library/Services/DependencyInjection/ScopeAccessor.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information + +namespace DotNetNuke.Services.DependencyInjection; + +using DotNetNuke.Common; +using DotNetNuke.Common.Extensions; +using Microsoft.Extensions.DependencyInjection; + +/// An implementation using . +public class ScopeAccessor : IScopeAccessor +{ + /// + public IServiceScope GetScope() + { + return HttpContextSource.Current.GetScope(); + } +}