Removes MB.Enabled since it doesn't make since, Removes MB.EnableFactory since that cannot ever be not enabled, fixes up settings usage, primarily we just check for models mode

This commit is contained in:
Shannon
2021-01-19 18:29:14 +11:00
parent 0ce34bea18
commit 1614e9a53c
13 changed files with 112 additions and 133 deletions

View File

@@ -14,22 +14,6 @@ namespace Umbraco.Core.Configuration.Models
private static string DefaultModelsDirectory => "~/umbraco/models";
/// <summary>
/// Gets or sets a value indicating whether the whole models experience is enabled.
/// </summary>
/// <remarks>
/// <para>If this is false then absolutely nothing happens.</para>
/// <para>Default value is <c>false</c> which means that unless we have this setting, nothing happens.</para>
/// </remarks>
// TODO: This setting makes no sense at all, this basically just disables haveing models be able to reset dynamically
// and configure some dashboards but the models are all still built and active!
// Can this be truly disabled or not?
// Then there's other ways to disable things - EnableFactory also causes odd flags but again, how can this be disabled?
// The other ways that flags change are ModelsMode.
// TODO: Make these make sense and test what is possible
// Confirmed A) Enabled = false, ModelsMode = Nothing, EnabledFagtory = false == EXPLODES, null refs because these things are needed unless you replace nucache.
public bool Enable { get; set; } = true;
/// <summary>
/// Gets or sets a value for the models mode.
/// </summary>
@@ -41,12 +25,6 @@ namespace Umbraco.Core.Configuration.Models
/// <remarks>That value could be overriden by other (attribute in user's code...). Return default if no value was supplied.</remarks>
public string ModelsNamespace { get; set; }
/// <summary>
/// Gets or sets a value indicating whether we should enable the models factory.
/// </summary>
/// <remarks>Default value is <c>true</c> because no factory is enabled by default in Umbraco.</remarks>
public bool EnableFactory { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether we should flag out-of-date models.
/// </summary>

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Core.Configuration
namespace Umbraco.Core.Configuration
{
/// <summary>
/// Defines the models generation modes.
@@ -6,9 +6,12 @@
public enum ModelsMode
{
/// <summary>
/// Do not generate models.
/// Do not generate strongly typed models.
/// </summary>
Nothing = 0, // default value
/// <remarks>
/// This means that only IPublishedContent instances will be used.
/// </remarks>
Nothing = 0,
/// <summary>
/// Generate models in memory.

View File

@@ -1,4 +1,4 @@
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration;
namespace Umbraco.Configuration
{
@@ -11,29 +11,18 @@ namespace Umbraco.Configuration
/// Gets a value indicating whether the mode is LiveAnything or PureLive.
/// </summary>
public static bool IsLive(this ModelsMode modelsMode)
{
return
modelsMode == ModelsMode.PureLive
|| modelsMode == ModelsMode.LiveAppData;
}
=> modelsMode == ModelsMode.PureLive || modelsMode == ModelsMode.LiveAppData;
/// <summary>
/// Gets a value indicating whether the mode is LiveAnything but not PureLive.
/// </summary>
public static bool IsLiveNotPure(this ModelsMode modelsMode)
{
return
modelsMode == ModelsMode.LiveAppData;
}
=> modelsMode == ModelsMode.LiveAppData;
/// <summary>
/// Gets a value indicating whether the mode supports explicit generation (as opposed to pure live).
/// </summary>
public static bool SupportsExplicitGeneration(this ModelsMode modelsMode)
{
return
modelsMode == ModelsMode.AppData
|| modelsMode == ModelsMode.LiveAppData;
}
=> modelsMode == ModelsMode.AppData || modelsMode == ModelsMode.LiveAppData;
}
}

View File

@@ -10,11 +10,6 @@ namespace Umbraco.Core
/// </summary>
public static class PublishedModelFactoryExtensions
{
/// <summary>
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory"/>
/// </summary>
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
/// <summary>
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory2"/> and is enabled
/// </summary>

View File

@@ -1,9 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Editors;
@@ -17,62 +18,75 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice
{
private readonly IOptions<ModelsBuilderSettings> _config;
public ContentTypeModelValidatorBase(IOptions<ModelsBuilderSettings> config)
{
_config = config;
}
public ContentTypeModelValidatorBase(IOptions<ModelsBuilderSettings> config) => _config = config;
protected override IEnumerable<ValidationResult> Validate(TModel model)
{
//don't do anything if we're not enabled
if (!_config.Value.Enable) yield break;
// don't do anything if we're not enabled
if (_config.Value.ModelsMode == ModelsMode.Nothing)
{
yield break;
}
//list of reserved/disallowed aliases for content/media/member types - more can be added as the need arises
// list of reserved/disallowed aliases for content/media/member types - more can be added as the need arises
var reservedModelAliases = new[] { "system" };
if(reservedModelAliases.Contains(model.Alias, StringComparer.OrdinalIgnoreCase))
if (reservedModelAliases.Contains(model.Alias, StringComparer.OrdinalIgnoreCase))
{
yield return new ValidationResult($"The model alias {model.Alias} is a reserved term and cannot be used", new[] { "Alias" });
}
var properties = model.Groups.SelectMany(x => x.Properties)
TProperty[] properties = model.Groups.SelectMany(x => x.Properties)
.Where(x => x.Inherited == false)
.ToArray();
foreach (var prop in properties)
foreach (TProperty prop in properties)
{
var propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
PropertyGroupBasic<TProperty> propertyGroup = model.Groups.Single(x => x.Properties.Contains(prop));
if (model.Alias.ToLowerInvariant() == prop.Alias.ToLowerInvariant())
yield return new ValidationResult(string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias), new[]
{
$"Groups[{model.Groups.IndexOf(propertyGroup)}].Properties[{propertyGroup.Properties.IndexOf(prop)}].Alias"
});
{
string[] memberNames = new[]
{
$"Groups[{model.Groups.IndexOf(propertyGroup)}].Properties[{propertyGroup.Properties.IndexOf(prop)}].Alias"
};
//we need to return the field name with an index so it's wired up correctly
yield return new ValidationResult(
string.Format("With Models Builder enabled, you can't have a property with a the alias \"{0}\" when the content type alias is also \"{0}\".", prop.Alias),
memberNames);
}
// we need to return the field name with an index so it's wired up correctly
var groupIndex = model.Groups.IndexOf(propertyGroup);
var propertyIndex = propertyGroup.Properties.IndexOf(prop);
var validationResult = ValidateProperty(prop, groupIndex, propertyIndex);
ValidationResult validationResult = ValidateProperty(prop, groupIndex, propertyIndex);
if (validationResult != null)
{
yield return validationResult;
}
}
}
private ValidationResult ValidateProperty(PropertyTypeBasic property, int groupIndex, int propertyIndex)
{
//don't let them match any properties or methods in IPublishedContent
//TODO: There are probably more!
// don't let them match any properties or methods in IPublishedContent
// TODO: There are probably more!
var reservedProperties = typeof(IPublishedContent).GetProperties().Select(x => x.Name).ToArray();
var reservedMethods = typeof(IPublishedContent).GetMethods().Select(x => x.Name).ToArray();
var alias = property.Alias;
if (reservedProperties.InvariantContains(alias) || reservedMethods.InvariantContains(alias))
return new ValidationResult(
$"The alias {alias} is a reserved term and cannot be used", new[]
{
string[] memberNames = new[]
{
$"Groups[{groupIndex}].Properties[{propertyIndex}].Alias"
});
};
return new ValidationResult(
$"The alias {alias} is a reserved term and cannot be used",
memberNames);
}
return null;
}

View File

@@ -27,38 +27,46 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice
public string Text()
{
if (!_config.Enable)
return "Version: " + ApiVersion.Current.Version + "<br />&nbsp;<br />ModelsBuilder is disabled<br />(the .Enable key is missing, or its value is not 'true').";
var sb = new StringBuilder();
sb.Append("Version: ");
sb.Append("<p>Version: ");
sb.Append(ApiVersion.Current.Version);
sb.Append("<br />&nbsp;<br />");
sb.Append("</p>");
sb.Append("ModelsBuilder is enabled, with the following configuration:");
sb.Append("<p>ModelsBuilder is enabled, with the following configuration:</p>");
sb.Append("<ul>");
sb.Append("<li>The <strong>models factory</strong> is ");
sb.Append("<li>The <strong>models mode</strong> is '");
sb.Append(_config.ModelsMode.ToString());
sb.Append("'. ");
// TODO: Test this - if models factory is entirely disabled will umbraco work at all?
// if not, is there a point to this?
sb.Append(_config.EnableFactory || _config.ModelsMode == ModelsMode.PureLive
? "enabled"
: "not enabled. Umbraco will <em>not</em> use models");
switch (_config.ModelsMode)
{
case ModelsMode.Nothing:
sb.Append("Strongly typed models are not generated. All content and cache will operate from instance of IPublishedContent only.");
break;
case ModelsMode.PureLive:
sb.Append("Strongly typed models are re-generated on startup and anytime schema changes (i.e. Content Type) are made. No recompilation necessary but the generated models are not available to code outside of Razor.");
break;
case ModelsMode.AppData:
sb.Append("Strongly typed models are generated on demand. Recompilation is necessary and models are available to all CSharp code.");
break;
case ModelsMode.LiveAppData:
sb.Append("Strong typed models are generated on demand and anytime schema changes (i.e. Content Type) are made. Recompilation is necessary and models are available to all CSharp code.");
break;
}
sb.Append(".</li>");
sb.Append("</li>");
sb.Append(_config.ModelsMode != ModelsMode.Nothing
? $"<li><strong>{_config.ModelsMode} models</strong> are enabled.</li>"
: "<li>No models mode is specified: models will <em>not</em> be generated.</li>");
if (_config.ModelsMode != ModelsMode.Nothing)
{
sb.Append($"<li>Models namespace is {_config.ModelsNamespace}.</li>");
sb.Append($"<li>Models namespace is {_config.ModelsNamespace}.</li>");
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled");
sb.Append(".</li>");
sb.Append("<li>Tracking of <strong>out-of-date models</strong> is ");
sb.Append(_config.FlagOutOfDateModels ? "enabled" : "not enabled");
sb.Append(".</li>");
}
sb.Append("</ul>");

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Umbraco.Configuration;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Hosting;
@@ -98,17 +99,14 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice
return GetDashboardResult();
}
private Dashboard GetDashboardResult()
private Dashboard GetDashboardResult() => new Dashboard
{
return new Dashboard
{
Enable = _config.Enable,
Text = _dashboardReport.Text(),
CanGenerate = _dashboardReport.CanGenerate(),
OutOfDateModels = _dashboardReport.AreModelsOutOfDate(),
LastError = _dashboardReport.LastError(),
};
}
Mode = _config.ModelsMode,
Text = _dashboardReport.Text(),
CanGenerate = _dashboardReport.CanGenerate(),
OutOfDateModels = _dashboardReport.AreModelsOutOfDate(),
LastError = _dashboardReport.LastError(),
};
[DataContract]
public class BuildResult
@@ -122,8 +120,8 @@ namespace Umbraco.ModelsBuilder.Embedded.BackOffice
[DataContract]
public class Dashboard
{
[DataMember(Name = "enable")]
public bool Enable;
[DataMember(Name = "mode")]
public ModelsMode Mode;
[DataMember(Name = "text")]
public string Text;
[DataMember(Name = "canGenerate")]

View File

@@ -105,7 +105,7 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
{
return factory.GetRequiredService<PureLiveModelFactory>();
}
else if (config.EnableFactory)
else
{
TypeLoader typeLoader = factory.GetRequiredService<TypeLoader>();
IPublishedValueFallback publishedValueFallback = factory.GetRequiredService<IPublishedValueFallback>();
@@ -114,8 +114,6 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
.Concat(typeLoader.GetTypes<PublishedContentModel>()); // content models
return new PublishedModelFactory(types, publishedValueFallback);
}
return null;
});
return builder;

View File

@@ -1,34 +1,31 @@
using System;
using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Configuration;
using Microsoft.Extensions.Options;
using Umbraco.Configuration;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Extensions;
using Umbraco.ModelsBuilder.Embedded.Building;
using Umbraco.Web.Cache;
using Umbraco.Core.Configuration.Models;
using Microsoft.Extensions.Options;
using Umbraco.Extensions;
namespace Umbraco.ModelsBuilder.Embedded
{
// supports LiveAppData - but not PureLive
public sealed class LiveModelsProvider
{
private static Mutex _mutex;
private static int _req;
private static Mutex s_mutex;
private static int s_req;
private readonly ILogger<LiveModelsProvider> _logger;
private readonly ModelsBuilderSettings _config;
private readonly ModelsGenerator _modelGenerator;
private readonly ModelsGenerationError _mbErrors;
private readonly IHostingEnvironment _hostingEnvironment;
// we do not manage pure live here
internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure();
/// <summary>
/// Initializes a new instance of the <see cref="LiveModelsProvider"/> class.
/// </summary>
public LiveModelsProvider(ILogger<LiveModelsProvider> logger, IOptions<ModelsBuilderSettings> config, ModelsGenerator modelGenerator, ModelsGenerationError mbErrors, IHostingEnvironment hostingEnvironment)
{
_logger = logger;
@@ -38,18 +35,23 @@ namespace Umbraco.ModelsBuilder.Embedded
_hostingEnvironment = hostingEnvironment;
}
// we do not manage pure live here
internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure();
internal void Install()
{
// just be sure
if (!IsEnabled)
{
return;
}
// initialize mutex
// ApplicationId will look like "/LM/W3SVC/1/Root/AppName"
// name is system-wide and must be less than 260 chars
var name = _hostingEnvironment.ApplicationId + "/UmbracoLiveModelsProvider";
_mutex = new Mutex(false, name); //TODO: Replace this with MainDom? Seems we now have 2x implementations of almost the same thing
s_mutex = new Mutex(false, name); //TODO: Replace this with MainDom? Seems we now have 2x implementations of almost the same thing
// anything changes, and we want to re-generate models.
ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration;
@@ -72,13 +74,13 @@ namespace Umbraco.ModelsBuilder.Embedded
{
//HttpContext.Current.Items[this] = true;
_logger.LogDebug("Requested to generate models.");
Interlocked.Exchange(ref _req, 1);
Interlocked.Exchange(ref s_req, 1);
}
public void GenerateModelsIfRequested()
{
//if (HttpContext.Current.Items[this] == null) return;
if (Interlocked.Exchange(ref _req, 0) == 0) return;
if (Interlocked.Exchange(ref s_req, 0) == 0) return;
// cannot use a simple lock here because we don't want another AppDomain
// to generate while we do... and there could be 2 AppDomains if the app restarts.
@@ -87,7 +89,7 @@ namespace Umbraco.ModelsBuilder.Embedded
{
_logger.LogDebug("Generate models...");
const int timeout = 2 * 60 * 1000; // 2 mins
_mutex.WaitOne(timeout); // wait until it is safe, and acquire
s_mutex.WaitOne(timeout); // wait until it is safe, and acquire
_logger.LogInformation("Generate models now.");
GenerateModels();
_mbErrors.Clear();
@@ -104,7 +106,7 @@ namespace Umbraco.ModelsBuilder.Embedded
}
finally
{
_mutex.ReleaseMutex(); // release
s_mutex.ReleaseMutex(); // release
}
}

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Configuration;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
@@ -65,7 +66,7 @@ namespace Umbraco.ModelsBuilder.Embedded
_modelBinder.ModelBindingException += ContentModelBinder_ModelBindingException;
if (_config.Enable)
if (_config.ModelsMode != ModelsMode.Nothing)
{
FileService.SavingTemplate += FileService_SavingTemplate;
}
@@ -126,7 +127,7 @@ namespace Umbraco.ModelsBuilder.Embedded
{
var settings = new Dictionary<string, object>
{
{"enabled", _config.Enable}
{"mode", _config.ModelsMode.ToString()}
};
return settings;
@@ -138,13 +139,6 @@ namespace Umbraco.ModelsBuilder.Embedded
/// </summary>
private void FileService_SavingTemplate(IFileService sender, SaveEventArgs<ITemplate> e)
{
// don't do anything if the factory is not enabled
// because, no factory = no models (even if generation is enabled)
if (!_config.EnableFactory)
{
return;
}
// don't do anything if this special key is not found
if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType"))
{

View File

@@ -132,7 +132,7 @@ namespace Umbraco.ModelsBuilder.Embedded
}
/// <inheritdoc />
public bool Enabled => _config.Enable;
public bool Enabled => _config.ModelsMode == ModelsMode.PureLive;
/// <summary>
/// Handle the event when a reference cannot be resolved from the default context and return our custom MB assembly reference if we have one

View File

@@ -45,7 +45,7 @@ namespace Umbraco.Web.Common.Filters
public void OnException(ExceptionContext filterContext)
{
var disabled = _exceptionFilterSettings?.Disabled ?? false;
if (_publishedModelFactory.IsLiveFactory()
if (_publishedModelFactory.IsLiveFactoryEnabled()
&& !disabled
&& !filterContext.ExceptionHandled
&& (filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException)

View File

@@ -28,7 +28,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje
generateModels: function () {
var deferred = $q.defer();
var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null;
var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled;
var modelsBuilderEnabled = Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.mode !== "Nothing";
if (modelsBuilderEnabled && modelsResource) {
modelsResource.buildModels().then(function (result) {
deferred.resolve(result);
@@ -49,7 +49,7 @@ function contentTypeHelper(contentTypeResource, dataTypeResource, $filter, $inje
checkModelsBuilderStatus: function () {
var deferred = $q.defer();
var modelsResource = $injector.has("modelsBuilderManagementResource") ? $injector.get("modelsBuilderManagementResource") : null;
var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.enabled === true);
var modelsBuilderEnabled = (Umbraco && Umbraco.Sys && Umbraco.Sys.ServerVariables && Umbraco.Sys.ServerVariables.umbracoPlugins && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder && Umbraco.Sys.ServerVariables.umbracoPlugins.modelsBuilder.mode !== "Nothing");
if (modelsBuilderEnabled && modelsResource) {
modelsResource.getModelsOutOfDateStatus().then(function (result) {