From 859505e75150b31ef2c4c049458d459449d07b2f Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 23 Sep 2025 11:58:09 +0200 Subject: [PATCH] Models builder: Move InMemoryAuto models builder and razor runtime compilation into its own package to enable hot reload (#20187) * Move in memory models builder out of core * Move runtime validations into backoffice development project * Obsolete ModelsMode enum * Move the InMemoryModelsbuilder/RRC novel into the Backoffice development umbraco builder extension * Add runtime validator to warn if InMemoryAuto is selected but the package isn't installed * Add backoffice development to template * Remove propertyGroup * Remove oopsie * Check for modelsbuilder in notification handler instead of runtime validator * Update src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove ModelsMode enum and ModelsModeExtensions * Apply suggestions from code review Co-authored-by: Kenn Jacobsen * Move project to source folder --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Kenn Jacobsen --- .../BuildModelsBuilderController.cs | 4 +- .../ModelsBuilderPresentationFactory.cs | 2 +- .../ModelsBuilderResponseModel.cs | 2 +- .../BackofficeDevelopmentComposer.cs | 9 ++ .../UmbracoBuilderExtensions.cs | 127 ++++++++++++++++++ .../InMemoryAuto/ChecksumValidator.cs | 2 +- .../CollectibleRuntimeViewCompiler.cs | 3 +- .../CompilationExceptionFactory.cs | 2 +- .../CompilationOptionsProvider.cs | 2 +- .../InMemoryAssemblyLoadContextManager.cs | 3 +- .../InMemoryAuto/InMemoryModelFactory.cs | 5 +- .../InMemoryModelsBuilderModeValidator.cs | 32 +++++ .../ModelsBuilderAssemblyAttribute.cs | 2 +- .../ModelsBuilderBindingErrorHandler.cs | 64 +++++++++ .../InMemoryAuto/ModelsModeConstants.cs | 6 + .../InMemoryAuto}/RoslynCompiler.cs | 2 +- .../RuntimeCompilationCacheBuster.cs | 2 +- .../UmbracoAssemblyLoadContext.cs | 2 +- .../UmbracoCompilationException.cs | 2 +- .../UmbracoRazorReferenceManager.cs | 2 +- .../UmbracoViewCompilerProvider.cs | 2 +- ...raco.Cms.DevelopmentMode.Backoffice.csproj | 17 +++ .../Models/ModelsBuilderSettings.cs | 18 +-- src/Umbraco.Core/Configuration/ModelsMode.cs | 42 ------ .../Configuration/ModelsModeExtensions.cs | 27 ---- src/Umbraco.Core/Constants-ModelsBuilder.cs | 9 ++ .../Extensions/ConfigurationExtensions.cs | 2 +- .../AutoModelsNotificationHandler.cs | 9 +- .../ModelsBuilder/Building/Builder.cs | 3 +- .../ModelsBuilder/Building/PropertyModel.cs | 2 +- .../ModelsBuilder/Building/TextBuilder.cs | 5 + .../ModelsBuilderModeValidator.cs | 13 +- .../RazorRuntimeCompilationValidator.cs | 31 +++++ .../UmbracoBuilderExtensions.cs | 5 - .../Filters/ModelBindingExceptionAttribute.cs | 2 +- .../Logging/RegisteredReloadableLogger.cs | 1 - ...acoBuilderDependencyInjectionExtensions.cs | 116 +--------------- .../ModelsBuilderNotificationHandler.cs | 61 +-------- .../Umbraco.Web.Common.csproj | 5 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 +- .../UmbracoProject/UmbracoProject.csproj | 3 + .../Services/SystemInformationServiceTests.cs | 16 ++- ...ootingInformationTelemetryProviderTests.cs | 15 ++- .../Umbraco.Tests.UnitTests.csproj | 1 + umbraco.sln | 8 ++ 45 files changed, 373 insertions(+), 318 deletions(-) create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/BackofficeDevelopmentComposer.cs create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/UmbracoBuilderExtensions.cs rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/ChecksumValidator.cs (98%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/CollectibleRuntimeViewCompiler.cs (99%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/CompilationExceptionFactory.cs (98%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/CompilationOptionsProvider.cs (99%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs (96%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/InMemoryModelFactory.cs (99%) create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelsBuilderModeValidator.cs rename src/{Umbraco.Infrastructure/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto}/ModelsBuilderAssemblyAttribute.cs (92%) create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderBindingErrorHandler.cs create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsModeConstants.cs rename src/{Umbraco.Infrastructure/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto}/RoslynCompiler.cs (98%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/RuntimeCompilationCacheBuster.cs (96%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/UmbracoAssemblyLoadContext.cs (91%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/UmbracoCompilationException.cs (77%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/UmbracoRazorReferenceManager.cs (98%) rename src/{Umbraco.Web.Common/ModelsBuilder => Umbraco.Cms.DevelopmentMode.Backoffice}/InMemoryAuto/UmbracoViewCompilerProvider.cs (98%) create mode 100644 src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj delete mode 100644 src/Umbraco.Core/Configuration/ModelsMode.cs delete mode 100644 src/Umbraco.Core/Configuration/ModelsModeExtensions.cs create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RazorRuntimeCompilationValidator.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs index bf6c52fbb6..4282de3697 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ModelsBuilder/BuildModelsBuilderController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Infrastructure.ModelsBuilder; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; @@ -39,7 +40,8 @@ public class BuildModelsBuilderController : ModelsBuilderControllerBase { try { - if (!_modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration()) + if (_modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeManual + && _modelsBuilderSettings.ModelsMode != Constants.ModelsBuilder.ModelsModes.SourceCodeAuto) { var problemDetailsModel = new ProblemDetails { diff --git a/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs index 576cc4fa4b..b021e0b6ce 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/ModelsBuilderPresentationFactory.cs @@ -29,7 +29,7 @@ public class ModelsBuilderPresentationFactory : IModelsBuilderPresentationFactor new() { Mode = _modelsBuilderSettings.ModelsMode, - CanGenerate = _modelsBuilderSettings.ModelsMode.SupportsExplicitGeneration(), + CanGenerate = _modelsBuilderSettings.ModelsMode is Constants.ModelsBuilder.ModelsModes.SourceCodeManual or Constants.ModelsBuilder.ModelsModes.SourceCodeAuto, OutOfDateModels = _outOfDateModels.IsOutOfDate, LastError = _mbErrors.GetLastError(), Version = ApiVersion.Current.Version.ToString(), diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ModelsBuilderDashboard/ModelsBuilderResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ModelsBuilderDashboard/ModelsBuilderResponseModel.cs index aef7eaece6..c5a773b142 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/ModelsBuilderDashboard/ModelsBuilderResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ModelsBuilderDashboard/ModelsBuilderResponseModel.cs @@ -4,7 +4,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.ModelsBuilderDashboard; public class ModelsBuilderResponseModel { - public ModelsMode Mode { get; set; } + public required string Mode { get; set; } public bool CanGenerate { get; set; } diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/BackofficeDevelopmentComposer.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/BackofficeDevelopmentComposer.cs new file mode 100644 index 0000000000..a076b8a791 --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/BackofficeDevelopmentComposer.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.DevelopmentMode.Backoffice.DependencyInjection; + +public class BackofficeDevelopmentComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) => builder.AddBackofficeDevelopment(); +} diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..3da32bcf9b --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; +using Umbraco.Extensions; + +namespace Umbraco.Cms.DevelopmentMode.Backoffice.DependencyInjection; + + +/* + * OVERVIEW: + * + * The CSharpCompiler is responsible for the actual compilation of razor at runtime. + * It creates a CSharpCompilation instance to do the compilation. This is where DLL references + * are applied. However, the way this works is not flexible for dynamic assemblies since the references + * are only discovered and loaded once before the first compilation occurs. This is done here: + * https://github.com/dotnet/aspnetcore/blob/114f0f6d1ef1d777fb93d90c87ac506027c55ea0/src/Mvc/Mvc.Razor.RuntimeCompilation/src/CSharpCompiler.cs#L79 + * The CSharpCompiler is internal and cannot be replaced or extended, however it's references come from: + * RazorReferenceManager. Unfortunately this is also internal and cannot be replaced, though it can be extended + * using MvcRazorRuntimeCompilationOptions, except this is the place where references are only loaded once which + * is done with a LazyInitializer. See https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs#L35. + * + * The way that RazorReferenceManager works is by resolving references from the ApplicationPartsManager - either by + * an application part that is specifically an ICompilationReferencesProvider or an AssemblyPart. So to fulfill this + * requirement, we add the MB assembly to the assembly parts manager within the InMemoryModelFactory when the assembly + * is (re)generated. But due to the above restrictions, when re-generating, this will have no effect since the references + * have already been resolved with the LazyInitializer in the RazorReferenceManager. There is a known public API + * where you can add reference paths to the runtime razor compiler via it's IOptions: MvcRazorRuntimeCompilationOptions + * however this falls short too because those references are just loaded via the RazorReferenceManager and lazy initialized. + * + * The services that can be replaced are: IViewCompilerProvider (default is the internal RuntimeViewCompilerProvider) and + * IViewCompiler (default is the internal RuntimeViewCompiler). There is one specific public extension point that I was + * hoping would solve all of the problems which was IMetadataReferenceFeature (implemented by LazyMetadataReferenceFeature + * which uses RazorReferencesManager) which is a razor feature that you can add + * to the RazorProjectEngine. It is used to resolve roslyn references and by default is backed by RazorReferencesManager. + * Unfortunately, this service is not used by the CSharpCompiler, it seems to only be used by some tag helper compilations. + * + * There are caches at several levels, all of which are not publicly accessible APIs (apart from RazorViewEngine.ViewLookupCache + * which is possible to clear by casting and then calling cache.Compact(100); but that doesn't get us far enough). + * + * For this to work, several caches must be cleared: + * - RazorViewEngine.ViewLookupCache + * - RazorReferencesManager._compilationReferences + * - RazorPageActivator._activationInfo (though this one may be optional) + * - RuntimeViewCompiler._cache + * + * What are our options? + * + * a) We can copy a ton of code into our application: CSharpCompiler, RuntimeViewCompilerProvider, RuntimeViewCompiler and + * RazorReferenceManager (probably more depending on the extent of Internal references). + * b) We can use reflection to try to access all of the above resources and try to forcefully clear caches and reset initialization flags. + * c) We hack these replace-able services with our own implementations that wrap the default services. To do this + * requires re-resolving the original services from a pre-built DI container. In effect this re-creates these + * services from scratch which means there is no caches. + * + * ... Option C worked, however after a breaking change from dotnet, we cannot go with this options any longer. + * The reason for this is that when the default RuntimeViewCompiler loads in the assembly using Assembly.Load, + * This will not work for us since this loads the compiled views into the default AssemblyLoadContext, + * and our compiled models are loaded in the collectible UmbracoAssemblyLoadContext, and as per the breaking change + * you're no longer allowed reference a collectible load context from a non-collectible one + * That is the non-collectible compiled views are not allowed to reference the collectible InMemoryAuto models. + * https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/7.0/collectible-assemblies + * + * So what do we do then? + * We've had to go with option a unfortunately, and we've cloned the above classes + * There has had to be some modifications to the ViewCompiler (CollectibleRuntimeViewCompiler) + * First off we've added a new class InMemoryAssemblyLoadContextManager, the role of this class is to ensure that + * no one will take a reference to the assembly load context (you cannot unload an assembly load context if there's any references to it). + * This means that both the InMemoryAutoFactory and the ViewCompiler uses the LoadContextManager to load their assemblies. + * This serves another purpose being that it keeps track of the location of the models assembly. + * This means that we no longer use the RazorReferencesManager to resolve that specific dependency, but instead add and explicit dependency to the models assembly. + * + * With this our assembly load context issue is solved, however the caching issue still persists now that we no longer use the RefreshingRazorViewEngine + * To clear these caches another class the RuntimeCompilationCacheBuster has been introduced, + * this keeps a reference to the CollectibleRuntimeViewCompiler and the RazorViewEngine and is injected into the InMemoryModelsFactory to clear the caches when rebuilding modes. + * In order to avoid having to copy all the RazorViewEngine code the cache buster uses reflection to call the internal ClearCache method of the RazorViewEngine. + */ +public static class UmbracoBuilderExtensions +{ + public static IUmbracoBuilder AddBackofficeDevelopment(this IUmbracoBuilder builder) + { + if (builder.Config.GetRuntimeMode() != RuntimeMode.BackofficeDevelopment) + { + return builder; + } + + builder.AddMvcAndRazor(mvcBuilder => + { + mvcBuilder.AddRazorRuntimeCompilation(); + }); + builder.AddInMemoryModelsRazorEngine(); + builder.RuntimeModeValidators() + .Add(); + builder.AddNotificationHandler(); + + return builder; + } + + + // See notes in RefreshingRazorViewEngine for information on what this is doing. + private static IUmbracoBuilder AddInMemoryModelsRazorEngine(this IUmbracoBuilder builder) + { + // We should only add/replace these services when models builder is InMemory, otherwise we'll cause issues. + // Since these services expect the ModelsMode to be InMemoryAuto + if (builder.Config.GetModelsMode() == ModelsModeConstants.InMemoryAuto) + { + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + builder.Services.AddSingleton(); + // Register the factory as IPublishedModelFactory + builder.Services.AddSingleton(); + return builder; + } + + // This is what the community MB would replace, all of the above services are fine to be registered + builder.Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); + + return builder; + } +} diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/ChecksumValidator.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ChecksumValidator.cs similarity index 98% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/ChecksumValidator.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ChecksumValidator.cs index da210ee255..e1a2cd913c 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/ChecksumValidator.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ChecksumValidator.cs @@ -5,7 +5,7 @@ using System.Globalization; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; /* * This is a clone of Microsofts implementation. diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CollectibleRuntimeViewCompiler.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CollectibleRuntimeViewCompiler.cs similarity index 99% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CollectibleRuntimeViewCompiler.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CollectibleRuntimeViewCompiler.cs index 46670862ed..bdc6b7ca14 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CollectibleRuntimeViewCompiler.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CollectibleRuntimeViewCompiler.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Text; -using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; @@ -16,7 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Umbraco.Extensions; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class CollectibleRuntimeViewCompiler : IViewCompiler { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationExceptionFactory.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationExceptionFactory.cs similarity index 98% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationExceptionFactory.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationExceptionFactory.cs index ddb79c4c3c..2f90f0a6b8 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationExceptionFactory.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationExceptionFactory.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; /* * This is a partial clone of the frameworks CompilationFailedExceptionFactory, a few things has been simplified to fit our needs. diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationOptionsProvider.cs similarity index 99% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationOptionsProvider.cs index b2c02e54b2..4a6d178889 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/CompilationOptionsProvider.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/CompilationOptionsProvider.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.Hosting; using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; /* * This is a partial Clone'n'Own of microsofts CSharpCompiler, this is just the parts relevant for getting the CompilationOptions diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs similarity index 96% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs index d999333f77..3285c08134 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryAssemblyLoadContextManager.cs @@ -1,8 +1,7 @@ using System.Reflection; using System.Runtime.Loader; -using Umbraco.Cms.Infrastructure.ModelsBuilder; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class InMemoryAssemblyLoadContextManager { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryModelFactory.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelFactory.cs similarity index 99% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryModelFactory.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelFactory.cs index 78c1b808a5..70f9327b0a 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/InMemoryModelFactory.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelFactory.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Hosting; @@ -20,7 +19,7 @@ using Umbraco.Extensions; using File = System.IO.File; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto { internal sealed partial class InMemoryModelFactory : IAutoPublishedModelFactory, IRegisteredObject, IDisposable { @@ -143,7 +142,7 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto } /// - public bool Enabled => _config.ModelsMode == ModelsMode.InMemoryAuto; + public bool Enabled => _config.ModelsMode == ModelsModeConstants.InMemoryAuto; public IPublishedElement CreateModel(IPublishedElement element) { diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelsBuilderModeValidator.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelsBuilderModeValidator.cs new file mode 100644 index 0000000000..9fab4d5f32 --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/InMemoryModelsBuilderModeValidator.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Infrastructure.Runtime; + +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; + +/// +/// Validates that the ModelsBuilder mode is not set to InMemoryAuto when in development runtime mode. +/// +public class InMemoryModelsBuilderModeValidator : IRuntimeModeValidator +{ + private readonly IOptionsMonitor _modelsBuilderSettings; + + public InMemoryModelsBuilderModeValidator(IOptionsMonitor modelsBuilderSettings) + { + _modelsBuilderSettings = modelsBuilderSettings; + } + + public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage) + { + if (runtimeMode != RuntimeMode.BackofficeDevelopment && + _modelsBuilderSettings.CurrentValue.ModelsMode == ModelsModeConstants.InMemoryAuto) + { + validationErrorMessage = "ModelsBuilder mode cannot be set to InMemoryAuto in development mode."; + return false; + } + + validationErrorMessage = null; + return true; + } +} diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/ModelsBuilderAssemblyAttribute.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderAssemblyAttribute.cs similarity index 92% rename from src/Umbraco.Infrastructure/ModelsBuilder/ModelsBuilderAssemblyAttribute.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderAssemblyAttribute.cs index 073f72c6ad..0d94ed480b 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/ModelsBuilderAssemblyAttribute.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderAssemblyAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Infrastructure.ModelsBuilder; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; /// /// Indicates that an Assembly is a Models Builder assembly. diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderBindingErrorHandler.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderBindingErrorHandler.cs new file mode 100644 index 0000000000..76ad284c67 --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsBuilderBindingErrorHandler.cs @@ -0,0 +1,64 @@ +using System.Reflection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; + +internal sealed class ModelsBuilderBindingErrorHandler : INotificationHandler +{ + /// + /// Handles when a model binding error occurs. + /// + public void Handle(ModelBindingErrorNotification notification) + { + ModelsBuilderAssemblyAttribute? sourceAttr = + notification.SourceType.Assembly.GetCustomAttribute(); + ModelsBuilderAssemblyAttribute? modelAttr = + notification.ModelType.Assembly.GetCustomAttribute(); + + // if source or model is not a ModelsBuider type... + if (sourceAttr == null || modelAttr == null) + { + // if neither are ModelsBuilder types, give up entirely + if (sourceAttr == null && modelAttr == null) + { + return; + } + + // else report, but better not restart (loops?) + notification.Message.Append(" The "); + notification.Message.Append(sourceAttr == null ? "view model" : "source"); + notification.Message.Append(" is a ModelsBuilder type, but the "); + notification.Message.Append(sourceAttr != null ? "view model" : "source"); + notification.Message.Append(" is not. The application is in an unstable state and should be restarted."); + return; + } + + // both are ModelsBuilder types + var pureSource = sourceAttr.IsInMemory; + var pureModel = modelAttr.IsInMemory; + + if (sourceAttr.IsInMemory || modelAttr.IsInMemory) + { + if (pureSource == false || pureModel == false) + { + // only one is pure - report, but better not restart (loops?) + notification.Message.Append(pureSource + ? " The content model is in memory generated, but the view model is not." + : " The view model is in memory generated, but the content model is not."); + notification.Message.Append(" The application is in an unstable state and should be restarted."); + } + else + { + // both are pure - report, and if different versions, restart + // if same version... makes no sense... and better not restart (loops?) + Version? sourceVersion = notification.SourceType.Assembly.GetName().Version; + Version? modelVersion = notification.ModelType.Assembly.GetName().Version; + notification.Message.Append(" Both view and content models are in memory generated, with "); + notification.Message.Append(sourceVersion == modelVersion + ? "same version. The application is in an unstable state and should be restarted." + : "different versions. The application is in an unstable state and should be restarted."); + } + } + } +} diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsModeConstants.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsModeConstants.cs new file mode 100644 index 0000000000..334c31e2ff --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/ModelsModeConstants.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; + +public class ModelsModeConstants +{ + public const string InMemoryAuto = "InMemoryAuto"; +} diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RoslynCompiler.cs similarity index 98% rename from src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RoslynCompiler.cs index bbdf0cbbcc..49753dc50e 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/RoslynCompiler.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RoslynCompiler.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.DependencyModel; -namespace Umbraco.Cms.Infrastructure.ModelsBuilder; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; public class RoslynCompiler { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/RuntimeCompilationCacheBuster.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RuntimeCompilationCacheBuster.cs similarity index 96% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/RuntimeCompilationCacheBuster.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RuntimeCompilationCacheBuster.cs index f13b815f9a..90fb5e0b70 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/RuntimeCompilationCacheBuster.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/RuntimeCompilationCacheBuster.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Umbraco.Cms.Core; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class RuntimeCompilationCacheBuster { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoAssemblyLoadContext.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoAssemblyLoadContext.cs similarity index 91% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoAssemblyLoadContext.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoAssemblyLoadContext.cs index b21a55cb6c..2c9e6c6f34 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoAssemblyLoadContext.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoAssemblyLoadContext.cs @@ -1,7 +1,7 @@ using System.Reflection; using System.Runtime.Loader; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class UmbracoAssemblyLoadContext : AssemblyLoadContext { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoCompilationException.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoCompilationException.cs similarity index 77% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoCompilationException.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoCompilationException.cs index fb39c8fb04..4cab7b0acc 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoCompilationException.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoCompilationException.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Diagnostics; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class UmbracoCompilationException : Exception, ICompilationException { diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoRazorReferenceManager.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoRazorReferenceManager.cs similarity index 98% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoRazorReferenceManager.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoRazorReferenceManager.cs index 92891d5ed4..109b148b8c 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoRazorReferenceManager.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoRazorReferenceManager.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; /* diff --git a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoViewCompilerProvider.cs b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoViewCompilerProvider.cs similarity index 98% rename from src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoViewCompilerProvider.cs rename to src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoViewCompilerProvider.cs index 600ca67a54..989928f4fe 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/InMemoryAuto/UmbracoViewCompilerProvider.cs +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/InMemoryAuto/UmbracoViewCompilerProvider.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Exceptions; -namespace Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; +namespace Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; internal sealed class UmbracoViewCompilerProvider : IViewCompilerProvider { diff --git a/src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj b/src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj new file mode 100644 index 0000000000..635bbeed4c --- /dev/null +++ b/src/Umbraco.Cms.DevelopmentMode.Backoffice/Umbraco.Cms.DevelopmentMode.Backoffice.csproj @@ -0,0 +1,17 @@ + + + + Umbraco CMS - DevelopmentMode - Backoffice + Adds backoffice development mode. + Umbraco.Cms.DevelopmentMode.Backoffice + + + + + + + + + + + diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index 127b7d9330..e28a0f4c42 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -23,7 +23,7 @@ public class ModelsBuilderSettings /// Gets or sets a value for the models mode. /// [DefaultValue(StaticModelsMode)] - public ModelsMode ModelsMode { get; set; } = Enum.Parse(StaticModelsMode); + public string ModelsMode { get; set; } = StaticModelsMode; /// /// Gets or sets a value for models namespace. @@ -35,23 +35,9 @@ public class ModelsBuilderSettings /// /// Gets or sets a value indicating whether we should flag out-of-date models. /// - /// - /// Models become out-of-date when data types or content types are updated. When this - /// setting is activated the ~/umbraco/models/PureLive/ood.txt file is then created. When models are - /// generated through the dashboard, the files is cleared. Default value is false. - /// public bool FlagOutOfDateModels { - get - { - if (ModelsMode == ModelsMode.Nothing ||ModelsMode.IsAuto()) - { - return false; - - } - - return _flagOutOfDateModels; - } + get => ModelsMode == Constants.ModelsBuilder.ModelsModes.SourceCodeManual && _flagOutOfDateModels; set => _flagOutOfDateModels = value; } diff --git a/src/Umbraco.Core/Configuration/ModelsMode.cs b/src/Umbraco.Core/Configuration/ModelsMode.cs deleted file mode 100644 index 9e76710e2b..0000000000 --- a/src/Umbraco.Core/Configuration/ModelsMode.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Umbraco.Cms.Core.Configuration; - -/// -/// Defines the models generation modes. -/// -public enum ModelsMode -{ - /// - /// Do not generate strongly typed models. - /// - /// - /// This means that only IPublishedContent instances will be used. - /// - Nothing = 0, - - /// - /// Generate models in memory. - /// When: a content type change occurs. - /// - /// The app does not restart. Models are available in views exclusively. - InMemoryAuto, - - /// - /// Generate models as *.cs files. - /// When: generation is triggered. - /// - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - /// - SourceCodeManual, - - /// - /// Generate models as *.cs files. - /// When: a content type change occurs, or generation is triggered. - /// - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - /// - SourceCodeAuto, -} diff --git a/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs b/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs deleted file mode 100644 index 52256a29f0..0000000000 --- a/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Umbraco.Cms.Core.Configuration; - -namespace Umbraco.Extensions; - -/// -/// Provides extensions for the enumeration. -/// -public static class ModelsModeExtensions -{ - /// - /// Gets a value indicating whether the mode is *Auto. - /// - public static bool IsAuto(this ModelsMode modelsMode) - => modelsMode == ModelsMode.InMemoryAuto || modelsMode == ModelsMode.SourceCodeAuto; - - /// - /// Gets a value indicating whether the mode is *Auto but not InMemory. - /// - public static bool IsAutoNotInMemory(this ModelsMode modelsMode) - => modelsMode == ModelsMode.SourceCodeAuto; - - /// - /// Gets a value indicating whether the mode supports explicit manual generation. - /// - public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) - => modelsMode == ModelsMode.SourceCodeManual || modelsMode == ModelsMode.SourceCodeAuto; -} diff --git a/src/Umbraco.Core/Constants-ModelsBuilder.cs b/src/Umbraco.Core/Constants-ModelsBuilder.cs index 63b852a600..60fca474e5 100644 --- a/src/Umbraco.Core/Constants-ModelsBuilder.cs +++ b/src/Umbraco.Core/Constants-ModelsBuilder.cs @@ -11,5 +11,14 @@ public static partial class Constants public static class ModelsBuilder { public const string DefaultModelsNamespace = "Umbraco.Cms.Web.Common.PublishedModels"; + + public static class ModelsModes + { + public const string SourceCodeAuto = "SourceCodeAuto"; + + public const string SourceCodeManual = "SourceCodeManual"; + + public const string Nothing = "Nothing"; + } } } diff --git a/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs b/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs index 3dffdd8e67..0f9f3a3725 100644 --- a/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs +++ b/src/Umbraco.Core/Extensions/ConfigurationExtensions.cs @@ -93,5 +93,5 @@ public static class ConfigurationExtensions public static RuntimeMode GetRuntimeMode(this IConfiguration configuration) => configuration.GetValue(Constants.Configuration.ConfigRuntimeMode); - public static ModelsMode GetModelsMode(this IConfiguration configuration) => (configuration.GetSection(Constants.Configuration.ConfigModelsBuilder).Get() ?? new ModelsBuilderSettings()).ModelsMode; + public static string GetModelsMode(this IConfiguration configuration) => (configuration.GetSection(Constants.Configuration.ConfigModelsBuilder).Get() ?? new ModelsBuilderSettings()).ModelsMode; } diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/AutoModelsNotificationHandler.cs b/src/Umbraco.Infrastructure/ModelsBuilder/AutoModelsNotificationHandler.cs index 47c86e6019..b24a1817c5 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/AutoModelsNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/AutoModelsNotificationHandler.cs @@ -1,20 +1,19 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Runtime; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; -using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.ModelsBuilder; /// -/// Notification handlers used by . +/// Notification handlers used by SourceCodeAuto. /// /// -/// supports mode but not mode. +/// supports SourceCodeAuto mode but not InMemoryAuto mode. /// public sealed class AutoModelsNotificationHandler : INotificationHandler, INotificationHandler, @@ -48,7 +47,7 @@ public sealed class AutoModelsNotificationHandler : INotificationHandler _config.ModelsMode.IsAutoNotInMemory(); + internal bool IsEnabled => _config.ModelsMode == Constants.ModelsBuilder.ModelsModes.SourceCodeAuto; public void Handle(ContentTypeCacheRefresherNotification notification) => RequestModelsGeneration(); diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs index 2f9d4ff4cc..15c0f6e4ed 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/Builder.cs @@ -122,7 +122,8 @@ public abstract class Builder { TypeModel.MapModelTypes(TypeModels, ModelsNamespace); - var isInMemoryMode = Config.ModelsMode == ModelsMode.InMemoryAuto; + // TODO: Remove this, this is a hack ideally InMemoryAuto should have its own builder in Umbraco.Cms.DevelopmentMode.Backoffice + var isInMemoryMode = Config.ModelsMode == "InMemoryAuto"; // for the first two of these two tests, // always throw, even in InMemory mode: cannot happen unless ppl start fidling with attributes to rename diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/PropertyModel.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/PropertyModel.cs index 2475aebc3f..170cb229a9 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/PropertyModel.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/PropertyModel.cs @@ -34,7 +34,7 @@ public class PropertyModel /// /// This should be null, unless something prevents the property from being /// generated, and then the value should explain what. This can be used to generate - /// commented out code eg in mode. + /// commented out code eg in InMemoryAuto mode. /// public List? Errors; diff --git a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs index 67c263818c..875dfaa218 100644 --- a/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs +++ b/src/Umbraco.Infrastructure/ModelsBuilder/Building/TextBuilder.cs @@ -84,6 +84,8 @@ public class TextBuilder : Builder /// The models to generate. public void Generate(StringBuilder sb, IEnumerable typeModels) { + // TODO: Ideally this should live in the Umbraco.Cms.DevelopmentMode.Backoffice project, but we dont want to clone the entire thing. + // This is only used for in memory auto. WriteHeader(sb, Config.IncludeVersionNumberInGeneratedModels); foreach (var t in TypesUsing) @@ -91,6 +93,9 @@ public class TextBuilder : Builder sb.AppendFormat("using {0};\n", t); } + // A hack to include the using for the assembly attribute (works because this method is only called from InMemoryModelFactory) + sb.AppendLine("using Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto;"); + // assembly attributes marker sb.Append("\n//ASSATTR\n"); diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs index 06f7735d60..d11402e1dd 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/ModelsBuilderModeValidator.cs @@ -1,12 +1,13 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; 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. +/// Validates that ModelsBuilderMode is set to when in production runtime mode. /// /// public class ModelsBuilderModeValidator : IRuntimeModeValidator @@ -23,15 +24,9 @@ public class ModelsBuilderModeValidator : IRuntimeModeValidator /// public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage) { - ModelsMode modelsMode = _modelsBuilderSettings.CurrentValue.ModelsMode; + var 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) + if (runtimeMode == RuntimeMode.Production && modelsMode != Constants.ModelsBuilder.ModelsModes.Nothing) { validationErrorMessage = "ModelsBuilder mode needs to be set to Nothing in production mode."; return false; diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RazorRuntimeCompilationValidator.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RazorRuntimeCompilationValidator.cs new file mode 100644 index 0000000000..0dd260ef1d --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidators/RazorRuntimeCompilationValidator.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Exceptions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators; + +public class RazorRuntimeCompilationValidator : INotificationHandler +{ + private readonly IOptionsMonitor _modelsBuilderSettings; + private readonly IPublishedModelFactory _publishedModelFactory; + + public RazorRuntimeCompilationValidator( + IOptionsMonitor modelsBuilderSettings, + IPublishedModelFactory publishedModelFactory) + { + _modelsBuilderSettings = modelsBuilderSettings; + _publishedModelFactory = publishedModelFactory; + } + + public void Handle(UmbracoApplicationStartedNotification notification) + { + if (_modelsBuilderSettings.CurrentValue.ModelsMode == "InMemoryAuto" && _publishedModelFactory.IsLiveFactoryEnabled() is false) + { + throw new BootFailedException("InMemoryAuto requires the Umbraco.Cms.DevelopmentMode.Backoffice package to be installed. Install the package or change ModelsBuilder mode."); + } + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index d0eec0039e..1d8b0f97d1 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -240,11 +240,6 @@ public static partial class UmbracoBuilderExtensions // this will directly affect developers who need to call that themselves. IMvcBuilder mvcBuilder = builder.Services.AddControllersWithViews(); - if (builder.Config.GetRuntimeMode() != RuntimeMode.Production) - { - mvcBuilder.AddRazorRuntimeCompilation(); - } - mvcBuilding?.Invoke(mvcBuilder); return builder; diff --git a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs index a4feac6f7d..89e96b4c32 100644 --- a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Web.Common.Filters; /// In which case it returns a redirect to the same page after 1 sec if not in debug mode. /// /// -/// This is only enabled when using mode +/// This is only enabled when using InMemoryAuto mode. /// public sealed class ModelBindingExceptionAttribute : TypeFilterAttribute { diff --git a/src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs b/src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs index dfc4be2e09..005b69ed56 100644 --- a/src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs +++ b/src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs @@ -1,6 +1,5 @@ using Serilog; using Serilog.Extensions.Hosting; -using Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; namespace Umbraco.Cms.Web.Common.Logging; diff --git a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs index 045a5107bb..c0fb2a83e5 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/DependencyInjection/UmbracoBuilderDependencyInjectionExtensions.cs @@ -1,92 +1,13 @@ -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Infrastructure.ModelsBuilder; using Umbraco.Cms.Infrastructure.ModelsBuilder.Building; using Umbraco.Cms.Infrastructure.ModelsBuilder.Options; +using Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators; using Umbraco.Cms.Web.Common.ModelsBuilder; -using Umbraco.Cms.Web.Common.ModelsBuilder.InMemoryAuto; - -/* - * OVERVIEW: - * - * The CSharpCompiler is responsible for the actual compilation of razor at runtime. - * It creates a CSharpCompilation instance to do the compilation. This is where DLL references - * are applied. However, the way this works is not flexible for dynamic assemblies since the references - * are only discovered and loaded once before the first compilation occurs. This is done here: - * https://github.com/dotnet/aspnetcore/blob/114f0f6d1ef1d777fb93d90c87ac506027c55ea0/src/Mvc/Mvc.Razor.RuntimeCompilation/src/CSharpCompiler.cs#L79 - * The CSharpCompiler is internal and cannot be replaced or extended, however it's references come from: - * RazorReferenceManager. Unfortunately this is also internal and cannot be replaced, though it can be extended - * using MvcRazorRuntimeCompilationOptions, except this is the place where references are only loaded once which - * is done with a LazyInitializer. See https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs#L35. - * - * The way that RazorReferenceManager works is by resolving references from the ApplicationPartsManager - either by - * an application part that is specifically an ICompilationReferencesProvider or an AssemblyPart. So to fulfill this - * requirement, we add the MB assembly to the assembly parts manager within the InMemoryModelFactory when the assembly - * is (re)generated. But due to the above restrictions, when re-generating, this will have no effect since the references - * have already been resolved with the LazyInitializer in the RazorReferenceManager. There is a known public API - * where you can add reference paths to the runtime razor compiler via it's IOptions: MvcRazorRuntimeCompilationOptions - * however this falls short too because those references are just loaded via the RazorReferenceManager and lazy initialized. - * - * The services that can be replaced are: IViewCompilerProvider (default is the internal RuntimeViewCompilerProvider) and - * IViewCompiler (default is the internal RuntimeViewCompiler). There is one specific public extension point that I was - * hoping would solve all of the problems which was IMetadataReferenceFeature (implemented by LazyMetadataReferenceFeature - * which uses RazorReferencesManager) which is a razor feature that you can add - * to the RazorProjectEngine. It is used to resolve roslyn references and by default is backed by RazorReferencesManager. - * Unfortunately, this service is not used by the CSharpCompiler, it seems to only be used by some tag helper compilations. - * - * There are caches at several levels, all of which are not publicly accessible APIs (apart from RazorViewEngine.ViewLookupCache - * which is possible to clear by casting and then calling cache.Compact(100); but that doesn't get us far enough). - * - * For this to work, several caches must be cleared: - * - RazorViewEngine.ViewLookupCache - * - RazorReferencesManager._compilationReferences - * - RazorPageActivator._activationInfo (though this one may be optional) - * - RuntimeViewCompiler._cache - * - * What are our options? - * - * a) We can copy a ton of code into our application: CSharpCompiler, RuntimeViewCompilerProvider, RuntimeViewCompiler and - * RazorReferenceManager (probably more depending on the extent of Internal references). - * b) We can use reflection to try to access all of the above resources and try to forcefully clear caches and reset initialization flags. - * c) We hack these replace-able services with our own implementations that wrap the default services. To do this - * requires re-resolving the original services from a pre-built DI container. In effect this re-creates these - * services from scratch which means there is no caches. - * - * ... Option C worked, however after a breaking change from dotnet, we cannot go with this options any longer. - * The reason for this is that when the default RuntimeViewCompiler loads in the assembly using Assembly.Load, - * This will not work for us since this loads the compiled views into the default AssemblyLoadContext, - * and our compiled models are loaded in the collectible UmbracoAssemblyLoadContext, and as per the breaking change - * you're no longer allowed reference a collectible load context from a non-collectible one - * That is the non-collectible compiled views are not allowed to reference the collectible InMemoryAuto models. - * https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/7.0/collectible-assemblies - * - * So what do we do then? - * We've had to go with option a unfortunately, and we've cloned the above classes - * There has had to be some modifications to the ViewCompiler (CollectibleRuntimeViewCompiler) - * First off we've added a new class InMemoryAssemblyLoadContextManager, the role of this class is to ensure that - * no one will take a reference to the assembly load context (you cannot unload an assembly load context if there's any references to it). - * This means that both the InMemoryAutoFactory and the ViewCompiler uses the LoadContextManager to load their assemblies. - * This serves another purpose being that it keeps track of the location of the models assembly. - * This means that we no longer use the RazorReferencesManager to resolve that specific dependency, but instead add and explicit dependency to the models assembly. - * - * With this our assembly load context issue is solved, however the caching issue still persists now that we no longer use the RefreshingRazorViewEngine - * To clear these caches another class the RuntimeCompilationCacheBuster has been introduced, - * this keeps a reference to the CollectibleRuntimeViewCompiler and the RazorViewEngine and is injected into the InMemoryModelsFactory to clear the caches when rebuilding modes. - * In order to avoid having to copy all the RazorViewEngine code the cache buster uses reflection to call the internal ClearCache method of the RazorViewEngine. - */ namespace Umbraco.Extensions; @@ -109,14 +30,6 @@ public static class UmbracoBuilderDependencyInjectionExtensions builder.Services.Add(umbServices); - if (builder.Config.GetRuntimeMode() == RuntimeMode.BackofficeDevelopment) - { - // Configure services to allow InMemoryAuto mode - builder.AddInMemoryModelsRazorEngine(); - - builder.AddNotificationHandler(); - } - if (builder.Config.GetRuntimeMode() != RuntimeMode.Production) { // Configure service to allow models generation @@ -144,31 +57,10 @@ public static class UmbracoBuilderDependencyInjectionExtensions builder.Services.ConfigureOptions(); - return builder; - } - - // See notes in RefreshingRazorViewEngine for information on what this is doing. - private static IUmbracoBuilder AddInMemoryModelsRazorEngine(this IUmbracoBuilder builder) - { - // We should only add/replace these services when models builder is InMemory, otherwise we'll cause issues. - // Since these services expect the ModelsMode to be InMemoryAuto - if (builder.Config.GetModelsMode() is ModelsMode.InMemoryAuto) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - builder.Services.AddSingleton(); - // Register the factory as IPublishedModelFactory - builder.Services.AddSingleton(); - return builder; - } - - // This is what the community MB would replace, all of the above services are fine to be registered - builder.Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); + builder.AddNotificationHandler(); return builder; } + + } diff --git a/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs b/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs index 98936a828a..2ca130ddb2 100644 --- a/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs +++ b/src/Umbraco.Web.Common/ModelsBuilder/ModelsBuilderNotificationHandler.cs @@ -17,7 +17,6 @@ namespace Umbraco.Cms.Web.Common.ModelsBuilder; /// internal sealed class ModelsBuilderNotificationHandler : INotificationHandler, - INotificationHandler, INotificationHandler { private readonly ModelsBuilderSettings _config; @@ -37,62 +36,6 @@ internal sealed class ModelsBuilderNotificationHandler : _defaultViewContentProvider = defaultViewContentProvider; } - /// - /// Handles when a model binding error occurs - /// - public void Handle(ModelBindingErrorNotification notification) - { - ModelsBuilderAssemblyAttribute? sourceAttr = - notification.SourceType.Assembly.GetCustomAttribute(); - ModelsBuilderAssemblyAttribute? modelAttr = - notification.ModelType.Assembly.GetCustomAttribute(); - - // if source or model is not a ModelsBuider type... - if (sourceAttr == null || modelAttr == null) - { - // if neither are ModelsBuilder types, give up entirely - if (sourceAttr == null && modelAttr == null) - { - return; - } - - // else report, but better not restart (loops?) - notification.Message.Append(" The "); - notification.Message.Append(sourceAttr == null ? "view model" : "source"); - notification.Message.Append(" is a ModelsBuilder type, but the "); - notification.Message.Append(sourceAttr != null ? "view model" : "source"); - notification.Message.Append(" is not. The application is in an unstable state and should be restarted."); - return; - } - - // both are ModelsBuilder types - var pureSource = sourceAttr.IsInMemory; - var pureModel = modelAttr.IsInMemory; - - if (sourceAttr.IsInMemory || modelAttr.IsInMemory) - { - if (pureSource == false || pureModel == false) - { - // only one is pure - report, but better not restart (loops?) - notification.Message.Append(pureSource - ? " The content model is in memory generated, but the view model is not." - : " The view model is in memory generated, but the content model is not."); - notification.Message.Append(" The application is in an unstable state and should be restarted."); - } - else - { - // both are pure - report, and if different versions, restart - // if same version... makes no sense... and better not restart (loops?) - Version? sourceVersion = notification.SourceType.Assembly.GetName().Version; - Version? modelVersion = notification.ModelType.Assembly.GetName().Version; - notification.Message.Append(" Both view and content models are in memory generated, with "); - notification.Message.Append(sourceVersion == modelVersion - ? "same version. The application is in an unstable state and should be restarted." - : "different versions. The application is in an unstable state and should be restarted."); - } - } - } - /// /// Handles the notification to add custom urls and MB mode /// @@ -135,7 +78,7 @@ internal sealed class ModelsBuilderNotificationHandler : /// public void Handle(TemplateSavingNotification notification) { - if (_config.ModelsMode == ModelsMode.Nothing) + if (_config.ModelsMode == Core.Constants.ModelsBuilder.ModelsModes.Nothing) { return; } @@ -181,7 +124,7 @@ internal sealed class ModelsBuilderNotificationHandler : private Dictionary GetModelsBuilderSettings() { - var settings = new Dictionary { { "mode", _config.ModelsMode.ToString() } }; + var settings = new Dictionary { { "mode", _config.ModelsMode } }; return settings; } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index 0274d838f8..c271238427 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -5,7 +5,7 @@ Contains the web assembly needed to run Umbraco CMS. Umbraco.Cms.Web.Common - + $(WarningsNotAsErrors),SA1117,SA1401,SA1134,ASP0019,CS0618,SYSLIB0051,IDE0040,SA1400,SA1405,CS0419,CS1574,SA1649 - + @@ -30,7 +30,6 @@ - diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index d3a32481d5..37fd91a3e5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -14,10 +14,11 @@ --> $(WarningsNotAsErrors),SA1119 - + + diff --git a/templates/UmbracoProject/UmbracoProject.csproj b/templates/UmbracoProject/UmbracoProject.csproj index d4b4b548fd..944b6f1274 100644 --- a/templates/UmbracoProject/UmbracoProject.csproj +++ b/templates/UmbracoProject/UmbracoProject.csproj @@ -13,6 +13,9 @@ #else #endif--> + + + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs index 628f33b5ab..b0d0f129c7 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/SystemInformationServiceTests.cs @@ -4,11 +4,13 @@ using Microsoft.Extensions.Options; using Moq; using NPoco; using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Telemetry.Providers; @@ -90,18 +92,18 @@ public class SystemInformationServiceTests } [Test] - [TestCase(ModelsMode.Nothing)] - [TestCase(ModelsMode.InMemoryAuto)] - [TestCase(ModelsMode.SourceCodeAuto)] - [TestCase(ModelsMode.SourceCodeManual)] - public void ReportsModelsModeCorrectly(ModelsMode modelsMode) + [TestCase(Constants.ModelsBuilder.ModelsModes.Nothing)] + [TestCase(ModelsModeConstants.InMemoryAuto)] + [TestCase(Constants.ModelsBuilder.ModelsModes.SourceCodeAuto)] + [TestCase(Constants.ModelsBuilder.ModelsModes.SourceCodeManual)] + public void ReportsModelsModeCorrectly(string modelsMode) { var userDataService = CreateSystemInformationService(modelsMode: modelsMode); var userData = userDataService.GetTroubleshootingInformation().ToArray(); var actual = userData.FirstOrDefault(x => x.Key == "Models Builder Mode"); Assert.IsNotNull(actual.Value); - Assert.AreEqual(modelsMode.ToString(), actual.Value); + Assert.AreEqual(modelsMode, actual.Value); } [Test] @@ -133,7 +135,7 @@ public class SystemInformationServiceTests private ISystemTroubleshootingInformationService CreateSystemInformationService( string culture = "", - ModelsMode modelsMode = ModelsMode.InMemoryAuto, + string modelsMode = ModelsModeConstants.InMemoryAuto, bool isDebug = true, RuntimeMode runtimeMode = RuntimeMode.BackofficeDevelopment) { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs index f4fedcbc5c..5334127a08 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Telemetry/SystemTroubleshootingInformationTelemetryProviderTests.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.DevelopmentMode.Backoffice.InMemoryAuto; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Telemetry.Providers; @@ -18,18 +19,18 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Telemetry; public class SystemTroubleshootingInformationTelemetryProviderTests { [Test] - [TestCase(ModelsMode.Nothing)] - [TestCase(ModelsMode.InMemoryAuto)] - [TestCase(ModelsMode.SourceCodeAuto)] - [TestCase(ModelsMode.SourceCodeManual)] - public void ReportsModelsModeCorrectly(ModelsMode modelsMode) + [TestCase(Constants.ModelsBuilder.ModelsModes.Nothing)] + [TestCase(ModelsModeConstants.InMemoryAuto)] + [TestCase(Constants.ModelsBuilder.ModelsModes.SourceCodeAuto)] + [TestCase(Constants.ModelsBuilder.ModelsModes.SourceCodeManual)] + public void ReportsModelsModeCorrectly(string modelsMode) { var telemetryProvider = CreateProvider(modelsMode); var usageInformation = telemetryProvider.GetInformation().ToArray(); var actual = usageInformation.FirstOrDefault(x => x.Name == Constants.Telemetry.ModelsBuilderMode); Assert.IsNotNull(actual?.Data); - Assert.AreEqual(modelsMode.ToString(), actual.Data); + Assert.AreEqual(modelsMode, actual.Data); } [Test] @@ -93,7 +94,7 @@ public class SystemTroubleshootingInformationTelemetryProviderTests } private SystemTroubleshootingInformationTelemetryProvider CreateProvider( - ModelsMode modelsMode = ModelsMode.InMemoryAuto, + string modelsMode = ModelsModeConstants.InMemoryAuto, bool isDebug = true, string environment = "", RuntimeMode runtimeMode = RuntimeMode.BackofficeDevelopment) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index bc6314297f..13e59d5417 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -36,6 +36,7 @@ + diff --git a/umbraco.sln b/umbraco.sln index 74fcc8dc3f..f8be7fa4ea 100644 --- a/umbraco.sln +++ b/umbraco.sln @@ -189,6 +189,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.Api.Management" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.PublishedCache.HybridCache", "src\Umbraco.PublishedCache.HybridCache\Umbraco.PublishedCache.HybridCache.csproj", "{CB0B9817-EDBC-4D6D-B4D2-969019C4606D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Cms.DevelopmentMode.Backoffice", "src\Umbraco.Cms.DevelopmentMode.Backoffice\Umbraco.Cms.DevelopmentMode.Backoffice.csproj", "{411D1547-C84C-4F0F-A207-A189B59F9486}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -363,6 +365,12 @@ Global {CB0B9817-EDBC-4D6D-B4D2-969019C4606D}.Release|Any CPU.Build.0 = Release|Any CPU {CB0B9817-EDBC-4D6D-B4D2-969019C4606D}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU {CB0B9817-EDBC-4D6D-B4D2-969019C4606D}.SkipTests|Any CPU.Build.0 = Debug|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.Release|Any CPU.Build.0 = Release|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.SkipTests|Any CPU.ActiveCfg = Debug|Any CPU + {411D1547-C84C-4F0F-A207-A189B59F9486}.SkipTests|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE