diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
new file mode 100644
index 0000000000..d5355c136f
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Hosting;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Extends the to enable Umbraco to be used as the service container.
+ ///
+ public static class HostBuilderExtensions
+ {
+ ///
+ /// Assigns a custom service provider factory to use Umbraco's container
+ ///
+ ///
+ ///
+ public static IHostBuilder UseUmbraco(this IHostBuilder builder)
+ {
+ return builder.UseServiceProviderFactory(new UmbracoServiceProviderFactory());
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
index 9794de733a..835bd0b9a8 100644
--- a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
+++ b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
@@ -1,6 +1,7 @@
using LightInject;
using LightInject.Microsoft.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using System;
using System.Reflection;
using Umbraco.Core.Composing.LightInject;
@@ -8,24 +9,13 @@ using Umbraco.Core.Configuration;
namespace Umbraco.Core.Composing
{
+
///
/// Creates the container.
///
public static class RegisterFactory
{
- ///
- /// Creates a new based on an existing MSDI IServiceCollection
- ///
- ///
- ///
- public static IRegister CreateFrom(IServiceCollection services, out IServiceProvider serviceProvider)
- {
- var lightInjectContainer = new ServiceContainer(ContainerOptions.Default.WithMicrosoftSettings());
- serviceProvider = lightInjectContainer.CreateServiceProvider(services);
- return new LightInjectContainer(lightInjectContainer);
- }
-
- //TODO: The following can die when net framework is gone
+ //TODO: This can die when net framework is gone
// cannot use typeof().AssemblyQualifiedName on the web container - we don't reference it
// a normal Umbraco site should run on the web container, but an app may run on the core one
diff --git a/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs
new file mode 100644
index 0000000000..1d79bf9837
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs
@@ -0,0 +1,72 @@
+using LightInject;
+using LightInject.Microsoft.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using Umbraco.Core.Composing.LightInject;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Used to create Umbraco's container and cross-wire it up before the applicaton starts
+ ///
+ public class UmbracoServiceProviderFactory : IServiceProviderFactory
+ {
+ public UmbracoServiceProviderFactory(ServiceContainer container)
+ {
+ _container = new LightInjectContainer(container);
+ }
+
+ ///
+ /// Default ctor for use in Host Builder configuration
+ ///
+ public UmbracoServiceProviderFactory()
+ {
+ var container = new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings());
+ UmbracoContainer = _container = new LightInjectContainer(container);
+ IsActive = true;
+ }
+
+ // see here for orig lightinject version https://github.com/seesharper/LightInject.Microsoft.DependencyInjection/blob/412566e3f70625e6b96471db5e1f7cd9e3e1eb18/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs#L263
+ // we don't really need all that, we're manually creating our container with the correct options and that
+ // is what we'll return in CreateBuilder
+
+ IServiceCollection _services;
+ readonly LightInjectContainer _container;
+
+ internal LightInjectContainer GetContainer() => _container;
+
+ ///
+ /// When the empty ctor is used this returns if this factory is active
+ ///
+ public static bool IsActive { get; private set; }
+
+ ///
+ /// When the empty ctor is used this returns the created IRegister
+ ///
+ public static IRegister UmbracoContainer { get; private set; }
+
+ ///
+ /// Create the container with the required settings for aspnetcore3
+ ///
+ ///
+ ///
+ public IServiceContainer CreateBuilder(IServiceCollection services)
+ {
+ _services = services;
+ return _container.Container;
+ }
+
+ ///
+ /// This cross-wires the container just before the application calls "Configure"
+ ///
+ ///
+ ///
+ public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder)
+ {
+ var provider = containerBuilder.CreateServiceProvider(_services);
+ return provider;
+ }
+
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
index 23ef0bfda5..5806bb90ec 100644
--- a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
+++ b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
@@ -1,9 +1,9 @@
using System.Data.Common;
-using StackExchange.Profiling.Internal;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
+
public interface IDbProviderFactoryCreator
{
DbProviderFactory CreateFactory();
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
new file mode 100644
index 0000000000..9c2c6273c2
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Data.Common;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence
+{
+ public class SqlServerDbProviderFactoryCreator : IDbProviderFactoryCreator
+ {
+ private readonly string _defaultProviderName;
+ private readonly Func _getFactory;
+
+ public SqlServerDbProviderFactoryCreator(string defaultProviderName, Func getFactory)
+ {
+ _defaultProviderName = defaultProviderName;
+ _getFactory = getFactory;
+ }
+
+ public DbProviderFactory CreateFactory() => CreateFactory(_defaultProviderName);
+
+ public DbProviderFactory CreateFactory(string providerName)
+ {
+ if (string.IsNullOrEmpty(providerName)) return null;
+ return _getFactory(providerName);
+ }
+
+ // gets the sql syntax provider that corresponds, from attribute
+ public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName)
+ {
+ return providerName switch
+ {
+ Constants.DbProviderNames.SqlCe => throw new NotSupportedException("SqlCe is not supported"),
+ Constants.DbProviderNames.SqlServer => new SqlServerSyntaxProvider(),
+ _ => throw new InvalidOperationException($"Unknown provider name \"{providerName}\""),
+ };
+ }
+
+ public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName)
+ {
+ switch (providerName)
+ {
+ case Constants.DbProviderNames.SqlCe:
+ throw new NotSupportedException("SqlCe is not supported");
+ case Constants.DbProviderNames.SqlServer:
+ return new SqlServerBulkSqlInsertProvider();
+ default:
+ return new BasicBulkSqlInsertProvider();
+ }
+ }
+
+ public void CreateDatabase()
+ {
+ throw new NotSupportedException("Embedded databases are not supported");
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index 797c8bb7fa..a5fea12a6b 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -10,6 +10,7 @@
+
@@ -57,6 +58,9 @@
<_Parameter1>Umbraco.Tests.Benchmarks
+
+ <_Parameter1>Umbraco.Tests.Integration
+
@@ -67,8 +71,4 @@
-
-
-
-
diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs
index b231e6def0..b85471b95a 100644
--- a/src/Umbraco.Tests.Integration/ContainerTests.cs
+++ b/src/Umbraco.Tests.Integration/ContainerTests.cs
@@ -1,5 +1,7 @@
using LightInject;
+using LightInject.Microsoft.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -26,7 +28,10 @@ namespace Umbraco.Tests.Integration
var msdiServiceProvider = services.BuildServiceProvider();
// LightInject / Umbraco
- var umbracoContainer = (LightInjectContainer)RegisterFactory.CreateFrom(services, out var lightInjectServiceProvider);
+ var container = new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings());
+ var serviceProviderFactory = new UmbracoServiceProviderFactory(container);
+ var umbracoContainer = serviceProviderFactory.GetContainer();
+ serviceProviderFactory.CreateBuilder(services); // called during Host Builder, needed to capture services
// Dependencies needed for creating composition/register essentials
var testHelper = new TestHelper();
@@ -42,7 +47,8 @@ namespace Umbraco.Tests.Integration
testHelper.AppCaches, umbracoDatabaseFactory, typeLoader, runtimeState, testHelper.GetTypeFinder(),
testHelper.IOHelper, testHelper.GetUmbracoVersion(), dbProviderFactoryCreator);
- // Resolve
+ // Cross wire - this would be called by the Host Builder at the very end of ConfigureServices
+ var lightInjectServiceProvider = serviceProviderFactory.CreateServiceProvider(umbracoContainer.Container);
// From MSDI
var foo1 = msdiServiceProvider.GetService();
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
index 871520d1c6..32eb327127 100644
--- a/src/Umbraco.Tests.Integration/RuntimeTests.cs
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -10,6 +10,7 @@ using Umbraco.Core.Runtime;
using Umbraco.Tests.Common;
using Umbraco.Tests.Common.Composing;
using Umbraco.Tests.Integration.Implementations;
+using Umbraco.Web.BackOffice.AspNetCore;
namespace Umbraco.Tests.Integration
{
@@ -19,20 +20,19 @@ namespace Umbraco.Tests.Integration
[Test]
public void BootCoreRuntime()
{
- // MSDI
- var services = new ServiceCollection();
-
// LightInject / Umbraco
- var umbracoContainer = (LightInjectContainer)RegisterFactory.CreateFrom(services, out var lightInjectServiceProvider);
+ var container = new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings());
+ var serviceProviderFactory = new UmbracoServiceProviderFactory(container);
+ var umbracoContainer = serviceProviderFactory.GetContainer();
- // Dependencies needed for Core Runtime
+ // Create the core runtime
var testHelper = new TestHelper();
-
var coreRuntime = new CoreRuntime(testHelper.GetConfigs(), testHelper.GetUmbracoVersion(),
testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker,
testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator,
testHelper.MainDom, testHelper.GetTypeFinder());
+ // boot it!
var factory = coreRuntime.Boot(umbracoContainer);
Assert.IsTrue(coreRuntime.MainDom.IsMainDom);
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
index 5470516cf7..ddd0a9b122 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
using Umbraco.Net;
namespace Umbraco.Web.BackOffice.AspNetCore
@@ -12,6 +13,17 @@ namespace Umbraco.Web.BackOffice.AspNetCore
_httpContextAccessor = httpContextAccessor;
}
- public string SessionId => _httpContextAccessor?.HttpContext.Session?.Id;
+
+ public string SessionId
+ {
+ get
+ {
+ // If session isn't enabled this will throw an exception so we check
+ var sessionFeature = _httpContextAccessor?.HttpContext?.Features.Get();
+ return sessionFeature != null
+ ? _httpContextAccessor?.HttpContext?.Session?.Id
+ : "0";
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
index 9441170ae4..0fbd45214d 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
@@ -1,4 +1,6 @@
-using System.Configuration;
+using System;
+using System.Data.Common;
+using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -6,27 +8,82 @@ using Microsoft.Extensions.Hosting;
using Umbraco.Composing;
using Umbraco.Core;
using Umbraco.Core.Cache;
+using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
+using Umbraco.Core.Persistence;
using Umbraco.Core.Runtime;
namespace Umbraco.Web.BackOffice.AspNetCore
{
+
public static class UmbracoBackOfficeServiceCollectionExtensions
{
- public static IServiceCollection AddUmbracoBackOffice(this IServiceCollection services)
+
+ ///
+ /// Adds the Umbraco Back Core requirements
+ ///
+ ///
+ ///
+ ///
+ /// Must be called after all services are added to the application because we are cross-wiring the container (currently)
+ ///
+ public static IServiceCollection AddUmbracoCore(this IServiceCollection services)
{
-
-
services.AddSingleton();
CreateCompositionRoot(services);
+ if (!UmbracoServiceProviderFactory.IsActive)
+ throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory");
+
+ var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer;
+
+ // TODO: Get rid of this 'Current' requirement
+ var globalSettings = Current.Configs.Global();
+ var umbracoVersion = new UmbracoVersion(globalSettings);
+
+ var coreRuntime = GetCoreRuntime(
+ Current.Configs,
+ umbracoVersion,
+ Current.IOHelper,
+ Current.Logger,
+ Current.Profiler,
+ Current.HostingEnvironment,
+ Current.BackOfficeInfo);
+
+ var factory = coreRuntime.Boot(umbContainer);
+
return services;
}
+ private static IRuntime GetCoreRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger,
+ IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
+ {
+ var connectionStringConfig = configs.ConnectionStrings()[Constants.System.UmbracoConnectionName];
+ var dbProviderFactoryCreator = new SqlServerDbProviderFactoryCreator(
+ connectionStringConfig?.ProviderName,
+ DbProviderFactories.GetFactory);
+
+ // Determine if we should use the sql main dom or the default
+ var appSettingMainDomLock = configs.Global().MainDomLock;
+ var mainDomLock = appSettingMainDomLock == "SqlMainDomLock"
+ ? (IMainDomLock)new SqlMainDomLock(logger, configs, dbProviderFactoryCreator)
+ : new MainDomSemaphoreLock(logger, hostingEnvironment);
+
+ var mainDom = new MainDom(logger, hostingEnvironment, mainDomLock);
+
+ // TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
+ // this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
+ var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()));
+
+ var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetCoreBootPermissionsChecker(),
+ hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder);
+
+ return coreRuntime;
+ }
private static void CreateCompositionRoot(IServiceCollection services)
{
@@ -43,7 +100,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore
var hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment, httpContextAccessor, hostApplicationLifetime);
var ioHelper = new IOHelper(hostingEnvironment);
- var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetCoreSessionIdResolver(httpContextAccessor), () => services.BuildServiceProvider().GetService(), coreDebug, ioHelper, new AspNetCoreMarchal());
+
+ var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, new AspNetCoreSessionIdResolver(httpContextAccessor), () => services.BuildServiceProvider().GetService(), coreDebug, ioHelper, new AspNetCoreMarchal());
var configs = configFactory.Create(ioHelper, logger);
var backOfficeInfo = new AspNetCoreBackOfficeInfo(configs.Global());
@@ -51,5 +109,13 @@ namespace Umbraco.Web.BackOffice.AspNetCore
Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);
}
+
+ private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker
+ {
+ public void ThrowIfNotPermissions()
+ {
+ // nothing to check
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
index 36238f1a6d..bd20769d45 100644
--- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
+++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
@@ -10,6 +10,10 @@
+
+
+
+
diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs
index 21eb1b6585..6b77cb5f93 100644
--- a/src/Umbraco.Web.UI.NetCore/Program.cs
+++ b/src/Umbraco.Web.UI.NetCore/Program.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
+using Serilog;
+using Umbraco.Core.Composing;
namespace Umbraco.Web.UI.BackOffice
{
@@ -19,7 +15,9 @@ namespace Umbraco.Web.UI.BackOffice
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); });
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
+ .UseUmbraco()
+ .UseSerilog();
}
}
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 8e4da28917..7e2d19ac02 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -19,8 +19,8 @@ namespace Umbraco.Web.UI.BackOffice
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
+ services.AddUmbracoCore();
services.AddUmbracoWebsite();
- services.AddUmbracoBackOffice();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.