diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMode.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMode.cs
new file mode 100644
index 0000000000..3f38167af8
--- /dev/null
+++ b/src/Umbraco.Core/Configuration/Models/RuntimeMode.cs
@@ -0,0 +1,22 @@
+namespace Umbraco.Cms.Core.Configuration.Models;
+
+///
+/// Represents the configured Umbraco runtime mode.
+///
+public enum RuntimeMode
+{
+ ///
+ /// The backoffice development mode ensures the runtime is configured for rapidly applying changes within the backoffice.
+ ///
+ BackofficeDevelopment = 0,
+
+ ///
+ /// The development mode ensures the runtime is configured for rapidly applying changes.
+ ///
+ Development = 1,
+
+ ///
+ /// The production mode ensures optimal performance settings are configured and denies any changes that would require recompilations.
+ ///
+ Production = 2
+}
diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs
index ac4e51a1c2..7f31c9319b 100644
--- a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs
@@ -1,16 +1,24 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
+using System.ComponentModel;
+
namespace Umbraco.Cms.Core.Configuration.Models;
///
-/// Typed configuration options for runtime settings.
+/// Typed configuration options for runtime settings.
///
[UmbracoOptions(Constants.Configuration.ConfigRuntime)]
public class RuntimeSettings
{
///
- /// Gets or sets a value for the maximum query string length.
+ /// Gets or sets the runtime mode.
+ ///
+ [DefaultValue(RuntimeMode.BackofficeDevelopment)]
+ public RuntimeMode Mode { get; set; } = RuntimeMode.BackofficeDevelopment;
+
+ ///
+ /// Gets or sets a value for the maximum query string length.
///
public int? MaxQueryStringLength { get; set; }
diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs
index 3f7f3188a9..11694fa5c0 100644
--- a/src/Umbraco.Core/Constants-Configuration.cs
+++ b/src/Umbraco.Core/Constants-Configuration.cs
@@ -5,23 +5,19 @@ public static partial class Constants
public static class Configuration
{
///
- /// Case insensitive prefix for all configurations
+ /// Case insensitive prefix for all configurations.
///
///
- /// ":" is used as marker for nested objects in json. E.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{....}}
+ /// ":" is used as marker for nested objects in JSON, e.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{...}}.
///
public const string ConfigPrefix = "Umbraco:CMS:";
-
public const string ConfigContentPrefix = ConfigPrefix + "Content:";
public const string ConfigContentNotificationsPrefix = ConfigContentPrefix + "Notifications:";
public const string ConfigCorePrefix = ConfigPrefix + "Core:";
public const string ConfigCustomErrorsPrefix = ConfigPrefix + "CustomErrors:";
public const string ConfigGlobalPrefix = ConfigPrefix + "Global:";
public const string ConfigGlobalId = ConfigGlobalPrefix + "Id";
-
- public const string ConfigGlobalDistributedLockingMechanism =
- ConfigGlobalPrefix + "DistributedLockingMechanism";
-
+ public const string ConfigGlobalDistributedLockingMechanism = ConfigGlobalPrefix + "DistributedLockingMechanism";
public const string ConfigHostingPrefix = ConfigPrefix + "Hosting:";
public const string ConfigModelsBuilderPrefix = ConfigPrefix + "ModelsBuilder:";
public const string ConfigSecurityPrefix = ConfigPrefix + "Security:";
@@ -49,6 +45,7 @@ public static partial class Constants
public const string ConfigPlugins = ConfigPrefix + "Plugins";
public const string ConfigRequestHandler = ConfigPrefix + "RequestHandler";
public const string ConfigRuntime = ConfigPrefix + "Runtime";
+ public const string ConfigRuntimeMode = ConfigRuntime + ":Mode";
public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification";
public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version";
public const string ConfigSecurity = ConfigPrefix + "Security";
@@ -62,7 +59,7 @@ public static partial class Constants
public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard";
public const string ConfigHelpPage = ConfigPrefix + "HelpPage";
public const string ConfigInstallDefaultData = ConfigPrefix + "InstallDefaultData";
- public const string ConfigDataTypes = ConfigPrefix + "DataTypes";
+ public const string ConfigDataTypes = ConfigPrefix + "DataTypes";
public static class NamedOptions
{
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
index 6ac06ae1b7..fd7923eec7 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
@@ -187,9 +187,6 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddSingleton();
- // by default, register a noop factory
- Services.AddUnique();
-
Services.AddUnique();
Services.AddSingleton(f => f.GetRequiredService().CreateDictionary());
diff --git a/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs b/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs
index e5428fb4c4..2003079736 100644
--- a/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs
+++ b/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs
@@ -1,10 +1,11 @@
using Microsoft.Extensions.Configuration;
using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Configuration.Models;
namespace Umbraco.Extensions;
///
-/// Extension methods for configuration.
+/// Extensions for .
///
public static class ConfigurationExtensions
{
@@ -90,4 +91,14 @@ public static class ConfigurationExtensions
return connectionString;
}
+
+ ///
+ /// Gets the Umbraco runtime mode.
+ ///
+ /// The configuration.
+ ///
+ /// The Umbraco runtime mode.
+ ///
+ public static RuntimeMode GetRuntimeMode(this IConfiguration configuration)
+ => configuration.GetValue(Constants.Configuration.ConfigRuntimeMode);
}
diff --git a/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs b/src/Umbraco.Core/Extensions/HealthCheckSettingsExtensions.cs
similarity index 100%
rename from src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs
rename to src/Umbraco.Core/Extensions/HealthCheckSettingsExtensions.cs
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index 94134ab0d1..1c874b2efa 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -51,6 +51,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0.DataTypes;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Infrastructure.Runtime;
+using Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Infrastructure.Serialization;
@@ -63,7 +64,7 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection;
public static partial class UmbracoBuilderExtensions
{
///
- /// Adds all core Umbraco services required to run which may be replaced later in the pipeline
+ /// Adds all core Umbraco services required to run which may be replaced later in the pipeline.
///
public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
{
@@ -83,6 +84,14 @@ public static partial class UmbracoBuilderExtensions
builder.AddNotificationAsyncHandler();
builder.AddNotificationAsyncHandler();
+ // Add runtime mode validation
+ builder.Services.AddSingleton();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
// composers
builder
.AddRepositories()
@@ -102,11 +111,9 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddSingleton(f => f.GetRequiredService());
builder.Services.AddSingleton(f => f.GetRequiredService());
- builder.Services.AddSingleton();
-
- builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddSingleton(f => f.GetRequiredService());
- builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddScoped();
diff --git a/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidationService.cs b/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidationService.cs
new file mode 100644
index 0000000000..3741c4065d
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidationService.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+///
+/// Provides a service to validate configuration based on the runtime mode.
+///
+public interface IRuntimeModeValidationService
+{
+ ///
+ /// Validates configuration based on the runtime mode.
+ ///
+ /// The validation error message.
+ ///
+ /// true when the validation passes; otherwise, false.
+ ///
+ bool Validate([NotNullWhen(false)] out string? validationErrorMessage);
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidator.cs b/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidator.cs
new file mode 100644
index 0000000000..dcfc39ed83
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/IRuntimeModeValidator.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+///
+/// Validates configuration based on the runtime mode.
+///
+public interface IRuntimeModeValidator
+{
+ ///
+ /// Validates configuration based on the specified .
+ ///
+ /// The runtime mode.
+ /// The validation error message.
+ ///
+ /// true when the validation passes; otherwise, false.
+ ///
+ bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage);
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs
new file mode 100644
index 0000000000..85eec91786
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs
@@ -0,0 +1,49 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+///
+internal class RuntimeModeValidationService : IRuntimeModeValidationService
+{
+ private readonly IOptionsMonitor _runtimeSettings;
+ private readonly IServiceProvider _serviceProvider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runtime settings.
+ /// The service provider.
+ public RuntimeModeValidationService(IOptionsMonitor runtimeSettings, IServiceProvider serviceProvider)
+ {
+ _runtimeSettings = runtimeSettings;
+ _serviceProvider = serviceProvider;
+ }
+
+ ///
+ public bool Validate([NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ var runtimeMode = _runtimeSettings.CurrentValue.Mode;
+ var validationMessages = new List();
+
+ // Runtime mode validators are registered transient, but this service is registered as singleton
+ foreach (var runtimeModeValidator in _serviceProvider.GetServices())
+ {
+ if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false)
+ {
+ validationMessages.Add(validationMessage);
+ }
+ }
+
+ if (validationMessages.Count > 0)
+ {
+ validationErrorMessage = $"Runtime mode validation failed for {runtimeMode}:\n" + string.Join("\n", validationMessages);
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/JITOptimizerValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/JITOptimizerValidator.cs
new file mode 100644
index 0000000000..d075001ecd
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/JITOptimizerValidator.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates whether the JIT/runtime optimizer of the entry assembly is enabled in production runtime mode.
+///
+///
+/// This can be ensured by building the application using the Release configuration.
+///
+///
+public class JITOptimizerValidator : RuntimeModeProductionValidatorBase
+{
+ ///
+ protected override bool Validate([NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ DebuggableAttribute? debuggableAttribute = Assembly.GetEntryAssembly()?.GetCustomAttribute();
+ if (debuggableAttribute != null && debuggableAttribute.IsJITOptimizerDisabled)
+ {
+ validationErrorMessage = "The JIT/runtime optimizer of the entry assembly needs to be enabled in production mode.";
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs
new file mode 100644
index 0000000000..06f7735d60
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs
@@ -0,0 +1,43 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates whether the ModelsBuilder mode is not set to when in development runtime mode and set to when in production runtime mode.
+///
+///
+public class ModelsBuilderModeValidator : IRuntimeModeValidator
+{
+ private readonly IOptionsMonitor _modelsBuilderSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The models builder settings.
+ public ModelsBuilderModeValidator(IOptionsMonitor modelsBuilderSettings)
+ => _modelsBuilderSettings = modelsBuilderSettings;
+
+ ///
+ public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ ModelsMode modelsMode = _modelsBuilderSettings.CurrentValue.ModelsMode;
+
+ if (runtimeMode == RuntimeMode.Development && modelsMode == ModelsMode.InMemoryAuto)
+ {
+ validationErrorMessage = "ModelsBuilder mode cannot be set to InMemoryAuto in development mode.";
+ return false;
+ }
+
+ if (runtimeMode == RuntimeMode.Production && modelsMode != ModelsMode.Nothing)
+ {
+ validationErrorMessage = "ModelsBuilder mode needs to be set to Nothing in production mode.";
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeMinificationValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeMinificationValidator.cs
new file mode 100644
index 0000000000..01bc0dd3dc
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeMinificationValidator.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates whether the runtime minification cache buster is not set to when in production runtime mode.
+///
+///
+public class RuntimeMinificationValidator : RuntimeModeProductionValidatorBase
+{
+ private readonly IOptionsMonitor _runtimeMinificationSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runtime minification settings.
+ public RuntimeMinificationValidator(IOptionsMonitor runtimeMinificationSettings)
+ => _runtimeMinificationSettings = runtimeMinificationSettings;
+
+ ///
+ protected override bool Validate([NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ if (_runtimeMinificationSettings.CurrentValue.CacheBuster == RuntimeMinificationCacheBuster.Timestamp)
+ {
+ validationErrorMessage = "Runtime minification setting needs to be set to a fixed cache buster (like Version or AppDomain) in production mode.";
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeModeProductionValidatorBase.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeModeProductionValidatorBase.cs
new file mode 100644
index 0000000000..7d23c0138b
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RuntimeModeProductionValidatorBase.cs
@@ -0,0 +1,32 @@
+using System.Diagnostics.CodeAnalysis;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates configuration based on the production runtime mode.
+///
+///
+public abstract class RuntimeModeProductionValidatorBase : IRuntimeModeValidator
+{
+ ///
+ public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ if (runtimeMode == RuntimeMode.Production)
+ {
+ return Validate(out validationErrorMessage);
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+
+ ///
+ /// Validates configuration based on the production runtime mode.
+ ///
+ /// The validation error message.
+ ///
+ /// true when the validation passes; otherwise, false.
+ ///
+ protected abstract bool Validate([NotNullWhen(false)] out string? validationErrorMessage);
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UmbracoApplicationUrlValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UmbracoApplicationUrlValidator.cs
new file mode 100644
index 0000000000..7d990dda5d
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UmbracoApplicationUrlValidator.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates whether a fixed Umbraco application URL is set when in production runtime mode.
+///
+///
+public class UmbracoApplicationUrlValidator : RuntimeModeProductionValidatorBase
+{
+ private readonly IOptionsMonitor _webRoutingSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The web routing settings.
+ public UmbracoApplicationUrlValidator(IOptionsMonitor webRoutingSettings)
+ => _webRoutingSettings = webRoutingSettings;
+
+ ///
+ protected override bool Validate([NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ if (string.IsNullOrWhiteSpace(_webRoutingSettings.CurrentValue.UmbracoApplicationUrl))
+ {
+ validationErrorMessage = "Umbraco application URL needs to be set in production mode.";
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UseHttpsValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UseHttpsValidator.cs
new file mode 100644
index 0000000000..1a02581ae6
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/UseHttpsValidator.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Configuration.Models;
+
+namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators;
+
+///
+/// Validates whether HTTPS is enforced when in production runtime mode.
+///
+///
+public class UseHttpsValidator : RuntimeModeProductionValidatorBase
+{
+ private readonly IOptionsMonitor _globalSettings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The global settings.
+ public UseHttpsValidator(IOptionsMonitor globalSettings)
+ => _globalSettings = globalSettings;
+
+ ///
+ protected override bool Validate([NotNullWhen(false)] out string? validationErrorMessage)
+ {
+ if (!_globalSettings.CurrentValue.UseHttps)
+ {
+ validationErrorMessage = "Using HTTPS should be enforced in production mode.";
+ return false;
+ }
+
+ validationErrorMessage = null;
+ return true;
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs
index 88c5a9389b..74b00d3644 100644
--- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs
@@ -12,151 +12,200 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Web.Common.DependencyInjection;
-namespace Umbraco.Cms.Infrastructure.Runtime
+namespace Umbraco.Cms.Infrastructure.Runtime;
+
+///
+/// Represents the state of the Umbraco runtime.
+///
+public class RuntimeState : IRuntimeState
{
+ internal const string PendingPackageMigrationsStateKey = "PendingPackageMigrations";
+
+ private readonly IOptions _globalSettings = null!;
+ private readonly IOptions _unattendedSettings = null!;
+ private readonly IUmbracoVersion _umbracoVersion = null!;
+ private readonly IUmbracoDatabaseFactory _databaseFactory = null!;
+ private readonly ILogger _logger = null!;
+ private readonly PendingPackageMigrations _packageMigrationState = null!;
+ private readonly Dictionary _startupState = new Dictionary();
+ private readonly IConflictingRouteService _conflictingRouteService = null!;
+ private readonly IEnumerable _databaseProviderMetadata = null!;
+ private readonly IRuntimeModeValidationService _runtimeModeValidationService = null!;
///
- /// Represents the state of the Umbraco runtime.
+ /// The initial
+ /// The initial
///
- public class RuntimeState : IRuntimeState
+ public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ private RuntimeState()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RuntimeState(
+ IOptions globalSettings,
+ IOptions unattendedSettings,
+ IUmbracoVersion umbracoVersion,
+ IUmbracoDatabaseFactory databaseFactory,
+ ILogger logger,
+ PendingPackageMigrations packageMigrationState,
+ IConflictingRouteService conflictingRouteService,
+ IEnumerable databaseProviderMetadata,
+ IRuntimeModeValidationService runtimeModeValidationService)
{
- internal const string PendingPackageMigrationsStateKey = "PendingPackageMigrations";
+ _globalSettings = globalSettings;
+ _unattendedSettings = unattendedSettings;
+ _umbracoVersion = umbracoVersion;
+ _databaseFactory = databaseFactory;
+ _logger = logger;
+ _packageMigrationState = packageMigrationState;
+ _conflictingRouteService = conflictingRouteService;
+ _databaseProviderMetadata = databaseProviderMetadata;
+ _runtimeModeValidationService = runtimeModeValidationService;
+ }
- private readonly IOptions _globalSettings = null!;
- private readonly IOptions _unattendedSettings = null!;
- private readonly IUmbracoVersion _umbracoVersion = null!;
- private readonly IUmbracoDatabaseFactory _databaseFactory = null!;
- private readonly ILogger _logger = null!;
- private readonly PendingPackageMigrations _packageMigrationState = null!;
- private readonly Dictionary _startupState = new Dictionary();
- private readonly IConflictingRouteService _conflictingRouteService = null!;
- private readonly IEnumerable _databaseProviderMetadata = null!;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [Obsolete("Use ctor with all params. This will be removed in Umbraco 12.")]
+ public RuntimeState(
+ IOptions globalSettings,
+ IOptions unattendedSettings,
+ IUmbracoVersion umbracoVersion,
+ IUmbracoDatabaseFactory databaseFactory,
+ ILogger logger,
+ PendingPackageMigrations packageMigrationState,
+ IConflictingRouteService conflictingRouteService,
+ IEnumerable databaseProviderMetadata)
+ : this(
+ globalSettings,
+ unattendedSettings,
+ umbracoVersion,
+ databaseFactory,
+ logger,
+ packageMigrationState,
+ conflictingRouteService,
+ databaseProviderMetadata,
+ StaticServiceProvider.Instance.GetRequiredService())
+ { }
- ///
- /// The initial
- /// The initial
- ///
- public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot };
+ [Obsolete("Use ctor with all params. This will be removed in Umbraco 12.")]
+ public RuntimeState(
+ IOptions globalSettings,
+ IOptions unattendedSettings,
+ IUmbracoVersion umbracoVersion,
+ IUmbracoDatabaseFactory databaseFactory,
+ ILogger logger,
+ PendingPackageMigrations packageMigrationState,
+ IConflictingRouteService conflictingRouteService)
+ : this(
+ globalSettings,
+ unattendedSettings,
+ umbracoVersion,
+ databaseFactory,
+ logger,
+ packageMigrationState,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetServices())
+ { }
- private RuntimeState()
- { }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [Obsolete("Use ctor with all params. This will be removed in Umbraco 12.")]
+ public RuntimeState(
+ IOptions globalSettings,
+ IOptions unattendedSettings,
+ IUmbracoVersion umbracoVersion,
+ IUmbracoDatabaseFactory databaseFactory,
+ ILogger logger,
+ PendingPackageMigrations packageMigrationState)
+ : this(
+ globalSettings,
+ unattendedSettings,
+ umbracoVersion,
+ databaseFactory,
+ logger,
+ packageMigrationState,
+ StaticServiceProvider.Instance.GetRequiredService())
+ { }
- public RuntimeState(
- IOptions globalSettings,
- IOptions unattendedSettings,
- IUmbracoVersion umbracoVersion,
- IUmbracoDatabaseFactory databaseFactory,
- ILogger logger,
- PendingPackageMigrations packageMigrationState,
- IConflictingRouteService conflictingRouteService,
- IEnumerable databaseProviderMetadata)
+ ///
+ public Version Version => _umbracoVersion.Version;
+
+ ///
+ public string VersionComment => _umbracoVersion.Comment;
+
+ ///
+ public SemVersion SemanticVersion => _umbracoVersion.SemanticVersion;
+
+ ///
+ public string? CurrentMigrationState { get; private set; }
+
+ ///
+ public string? FinalMigrationState { get; private set; }
+
+ ///
+ public RuntimeLevel Level { get; internal set; } = RuntimeLevel.Unknown;
+
+ ///
+ public RuntimeLevelReason Reason { get; internal set; } = RuntimeLevelReason.Unknown;
+
+ ///
+ public BootFailedException? BootFailedException { get; internal set; }
+
+ ///
+ public IReadOnlyDictionary StartupState => _startupState;
+
+ ///
+ public void DetermineRuntimeLevel()
+ {
+ if (_databaseFactory.Configured == false)
{
- _globalSettings = globalSettings;
- _unattendedSettings = unattendedSettings;
- _umbracoVersion = umbracoVersion;
- _databaseFactory = databaseFactory;
- _logger = logger;
- _packageMigrationState = packageMigrationState;
- _conflictingRouteService = conflictingRouteService;
- _databaseProviderMetadata = databaseProviderMetadata;
+ // local version *does* match code version, but the database is not configured
+ // install - may happen with Deploy/Cloud/etc
+ _logger.LogDebug("Database is not configured, need to install Umbraco.");
+
+ Level = RuntimeLevel.Install;
+ Reason = RuntimeLevelReason.InstallNoDatabase;
+
+ return;
}
- [Obsolete("Use ctor with all params. This will be removed in Umbraco 12")]
- public RuntimeState(
- IOptions globalSettings,
- IOptions unattendedSettings,
- IUmbracoVersion umbracoVersion,
- IUmbracoDatabaseFactory databaseFactory,
- ILogger logger,
- PendingPackageMigrations packageMigrationState,
- IConflictingRouteService conflictingRouteService)
- : this(
- globalSettings,
- unattendedSettings,
- umbracoVersion,
- databaseFactory,
- logger,
- packageMigrationState,
- StaticServiceProvider.Instance.GetRequiredService(),
- StaticServiceProvider.Instance.GetServices())
- { }
-
- ///
- /// Initializes a new instance of the class.
- ///
- [Obsolete("use ctor with all params")]
- public RuntimeState(
- IOptions globalSettings,
- IOptions unattendedSettings,
- IUmbracoVersion umbracoVersion,
- IUmbracoDatabaseFactory databaseFactory,
- ILogger logger,
- PendingPackageMigrations packageMigrationState)
- : this(
- globalSettings,
- unattendedSettings,
- umbracoVersion,
- databaseFactory,
- logger,
- packageMigrationState,
- StaticServiceProvider.Instance.GetRequiredService())
- { }
-
- ///
- public Version Version => _umbracoVersion.Version;
-
- ///
- public string VersionComment => _umbracoVersion.Comment;
-
- ///
- public SemVersion SemanticVersion => _umbracoVersion.SemanticVersion;
-
- ///
- public string? CurrentMigrationState { get; private set; }
-
- ///
- public string? FinalMigrationState { get; private set; }
-
- ///
- public RuntimeLevel Level { get; internal set; } = RuntimeLevel.Unknown;
-
- ///
- public RuntimeLevelReason Reason { get; internal set; } = RuntimeLevelReason.Unknown;
-
- ///
- public BootFailedException? BootFailedException { get; internal set; }
-
- ///
- public IReadOnlyDictionary StartupState => _startupState;
-
- ///
- public void DetermineRuntimeLevel()
+ // Validate runtime mode
+ if (_runtimeModeValidationService.Validate(out var validationErrorMessage) == false)
{
- if (_databaseFactory.Configured == false)
- {
- // local version *does* match code version, but the database is not configured
- // install - may happen with Deploy/Cloud/etc
- _logger.LogDebug("Database is not configured, need to install Umbraco.");
- Level = RuntimeLevel.Install;
- Reason = RuntimeLevelReason.InstallNoDatabase;
- return;
- }
+ _logger.LogError(validationErrorMessage);
- // Check if we have multiple controllers with the same name.
- if (_conflictingRouteService.HasConflictingRoutes(out string controllerName))
- {
- Level = RuntimeLevel.BootFailed;
- Reason = RuntimeLevelReason.BootFailedOnException;
- BootFailedException = new BootFailedException($"Conflicting routes, you cannot have multiple controllers with the same name: {controllerName}");
+ Level = RuntimeLevel.BootFailed;
+ Reason = RuntimeLevelReason.BootFailedOnException;
+ BootFailedException = new BootFailedException(validationErrorMessage);
- return;
- }
+ return;
+ }
- // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
+ // Check if we have multiple controllers with the same name.
+ if (_conflictingRouteService.HasConflictingRoutes(out string controllerName))
+ {
+ var message = $"Conflicting routes, you cannot have multiple controllers with the same name: {controllerName}";
+ _logger.LogError(message);
- switch (GetUmbracoDatabaseState(_databaseFactory))
- {
- case UmbracoDatabaseState.CannotConnect:
+ Level = RuntimeLevel.BootFailed;
+ Reason = RuntimeLevelReason.BootFailedOnException;
+ BootFailedException = new BootFailedException(message);
+
+ return;
+ }
+
+ // Check the database state, whether we can connect or if it's in an upgrade or empty state, etc...
+ switch (GetUmbracoDatabaseState(_databaseFactory))
+ {
+ case UmbracoDatabaseState.CannotConnect:
{
// cannot connect to configured database, this is bad, fail
_logger.LogDebug("Could not connect to database.");
@@ -174,14 +223,14 @@ namespace Umbraco.Cms.Infrastructure.Runtime
BootFailedException = new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
throw BootFailedException;
}
- case UmbracoDatabaseState.NotInstalled:
+ case UmbracoDatabaseState.NotInstalled:
{
// ok to install on an empty database
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallEmptyDatabase;
return;
}
- case UmbracoDatabaseState.NeedsUpgrade:
+ case UmbracoDatabaseState.NeedsUpgrade:
{
// the db version does not match... but we do have a migration table
// so, at least one valid table, so we quite probably are installed & need to upgrade
@@ -193,26 +242,26 @@ namespace Umbraco.Cms.Infrastructure.Runtime
Reason = RuntimeLevelReason.UpgradeMigrations;
}
break;
- case UmbracoDatabaseState.NeedsPackageMigration:
+ case UmbracoDatabaseState.NeedsPackageMigration:
- // no matter what the level is run for package migrations.
- // they either run unattended, or only manually via the back office.
- Level = RuntimeLevel.Run;
+ // no matter what the level is run for package migrations.
+ // they either run unattended, or only manually via the back office.
+ Level = RuntimeLevel.Run;
- if (_unattendedSettings.Value.PackageMigrationsUnattended)
- {
- _logger.LogDebug("Package migrations need to execute.");
- Reason = RuntimeLevelReason.UpgradePackageMigrations;
- }
- else
- {
- _logger.LogInformation("Package migrations need to execute but unattended package migrations is disabled. They will need to be run from the back office.");
- Reason = RuntimeLevelReason.Run;
- }
+ if (_unattendedSettings.Value.PackageMigrationsUnattended)
+ {
+ _logger.LogDebug("Package migrations need to execute.");
+ Reason = RuntimeLevelReason.UpgradePackageMigrations;
+ }
+ else
+ {
+ _logger.LogInformation("Package migrations need to execute but unattended package migrations is disabled. They will need to be run from the back office.");
+ Reason = RuntimeLevelReason.Run;
+ }
- break;
- case UmbracoDatabaseState.Ok:
- default:
+ break;
+ case UmbracoDatabaseState.Ok:
+ default:
{
@@ -221,116 +270,115 @@ namespace Umbraco.Cms.Infrastructure.Runtime
Reason = RuntimeLevelReason.Run;
}
break;
- }
- }
-
- public void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception? bootFailedException = null)
- {
- Level = level;
- Reason = reason;
-
- if (bootFailedException != null)
- {
- BootFailedException = new BootFailedException(bootFailedException.Message, bootFailedException);
- }
- }
-
- private enum UmbracoDatabaseState
- {
- Ok,
- CannotConnect,
- NotInstalled,
- NeedsUpgrade,
- NeedsPackageMigration
- }
-
- private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
- {
- try
- {
- if (!TryDbConnect(databaseFactory))
- {
- return UmbracoDatabaseState.CannotConnect;
- }
-
- // no scope, no service - just directly accessing the database
- using (IUmbracoDatabase database = databaseFactory.CreateDatabase())
- {
- if (!database.IsUmbracoInstalled())
- {
- return UmbracoDatabaseState.NotInstalled;
- }
-
- // Make ONE SQL call to determine Umbraco upgrade vs package migrations state.
- // All will be prefixed with the same key.
- IReadOnlyDictionary? keyValues = database.GetFromKeyValueTable(Constants.Conventions.Migrations.KeyValuePrefix);
-
- // This could need both an upgrade AND package migrations to execute but
- // we will process them one at a time, first the upgrade, then the package migrations.
- if (DoesUmbracoRequireUpgrade(keyValues))
- {
- return UmbracoDatabaseState.NeedsUpgrade;
- }
-
- IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues);
- if (packagesRequiringMigration.Count > 0)
- {
- _startupState[PendingPackageMigrationsStateKey] = packagesRequiringMigration;
-
- return UmbracoDatabaseState.NeedsPackageMigration;
- }
- }
-
- return UmbracoDatabaseState.Ok;
- }
- catch (Exception e)
- {
- // can connect to the database so cannot check the upgrade state... oops
- _logger.LogWarning(e, "Could not check the upgrade state.");
-
- // else it is bad enough that we want to throw
- Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
- BootFailedException = new BootFailedException("Could not check the upgrade state.", e);
- throw BootFailedException;
- }
- }
-
- private bool DoesUmbracoRequireUpgrade(IReadOnlyDictionary? keyValues)
- {
- var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion));
- var stateValueKey = upgrader.StateValueKey;
-
- if(keyValues?.TryGetValue(stateValueKey, out var value) ?? false)
- {
- CurrentMigrationState = value;
- }
-
- FinalMigrationState = upgrader.Plan.FinalState;
-
- _logger.LogDebug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "");
-
- return CurrentMigrationState != FinalMigrationState;
- }
-
- private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
- {
- // anything other than install wants a database - see if we can connect
- // (since this is an already existing database, assume localdb is ready)
- bool canConnect;
- var tries = _globalSettings.Value.InstallMissingDatabase ? 2 : 5;
- for (var i = 0; ;)
- {
- canConnect = databaseFactory.CanConnect;
- if (canConnect || ++i == tries)
- {
- break;
- }
-
- _logger.LogDebug("Could not immediately connect to database, trying again.");
- Thread.Sleep(1000);
- }
-
- return canConnect;
}
}
+
+ public void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception? bootFailedException = null)
+ {
+ Level = level;
+ Reason = reason;
+
+ if (bootFailedException != null)
+ {
+ BootFailedException = new BootFailedException(bootFailedException.Message, bootFailedException);
+ }
+ }
+
+ private enum UmbracoDatabaseState
+ {
+ Ok,
+ CannotConnect,
+ NotInstalled,
+ NeedsUpgrade,
+ NeedsPackageMigration
+ }
+
+ private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory databaseFactory)
+ {
+ try
+ {
+ if (!TryDbConnect(databaseFactory))
+ {
+ return UmbracoDatabaseState.CannotConnect;
+ }
+
+ // no scope, no service - just directly accessing the database
+ using (IUmbracoDatabase database = databaseFactory.CreateDatabase())
+ {
+ if (!database.IsUmbracoInstalled())
+ {
+ return UmbracoDatabaseState.NotInstalled;
+ }
+
+ // Make ONE SQL call to determine Umbraco upgrade vs package migrations state.
+ // All will be prefixed with the same key.
+ IReadOnlyDictionary? keyValues = database.GetFromKeyValueTable(Constants.Conventions.Migrations.KeyValuePrefix);
+
+ // This could need both an upgrade AND package migrations to execute but
+ // we will process them one at a time, first the upgrade, then the package migrations.
+ if (DoesUmbracoRequireUpgrade(keyValues))
+ {
+ return UmbracoDatabaseState.NeedsUpgrade;
+ }
+
+ IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues);
+ if (packagesRequiringMigration.Count > 0)
+ {
+ _startupState[PendingPackageMigrationsStateKey] = packagesRequiringMigration;
+
+ return UmbracoDatabaseState.NeedsPackageMigration;
+ }
+ }
+
+ return UmbracoDatabaseState.Ok;
+ }
+ catch (Exception e)
+ {
+ // can connect to the database so cannot check the upgrade state... oops
+ _logger.LogWarning(e, "Could not check the upgrade state.");
+
+ // else it is bad enough that we want to throw
+ Reason = RuntimeLevelReason.BootFailedCannotCheckUpgradeState;
+ BootFailedException = new BootFailedException("Could not check the upgrade state.", e);
+ throw BootFailedException;
+ }
+ }
+
+ private bool DoesUmbracoRequireUpgrade(IReadOnlyDictionary? keyValues)
+ {
+ var upgrader = new Upgrader(new UmbracoPlan(_umbracoVersion));
+ var stateValueKey = upgrader.StateValueKey;
+
+ if (keyValues?.TryGetValue(stateValueKey, out var value) ?? false)
+ {
+ CurrentMigrationState = value;
+ }
+
+ FinalMigrationState = upgrader.Plan.FinalState;
+
+ _logger.LogDebug("Final upgrade state is {FinalMigrationState}, database contains {DatabaseState}", FinalMigrationState, CurrentMigrationState ?? "");
+
+ return CurrentMigrationState != FinalMigrationState;
+ }
+
+ private bool TryDbConnect(IUmbracoDatabaseFactory databaseFactory)
+ {
+ // anything other than install wants a database - see if we can connect
+ // (since this is an already existing database, assume localdb is ready)
+ bool canConnect;
+ var tries = _globalSettings.Value.InstallMissingDatabase ? 2 : 5;
+ for (var i = 0; ;)
+ {
+ canConnect = databaseFactory.CanConnect;
+ if (canConnect || ++i == tries)
+ {
+ break;
+ }
+
+ _logger.LogDebug("Could not immediately connect to database, trying again.");
+ Thread.Sleep(1000);
+ }
+
+ return canConnect;
+ }
}
diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
index b72de65682..0d8cc49bdf 100644
--- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -23,39 +23,38 @@ using Umbraco.Cms.Web.BackOffice.Trees;
namespace Umbraco.Extensions;
///
-/// Extension methods for for the Umbraco back office
+/// Extension methods for for the Umbraco back office
///
public static partial class UmbracoBuilderExtensions
{
///
- /// Adds all required components to run the Umbraco back office
+ /// Adds all required components to run the Umbraco back office
///
- public static IUmbracoBuilder
- AddBackOffice(this IUmbracoBuilder builder, Action? configureMvc = null) => builder
- .AddConfiguration()
- .AddUmbracoCore()
- .AddWebComponents()
- .AddRuntimeMinifier()
- .AddBackOfficeCore()
- .AddBackOfficeAuthentication()
- .AddBackOfficeIdentity()
- .AddMembersIdentity()
- .AddBackOfficeAuthorizationPolicies()
- .AddUmbracoProfiler()
- .AddMvcAndRazor(configureMvc)
- .AddWebServer()
- .AddPreviewSupport()
- .AddHostedServices()
- .AddNuCache()
- .AddDistributedCache()
- .AddModelsBuilderDashboard()
- .AddUnattendedInstallInstallCreateUser()
- .AddCoreNotifications()
- .AddLogViewer()
- .AddExamine()
- .AddExamineIndexes()
- .AddControllersWithAmbiguousConstructors()
- .AddSupplemenataryLocalizedTextFileSources();
+ public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder, Action? configureMvc = null) => builder
+ .AddConfiguration()
+ .AddUmbracoCore()
+ .AddWebComponents()
+ .AddRuntimeMinifier()
+ .AddBackOfficeCore()
+ .AddBackOfficeAuthentication()
+ .AddBackOfficeIdentity()
+ .AddMembersIdentity()
+ .AddBackOfficeAuthorizationPolicies()
+ .AddUmbracoProfiler()
+ .AddMvcAndRazor(configureMvc)
+ .AddWebServer()
+ .AddPreviewSupport()
+ .AddHostedServices()
+ .AddNuCache()
+ .AddDistributedCache()
+ .TryAddModelsBuilderDashboard()
+ .AddUnattendedInstallInstallCreateUser()
+ .AddCoreNotifications()
+ .AddLogViewer()
+ .AddExamine()
+ .AddExamineIndexes()
+ .AddControllersWithAmbiguousConstructors()
+ .AddSupplemenataryLocalizedTextFileSources();
public static IUmbracoBuilder AddUnattendedInstallInstallCreateUser(this IUmbracoBuilder builder)
{
diff --git a/src/Umbraco.Web.BackOffice/ModelsBuilder/DisableModelsBuilderNotificationHandler.cs b/src/Umbraco.Web.BackOffice/ModelsBuilder/DisableModelsBuilderNotificationHandler.cs
index 970fe7e778..2e11af0d1f 100644
--- a/src/Umbraco.Web.BackOffice/ModelsBuilder/DisableModelsBuilderNotificationHandler.cs
+++ b/src/Umbraco.Web.BackOffice/ModelsBuilder/DisableModelsBuilderNotificationHandler.cs
@@ -5,7 +5,7 @@ using Umbraco.Cms.Core.Notifications;
namespace Umbraco.Cms.Web.BackOffice.ModelsBuilder;
///
-/// Used in conjunction with
+/// Used in conjunction with
///
internal class DisableModelsBuilderNotificationHandler : INotificationHandler
{
diff --git a/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs
index f9bdcd1b77..a0a0aeec8c 100644
--- a/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/ModelsBuilder/UmbracoBuilderExtensions.cs
@@ -1,30 +1,59 @@
-using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.Configuration.Models;
+using Umbraco.Cms.Core.Dashboards;
using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core.Notifications;
+using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.ModelsBuilder;
using Umbraco.Extensions;
namespace Umbraco.Cms.Web.BackOffice.ModelsBuilder;
///
-/// Extension methods for for the common Umbraco functionality
+/// Extension methods for for the common Umbraco functionality
///
public static class UmbracoBuilderExtensions
{
///
- /// Adds the ModelsBuilder dashboard.
+ /// Adds the ModelsBuilder dashboard, but only when not in production mode.
///
- public static IUmbracoBuilder AddModelsBuilderDashboard(this IUmbracoBuilder builder)
+ internal static IUmbracoBuilder TryAddModelsBuilderDashboard(this IUmbracoBuilder builder)
{
- builder.Services.AddUnique();
+ if (builder.Config.GetRuntimeMode() == RuntimeMode.Production)
+ {
+ builder.RemoveModelsBuilderDashboard();
+ }
+ else
+ {
+ builder.AddModelsBuilderDashboard();
+ }
+
return builder;
}
///
- /// Can be called if using an external models builder to remove the embedded models builder controller features
+ /// Adds the ModelsBuilder dashboard (dashboard and API controller are automatically added).
///
- public static IUmbracoBuilder DisableModelsBuilderControllers(this IUmbracoBuilder builder)
+ public static IUmbracoBuilder AddModelsBuilderDashboard(this IUmbracoBuilder builder)
{
- builder.Services.AddSingleton();
+ builder.Services.AddUnique();
+
return builder;
}
+
+ ///
+ /// Removes the ModelsBuilder dashboard (and API controller).
+ ///
+ public static IUmbracoBuilder RemoveModelsBuilderDashboard(this IUmbracoBuilder builder)
+ {
+ builder.Dashboards().Remove();
+ builder.WithCollectionBuilder().Remove();
+
+ return builder;
+ }
+
+ ///
+ /// Can be called if using an external models builder to remove the embedded models builder controller features.
+ ///
+ public static IUmbracoBuilder DisableModelsBuilderControllers(this IUmbracoBuilder builder)
+ => builder.AddNotificationHandler();
}
diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
index 666342dc10..40b84a0987 100644
--- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -224,13 +224,12 @@ public static partial class UmbracoBuilderExtensions
{
// TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important
// this will directly affect developers who need to call that themselves.
- // We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set.
- // But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay.
- IMvcBuilder mvcBuilder = builder.Services
- .AddControllersWithViews();
+ IMvcBuilder mvcBuilder = builder.Services.AddControllersWithViews();
- FixForDotnet6Preview1(builder.Services);
- mvcBuilder.AddRazorRuntimeCompilation();
+ if (builder.Config.GetRuntimeMode() != RuntimeMode.Production)
+ {
+ mvcBuilder.AddRazorRuntimeCompilation();
+ }
mvcBuilding?.Invoke(mvcBuilder);
@@ -421,30 +420,4 @@ public static partial class UmbracoBuilderExtensions
wrappedWebRoutingSettings,
webHostEnvironment);
}
-
- ///
- /// This fixes an issue for .NET6 Preview1, that in AddRazorRuntimeCompilation cannot remove the existing
- /// IViewCompilerProvider.
- ///
- ///
- /// When running .NET6 Preview1 there is an issue with looks to be fixed when running ASP.NET Core 6.
- /// This issue is because the default implementation of IViewCompilerProvider has changed, so the
- /// AddRazorRuntimeCompilation extension can't remove the default and replace with the runtimeviewcompiler.
- /// This method basically does the same as the ASP.NET Core 6 version of AddRazorRuntimeCompilation
- /// https://github.com/dotnet/aspnetcore/blob/f7dc5e24af7f9692a1db66741954b90b42d84c3a/src/Mvc/Mvc.Razor.RuntimeCompilation/src/DependencyInjection/RazorRuntimeCompilationMvcCoreBuilderExtensions.cs#L71-L80
- /// While running .NET5 this does nothing as the ImplementationType has another FullName, and this is handled by the
- /// .NET5 version of AddRazorRuntimeCompilation
- ///
- private static void FixForDotnet6Preview1(IServiceCollection services)
- {
- ServiceDescriptor? compilerProvider = services.FirstOrDefault(f =>
- f.ServiceType == typeof(IViewCompilerProvider) &&
- f.ImplementationType?.Assembly == typeof(IViewCompilerProvider).Assembly &&
- f.ImplementationType.FullName == "Microsoft.AspNetCore.Mvc.Razor.Compilation.DefaultViewCompiler");
-
- if (compilerProvider != null)
- {
- services.Remove(compilerProvider);
- }
- }
}
diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs
index 7749c4cbc9..95ae91d7b7 100644
--- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs
+++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Razor;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@@ -81,8 +82,7 @@ public static class UmbracoBuilderDependencyInjectionExtensions
///
public static IUmbracoBuilder AddModelsBuilder(this IUmbracoBuilder builder)
{
- var umbServices =
- new UniqueServiceDescriptor(typeof(UmbracoServices), typeof(UmbracoServices), ServiceLifetime.Singleton);
+ var umbServices = new UniqueServiceDescriptor(typeof(UmbracoServices), typeof(UmbracoServices), ServiceLifetime.Singleton);
if (builder.Services.Contains(umbServices))
{
// if this ext method is called more than once just exit
@@ -91,44 +91,35 @@ public static class UmbracoBuilderDependencyInjectionExtensions
builder.Services.Add(umbServices);
- builder.AddInMemoryModelsRazorEngine();
+ if (builder.Config.GetRuntimeMode() == RuntimeMode.BackofficeDevelopment)
+ {
+ // Configure services to allow InMemoryAuto mode
+ builder.AddInMemoryModelsRazorEngine();
- // TODO: I feel like we could just do builder.AddNotificationHandler() and it
- // would automatically just register for all implemented INotificationHandler{T}?
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ }
+ if (builder.Config.GetRuntimeMode() != RuntimeMode.Production)
+ {
+ // Configure service to allow models generation
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ builder.AddNotificationHandler();
+ }
+
+ builder.Services.TryAddSingleton();
+
+ // Register required services for ModelsBuilderDashboardController
builder.Services.AddSingleton();
builder.Services.AddSingleton();
- builder.AddNotificationHandler();
- builder.AddNotificationHandler();
builder.Services.AddSingleton();
- builder.Services.AddSingleton();
-
- // This is what the community MB would replace, all of the above services are fine to be registered
- // even if the community MB is in place.
- builder.Services.AddSingleton(factory =>
- {
- ModelsBuilderSettings config = factory.GetRequiredService>().Value;
- if (config.ModelsMode == ModelsMode.InMemoryAuto)
- {
- return factory.GetRequiredService();
- }
-
- return factory.CreateDefaultPublishedModelFactory();
- });
-
- if (!builder.Services.Any(x => x.ServiceType == typeof(IModelsBuilderDashboardProvider)))
- {
- builder.Services.AddUnique();
- }
-
return builder;
}
@@ -152,6 +143,23 @@ public static class UmbracoBuilderDependencyInjectionExtensions
},
s.GetRequiredService()));
+ builder.Services.AddSingleton();
+
+ // This is what the community MB would replace, all of the above services are fine to be registered
+ // even if the community MB is in place.
+ builder.Services.AddSingleton(factory =>
+ {
+ ModelsBuilderSettings modelsBuilderSettings = factory.GetRequiredService>().Value;
+ if (modelsBuilderSettings.ModelsMode == ModelsMode.InMemoryAuto)
+ {
+ return factory.GetRequiredService();
+ }
+ else
+ {
+ return factory.CreateDefaultPublishedModelFactory();
+ }
+ });
+
return builder;
}
}
diff --git a/src/Umbraco.Web.Common/ModelsBuilder/NoopModelsBuilderDashboardProvider.cs b/src/Umbraco.Web.Common/ModelsBuilder/NoopModelsBuilderDashboardProvider.cs
index 1be67c575c..169f6af0f5 100644
--- a/src/Umbraco.Web.Common/ModelsBuilder/NoopModelsBuilderDashboardProvider.cs
+++ b/src/Umbraco.Web.Common/ModelsBuilder/NoopModelsBuilderDashboardProvider.cs
@@ -2,5 +2,5 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder;
public class NoopModelsBuilderDashboardProvider : IModelsBuilderDashboardProvider
{
- public string GetUrl() => string.Empty;
+ public string GetUrl() => null!;
}
diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
index 42e894a7f3..5f067d8a9d 100644
--- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
+++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
@@ -1,75 +1,57 @@
-
-
+
net6.0
Umbraco.Cms.Web.UI
-
- bin/Release/Umbraco.Web.UI.xml
-
-
-
- true
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
-
+
+
+
+
+
+
+
+
+
false
false
- false
+ true
$(ProjectDir)appsettings-schema.json
$(ProjectDir)../JsonSchema/JsonSchema.csproj
-
+
-
-
-
-
-
-
+
-
+
-
-
+
+
-
+
-
diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj
index efaa0edbb0..574ff04452 100644
--- a/templates/UmbracoProject/UmbracoProject.csproj
+++ b/templates/UmbracoProject/UmbracoProject.csproj
@@ -21,17 +21,14 @@
-
- true
-
-
-
+
false
false
+ true