diff --git a/DNN Platform/DotNetNuke.Web.Mvc/DnnMvcDependencyResolver.cs b/DNN Platform/DotNetNuke.Web.Mvc/DnnMvcDependencyResolver.cs index bbd08a99203..d94b09cbf8e 100644 --- a/DNN Platform/DotNetNuke.Web.Mvc/DnnMvcDependencyResolver.cs +++ b/DNN Platform/DotNetNuke.Web.Mvc/DnnMvcDependencyResolver.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Web.Mvc; +using DotNetNuke.Services.DependencyInjection; namespace DotNetNuke.Web.Mvc { @@ -16,18 +17,10 @@ namespace DotNetNuke.Web.Mvc internal class DnnMvcDependencyResolver : IDependencyResolver { private readonly IServiceProvider _serviceProvider; - private readonly IDependencyResolver _resolver; - /// - /// Instantiate a new instance of the . - /// - /// - /// The to be used in the - /// - public DnnMvcDependencyResolver(IServiceProvider serviceProvider, IDependencyResolver resolver) + public DnnMvcDependencyResolver(IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; - _resolver = resolver; + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// @@ -41,14 +34,12 @@ public DnnMvcDependencyResolver(IServiceProvider serviceProvider, IDependencyRes /// public object GetService(Type serviceType) { - try - { - return _serviceProvider.GetService(serviceType); - } - catch - { - return _resolver.GetService(serviceType); - } + var accessor = _serviceProvider.GetRequiredService(); + var scope = accessor.GetScope(); + if (scope != null) + return scope.ServiceProvider.GetService(serviceType); + + throw new InvalidOperationException("IServiceScope not provided"); } /// @@ -62,14 +53,12 @@ public object GetService(Type serviceType) /// public IEnumerable GetServices(Type serviceType) { - try - { - return _serviceProvider.GetServices(serviceType); - } - catch - { - return _resolver.GetServices(serviceType); - } + var accessor = _serviceProvider.GetRequiredService(); + var scope = accessor.GetScope(); + if (scope != null) + return scope.ServiceProvider.GetServices(serviceType); + + throw new InvalidOperationException("IServiceScope not provided"); } } } diff --git a/DNN Platform/DotNetNuke.Web.Mvc/DotNetNuke.Web.Mvc.csproj b/DNN Platform/DotNetNuke.Web.Mvc/DotNetNuke.Web.Mvc.csproj index 69bf2871dd8..7960f3f49ef 100644 --- a/DNN Platform/DotNetNuke.Web.Mvc/DotNetNuke.Web.Mvc.csproj +++ b/DNN Platform/DotNetNuke.Web.Mvc/DotNetNuke.Web.Mvc.csproj @@ -117,6 +117,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web.Mvc/Extensions/StartupExtensions.cs b/DNN Platform/DotNetNuke.Web.Mvc/Extensions/StartupExtensions.cs new file mode 100644 index 00000000000..33f91fe2d9c --- /dev/null +++ b/DNN Platform/DotNetNuke.Web.Mvc/Extensions/StartupExtensions.cs @@ -0,0 +1,28 @@ +using DotNetNuke.DependencyInjection.Extensions; +using DotNetNuke.Web.Mvc.Framework.Controllers; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DotNetNuke.Web.Mvc.Extensions +{ + public static class StartupExtensions + { + public static void AddWebApiControllers(this IServiceCollection services) + { + var controllerTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(TypeExtensions.SafeGetTypes) + .Where(x => typeof(IDnnController).IsAssignableFrom(x) + && x.IsClass + && !x.IsAbstract + ); + foreach (var controller in controllerTypes) + { + services.AddScoped(controller); + } + } + } +} diff --git a/DNN Platform/DotNetNuke.Web.Mvc/Startup.cs b/DNN Platform/DotNetNuke.Web.Mvc/Startup.cs index 2e3033a0b5c..4f7e14f1b8c 100644 --- a/DNN Platform/DotNetNuke.Web.Mvc/Startup.cs +++ b/DNN Platform/DotNetNuke.Web.Mvc/Startup.cs @@ -2,10 +2,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // -using DotNetNuke.Common; using DotNetNuke.DependencyInjection; using Microsoft.Extensions.DependencyInjection; +using DotNetNuke.Web.Mvc.Extensions; using System.Web.Mvc; +using DotNetNuke.Common; namespace DotNetNuke.Web.Mvc { @@ -13,11 +14,12 @@ public class Startup : IDnnStartup { public void ConfigureServices(IServiceCollection services) { - services.AddSingleton(serviceProvider => ControllerBuilder.Current.GetControllerFactory()); + services.AddSingleton(); services.AddSingleton(); - IDependencyResolver resolver = new DnnMvcDependencyResolver(Globals.DependencyProvider, DependencyResolver.Current); - DependencyResolver.SetResolver(resolver); + services.AddWebApiControllers(); + + DependencyResolver.SetResolver(new DnnMvcDependencyResolver(Globals.DependencyProvider)); } } } diff --git a/DNN Platform/DotNetNuke.Web/Api/Extensions/StartupExtensions.cs b/DNN Platform/DotNetNuke.Web/Api/Extensions/StartupExtensions.cs index 11110923301..3b7daab0a6e 100644 --- a/DNN Platform/DotNetNuke.Web/Api/Extensions/StartupExtensions.cs +++ b/DNN Platform/DotNetNuke.Web/Api/Extensions/StartupExtensions.cs @@ -25,14 +25,14 @@ internal static class StartupExtensions /// public static void AddWebApi(this IServiceCollection services) { - var startuptypes = AppDomain.CurrentDomain.GetAssemblies() + var controllerTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.SafeGetTypes()) .Where(x => typeof(DnnApiController).IsAssignableFrom(x) && x.IsClass && !x.IsAbstract); - foreach (var controller in startuptypes) + foreach (var controller in controllerTypes) { - services.AddTransient(controller); + services.AddScoped(controller); } } } diff --git a/DNN Platform/DotNetNuke.Web/Api/Internal/DnnDependencyResolver.cs b/DNN Platform/DotNetNuke.Web/Api/Internal/DnnDependencyResolver.cs index 99a9cee6cef..84da1e85b6d 100644 --- a/DNN Platform/DotNetNuke.Web/Api/Internal/DnnDependencyResolver.cs +++ b/DNN Platform/DotNetNuke.Web/Api/Internal/DnnDependencyResolver.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using System.Web.Http.Dependencies; +using DotNetNuke.Common.Extensions; +using DotNetNuke.Services.DependencyInjection; namespace DotNetNuke.Web.Api.Internal { @@ -25,7 +27,7 @@ internal class DnnDependencyResolver : IDependencyResolver /// public DnnDependencyResolver(IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); } /// @@ -36,7 +38,9 @@ public DnnDependencyResolver(IServiceProvider serviceProvider) /// public IDependencyScope BeginScope() { - return new DnnDependencyResolver(_serviceProvider.CreateScope().ServiceProvider); + var accessor = _serviceProvider.GetRequiredService(); + var scope = accessor.GetScope(); + return new DnnDependencyResolver(scope.ServiceProvider); } /// diff --git a/DNN Platform/DotNetNuke.Web/Common/DotNetNukeHttpApplication.cs b/DNN Platform/DotNetNuke.Web/Common/DotNetNukeHttpApplication.cs index 411f310330d..e301715dedb 100644 --- a/DNN Platform/DotNetNuke.Web/Common/DotNetNukeHttpApplication.cs +++ b/DNN Platform/DotNetNuke.Web/Common/DotNetNukeHttpApplication.cs @@ -35,7 +35,7 @@ using DotNetNuke.Instrumentation; using DotNetNuke.Security.Cookies; using DotNetNuke.Services.Installer.Blocker; -using Microsoft.Extensions.DependencyInjection; +using DotNetNuke.HttpModules.DependencyInjection; #endregion @@ -67,8 +67,10 @@ private void Application_Start(object sender, EventArgs eventArgs) var name = Config.GetSetting("ServerName"); Globals.ServerName = String.IsNullOrEmpty(name) ? Dns.GetHostName() : name; + Globals.DependencyProvider = new LazyServiceProvider(); var startup = new Startup(); - Globals.DependencyProvider = startup.DependencyProvider; + (Globals.DependencyProvider as LazyServiceProvider).SetProvider(startup.DependencyProvider); + ServiceRequestScopeModule.SetServiceProvider(Globals.DependencyProvider); ComponentFactory.Container = new SimpleContainer(); diff --git a/DNN Platform/DotNetNuke.Web/Common/LazyServiceProvider.cs b/DNN Platform/DotNetNuke.Web/Common/LazyServiceProvider.cs new file mode 100644 index 00000000000..3b2c47ca81e --- /dev/null +++ b/DNN Platform/DotNetNuke.Web/Common/LazyServiceProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DotNetNuke.Web.Common +{ + public class LazyServiceProvider : IServiceProvider + { + private IServiceProvider _serviceProvider; + + public object GetService(Type serviceType) + { + if (_serviceProvider is null) + throw new Exception("Cannot resolve services until the service provider is built."); + + return _serviceProvider.GetService(serviceType); + } + + internal void SetProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + } +} diff --git a/DNN Platform/DotNetNuke.Web/DotNetNuke.Web.csproj b/DNN Platform/DotNetNuke.Web/DotNetNuke.Web.csproj index 46cd421a9c5..04a05b4dad4 100644 --- a/DNN Platform/DotNetNuke.Web/DotNetNuke.Web.csproj +++ b/DNN Platform/DotNetNuke.Web/DotNetNuke.Web.csproj @@ -203,6 +203,7 @@ + diff --git a/DNN Platform/DotNetNuke.Web/Startup.cs b/DNN Platform/DotNetNuke.Web/Startup.cs index 8ba659b9636..ce0c37fc09c 100644 --- a/DNN Platform/DotNetNuke.Web/Startup.cs +++ b/DNN Platform/DotNetNuke.Web/Startup.cs @@ -5,6 +5,7 @@ using DotNetNuke.DependencyInjection; using DotNetNuke.DependencyInjection.Extensions; using DotNetNuke.Instrumentation; +using DotNetNuke.Services.DependencyInjection; using DotNetNuke.Web.Extensions; using Microsoft.Extensions.DependencyInjection; using System; @@ -25,6 +26,7 @@ public Startup() private void Configure() { var services = new ServiceCollection(); + services.AddSingleton(); ConfigureServices(services); DependencyProvider = services.BuildServiceProvider(); } diff --git a/DNN Platform/HttpModules/DependencyInjection/ServiceRequestScopeModule.cs b/DNN Platform/HttpModules/DependencyInjection/ServiceRequestScopeModule.cs new file mode 100644 index 00000000000..f7b151cd4db --- /dev/null +++ b/DNN Platform/HttpModules/DependencyInjection/ServiceRequestScopeModule.cs @@ -0,0 +1,65 @@ +using System; +using System.Web; +using DotNetNuke.Common.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Web.Infrastructure.DynamicModuleHelper; + +[assembly: PreApplicationStartMethod(typeof(DotNetNuke.HttpModules.DependencyInjection.ServiceRequestScopeModule), nameof(DotNetNuke.HttpModules.DependencyInjection.ServiceRequestScopeModule.InitModule))] + +namespace DotNetNuke.HttpModules.DependencyInjection +{ + public class ServiceRequestScopeModule : IHttpModule + { + public static void InitModule() + { + DynamicModuleUtility.RegisterModule(typeof(ServiceRequestScopeModule)); + } + + private static IServiceProvider _serviceProvider; + + public void Init(HttpApplication context) + { + context.BeginRequest += Context_BeginRequest; + context.EndRequest += Context_EndRequest; + } + + public static void SetServiceProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + private void Context_BeginRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + context.SetScope(_serviceProvider.CreateScope()); + } + + private void Context_EndRequest(object sender, EventArgs e) + { + var context = ((HttpApplication)sender).Context; + context.GetScope()?.Dispose(); + context.ClearScope(); + } + + /// + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Performs application-defined tasks associated with freeing, + /// releasing, or resetting unmanaged resources. + /// + /// + /// true if the object is currently disposing. + /// + protected virtual void Dispose(bool disposing) + { + // left empty by design + } + } +} diff --git a/DNN Platform/HttpModules/DotNetNuke.HttpModules.csproj b/DNN Platform/HttpModules/DotNetNuke.HttpModules.csproj index 8e0d789eada..617a0078af6 100644 --- a/DNN Platform/HttpModules/DotNetNuke.HttpModules.csproj +++ b/DNN Platform/HttpModules/DotNetNuke.HttpModules.csproj @@ -56,6 +56,10 @@ False ..\DotNetNuke.Instrumentation\bin\DotNetNuke.Instrumentation.dll + + + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + @@ -67,6 +71,7 @@ SolutionInfo.cs + @@ -114,6 +119,9 @@ + + + diff --git a/DNN Platform/HttpModules/packages.config b/DNN Platform/HttpModules/packages.config new file mode 100644 index 00000000000..50cd50d0ad8 --- /dev/null +++ b/DNN Platform/HttpModules/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs b/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs new file mode 100644 index 00000000000..58cd7973c28 --- /dev/null +++ b/DNN Platform/Library/Common/Extensions/HttpContextDependencyInjectionExtensions.cs @@ -0,0 +1,41 @@ +using System.Web; +using Microsoft.Extensions.DependencyInjection; + +namespace DotNetNuke.Common.Extensions +{ + public static class HttpContextDependencyInjectionExtensions + { + public static void SetScope(this HttpContextBase httpContext, IServiceScope scope) + { + httpContext.Items[typeof(IServiceScope)] = scope; + } + + public static void SetScope(this HttpContext httpContext, IServiceScope scope) + { + httpContext.Items[typeof(IServiceScope)] = scope; + } + + public static void ClearScope(this HttpContext httpContext) + { + httpContext.Items.Remove(typeof(IServiceScope)); + } + + public static IServiceScope GetScope(this HttpContextBase httpContext) + { + return GetScope(httpContext.Items); + } + + public static IServiceScope GetScope(this HttpContext httpContext) + { + return GetScope(httpContext.Items); + } + + internal static IServiceScope GetScope(System.Collections.IDictionary contextItems) + { + if (!contextItems.Contains(typeof(IServiceScope))) + return null; + + return contextItems[typeof(IServiceScope)] is IServiceScope scope ? scope : null; + } + } +} diff --git a/DNN Platform/Library/DotNetNuke.Library.csproj b/DNN Platform/Library/DotNetNuke.Library.csproj index 6a6f76d7889..2859ef0ac6c 100644 --- a/DNN Platform/Library/DotNetNuke.Library.csproj +++ b/DNN Platform/Library/DotNetNuke.Library.csproj @@ -189,6 +189,7 @@ + @@ -700,6 +701,7 @@ + diff --git a/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs b/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs new file mode 100644 index 00000000000..d5b0606e58c --- /dev/null +++ b/DNN Platform/Library/Services/DependencyInjection/IScopeAccessor.cs @@ -0,0 +1,35 @@ +using DotNetNuke.Common.Extensions; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections; +using System.Web; + +namespace DotNetNuke.Services.DependencyInjection +{ + public interface IScopeAccessor + { + IServiceScope GetScope(); + } + + public class ScopeAccessor : IScopeAccessor + { + private static Func fallbackGetContextItems = () => HttpContext.Current?.Items; + + private Func _getContextItems; + + public ScopeAccessor() + { + _getContextItems = fallbackGetContextItems; + } + + public IServiceScope GetScope() + { + return HttpContextDependencyInjectionExtensions.GetScope(_getContextItems()); + } + + internal void SetContextItemsFunc(Func getContextItems) + { + _getContextItems = getContextItems ?? fallbackGetContextItems; + } + } +} diff --git a/DNN Platform/Tests/DotNetNuke.Tests.Web/Api/Internals/DnnDependencyResolverTests.cs b/DNN Platform/Tests/DotNetNuke.Tests.Web/Api/Internals/DnnDependencyResolverTests.cs index 511b02f48fc..59482e88d30 100644 --- a/DNN Platform/Tests/DotNetNuke.Tests.Web/Api/Internals/DnnDependencyResolverTests.cs +++ b/DNN Platform/Tests/DotNetNuke.Tests.Web/Api/Internals/DnnDependencyResolverTests.cs @@ -2,6 +2,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. // +using DotNetNuke.Services.DependencyInjection; using DotNetNuke.Web.Api.Internal; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -21,6 +22,7 @@ public class DnnDependencyResolverTests public void FixtureSetUp() { var services = new ServiceCollection(); + services.AddSingleton(sp => new FakeScopeAccessor(sp.CreateScope())); services.AddScoped(typeof(ITestService), typeof(TestService)); _serviceProvider = services.BuildServiceProvider(); @@ -107,5 +109,20 @@ public void BeginScope_GetServices() private interface ITestService { } private class TestService : ITestService { } + + private class FakeScopeAccessor : IScopeAccessor + { + private IServiceScope fakeScope; + + public FakeScopeAccessor(IServiceScope fakeScope) + { + this.fakeScope = fakeScope; + } + + public IServiceScope GetScope() + { + return fakeScope; + } + } } }