diff --git a/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs
index 618c3d7703..ba43667fbe 100644
--- a/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs
+++ b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParser.cs
@@ -1,24 +1,32 @@
-using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
+using Umbraco.Core.Events;
namespace Umbraco.Web.WebAssets
{
+ ///
+ /// Ensures the server variables are included in the outgoing JS script
+ ///
public class ServerVariablesParser
{
+ private const string Token = "##Variables##";
+ private readonly IEventAggregator _eventAggregator;
+
///
- /// Allows developers to add custom variables on parsing
+ /// Initializes a new instance of the class.
///
- public static event EventHandler> Parsing;
+ public ServerVariablesParser(IEventAggregator eventAggregator) => _eventAggregator = eventAggregator;
- internal const string Token = "##Variables##";
-
- public static string Parse(Dictionary items)
+ ///
+ /// Ensures the server variables in the dictionary are included in the outgoing JS script
+ ///
+ public async Task ParseAsync(Dictionary items)
{
var vars = Resources.ServerVariables;
- //Raise event for developers to add custom variables
- Parsing?.Invoke(null, items);
+ // Raise event for developers to add custom variables
+ await _eventAggregator.PublishAsync(new ServerVariablesParsing(items));
var json = JObject.FromObject(items);
return vars.Replace(Token, json.ToString());
diff --git a/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParsing.cs b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParsing.cs
new file mode 100644
index 0000000000..2fae2b13e0
--- /dev/null
+++ b/src/Umbraco.Infrastructure/WebAssets/ServerVariablesParsing.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using Umbraco.Core.Events;
+
+namespace Umbraco.Web.WebAssets
+{
+ ///
+ /// A notification for when server variables are parsing
+ ///
+ public class ServerVariablesParsing : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ServerVariablesParsing(IDictionary serverVariables) => ServerVariables = serverVariables;
+
+ ///
+ /// Gets a mutable dictionary of server variables
+ ///
+ public IDictionary ServerVariables { get; }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/DisabledModelsBuilderComponent.cs
similarity index 100%
rename from src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
rename to src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/DisabledModelsBuilderComponent.cs
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
similarity index 63%
rename from src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
rename to src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
index 94237ccf3d..85f15942dc 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -1,23 +1,29 @@
+using System;
+using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
-using Umbraco.Core.Configuration;
+using Microsoft.Extensions.Options;
using Umbraco.Core.Composing;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.ModelsBuilder.Embedded.Building;
-using Umbraco.Core.Configuration.Models;
-using Microsoft.Extensions.Options;
-using Umbraco.Core.DependencyInjection;
-namespace Umbraco.ModelsBuilder.Embedded.Compose
+namespace Umbraco.ModelsBuilder.Embedded.DependencyInjection
{
- // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there
- // This needs to execute before the AddNuCache call
- public sealed class ModelsBuilderComposer : ICoreComposer
+ ///
+ /// Extension methods for for the common Umbraco functionality
+ ///
+ public static class UmbracoBuilderExtensions
{
- public void Compose(IUmbracoBuilder builder)
+ ///
+ /// Adds umbraco's embedded model builder support
+ ///
+ public static IUmbracoBuilder AddModelsBuilder(this IUmbracoBuilder builder)
{
- builder.Components().Append();
builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
@@ -26,14 +32,15 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
builder.Services.AddUnique();
builder.Services.AddUnique(factory =>
{
- var config = factory.GetRequiredService>().Value;
+ ModelsBuilderSettings config = factory.GetRequiredService>().Value;
if (config.ModelsMode == ModelsMode.PureLive)
{
return factory.GetRequiredService();
+
// the following would add @using statement in every view so user's don't
// have to do it - however, then noone understands where the @using statement
// comes from, and it cannot be avoided / removed --- DISABLED
- //
+
/*
// no need for @using in views
// note:
@@ -48,9 +55,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
}
else if (config.EnableFactory)
{
- var typeLoader = factory.GetRequiredService();
- var publishedValueFallback = factory.GetRequiredService();
- var types = typeLoader
+ TypeLoader typeLoader = factory.GetRequiredService();
+ IPublishedValueFallback publishedValueFallback = factory.GetRequiredService();
+ IEnumerable types = typeLoader
.GetTypes() // element models
.Concat(typeLoader.GetTypes()); // content models
return new PublishedModelFactory(types, publishedValueFallback);
@@ -59,6 +66,16 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
return null;
});
+ return builder;
+ }
+
+ ///
+ /// Can be called if using an external models builder to remove the embedded models builder controller features
+ ///
+ public static IUmbracoBuilder DisableModelsBuilderControllers(this IUmbracoBuilder builder)
+ {
+ builder.Services.AddSingleton();
+ return builder;
}
}
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs b/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs
new file mode 100644
index 0000000000..8f7e118b6a
--- /dev/null
+++ b/src/Umbraco.ModelsBuilder.Embedded/DisableModelsBuilderNotificationHandler.cs
@@ -0,0 +1,29 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Umbraco.Core.Events;
+using Umbraco.ModelsBuilder.Embedded.BackOffice;
+using Umbraco.ModelsBuilder.Embedded.DependencyInjection;
+using Umbraco.Web.Features;
+
+namespace Umbraco.ModelsBuilder.Embedded
+{
+ ///
+ /// Used in conjunction with
+ ///
+ internal class DisableModelsBuilderNotificationHandler : INotificationHandler
+ {
+ private readonly UmbracoFeatures _features;
+
+ public DisableModelsBuilderNotificationHandler(UmbracoFeatures features) => _features = features;
+
+ ///
+ /// Handles the notification to disable MB controller features
+ ///
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ {
+ // disable the embedded dashboard controller
+ _features.Disabled.Controllers.Add();
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs
similarity index 74%
rename from src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
rename to src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs
index 7afb166069..8883069ca7 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/ModelsBuilderNotificationHandler.cs
@@ -1,13 +1,15 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Umbraco.Configuration;
-using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.Models;
-using Umbraco.Core.Hosting;
+using Umbraco.Core.Events;
using Umbraco.Core.IO;
+using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Strings;
@@ -17,21 +19,28 @@ using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Common.ModelBinders;
using Umbraco.Web.WebAssets;
-namespace Umbraco.ModelsBuilder.Embedded.Compose
+namespace Umbraco.ModelsBuilder.Embedded
{
- internal class ModelsBuilderComponent : IComponent
+
+ ///
+ /// Handles and notifications to initialize MB
+ ///
+ internal class ModelsBuilderNotificationHandler : INotificationHandler, INotificationHandler
{
private readonly ModelsBuilderSettings _config;
private readonly IShortStringHelper _shortStringHelper;
private readonly LiveModelsProvider _liveModelsProvider;
private readonly OutOfDateModelsStatus _outOfDateModels;
private readonly LinkGenerator _linkGenerator;
- private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly IUmbracoRequestLifetime _umbracoRequestLifetime;
- public ModelsBuilderComponent(IOptions config, IShortStringHelper shortStringHelper,
- LiveModelsProvider liveModelsProvider, OutOfDateModelsStatus outOfDateModels, LinkGenerator linkGenerator,
- IUmbracoRequestLifetime umbracoRequestLifetime, IUmbracoApplicationLifetime umbracoApplicationLifetime)
+ public ModelsBuilderNotificationHandler(
+ IOptions config,
+ IShortStringHelper shortStringHelper,
+ LiveModelsProvider liveModelsProvider,
+ OutOfDateModelsStatus outOfDateModels,
+ LinkGenerator linkGenerator,
+ IUmbracoRequestLifetime umbracoRequestLifetime)
{
_config = config.Value;
_shortStringHelper = shortStringHelper;
@@ -40,63 +49,74 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
_shortStringHelper = shortStringHelper;
_linkGenerator = linkGenerator;
_umbracoRequestLifetime = umbracoRequestLifetime;
- _umbracoApplicationLifetime = umbracoApplicationLifetime;
}
- public void Initialize()
+ ///
+ /// Handles the notification
+ ///
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
// always setup the dashboard
// note: UmbracoApiController instances are automatically registered
- InstallServerVars();
- _umbracoApplicationLifetime.ApplicationInit += InitializeApplication;
+ _umbracoRequestLifetime.RequestEnd += (sender, context) => _liveModelsProvider.AppEndRequest(context);
ContentModelBinder.ModelBindingException += ContentModelBinder_ModelBindingException;
if (_config.Enable)
+ {
FileService.SavingTemplate += FileService_SavingTemplate;
+ }
if (_config.ModelsMode.IsLiveNotPure())
+ {
_liveModelsProvider.Install();
+ }
if (_config.FlagOutOfDateModels)
+ {
_outOfDateModels.Install();
+ }
+
+ return Task.CompletedTask;
}
- public void Terminate()
+ ///
+ /// Handles the notification
+ ///
+ public Task HandleAsync(ServerVariablesParsing notification, CancellationToken cancellationToken)
{
- ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing;
- ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException;
- FileService.SavingTemplate -= FileService_SavingTemplate;
- }
+ var serverVars = notification.ServerVariables;
- private void InitializeApplication(object sender, EventArgs args)
- {
- _umbracoRequestLifetime.RequestEnd += (sender, context) => _liveModelsProvider.AppEndRequest(context);
- }
-
- private void InstallServerVars()
- {
- // register our URL - for the backoffice API
- ServerVariablesParser.Parsing += ServerVariablesParser_Parsing;
- }
-
- private void ServerVariablesParser_Parsing(object sender, Dictionary serverVars)
- {
if (!serverVars.ContainsKey("umbracoUrls"))
+ {
throw new ArgumentException("Missing umbracoUrls.");
+ }
+
var umbracoUrlsObject = serverVars["umbracoUrls"];
if (umbracoUrlsObject == null)
+ {
throw new ArgumentException("Null umbracoUrls");
+ }
+
if (!(umbracoUrlsObject is Dictionary umbracoUrls))
+ {
throw new ArgumentException("Invalid umbracoUrls");
+ }
if (!serverVars.ContainsKey("umbracoPlugins"))
+ {
throw new ArgumentException("Missing umbracoPlugins.");
- if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins))
- throw new ArgumentException("Invalid umbracoPlugins");
+ }
- umbracoUrls["modelsBuilderBaseUrl"] = _linkGenerator.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels());
- umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
+ if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins))
+ {
+ throw new ArgumentException("Invalid umbracoPlugins");
+ }
+
+ umbracoUrls["modelsBuilderBaseUrl"] = _linkGenerator.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels());
+ umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings();
+
+ return Task.CompletedTask;
}
private Dictionary GetModelsBuilderSettings()
@@ -113,22 +133,29 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
/// Used to check if a template is being created based on a document type, in this case we need to
/// ensure the template markup is correct based on the model name of the document type
///
- ///
- ///
- private void FileService_SavingTemplate(IFileService sender, Core.Events.SaveEventArgs e)
+ private void FileService_SavingTemplate(IFileService sender, SaveEventArgs 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;
+ if (!_config.EnableFactory)
+ {
+ return;
+ }
// don't do anything if this special key is not found
- if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType")) return;
+ if (!e.AdditionalData.ContainsKey("CreateTemplateForContentType"))
+ {
+ return;
+ }
// ensure we have the content type alias
if (!e.AdditionalData.ContainsKey("ContentTypeAlias"))
+ {
throw new InvalidOperationException("The additionalData key: ContentTypeAlias was not found");
+ }
- foreach (var template in e.SavedEntities)
+ foreach (ITemplate template in e.SavedEntities)
+ {
// if it is in fact a new entity (not been saved yet) and the "CreateTemplateForContentType" key
// is found, then it means a new template is being created based on the creation of a document type
if (!template.HasIdentity && string.IsNullOrWhiteSpace(template.Content))
@@ -142,29 +169,31 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
var modelNamespace = _config.ModelsNamespace;
// we do not support configuring this at the moment, so just let Umbraco use its default value
- //var modelNamespaceAlias = ...;
-
+ // var modelNamespaceAlias = ...;
var markup = ViewHelper.GetDefaultFileContent(
modelClassName: className,
modelNamespace: modelNamespace/*,
modelNamespaceAlias: modelNamespaceAlias*/);
- //set the template content to the new markup
+ // set the template content to the new markup
template.Content = markup;
}
+ }
}
private void ContentModelBinder_ModelBindingException(object sender, ContentModelBinder.ModelBindingArgs args)
{
- var sourceAttr = args.SourceType.Assembly.GetCustomAttribute();
- var modelAttr = args.ModelType.Assembly.GetCustomAttribute();
+ ModelsBuilderAssemblyAttribute sourceAttr = args.SourceType.Assembly.GetCustomAttribute();
+ ModelsBuilderAssemblyAttribute modelAttr = args.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?)
args.Message.Append(" The ");
@@ -180,6 +209,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
var pureModel = modelAttr.PureLive;
if (sourceAttr.PureLive || modelAttr.PureLive)
+ {
if (pureSource == false || pureModel == false)
{
// only one is pure - report, but better not restart (loops?)
@@ -200,6 +230,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
: "different versions. The application is in an unstable state and is going to be restarted.");
args.Restart = sourceVersion != modelVersion;
}
+ }
}
}
}
diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs
index fd9f28946f..6ca8bb588c 100644
--- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs
+++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs
@@ -2,8 +2,11 @@
// See LICENSE for more details.
using System.Collections.Generic;
+using System.Threading.Tasks;
+using Moq;
using NUnit.Framework;
using Umbraco.Core;
+using Umbraco.Core.Events;
using Umbraco.Web.WebAssets;
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration
@@ -12,8 +15,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration
public class ServerVariablesParserTests
{
[Test]
- public void Parse()
+ public async Task Parse()
{
+ var parser = new ServerVariablesParser(Mock.Of());
+
var d = new Dictionary
{
{ "test1", "Test 1" },
@@ -23,7 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration
{ "test5", "Test 5" }
};
- var output = ServerVariablesParser.Parse(d).StripWhitespace();
+ var output = (await parser.ParseAsync(d)).StripWhitespace();
Assert.IsTrue(output.Contains(@"Umbraco.Sys.ServerVariables = {
""test1"": ""Test 1"",
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
index 34d3a96ca3..63e7e09513 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs
@@ -63,6 +63,7 @@ namespace Umbraco.Web.BackOffice.Controllers
private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
+ private readonly ServerVariablesParser _serverVariables;
public BackOfficeController(
IBackOfficeUserManager userManager,
@@ -79,7 +80,8 @@ namespace Umbraco.Web.BackOffice.Controllers
IJsonSerializer jsonSerializer,
IBackOfficeExternalLoginProviders externalLogins,
IHttpContextAccessor httpContextAccessor,
- IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions)
+ IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
+ ServerVariablesParser serverVariables)
{
_userManager = userManager;
_runtimeMinifier = runtimeMinifier;
@@ -96,6 +98,7 @@ namespace Umbraco.Web.BackOffice.Controllers
_externalLogins = externalLogins;
_httpContextAccessor = httpContextAccessor;
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
+ _serverVariables = serverVariables;
}
[HttpGet]
@@ -266,13 +269,12 @@ namespace Umbraco.Web.BackOffice.Controllers
///
/// Returns the JavaScript object representing the static server variables javascript object
///
- ///
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[MinifyJavaScriptResult(Order = 1)]
public async Task ServerVariables()
{
- //cache the result if debugging is disabled
- var serverVars = ServerVariablesParser.Parse(await _backOfficeServerVariables.GetServerVariablesAsync());
+ // cache the result if debugging is disabled
+ var serverVars = await _serverVariables.ParseAsync(await _backOfficeServerVariables.GetServerVariablesAsync());
var result = _hostingEnvironment.IsDebugMode
? serverVars
: _appCaches.RuntimeCache.GetCacheItem(
diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
index 0d12fae687..739a90b7c4 100644
--- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -19,6 +19,7 @@ using Umbraco.Web.BackOffice.Services;
using Umbraco.Web.BackOffice.Trees;
using Umbraco.Web.Common.Authorization;
using Umbraco.Web.Common.DependencyInjection;
+using Umbraco.Web.WebAssets;
namespace Umbraco.Web.BackOffice.DependencyInjection
{
@@ -136,6 +137,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection
public static IUmbracoBuilder AddBackOfficeCore(this IUmbracoBuilder builder)
{
+ builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
diff --git a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs
index 616a75bfe7..38ad66121e 100644
--- a/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs
+++ b/src/Umbraco.Web.Common/Lifetime/IUmbracoRequestLifetime.cs
@@ -1,10 +1,11 @@
-using System;
+using System;
using Microsoft.AspNetCore.Http;
namespace Umbraco.Web.Common.Lifetime
{
+ // TODO: Should be killed and replaced with IEventAggregator
public interface IUmbracoRequestLifetime
- {
+ {
event EventHandler RequestStart;
event EventHandler RequestEnd;
}