diff --git a/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs index dd6129ec17..05df1f9a7b 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs @@ -92,6 +92,8 @@ namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection // would automatically just register for all implemented INotificationHandler{T}? builder.AddNotificationHandler(); builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index 9ee4d96482..1e0008dd43 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,19 +1,22 @@ using System; using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Extensions; using Umbraco.ModelsBuilder.Embedded.Building; using Umbraco.Web.Cache; +using Umbraco.Web.Common.Lifetime; namespace Umbraco.ModelsBuilder.Embedded { // supports LiveAppData - but not PureLive - public sealed class LiveModelsProvider + public sealed class LiveModelsProvider : INotificationHandler { private static Mutex s_mutex; private static int s_req; @@ -22,30 +25,49 @@ namespace Umbraco.ModelsBuilder.Embedded private readonly ModelsGenerator _modelGenerator; private readonly ModelsGenerationError _mbErrors; private readonly IHostingEnvironment _hostingEnvironment; + private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; /// /// Initializes a new instance of the class. /// - public LiveModelsProvider(ILogger logger, IOptions config, ModelsGenerator modelGenerator, ModelsGenerationError mbErrors, IHostingEnvironment hostingEnvironment) + public LiveModelsProvider( + ILogger logger, + IOptions config, + ModelsGenerator modelGenerator, + ModelsGenerationError mbErrors, + IHostingEnvironment hostingEnvironment, + IUmbracoRequestLifetime umbracoRequestLifetime) { _logger = logger; _config = config.Value ?? throw new ArgumentNullException(nameof(config)); _modelGenerator = modelGenerator; _mbErrors = mbErrors; _hostingEnvironment = hostingEnvironment; + _umbracoRequestLifetime = umbracoRequestLifetime; } // we do not manage pure live here internal bool IsEnabled => _config.ModelsMode.IsLiveNotPure(); - internal void Install() + /// + /// Handles the notification + /// + public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) { - // just be sure + Install(); + return Task.CompletedTask; + } + + private void Install() + { + // don't run if not enabled if (!IsEnabled) { return; } + _umbracoRequestLifetime.RequestEnd += (sender, context) => AppEndRequest(context); + // initialize mutex // ApplicationId will look like "/LM/W3SVC/1/Root/AppName" // name is system-wide and must be less than 260 chars @@ -56,34 +78,27 @@ namespace Umbraco.ModelsBuilder.Embedded // anything changes, and we want to re-generate models. ContentTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; DataTypeCacheRefresher.CacheUpdated += RequestModelsGeneration; - - // at the end of a request since we're restarting the pool - // NOTE - this does NOT trigger - see module below - //umbracoApplication.EndRequest += GenerateModelsIfRequested; } // NOTE - // Using HttpContext Items fails because CacheUpdated triggers within - // some asynchronous backend task where we seem to have no HttpContext. - - // So we use a static (non request-bound) var to register that models + // CacheUpdated triggers within some asynchronous backend task where + // we have no HttpContext. So we use a static (non request-bound) + // var to register that models // need to be generated. Could be by another request. Anyway. We could // have collisions but... you know the risk. private void RequestModelsGeneration(object sender, EventArgs args) { - //HttpContext.Current.Items[this] = true; _logger.LogDebug("Requested to generate models."); Interlocked.Exchange(ref s_req, 1); } - public void GenerateModelsIfRequested() + private void GenerateModelsIfRequested() { - //if (HttpContext.Current.Items[this] == null) 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. + if (Interlocked.Exchange(ref s_req, 0) == 0) + { + return; + } try { @@ -91,7 +106,7 @@ namespace Umbraco.ModelsBuilder.Embedded const int timeout = 2 * 60 * 1000; // 2 mins s_mutex.WaitOne(timeout); // wait until it is safe, and acquire _logger.LogInformation("Generate models now."); - GenerateModels(); + _modelGenerator.GenerateModels(); _mbErrors.Clear(); _logger.LogInformation("Generated."); } @@ -110,13 +125,7 @@ namespace Umbraco.ModelsBuilder.Embedded } } - private void GenerateModels() - { - // EnableDllModels will recycle the app domain - but this request will end properly - _modelGenerator.GenerateModels(); - } - - public void AppEndRequest(HttpContext context) + private void AppEndRequest(HttpContext context) { if (context.Request.IsClientSideRequest()) { diff --git a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs index 002ff67a4b..37ff1c735a 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs @@ -5,7 +5,6 @@ using System.Threading; 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; @@ -16,7 +15,6 @@ using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.ModelsBuilder.Embedded.BackOffice; -using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.WebAssets; @@ -30,28 +28,19 @@ namespace Umbraco.ModelsBuilder.Embedded { private readonly ModelsBuilderSettings _config; private readonly IShortStringHelper _shortStringHelper; - private readonly LiveModelsProvider _liveModelsProvider; - private readonly OutOfDateModelsStatus _outOfDateModels; private readonly LinkGenerator _linkGenerator; - private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; private readonly ContentModelBinder _modelBinder; public ModelsBuilderNotificationHandler( IOptions config, IShortStringHelper shortStringHelper, - LiveModelsProvider liveModelsProvider, - OutOfDateModelsStatus outOfDateModels, LinkGenerator linkGenerator, - IUmbracoRequestLifetime umbracoRequestLifetime, ContentModelBinder modelBinder) { _config = config.Value; _shortStringHelper = shortStringHelper; - _liveModelsProvider = liveModelsProvider; - _outOfDateModels = outOfDateModels; _shortStringHelper = shortStringHelper; _linkGenerator = linkGenerator; - _umbracoRequestLifetime = umbracoRequestLifetime; _modelBinder = modelBinder; } @@ -62,8 +51,6 @@ namespace Umbraco.ModelsBuilder.Embedded { // always setup the dashboard // note: UmbracoApiController instances are automatically registered - _umbracoRequestLifetime.RequestEnd += (sender, context) => _liveModelsProvider.AppEndRequest(context); - _modelBinder.ModelBindingException += ContentModelBinder_ModelBindingException; if (_config.ModelsMode != ModelsMode.Nothing) @@ -71,16 +58,6 @@ namespace Umbraco.ModelsBuilder.Embedded FileService.SavingTemplate += FileService_SavingTemplate; } - if (_config.ModelsMode.IsLiveNotPure()) - { - _liveModelsProvider.Install(); - } - - if (_config.FlagOutOfDateModels) - { - _outOfDateModels.Install(); - } - return Task.CompletedTask; } @@ -89,7 +66,7 @@ namespace Umbraco.ModelsBuilder.Embedded /// public Task HandleAsync(ServerVariablesParsing notification, CancellationToken cancellationToken) { - var serverVars = notification.ServerVariables; + IDictionary serverVars = notification.ServerVariables; if (!serverVars.ContainsKey("umbracoUrls")) { diff --git a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs index 92e0604a16..33c90d13bd 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/OutOfDateModelsStatus.cs @@ -1,13 +1,16 @@ -using System.IO; +using System.IO; using Microsoft.Extensions.Options; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Configuration.Models; using Umbraco.Web.Cache; +using Umbraco.Core.Events; +using System.Threading.Tasks; +using System.Threading; namespace Umbraco.ModelsBuilder.Embedded { - public sealed class OutOfDateModelsStatus + public sealed class OutOfDateModelsStatus : INotificationHandler { private readonly ModelsBuilderSettings _config; private readonly IHostingEnvironment _hostingEnvironment; @@ -18,11 +21,38 @@ namespace Umbraco.ModelsBuilder.Embedded _hostingEnvironment = hostingEnvironment; } - internal void Install() + public bool IsEnabled => _config.FlagOutOfDateModels; + + public bool IsOutOfDate { - // just be sure - if (_config.FlagOutOfDateModels == false) + get + { + if (_config.FlagOutOfDateModels == false) + { + return false; + } + + var path = GetFlagPath(); + return path != null && File.Exists(path); + } + } + + /// + /// Handles the notification + /// + public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + { + Install(); + return Task.CompletedTask; + } + + private void Install() + { + // don't run if not configured + if (!IsEnabled) + { return; + } ContentTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); DataTypeCacheRefresher.CacheUpdated += (sender, args) => Write(); @@ -32,35 +62,38 @@ namespace Umbraco.ModelsBuilder.Embedded { var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment); if (!Directory.Exists(modelsDirectory)) + { Directory.CreateDirectory(modelsDirectory); + } + return Path.Combine(modelsDirectory, "ood.flag"); } private void Write() { var path = GetFlagPath(); - if (path == null || File.Exists(path)) return; + if (path == null || File.Exists(path)) + { + return; + } + File.WriteAllText(path, "THIS FILE INDICATES THAT MODELS ARE OUT-OF-DATE\n\n"); } public void Clear() { - if (_config.FlagOutOfDateModels == false) return; - var path = GetFlagPath(); - if (path == null || !File.Exists(path)) return; - File.Delete(path); - } - - public bool IsEnabled => _config.FlagOutOfDateModels; - - public bool IsOutOfDate - { - get + if (_config.FlagOutOfDateModels == false) { - if (_config.FlagOutOfDateModels == false) return false; - var path = GetFlagPath(); - return path != null && File.Exists(path); + return; } - } + + var path = GetFlagPath(); + if (path == null || !File.Exists(path)) + { + return; + } + + File.Delete(path); + } } }