From b272a3b791a80c566459198df8e11ab920b20130 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 16 Jan 2020 19:39:17 +0100 Subject: [PATCH 1/6] https://github.com/umbraco/Umbraco-CMS/issues/7466 - Fixes issue with external ModelsBuilder and Dll or LiveDll mode. The issue is because the Mode is determined in the contructor, and not lazy. --- .../Configuration/ModelsBuilderConfig.cs | 90 ++++++++++++------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index c6bccdcf87..b5c66b649c 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -12,6 +12,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// public class ModelsBuilderConfig : IModelsBuilderConfig { + private const string prefix = "Umbraco.ModelsBuilder."; + private ModelsMode? _modelsMode; + private bool? _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public const string DefaultModelsDirectory = "~/App_Data/Models"; @@ -20,8 +23,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// public ModelsBuilderConfig() { - const string prefix = "Umbraco.ModelsBuilder."; - // giant kill switch, default: false // must be explicitely set to true for anything else to happen Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; @@ -34,36 +35,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration // stop here, everything is false if (!Enable) return; - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - ModelsMode = ModelsMode.Nothing; - break; - case nameof(ModelsMode.PureLive): - ModelsMode = ModelsMode.PureLive; - break; - case nameof(ModelsMode.AppData): - ModelsMode = ModelsMode.AppData; - break; - case nameof(ModelsMode.LiveAppData): - ModelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); - } - } - // default: false AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // default: true EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); - FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); // default: initialized above with DefaultModelsNamespace const var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; @@ -92,9 +68,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration DebugLevel = debugLevel; } - // not flagging if not generating, or live (incl. pure) - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - FlagOutOfDateModels = false; } /// @@ -111,11 +84,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration int debugLevel = 0) { Enable = enable; - ModelsMode = modelsMode; + _modelsMode = modelsMode; ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace; EnableFactory = enableFactory; - FlagOutOfDateModels = flagOutOfDateModels; + _flagOutOfDateModels = flagOutOfDateModels; ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory; AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory; DebugLevel = debugLevel; @@ -164,7 +137,39 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode { get; } + public ModelsMode ModelsMode + { + get + { + if (!_modelsMode.HasValue) + { + // mode + var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; + if (!string.IsNullOrWhiteSpace(modelsMode)) + { + switch (modelsMode) + { + case nameof(ModelsMode.Nothing): + _modelsMode = ModelsMode.Nothing; + break; + case nameof(ModelsMode.PureLive): + _modelsMode = ModelsMode.PureLive; + break; + case nameof(ModelsMode.AppData): + _modelsMode = ModelsMode.AppData; + break; + case nameof(ModelsMode.LiveAppData): + _modelsMode = ModelsMode.LiveAppData; + break; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); + } + } + } + return _modelsMode.Value; + } + } /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -196,7 +201,24 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// Models become out-of-date when data types or content types are updated. When this /// setting is activated the ~/App_Data/Models/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; } + public bool FlagOutOfDateModels + { + get + { + if (!_flagOutOfDateModels.HasValue) + { + var flagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) + { + flagOutOfDateModels = false; + } + + _flagOutOfDateModels = flagOutOfDateModels; + } + return _flagOutOfDateModels.Value; + } + } /// /// Gets the models directory. From 1b56922213431313195a694738fa9afd63ea6bb8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 17 Jan 2020 13:31:22 +1100 Subject: [PATCH 2/6] Fixes threadsafety for settings value --- .../Configuration/ModelsBuilderConfig.cs | 94 ++++++++----------- 1 file changed, 39 insertions(+), 55 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs index b5c66b649c..179fcecfcb 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs @@ -1,6 +1,7 @@ using System; using System.Configuration; using System.IO; +using System.Threading; using System.Web.Configuration; using Umbraco.Core; using Umbraco.Core.IO; @@ -12,9 +13,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// public class ModelsBuilderConfig : IModelsBuilderConfig { - private const string prefix = "Umbraco.ModelsBuilder."; - private ModelsMode? _modelsMode; - private bool? _flagOutOfDateModels; + private const string Prefix = "Umbraco.ModelsBuilder."; + private object _modelsModelLock; + private bool _modelsModelConfigured; + private ModelsMode _modelsMode; + private object _flagOutOfDateModelsLock; + private bool _flagOutOfDateModelsConfigured; + private bool _flagOutOfDateModels; public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels"; public const string DefaultModelsDirectory = "~/App_Data/Models"; @@ -25,7 +30,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration { // giant kill switch, default: false // must be explicitely set to true for anything else to happen - Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true"; + Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true"; // ensure defaults are initialized for tests ModelsNamespace = DefaultModelsNamespace; @@ -36,18 +41,18 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration if (!Enable) return; // default: false - AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); + AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true"); // default: true - EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false"); + EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false"); // default: initialized above with DefaultModelsNamespace const - var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"]; + var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"]; if (!string.IsNullOrWhiteSpace(value)) ModelsNamespace = value; // default: initialized above with DefaultModelsDirectory const - value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"]; + value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"]; if (!string.IsNullOrWhiteSpace(value)) { var root = IOHelper.MapPath("~/"); @@ -59,11 +64,10 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration } // default: 0 - value = ConfigurationManager.AppSettings[prefix + "DebugLevel"]; + value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"]; if (!string.IsNullOrWhiteSpace(value)) { - int debugLevel; - if (!int.TryParse(value, out debugLevel)) + if (!int.TryParse(value, out var debugLevel)) throw new ConfigurationErrorsException($"Invalid debug level \"{value}\"."); DebugLevel = debugLevel; } @@ -137,39 +141,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// /// Gets the models mode. /// - public ModelsMode ModelsMode - { - get + public ModelsMode ModelsMode => + LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () => { - if (!_modelsMode.HasValue) + // mode + var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"]; + if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default + switch (modelsMode) { - // mode - var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"]; - if (!string.IsNullOrWhiteSpace(modelsMode)) - { - switch (modelsMode) - { - case nameof(ModelsMode.Nothing): - _modelsMode = ModelsMode.Nothing; - break; - case nameof(ModelsMode.PureLive): - _modelsMode = ModelsMode.PureLive; - break; - case nameof(ModelsMode.AppData): - _modelsMode = ModelsMode.AppData; - break; - case nameof(ModelsMode.LiveAppData): - _modelsMode = ModelsMode.LiveAppData; - break; - default: - throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." - + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); - } - } + case nameof(ModelsMode.Nothing): + return ModelsMode.Nothing; + case nameof(ModelsMode.PureLive): + return ModelsMode.PureLive; + case nameof(ModelsMode.AppData): + return ModelsMode.AppData; + case nameof(ModelsMode.LiveAppData): + return ModelsMode.LiveAppData; + default: + throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode)))); } - return _modelsMode.Value; - } - } + }); /// /// Gets a value indicating whether system.web/compilation/@debug is true. @@ -202,23 +193,16 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration /// setting is activated the ~/App_Data/Models/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 + => LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () => { - if (!_flagOutOfDateModels.HasValue) + var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false"); + if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) { - var flagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false"); - - if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive()) - { - flagOutOfDateModels = false; - } - - _flagOutOfDateModels = flagOutOfDateModels; + flagOutOfDateModels = false; } - return _flagOutOfDateModels.Value; - } - } + + return flagOutOfDateModels; + }); /// /// Gets the models directory. From 5ddc4b8efb73b15b1ab99286fa27cb042fc39057 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 11:51:31 +0100 Subject: [PATCH 3/6] Bump version to 8.5.2 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 3ffc767f06..9dba3adef1 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.5.1")] -[assembly: AssemblyInformationalVersion("8.5.1")] +[assembly: AssemblyFileVersion("8.5.2")] +[assembly: AssemblyInformationalVersion("8.5.2")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 87712d497e..0c80ccb70b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8510 + 8520 / - http://localhost:8510 + http://localhost:8520 False False From 6637bf520c62e2eab5717b74e68c9206595c4896 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 13:22:13 +0100 Subject: [PATCH 4/6] https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue --- .../PublishedElementExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 29429ba74f..33b1803169 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,14 +17,13 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue GetValue(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) where TModel : IPublishedElement { var alias = GetAlias(model, property); return model.Value(alias, culture, segment, fallback, defaultValue); } - // fixme that one should be public so ppl can use it private static string GetAlias(TModel model, Expression> property) { if (property.NodeType != ExpressionType.Lambda) @@ -45,7 +44,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } } From baef282b108599166fcf470bbc07ec5923519270 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 17 Jan 2020 13:53:03 +0100 Subject: [PATCH 5/6] https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue --- .../PublishedElementExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 33b1803169..2c22caf87d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,13 +17,14 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue GetValue(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue ValueByExpression(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) where TModel : IPublishedElement { var alias = GetAlias(model, property); return model.Value(alias, culture, segment, fallback, defaultValue); } + //This cannot be public due to ambiguous issue with external ModelsBuilder if we do not rename. private static string GetAlias(TModel model, Expression> property) { if (property.NodeType != ExpressionType.Lambda) From 7fbe4829196d7f131a4bb5e9a6d8263b25eda8a4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 20 Jan 2020 14:09:13 +0100 Subject: [PATCH 6/6] Revert "https://github.com/umbraco/Umbraco-CMS/issues/7469 - Fixed Ambiguous extension method issue by renaming our extension method from Value to GetValue" This reverts commit baef282b --- .../PublishedElementExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs index 2c22caf87d..29429ba74f 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/PublishedElementExtensions.cs @@ -17,14 +17,14 @@ namespace Umbraco.Web /// /// Gets the value of a property. /// - public static TValue ValueByExpression(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) + public static TValue Value(this TModel model, Expression> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default) where TModel : IPublishedElement { var alias = GetAlias(model, property); return model.Value(alias, culture, segment, fallback, defaultValue); } - //This cannot be public due to ambiguous issue with external ModelsBuilder if we do not rename. + // fixme that one should be public so ppl can use it private static string GetAlias(TModel model, Expression> property) { if (property.NodeType != ExpressionType.Lambda) @@ -45,7 +45,7 @@ namespace Umbraco.Web var attribute = member.GetCustomAttribute(); if (attribute == null) throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute."); - + return attribute.Alias; } }