From cdd39050469b8bb20020724104914998cb519015 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 2 Dec 2020 10:10:48 +0100 Subject: [PATCH 01/88] Add timeout to Create data type test --- .../cypress/integration/Settings/dataTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts index 5803810f54..53fd55a3fc 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/dataTypes.ts @@ -23,7 +23,7 @@ context('Data Types', () => { cy.umbracoEditorHeaderName(name); - cy.get('select[name="selectedEditor"]').select('Label'); + cy.get('select[name="selectedEditor"]', {timeout: 5000}).select('Label'); cy.get('.umb-property-editor select').select('Time'); From b6b17e4c9a5600a802b6af8e8fa10f03f8111eff Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 2 Dec 2020 13:11:17 +0100 Subject: [PATCH 02/88] Update cypress to 6.0.1 --- .../cypress/integration/Settings/templates.ts | 2 +- src/Umbraco.Tests.AcceptanceTest/package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts index da1adedaeb..c586384af7 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/templates.ts @@ -41,7 +41,7 @@ context('Templates', () => { cy.umbracoSuccessNotification().should('be.visible'); // For some reason cy.umbracoErrorNotification tries to click the element which is not possible // if it doesn't actually exist, making should('not.be.visible') impossible. - cy.get('.umb-notifications__notifications > .alert-error').should('not.be.visible'); + cy.get('.umb-notifications__notifications > .alert-error').should('not.exist'); //Clean up cy.umbracoEnsureTemplateNameNotExists(name); diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index 996a0cd2f8..1b39ee0ad9 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -7,10 +7,10 @@ }, "devDependencies": { "cross-env": "^7.0.2", - "cypress": "^5.1.0", + "cypress": "^6.0.1", "ncp": "^2.0.0", - "umbraco-cypress-testhelpers": "^1.0.0-beta-51", - "prompt": "^1.0.0" + "prompt": "^1.0.0", + "umbraco-cypress-testhelpers": "^1.0.0-beta-51" }, "dependencies": { "typescript": "^3.9.2" From c2d38640ea50f9a224130a2b925d1e41083fc06c Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 2 Dec 2020 14:31:23 +0100 Subject: [PATCH 03/88] Remove duplicate square bracket --- .../cypress/integration/Content/content.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts index 68f31e80bb..22f1f883d0 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Content/content.ts @@ -321,7 +321,7 @@ context('Content', () => { cy.get('.umb-box-content > div > .input-block-level') .find('option[label*=' + new Date().getDate() + ']') .then(elements => { - const option = elements[[elements.length - 1]].getAttribute('value'); + const option = elements[elements.length - 1].getAttribute('value'); cy.get('.umb-box-content > div > .input-block-level') .select(option); }); @@ -521,7 +521,7 @@ context('Content', () => { .done() .done() .build(); - + cy.saveDocumentType(pickedDocType).then((generatedType) => { const pickedContentNode = new ContentBuilder() .withContentTypeAlias(generatedType["alias"]) @@ -563,7 +563,7 @@ context('Content', () => { @{ Layout = null; } - + @{ IPublishedContent typedContentPicker = Model.Value("picker"); if (typedContentPicker != null) From cba3f3c6a91b2916e82fb0981416ff4e1d254d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 15 Dec 2020 09:27:30 +0100 Subject: [PATCH 04/88] added missing Y --- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 7fddae5bea..54e55dc217 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -2532,7 +2532,7 @@ To manage your website, simply open the Umbraco back office and start adding con Preview website Open website in preview mode Preview website? - ou have ended preview mode, do you want to enable it again to view the latest saved version of your website? + You have ended preview mode, do you want to enable it again to view the latest saved version of your website? Preview latest version View published version View published version? diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 549f2783f8..e3d5e42c42 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -2554,7 +2554,7 @@ To manage your website, simply open the Umbraco back office and start adding con Preview website Open website in preview mode Preview website? - ou have ended preview mode, do you want to enable it again to view the latest saved version of your website? + You have ended preview mode, do you want to enable it again to view the latest saved version of your website? Preview latest version View published version View published version? From cd1120e062dc95fba2f7d9b2a29c0193e6ba8919 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 23 Nov 2020 07:43:24 +0100 Subject: [PATCH 05/88] Merge pull request #9408 from umbraco/v8/bugfix/task-scheduler-maindom-logging Ensure that TaskScheduler.Default is used anywhere that ContinueWith is used, adds more debug logging to MainDom operations (cherry picked from commit 9a1b4569494d6bce3e04687868ba944d69dbb309) # Conflicts: # src/Umbraco.Core/Runtime/SqlMainDomLock.cs --- .../Logging/LoggingTaskExtension.cs | 13 +++++++++-- src/Umbraco.Core/Runtime/MainDom.cs | 12 +++++++++- src/Umbraco.Core/Runtime/SqlMainDomLock.cs | 17 ++++++++++++-- .../PublishedCache/NuCache/ContentStore.cs | 6 ++++- .../PublishedCache/NuCache/SnapDictionary.cs | 6 ++++- .../Scheduling/BackgroundTaskRunner.cs | 10 ++++++++- .../Scheduling/TaskAndFactoryExtensions.cs | 22 +++++++++++++++++-- .../WebApi/HttpActionContextExtensions.cs | 4 +++- 8 files changed, 79 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 1f742133c3..2e3aa0a883 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Umbraco.Core.Logging @@ -14,7 +15,12 @@ namespace Umbraco.Core.Logging /// public static Task LogErrors(this Task task, Action logMethod) { - return task.ContinueWith(t => LogErrorsInner(t, logMethod), TaskContinuationOptions.OnlyOnFaulted); + return task.ContinueWith( + t => LogErrorsInner(t, logMethod), + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } /// @@ -26,7 +32,10 @@ namespace Umbraco.Core.Logging /// public static Task LogErrorsWaitable(this Task task, Action logMethod) { - return task.ContinueWith(t => LogErrorsInner(t, logMethod)); + return task.ContinueWith( + t => LogErrorsInner(t, logMethod), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } private static void LogErrorsInner(Task task, Action logAction) diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 02f37f654e..71842905dd 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Threading; +using System.Threading.Tasks; using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Logging; @@ -38,6 +39,9 @@ namespace Umbraco.Core.Runtime private const int LockTimeoutMilliseconds = 40000; // 40 seconds + private Task _listenTask; + private Task _listenCompleteTask; + #endregion #region Ctor @@ -172,7 +176,13 @@ namespace Umbraco.Core.Runtime try { // Listen for the signal from another AppDomain coming online to release the lock - _mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal")); + _listenTask = _mainDomLock.ListenAsync(); + _listenCompleteTask = _listenTask.ContinueWith(t => + { + _logger.Debug("Listening task completed with {TaskStatus}", _listenTask.Status); + + OnSignal("signal"); + }, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html } catch (OperationCanceledException ex) { diff --git a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs index 84d98775d9..48b5804305 100644 --- a/src/Umbraco.Core/Runtime/SqlMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/SqlMainDomLock.cs @@ -124,7 +124,12 @@ namespace Umbraco.Core.Runtime // Create a long running task (dedicated thread) // to poll to check if we are still the MainDom registered in the DB - return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + return Task.Factory.StartNew( + ListeningLoop, + _cancellationTokenSource.Token, + TaskCreationOptions.LongRunning, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); } @@ -159,7 +164,11 @@ namespace Umbraco.Core.Runtime // the other MainDom is taking to startup. In this case the db row will just be deleted and the // new MainDom will just take over. if (_cancellationTokenSource.IsCancellationRequested) + { + _logger.Debug("Task canceled, exiting loop"); return; + } + IUmbracoDatabase db = null; try { @@ -183,8 +192,10 @@ namespace Umbraco.Core.Runtime // We need to keep on listening unless we've been notified by our own AppDomain to shutdown since // we don't want to shutdown resources controlled by MainDom inadvertently. We'll just keep listening otherwise. if (_cancellationTokenSource.IsCancellationRequested) + { + _logger.Debug("Task canceled, exiting loop"); return; - + } } finally { @@ -389,6 +400,8 @@ namespace Umbraco.Core.Runtime { lock (_locker) { + _logger.Debug($"{nameof(SqlMainDomLock)} Disposing..."); + // immediately cancel all sub-tasks, we don't want them to keep querying _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 34d21497a2..07b10c9fbc 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -1335,7 +1335,11 @@ namespace Umbraco.Web.PublishedCache.NuCache { _collectTask = null; } - }, TaskContinuationOptions.ExecuteSynchronously); + }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); // ReSharper restore InconsistentlySynchronizedField return task; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs index c38940da25..589cd06d8a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/SnapDictionary.cs @@ -380,7 +380,11 @@ namespace Umbraco.Web.PublishedCache.NuCache { _collectTask = null; } - }, TaskContinuationOptions.ExecuteSynchronously); + }, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); // ReSharper restore InconsistentlySynchronizedField return task; diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index c0475b1f79..81bb45e270 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -756,9 +756,17 @@ namespace Umbraco.Web.Scheduling lock (_locker) { if (_runningTask != null) - _runningTask.ContinueWith(_ => StopImmediate()); + { + _runningTask.ContinueWith( + _ => StopImmediate(), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); + } else + { StopImmediate(); + } + } } diff --git a/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs index 7220e77e0c..557fe37709 100644 --- a/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs +++ b/src/Umbraco.Web/Scheduling/TaskAndFactoryExtensions.cs @@ -8,6 +8,7 @@ namespace Umbraco.Web.Scheduling { #region Task Extensions + // TODO: Not used, is this used in Deploy or something? static void SetCompletionSource(TaskCompletionSource completionSource, Task task) { if (task.IsFaulted) @@ -16,6 +17,7 @@ namespace Umbraco.Web.Scheduling completionSource.SetResult(default(TResult)); } + // TODO: Not used, is this used in Deploy or something? static void SetCompletionSource(TaskCompletionSource completionSource, Task task) { if (task.IsFaulted) @@ -24,17 +26,33 @@ namespace Umbraco.Web.Scheduling completionSource.SetResult(task.Result); } + // TODO: Not used, is this used in Deploy or something? public static Task ContinueWithTask(this Task task, Func continuation) { var completionSource = new TaskCompletionSource(); - task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2))); + task.ContinueWith(atask => continuation(atask).ContinueWith( + atask2 => SetCompletionSource(completionSource, atask2), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); return completionSource.Task; } + // TODO: Not used, is this used in Deploy or something? public static Task ContinueWithTask(this Task task, Func continuation, CancellationToken token) { var completionSource = new TaskCompletionSource(); - task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2), token), token); + task.ContinueWith(atask => continuation(atask).ContinueWith( + atask2 => SetCompletionSource(completionSource, atask2), + token, + TaskContinuationOptions.None, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default), + token, + TaskContinuationOptions.None, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); return completionSource.Task; } diff --git a/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs b/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs index c67ec2f6a7..08d0ac8254 100644 --- a/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs @@ -89,7 +89,9 @@ namespace Umbraco.Web.WebApi throw x.Exception; } result = x.ConfigureAwait(false).GetAwaiter().GetResult(); - }); + }, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); task.Wait(); if (result == null) From 7b32aeb1271d89911f173b19e2383a675bc7e48c Mon Sep 17 00:00:00 2001 From: Matt Darby Date: Wed, 28 Oct 2020 20:07:35 +0000 Subject: [PATCH 06/88] Change defaultButton type to "button" (cherry picked from commit 7ddb52a34feffebe3e11c86066b59aae795248c2) --- .../src/views/documenttypes/edit.controller.js | 2 +- .../src/views/mediatypes/edit.controller.js | 2 +- .../src/views/membertypes/edit.controller.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index e39a36a439..dfe229cf72 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -176,7 +176,7 @@ hotKeyWhenHidden: true, labelKey: vm.submitButtonKey, letter: "S", - type: "submit", + type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js index fc2b83ea93..43db6e7bb8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -190,7 +190,7 @@ hotKeyWhenHidden: true, labelKey: vm.saveButtonKey, letter: "S", - type: "submit", + type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index c81de0ec4d..31c062e41b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -111,7 +111,7 @@ hotKeyWhenHidden: true, labelKey: vm.saveButtonKey, letter: "S", - type: "submit", + type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ From 015882daa2f7acdcc968f4ba0feb2159947ccfa6 Mon Sep 17 00:00:00 2001 From: Matt Darby Date: Wed, 28 Oct 2020 20:11:47 +0000 Subject: [PATCH 07/88] Remove type as it defaults to button (cherry picked from commit bc2faefb1881969ff25a0d381808fc41280358c6) --- .../src/views/documenttypes/edit.controller.js | 1 - .../src/views/mediatypes/edit.controller.js | 1 - .../src/views/membertypes/edit.controller.js | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index dfe229cf72..3946d09578 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -176,7 +176,6 @@ hotKeyWhenHidden: true, labelKey: vm.submitButtonKey, letter: "S", - type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ diff --git a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js index 43db6e7bb8..ecf2aec30c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/mediatypes/edit.controller.js @@ -190,7 +190,6 @@ hotKeyWhenHidden: true, labelKey: vm.saveButtonKey, letter: "S", - type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ diff --git a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js index 31c062e41b..53bb4adb9b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/membertypes/edit.controller.js @@ -111,7 +111,6 @@ hotKeyWhenHidden: true, labelKey: vm.saveButtonKey, letter: "S", - type: "button", handler: function () { vm.save(); } }; vm.page.subButtons = [{ From 3f0f5c14495eb7dd6194b15f5309e7d0cddc20ee Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 18 Dec 2020 12:23:44 +0100 Subject: [PATCH 08/88] Fixed tests and update testhelper --- .../integration/Settings/documentTypes.ts | 4 +- .../integration/Settings/mediaTypes.ts | 2 +- .../integration/Settings/memberTypes.ts | 2 +- src/Umbraco.Tests.AcceptanceTest/package.json | 2 +- src/Umbraco.Web.UI.Client/package-lock.json | 1911 ++++++++++------- 5 files changed, 1148 insertions(+), 773 deletions(-) diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts index c40d65d541..1a86e90852 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/documentTypes.ts @@ -32,7 +32,7 @@ context('Document Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); @@ -70,7 +70,7 @@ context('Document Types', () => { cy.umbracoContextMenuAction("action-delete").click(); cy.get('label.checkbox').click(); - cy.umbracoButtonByLabelKey("general_ok").click(); + cy.umbracoButtonByLabelKey("delete").click(); cy.contains(name).should('not.exist'); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts index 646654bbab..4064c1f41e 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/mediaTypes.ts @@ -31,7 +31,7 @@ context('Media Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts index fe2d88d64f..e84c29c0d8 100644 --- a/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts +++ b/src/Umbraco.Tests.AcceptanceTest/cypress/integration/Settings/memberTypes.ts @@ -29,7 +29,7 @@ context('Member Types', () => { cy.get('[data-element="editor-add"]').click(); //Search for textstring - cy.get('.umb-search-field').type('Textstring'); + cy.get('#datatype-search').type('Textstring'); // Choose first item cy.get('ul.umb-card-grid li [title="Textstring"]').closest("li").click(); diff --git a/src/Umbraco.Tests.AcceptanceTest/package.json b/src/Umbraco.Tests.AcceptanceTest/package.json index 1b39ee0ad9..378fe719fc 100644 --- a/src/Umbraco.Tests.AcceptanceTest/package.json +++ b/src/Umbraco.Tests.AcceptanceTest/package.json @@ -10,7 +10,7 @@ "cypress": "^6.0.1", "ncp": "^2.0.0", "prompt": "^1.0.0", - "umbraco-cypress-testhelpers": "^1.0.0-beta-51" + "umbraco-cypress-testhelpers": "^1.0.0-beta-52" }, "dependencies": { "typescript": "^3.9.2" diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index df2f3637f6..1b28cfb029 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -104,7 +104,7 @@ "@babel/helper-annotate-as-pure": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "integrity": "sha1-Mj053QtQ4Qx8Bsp9djjmhk2MXDI=", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -113,7 +113,7 @@ "@babel/helper-builder-binary-assignment-operator-visitor": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "integrity": "sha1-a2lijf5Ah3mODE7Zjj1Kay+9L18=", "dev": true, "requires": { "@babel/helper-explode-assignable-expression": "^7.1.0", @@ -145,7 +145,7 @@ "@babel/helper-explode-assignable-expression": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "integrity": "sha1-U3+hP28WdN90WwwA7I/k6ZaByPY=", "dev": true, "requires": { "@babel/traverse": "^7.1.0", @@ -155,7 +155,7 @@ "@babel/helper-function-name": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "integrity": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.0.0", @@ -166,7 +166,7 @@ "@babel/helper-get-function-arity": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "integrity": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -193,7 +193,7 @@ "@babel/helper-module-imports": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "integrity": "sha1-lggbcRHkhtpNLNlxrRpP4hbMLj0=", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -216,7 +216,7 @@ "@babel/helper-optimise-call-expression": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "integrity": "sha1-opIMVwKwc8Fd5REGIAqoytIEl9U=", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -225,7 +225,7 @@ "@babel/helper-plugin-utils": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "integrity": "sha1-u7P77phmHFaQNCN8wDlnupm08lA=", "dev": true }, "@babel/helper-regex": { @@ -240,7 +240,7 @@ "@babel/helper-remap-async-to-generator": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "integrity": "sha1-Nh2AghtvONp1vT8HheziCojF/n8=", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.0.0", @@ -265,7 +265,7 @@ "@babel/helper-simple-access": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "integrity": "sha1-Ze65VMjCRb6qToWdphiPOdceWFw=", "dev": true, "requires": { "@babel/template": "^7.1.0", @@ -862,6 +862,43 @@ } } }, + "@gulp-sourcemaps/identity-map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", + "integrity": "sha512-ciiioYMLdo16ShmfHBXJBOFm3xPC4AuwO4xeRpFeHz7WK9PYsWCmigagG2XyzZpubK4a3qNKoUBDhbzHfa50LQ==", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -935,6 +972,12 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -948,7 +991,7 @@ "accord": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/accord/-/accord-0.29.0.tgz", - "integrity": "sha512-3OOR92FTc2p5/EcOzPcXp+Cbo+3C15nV9RXHlOUBCBpHhcB+0frbSNR9ehED/o7sTcyGVtqGJpguToEdlXhD0w==", + "integrity": "sha1-t0HBdtAENcWSnUZt/oz2vukzseQ=", "dev": true, "requires": { "convert-source-map": "^1.5.0", @@ -984,7 +1027,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -995,7 +1038,7 @@ "ace-builds": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.2.tgz", - "integrity": "sha512-M1JtZctO2Zg+1qeGUFZXtYKsyaRptqQtqpVzlj80I0NzGW9MF3um0DBuizIvQlrPYUlTdm+wcOPZpZoerkxQdA==" + "integrity": "sha1-avwuQ6e17/3ETYQHQ2EShSVo6A0=" }, "acorn": { "version": "7.1.0", @@ -1003,12 +1046,36 @@ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, "acorn-jsx": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", @@ -1062,19 +1129,19 @@ "dev": true }, "angular": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", - "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.0.tgz", + "integrity": "sha512-VdaMx+Qk0Skla7B5gw77a8hzlcOakwF8mjlW13DpIWIDlfqwAbSSLfd8N/qZnzEmQF4jC4iofInd3gE7vL8ZZg==" }, "angular-animate": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz", - "integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ==" + "integrity": "sha1-H/xsKpze4ieiunnMbNj3HsRNtdw=" }, "angular-aria": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.5.tgz", - "integrity": "sha512-X2dGRw+PK7hrV7/X1Ns4e5P3KC/OBFi1l7z//D/v7zbZObsAx48qBoX7unsck+s4+mnO+ikNNkHG5N49VfAyRw==" + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.9.tgz", + "integrity": "sha512-luI3Jemd1AbOQW0krdzfEG3fM0IFtLY0bSSqIDEx3POE0XjKIC1MkrO8Csyq9PPgueLphyAPofzUwZ8YeZ88SA==" }, "angular-chart.js": { "version": "1.1.1", @@ -1099,12 +1166,12 @@ "angular-cookies": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.5.tgz", - "integrity": "sha512-/8xvvSl/Z9Vwu8ChRm+OQE3vmli8Icwl8uTYkHqD7j7cknJP9kNaf7SgsENlsLVtOqLE/I7TCFYrSx3bmSeNQA==" + "integrity": "sha1-HFqzwFzcQ/F3e+lQbmRYfLNUNjQ=" }, "angular-dynamic-locale": { "version": "0.1.37", "resolved": "https://registry.npmjs.org/angular-dynamic-locale/-/angular-dynamic-locale-0.1.37.tgz", - "integrity": "sha512-m5Kyk8W8/mOZSqRxuByOwHBjv8labLBAgvl0Z3iQx2xT/tWCqb94imKUPwumudszdPDjxeopwyucQvm8Sw7ogw==", + "integrity": "sha1-fon70uxFvdaryJ82zaiJODjkk1Q=", "requires": { "@types/angular": "^1.6.25" } @@ -1112,7 +1179,7 @@ "angular-i18n": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-i18n/-/angular-i18n-1.7.5.tgz", - "integrity": "sha512-52+Jpt8HRJV2bqSbSU6fWkwOvGzj/DxbNpKXxnTuCS9heuJrlm77BS/lhrF4BA8+Uudnh7npr5/yRELobP+8Yw==" + "integrity": "sha1-Lie2Thl3qMa2sFHFHQF1xtTcglI=" }, "angular-local-storage": { "version": "0.7.1", @@ -1122,32 +1189,32 @@ "angular-messages": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-messages/-/angular-messages-1.7.5.tgz", - "integrity": "sha512-YDpJpFLyrIgZjE/sIAjgww1y6r3QqXBJbNDI0QjftD37vHXLkwvAOo3A4bxPw8BikyGLcJrFrgf6hRAzntJIWA==" + "integrity": "sha1-fC/XgTFaQ6GYOLEX2gFCqYhFThQ=" }, "angular-mocks": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.5.tgz", - "integrity": "sha512-I+Ue2Bkx6R9W5178DYrNvzjIdGh4wKKoCWsgz8dc7ysH4mA70Q3M9v5xRF0RUu7r+2CZj+nDeUecvh2paxcYvg==" + "integrity": "sha1-yLq6WgbtYLk0aXAmtJIWliavOEs=" }, "angular-route": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-route/-/angular-route-1.7.5.tgz", - "integrity": "sha512-7KfyEVVOWTI+jTY/j5rUNCIHGRyeCOx7YqZI/Ci3IbDK7GIsy6xH+hS5ai0Xi0sLjzDZ0PUDO4gBn+K0dVtlOg==" + "integrity": "sha1-NKNkjEB6FKAw0HXPSFMY4zuiPw4=" }, "angular-sanitize": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.5.tgz", - "integrity": "sha512-wjKCJOIwrkEvfD0keTnKGi6We13gtoCAQIHcdoqyoo3gwvcgNfYymVQIS3+iCGVcjfWz0jHuS3KgB4ysRWsTTA==" + "integrity": "sha1-ddSeFQccqccFgedtIJQPJjcuJNI=" }, "angular-touch": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/angular-touch/-/angular-touch-1.7.5.tgz", - "integrity": "sha512-XNAZNG0RA1mtdwBJheViCF1H/7wOygp4MLIfs5y1K+rne6AeaYKZcV6EJs9fvgfLKLO6ecm1+3J8hoCkdhhxQw==" + "integrity": "sha1-7SYyKmhfApmyPLauqYNMEZQk2kY=" }, "angular-ui-sortable": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/angular-ui-sortable/-/angular-ui-sortable-0.19.0.tgz", - "integrity": "sha512-u/uc981Nzg4XN1bMU9qKleMTSt7F1XjMWnyGw6gxPLIeQeLZm8jWNy7tj8y2r2HmvzXFbQVq2z6rObznFKAekQ==", + "integrity": "sha1-SsQ5H8TU3lcRDbS10xp8GY0xT9A=", "requires": { "angular": ">=1.2.x", "jquery": ">=3.1.x", @@ -1162,7 +1229,7 @@ "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "integrity": "sha1-Y3S03V1HGP884npnGjscrQdxMqk=", "dev": true, "requires": { "ansi-wrap": "^0.1.0" @@ -1210,7 +1277,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -1242,7 +1309,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "requires": { "micromatch": "^2.1.5", @@ -1390,7 +1457,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -1414,7 +1481,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "arr-map": { @@ -1489,13 +1556,13 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-sort": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "integrity": "sha1-5MBTVkU/VvU1EqfR1hI/LFTAqIo=", "dev": true, "requires": { "default-compare": "^1.0.0", @@ -1506,7 +1573,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -1532,7 +1599,7 @@ "arraybuffer.slice": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "integrity": "sha1-O7xCdd1YTMGxCAm4nU6LY6aednU=", "dev": true }, "asap": { @@ -1545,7 +1612,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -1566,7 +1633,7 @@ "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "integrity": "sha1-bIw/uCfdQ+45GPJ7gngqt2WKb9k=", "dev": true }, "async": { @@ -1628,7 +1695,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", "dev": true }, "autoprefixer": { @@ -1663,7 +1730,7 @@ "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "integrity": "sha1-8OAD2cqef1nHpQiUXXsu+aBKVC8=", "dev": true }, "babel-plugin-dynamic-import-node": { @@ -1707,7 +1774,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -1731,7 +1798,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1740,7 +1807,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1749,7 +1816,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -2057,7 +2124,7 @@ "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "integrity": "sha1-oWCRFxcQPAdBDO9j71Gzl8Alr5w=", "dev": true, "optional": true, "requires": { @@ -2075,7 +2142,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "optional": true, "requires": { @@ -2091,7 +2158,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "optional": true, "requires": { @@ -2103,7 +2170,7 @@ "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "integrity": "sha1-1oDu7yX4zZGtUz9bAe7UjmTK9oM=", "dev": true }, "bluebird": { @@ -2133,7 +2200,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -2176,7 +2243,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -2186,7 +2253,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -2212,6 +2279,12 @@ } } }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browserslist": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", @@ -2237,7 +2310,7 @@ "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "integrity": "sha1-iQ3ZDZI6hz4I4Q5f1RpX5bfM4Ow=", "dev": true, "requires": { "buffer-alloc-unsafe": "^1.1.0", @@ -2247,7 +2320,7 @@ "buffer-alloc-unsafe": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "integrity": "sha1-vX3CauKXLQ7aJTvgYdupkjScGfA=", "dev": true }, "buffer-crc32": { @@ -2272,7 +2345,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", "dev": true }, "bufferstreams": { @@ -2293,7 +2366,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -2420,7 +2493,7 @@ "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "integrity": "sha1-Xk2Q4idJYdRikZl99Znj7QCO5MA=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -2430,9 +2503,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001002", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001002.tgz", - "integrity": "sha512-pRuxPE8wdrWmVPKcDmJJiGBxr6lFJq4ivdSeo9FTmGj5Rb8NX3Mby2pARG57MXF15hYAhZ0nHV5XxT2ig4bz3g==", + "version": "1.0.30001168", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz", + "integrity": "sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==", "dev": true }, "caseless": { @@ -2478,13 +2551,13 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "integrity": "sha1-kAlISfCTfy7twkJdDSip5fDLrZ4=", "dev": true }, "chart.js": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.8.0.tgz", - "integrity": "sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", "requires": { "chartjs-color": "^2.1.0", "moment": "^2.10.2" @@ -2530,7 +2603,7 @@ "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", "dev": true, "requires": { "micromatch": "^3.1.4", @@ -2568,7 +2641,7 @@ "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -2591,7 +2664,7 @@ "clean-css": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "integrity": "sha1-LUEe92uFabbQyEBo2r6FsKpeXBc=", "dev": true, "requires": { "source-map": "~0.6.0" @@ -2600,7 +2673,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -2608,7 +2681,7 @@ "cli-color": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.4.0.tgz", - "integrity": "sha512-xu6RvQqqrWEo6MPR1eixqGPywhYBHRs653F9jfXB2Hx4jdM/3WxiNE1vppRmxtMIfl16SFYTpYlrnqH/HsK/2w==", + "integrity": "sha1-fRBzj0hSaCT4/n2lGFfLD1cv4B8=", "dev": true, "requires": { "ansi-regex": "^2.1.1", @@ -2637,7 +2710,7 @@ "clipboard": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "integrity": "sha1-g22v1mzw/qXXHOXVsL9ulYAJES0=", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -2703,7 +2776,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2718,7 +2791,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -2804,7 +2877,7 @@ "color-string": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "integrity": "sha1-ybvF8BtYtUkvPWhXRZy2WQziBMw=", "dev": true, "requires": { "color-name": "^1.0.0", @@ -2814,7 +2887,7 @@ "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "integrity": "sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=", "dev": true }, "colornames": { @@ -2842,7 +2915,7 @@ "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "integrity": "sha1-2SC0Mo1TSjrIKV1o971LpsQnvpo=", "dev": true, "requires": { "color-convert": "^1.9.1", @@ -2912,7 +2985,7 @@ "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -2928,9 +3001,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -2945,7 +3018,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -2956,7 +3029,7 @@ "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "integrity": "sha1-1OqT8FriV5CVG5nns7CeOQikCC4=", "dev": true, "requires": { "source-map": "^0.6.1" @@ -2965,7 +3038,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -2996,7 +3069,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -3039,13 +3112,13 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", "dev": true }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "integrity": "sha1-UbU3qMQ+DwTewZk7/83VBOdYrCA=", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -3066,7 +3139,7 @@ "copy-props": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "integrity": "sha1-k7scrfr9MdpbuKnUtB9HHsOnLf4=", "dev": true, "requires": { "each-props": "^1.3.0", @@ -3138,7 +3211,7 @@ "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "integrity": "sha1-Sl7Hxk364iw6FBJNus3uhG2Ay8Q=", "dev": true, "requires": { "nice-try": "^1.0.4", @@ -3148,6 +3221,26 @@ "which": "^1.2.9" } }, + "css": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3157,7 +3250,7 @@ "css-declaration-sorter": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "integrity": "sha1-wZiUD2OnbX42wecQGLABchBUyyI=", "dev": true, "requires": { "postcss": "^7.0.1", @@ -3167,7 +3260,7 @@ "css-select": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", - "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "integrity": "sha1-q0OGzsnh9miFVWSxfDcztDsqXt4=", "dev": true, "requires": { "boolbase": "^1.0.0", @@ -3179,7 +3272,7 @@ "css-select-base-adapter": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "integrity": "sha1-Oy/0lyzDYquIVhUHqVQIoUMhNdc=", "dev": true }, "css-tree": { @@ -3207,7 +3300,7 @@ "cssesc": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "integrity": "sha1-OxO9G7HLNuG8taTc0n9UxdyzVwM=", "dev": true }, "cssnano": { @@ -3275,7 +3368,7 @@ "cssnano-util-raw-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "integrity": "sha1-sm1f1fcqEd/np4RvtMZyYPlr8oI=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -3284,13 +3377,13 @@ "cssnano-util-same-parent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "integrity": "sha1-V0CC+yhZ0ttDOFWDXZqEVuoYu/M=", "dev": true }, "csso": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", - "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "integrity": "sha1-e564vmFiiXPBsmHhadLwJACOdYs=", "dev": true, "requires": { "css-tree": "1.0.0-alpha.29" @@ -3299,7 +3392,7 @@ "css-tree": { "version": "1.0.0-alpha.29", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", - "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "integrity": "sha1-P6nU7zFCy9HDAedmTB81K9gvWjk=", "dev": true, "requires": { "mdn-data": "~1.1.0", @@ -3314,6 +3407,29 @@ } } }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3349,6 +3465,17 @@ "assert-plus": "^1.0.0" } }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, "date-format": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", @@ -3370,12 +3497,46 @@ "ms": "^2.1.1" } }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decimal.js": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", + "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", + "dev": true + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -3544,7 +3705,7 @@ "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "integrity": "sha1-y2ETGESthNhHiPto/QFoHKd4Gi8=", "dev": true, "requires": { "kind-of": "^5.0.2" @@ -3553,7 +3714,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -3567,7 +3728,7 @@ "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", "dev": true, "requires": { "object-keys": "^1.0.12" @@ -3576,7 +3737,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -3586,7 +3747,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3595,7 +3756,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3604,7 +3765,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -3623,7 +3784,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" }, "depd": { "version": "1.1.2", @@ -3637,6 +3798,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -3646,7 +3813,7 @@ "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", - "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "integrity": "sha1-yrasM99wydmnJ0kK5DrJladpsio=", "dev": true, "requires": { "colorspace": "1.1.x", @@ -3657,7 +3824,7 @@ "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=" }, "dir-glob": { "version": "3.0.1", @@ -3721,10 +3888,27 @@ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", "dev": true }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, "domhandler": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "integrity": "sha1-iAUJfpM9ZehVRvcm1g9euItE+AM=", "dev": true, "requires": { "domelementtype": "1" @@ -3733,7 +3917,7 @@ "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "integrity": "sha1-Vuo0HoNOBuZ0ivehyyXaZ+qfjCo=", "dev": true, "requires": { "dom-serializer": "0", @@ -3743,7 +3927,7 @@ "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", "dev": true, "requires": { "is-obj": "^1.0.0" @@ -3832,7 +4016,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -3847,7 +4031,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -3858,7 +4042,7 @@ "each-props": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "integrity": "sha1-6kWkFNFt1c+kGbGoFyDVygaJIzM=", "dev": true, "requires": { "is-plain-object": "^2.0.1", @@ -3937,7 +4121,7 @@ "engine.io": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.1.tgz", - "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", + "integrity": "sha1-tgKBw1SEpw7gNR6g6/+D7IyVIqI=", "dev": true, "requires": { "accepts": "~1.3.4", @@ -3951,7 +4135,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -3968,7 +4152,7 @@ "engine.io-client": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", - "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "integrity": "sha1-b1TAR13khxWKGnx30QF4cItq3TY=", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -4010,7 +4194,7 @@ "engine.io-parser": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz", - "integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==", + "integrity": "sha1-dXq5cPvy37Mse3SwMyFtVznveaY=", "dev": true, "requires": { "after": "0.8.2", @@ -4035,13 +4219,13 @@ "env-variable": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", - "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==", + "integrity": "sha1-kT3YML7xHpagOcA41BMGBOujf4g=", "dev": true }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", "dev": true, "optional": true, "requires": { @@ -4051,7 +4235,7 @@ "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "integrity": "sha1-tKxAZIEH/c3PriQvQovqihTU8b8=", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -4078,7 +4262,7 @@ "es-to-primitive": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "integrity": "sha1-7fckeAM0VujdqO8J4ArZZQcH83c=", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -4170,7 +4354,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -4317,13 +4501,13 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", "dev": true }, "esquery": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -4332,7 +4516,7 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", "dev": true, "requires": { "estraverse": "^4.1.0" @@ -4387,7 +4571,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4465,7 +4649,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -4509,7 +4693,7 @@ "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -4587,7 +4771,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=", "dev": true }, "extend-shallow": { @@ -4603,7 +4787,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -4625,7 +4809,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -4659,7 +4843,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4668,7 +4852,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4677,7 +4861,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -4687,53 +4871,6 @@ } } }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - } - } - }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", @@ -4743,7 +4880,7 @@ "fancy-log": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "integrity": "sha1-28GRVPVYaQFQojlToK29A1vkX8c=", "dev": true, "requires": { "ansi-gray": "^0.1.1", @@ -4955,7 +5092,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -5000,15 +5137,26 @@ } }, "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", "dev": true, "requires": { "detect-file": "^1.0.0", - "is-glob": "^3.1.0", + "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + } } }, "fined": { @@ -5085,7 +5233,7 @@ "flatpickr": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.5.2.tgz", - "integrity": "sha512-jDy4QYGpmiy7+Qk8QvKJ4spjDdxcx9cxMydmq1x427HkKWBw0qizLYeYM2F6tMcvvqGjU5VpJS55j4LnsaBblA==" + "integrity": "sha1-R8itRyoJbl+350uAmwcDU1OD8g0=" }, "flatted": { "version": "2.0.1", @@ -5184,7 +5332,7 @@ "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -5256,29 +5404,10 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true, "optional": true }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true - } - } - }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -5855,7 +5984,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", "dev": true }, "functional-red-black-tree": { @@ -5867,7 +5996,7 @@ "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "integrity": "sha1-+Xj6TJDR3+f/LWvtoqUV5xO9z0o=", "dev": true }, "get-proxy": { @@ -6100,7 +6229,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6252,7 +6381,7 @@ "gulp-babel": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-babel/-/gulp-babel-8.0.0.tgz", - "integrity": "sha512-oomaIqDXxFkg7lbpBou/gnUkX51/Y/M2ZfSjL2hdqXTAlSWZcgZtd2o0cOH0r/eE8LWD0+Q/PsLsr2DKOoqToQ==", + "integrity": "sha1-4NqW9PLsSojdOjAw9HbjirISbYc=", "dev": true, "requires": { "plugin-error": "^1.0.1", @@ -6319,9 +6448,9 @@ } }, "gulp-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", - "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -6332,7 +6461,7 @@ "copy-props": "^2.0.1", "fancy-log": "^1.3.2", "gulplog": "^1.0.0", - "interpret": "^1.1.0", + "interpret": "^1.4.0", "isobject": "^3.0.1", "liftoff": "^3.1.0", "matchdep": "^2.0.0", @@ -6340,56 +6469,8 @@ "pretty-hrtime": "^1.0.0", "replace-homedir": "^1.0.0", "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", + "v8flags": "^3.2.0", "yargs": "^7.1.0" - }, - "dependencies": { - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "requires": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - } - }, - "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - } } }, "gulp-concat": { @@ -6424,7 +6505,7 @@ "vinyl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "integrity": "sha1-2FsH2pbkWNJbL/4Z/s6fLKoT7YY=", "dev": true, "requires": { "clone": "^2.1.1", @@ -6470,7 +6551,7 @@ "gulp-less": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/gulp-less/-/gulp-less-4.0.1.tgz", - "integrity": "sha512-hmM2k0FfQp7Ptm3ZaqO2CkMX3hqpiIOn4OHtuSsCeFym63F7oWlEua5v6u1cIjVUKYsVIs9zPg9vbqTEb/udpA==", + "integrity": "sha1-NIwzpd3nogfFdxsdgmHRrBAhzu0=", "dev": true, "requires": { "accord": "^0.29.0", @@ -6549,7 +6630,7 @@ "gulp-notify": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/gulp-notify/-/gulp-notify-3.2.0.tgz", - "integrity": "sha512-qEocs1UVoDKKUjfsxJNMNwkRla0PbsyJwsqNNXpzYWsLQ29LhxRMY3wnTGZcc4hMHtalnvah/Dwlwb4NijH/0A==", + "integrity": "sha1-KugiUAnfiB7vWb5d1aLxM3OHdk4=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -6635,7 +6716,7 @@ "gulp-postcss": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gulp-postcss/-/gulp-postcss-8.0.0.tgz", - "integrity": "sha512-Wtl6vH7a+8IS/fU5W9IbOpcaLqKxd5L1DUOzaPmlnCbX1CrG0aWdwVnC3Spn8th0m8D59YbysV5zPUe1n/GJYg==", + "integrity": "sha1-jTdyzU0nvKVeyMtMjlduO95NxVA=", "dev": true, "requires": { "fancy-log": "^1.3.2", @@ -6648,7 +6729,7 @@ "gulp-rename": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.4.0.tgz", - "integrity": "sha512-swzbIGb/arEoFK89tPY58vg3Ok1bw+d35PfUNwWqdo7KM4jkmuGA78JiDNqR+JeZFaeeHnRg9N7aihX3YPmsyg==", + "integrity": "sha1-3hxxjnxAla6GH3KW708ySGSCQL0=", "dev": true }, "gulp-sort": { @@ -6660,6 +6741,39 @@ "through2": "^2.0.1" } }, + "gulp-sourcemaps": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.5.tgz", + "integrity": "sha512-SYLBRzPTew8T5Suh2U8jCSDKY+4NARua4aqjj8HOysBh2tSgT9u4jc1FYirAdPx1akUxxDeK++fqw6Jg0LkQRg==", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", @@ -6716,7 +6830,7 @@ "gulp-watch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.1.tgz", - "integrity": "sha512-HnTSBdzAOFIT4wmXYPDUn783TaYAq9bpaN05vuZNP5eni3z3aRx0NAKbjhhMYtcq76x4R1wf4oORDGdlrEjuog==", + "integrity": "sha1-g9N4dS9b+0baAj5zwX7R2nBmIV0=", "dev": true, "requires": { "ansi-colors": "1.1.0", @@ -6771,7 +6885,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6792,7 +6906,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -6801,7 +6915,7 @@ "vinyl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "integrity": "sha1-2FsH2pbkWNJbL/4Z/s6fLKoT7YY=", "dev": true, "requires": { "clone": "^2.1.1", @@ -6927,7 +7041,7 @@ "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", "dev": true, "requires": { "ajv": "^6.5.5", @@ -6937,7 +7051,7 @@ "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", "dev": true, "requires": { "function-bind": "^1.1.1" @@ -6955,7 +7069,7 @@ "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "integrity": "sha1-d3asYn8+p3JQz8My2rfd9eT10R0=", "dev": true, "requires": { "isarray": "2.0.1" @@ -7045,20 +7159,10 @@ } } }, - "hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "dev": true, - "requires": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" - } - }, "hex-color-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "integrity": "sha1-TAb8y0YC/iYCs8k9+C1+fb8aio4=", "dev": true }, "homedir-polyfill": { @@ -7091,9 +7195,18 @@ "html-comment-regex": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "integrity": "sha1-l9RoiutcgYhqNk+qDK0d2hTUM6c=", "dev": true }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, "htmlparser2": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", @@ -7123,7 +7236,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -7138,7 +7251,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -7199,7 +7312,7 @@ "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -7215,7 +7328,7 @@ "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "integrity": "sha1-dQ49tYYgh7RzfrrIIH/9HvJ7Jfw=", "dev": true }, "image-size": { @@ -7382,7 +7495,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", "dev": true }, "inquirer": { @@ -7459,9 +7572,9 @@ } }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "into-stream": { @@ -7478,7 +7591,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -7490,6 +7603,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "irregular-plurals": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", @@ -7505,7 +7624,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -7556,13 +7675,13 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "integrity": "sha1-HhrfIZ4e62hNaR+dagX/DTCiTXU=", "dev": true }, "is-color-stop": { @@ -7608,7 +7727,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -7619,7 +7738,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", "dev": true } } @@ -7767,7 +7886,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "^3.0.1" @@ -7786,6 +7905,12 @@ "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", "dev": true }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", @@ -7810,7 +7935,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -7819,7 +7944,7 @@ "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "integrity": "sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg=", "dev": true }, "is-retry-allowed": { @@ -7833,12 +7958,13 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "dev": true, + "optional": true }, "is-svg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "integrity": "sha1-kyHb0pwhLlypnE+peUxxS8r6L3U=", "dev": true, "requires": { "html-comment-regex": "^1.1.0" @@ -7847,7 +7973,7 @@ "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "integrity": "sha1-oFX2rlcZLK7jKeeoYBGLSXqVDzg=", "dev": true, "requires": { "has-symbols": "^1.0.0" @@ -7862,7 +7988,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -7883,7 +8009,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", "dev": true }, "is-wsl": { @@ -7901,7 +8027,7 @@ "isbinaryfile": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", - "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "integrity": "sha1-XW3vPt6/boyoyunDAYOoBLX4voA=", "dev": true, "requires": { "buffer-alloc": "^1.2.0" @@ -7955,9 +8081,9 @@ } }, "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", + "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" }, "jquery-ui-dist": { "version": "1.12.1", @@ -7978,7 +8104,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=", "dev": true }, "js-yaml": { @@ -7997,10 +8123,129 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ws": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", + "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==", + "dev": true + } + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", "dev": true }, "json-buffer": { @@ -8013,7 +8258,7 @@ "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "integrity": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", "dev": true }, "json-schema": { @@ -8025,7 +8270,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8049,24 +8294,6 @@ "minimist": "^1.2.0" } }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true, - "optional": true - } - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8239,7 +8466,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "to-regex-range": { @@ -8256,12 +8483,18 @@ "karma-jasmine": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-2.0.1.tgz", - "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", + "integrity": "sha1-JuPjHy+vJy3YDrsOGJiRTMOhl2M=", "dev": true, "requires": { "jasmine-core": "^3.3" } }, + "karma-jsdom-launcher": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/karma-jsdom-launcher/-/karma-jsdom-launcher-8.0.2.tgz", + "integrity": "sha512-jxO+Nf9U/XNSnHXrNpxBbbMyeYuvJH1V++bRdZv20vJ9pvaLuQ6LFNIgn4hA1WAVmzMsvW9j0P2Q2hTLMWcSvw==", + "dev": true + }, "karma-junit-reporter": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz", @@ -8272,16 +8505,6 @@ "xmlbuilder": "12.0.0" } }, - "karma-phantomjs-launcher": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz", - "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=", - "dev": true, - "requires": { - "lodash": "^4.0.1", - "phantomjs-prebuilt": "^2.1.7" - } - }, "karma-spec-reporter": { "version": "0.0.32", "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.32.tgz", @@ -8291,12 +8514,6 @@ "colors": "^1.1.2" } }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true - }, "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -8310,31 +8527,13 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", "dev": true }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", - "dev": true, - "optional": true - } - } - }, "kuler": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", - "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "integrity": "sha1-73x4TzbJ+24W3TFQ0VJneysCKKY=", "dev": true, "requires": { "colornames": "^1.1.1" @@ -8379,7 +8578,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8394,7 +8593,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -8446,7 +8645,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true, "optional": true } @@ -8462,6 +8661,22 @@ "type-check": "~0.3.2" } }, + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -8502,9 +8717,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash._basecopy": { @@ -8641,6 +8856,12 @@ "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", @@ -8727,7 +8948,7 @@ "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -8747,7 +8968,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true, "optional": true }, @@ -8803,7 +9024,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -8847,6 +9068,20 @@ "micromatch": "^3.0.4", "resolve": "^1.4.0", "stack-trace": "0.0.10" + }, + "dependencies": { + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + } } }, "math-random": { @@ -8870,7 +9105,7 @@ "memoizee": { "version": "0.4.14", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", + "integrity": "sha1-B6APIEaZ+alcLZ53IYJxx81hDVc=", "dev": true, "requires": { "d": "1", @@ -8926,7 +9161,7 @@ "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -8969,7 +9204,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=", "dev": true }, "mimic-response": { @@ -8997,7 +9232,7 @@ "minimize": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/minimize/-/minimize-2.2.0.tgz", - "integrity": "sha512-IxR2XMbw9pXCxApkdD9BTcH2U4XlXhbeySUrv71rmMS9XDA8BVXEsIuFu24LtwCfBgfbL7Fuh8/ZzkO5DaTLlQ==", + "integrity": "sha1-ixZ28wBR2FmNdDZGvRJpCwdNpMM=", "dev": true, "requires": { "argh": "^0.1.4", @@ -9022,7 +9257,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -9070,7 +9305,7 @@ "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", + "integrity": "sha1-rLAwDrTeI6fd7sAU4+lgRLNHIzE=", "dev": true }, "mute-stream": { @@ -9089,7 +9324,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9131,7 +9366,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "integrity": "sha1-ozeKdpbOfSI+iPybdkvX7xCJ42Y=", "dev": true }, "node-notifier": { @@ -9196,13 +9431,13 @@ "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "integrity": "sha1-suHE3E98bVd0PfczpPWXjRhlBVk=", "dev": true }, "nouislider": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz", - "integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA==" + "version": "14.6.2", + "resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.6.2.tgz", + "integrity": "sha512-/lJeqJBghNAZS3P2VYrHzm1RM6YJPvvC/1wNpGaHBRX+05wpzUDafrW/ohAYp4kjKhRH8+BJ0vkorCHiMmgTMQ==" }, "now-and-later": { "version": "2.0.1", @@ -9214,9 +9449,9 @@ } }, "npm": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.13.0.tgz", - "integrity": "sha512-zjSJ8zjk0cDBZXqTWbQ6+qOdm1m2k489YDFP60RQRUhOxT5LOBhl+cDtFlEXEIblcNjofmsZ/qQ/wzmn5frimQ==", + "version": "6.14.9", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.9.tgz", + "integrity": "sha512-yHi1+i9LyAZF1gAmgyYtVk+HdABlLy94PMIDoK1TRKWvmFQAt5z3bodqVwKvzY0s6dLqQPVsRLiwhJfNtiHeCg==", "requires": { "JSONStream": "^1.3.5", "abbrev": "~1.1.1", @@ -9224,12 +9459,12 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.3", + "bin-links": "^1.1.8", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", "call-limit": "^1.1.1", - "chownr": "^1.1.3", + "chownr": "^1.1.4", "ci-info": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.5.1", @@ -9245,11 +9480,11 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.2.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", + "gentle-fs": "^2.3.1", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", "has-unicode": "~2.0.1", - "hosted-git-info": "^2.8.5", + "hosted-git-info": "^2.8.8", "iferr": "^1.0.2", "imurmurhash": "*", "infer-owner": "^1.0.4", @@ -9260,14 +9495,14 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.7", + "libcipm": "^4.0.8", "libnpm": "^3.0.1", "libnpmaccess": "^3.0.2", "libnpmhook": "^5.0.3", "libnpmorg": "^1.0.1", "libnpmsearch": "^2.0.2", "libnpmteam": "^1.0.2", - "libnpx": "^10.2.0", + "libnpx": "^10.2.4", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", "lodash._baseindexof": "*", @@ -9282,28 +9517,28 @@ "lodash.uniq": "~4.5.0", "lodash.without": "~4.4.0", "lru-cache": "^5.1.1", - "meant": "~1.0.1", + "meant": "^1.0.2", "mississippi": "^3.0.0", - "mkdirp": "~0.5.1", + "mkdirp": "^0.5.5", "move-concurrently": "^1.0.1", - "node-gyp": "^5.0.5", - "nopt": "~4.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.2", + "npm-audit-report": "^1.3.3", "npm-cache-filename": "~1.0.2", "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.4", + "npm-lifecycle": "^3.1.5", "npm-package-arg": "^6.1.1", - "npm-packlist": "^1.4.6", + "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", - "npm-profile": "^4.0.2", - "npm-registry-fetch": "^4.0.2", - "npm-user-validate": "~1.0.0", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.7", + "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "once": "~1.4.0", "opener": "^1.5.1", "osenv": "^0.1.5", - "pacote": "^9.5.9", + "pacote": "^9.5.12", "path-is-inside": "~1.0.2", "promise-inflight": "~1.0.1", "qrcode-terminal": "^0.12.0", @@ -9312,13 +9547,13 @@ "read": "~1.0.7", "read-cmd-shim": "^1.0.5", "read-installed": "~4.0.3", - "read-package-json": "^2.1.0", + "read-package-json": "^2.1.1", "read-package-tree": "^5.3.1", - "readable-stream": "^3.4.0", + "readable-stream": "^3.6.0", "readdir-scoped-modules": "^1.1.0", "request": "^2.88.0", "retry": "^0.12.0", - "rimraf": "^2.6.3", + "rimraf": "^2.7.1", "safe-buffer": "^5.1.2", "semver": "^5.7.1", "sha": "^3.0.0", @@ -9369,16 +9604,6 @@ "humanize-ms": "^1.2.1" } }, - "ajv": { - "version": "5.5.2", - "bundled": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, "ansi-align": { "version": "2.0.0", "bundled": true, @@ -9483,13 +9708,14 @@ } }, "bin-links": { - "version": "1.1.3", + "version": "1.1.8", "bundled": true, "requires": { "bluebird": "^3.5.3", "cmd-shim": "^3.0.0", - "gentle-fs": "^2.0.1", + "gentle-fs": "^2.3.0", "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", "write-file-atomic": "^2.3.0" } }, @@ -9581,7 +9807,7 @@ } }, "chownr": { - "version": "1.1.3", + "version": "1.1.4", "bundled": true }, "ci-info": { @@ -9617,23 +9843,36 @@ } }, "cliui": { - "version": "4.1.0", + "version": "5.0.0", "bundled": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", + "version": "4.1.0", "bundled": true }, - "strip-ansi": { - "version": "4.0.0", + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", "bundled": true, "requires": { - "ansi-regex": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" } } } @@ -9650,10 +9889,6 @@ "mkdirp": "~0.5.0" } }, - "co": { - "version": "4.6.0", - "bundled": true - }, "code-point-at": { "version": "1.1.0", "bundled": true @@ -9734,10 +9969,10 @@ } }, "configstore": { - "version": "3.1.2", + "version": "3.1.5", "bundled": true, "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", "make-dir": "^1.0.0", "unique-string": "^1.0.0", @@ -9846,7 +10081,7 @@ "bundled": true }, "deep-extend": { - "version": "0.5.1", + "version": "0.6.0", "bundled": true }, "defaults": { @@ -9888,7 +10123,7 @@ } }, "dot-prop": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true, "requires": { "is-obj": "^1.0.0" @@ -9947,6 +10182,10 @@ "version": "1.0.0", "bundled": true }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true + }, "encoding": { "version": "0.1.12", "bundled": true, @@ -9962,7 +10201,7 @@ } }, "env-paths": { - "version": "1.0.0", + "version": "2.2.0", "bundled": true }, "err-code": { @@ -10038,10 +10277,6 @@ "version": "1.3.0", "bundled": true }, - "fast-deep-equal": { - "version": "1.1.0", - "bundled": true - }, "fast-json-stable-stringify": { "version": "2.0.0", "bundled": true @@ -10054,13 +10289,6 @@ "version": "1.0.2", "bundled": true }, - "find-up": { - "version": "2.1.0", - "bundled": true, - "requires": { - "locate-path": "^2.0.0" - } - }, "flush-write-stream": { "version": "1.0.3", "bundled": true, @@ -10238,11 +10466,12 @@ "bundled": true }, "gentle-fs": { - "version": "2.2.1", + "version": "2.3.1", "bundled": true, "requires": { "aproba": "^1.1.2", "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", "fs-vacuum": "^1.2.10", "graceful-fs": "^4.1.11", "iferr": "^0.1.5", @@ -10264,7 +10493,7 @@ } }, "get-caller-file": { - "version": "1.0.2", + "version": "2.0.5", "bundled": true }, "get-stream": { @@ -10282,7 +10511,7 @@ } }, "glob": { - "version": "7.1.4", + "version": "7.1.6", "bundled": true, "requires": { "fs.realpath": "^1.0.0", @@ -10324,7 +10553,7 @@ } }, "graceful-fs": { - "version": "4.2.3", + "version": "4.2.4", "bundled": true }, "har-schema": { @@ -10332,11 +10561,31 @@ "bundled": true }, "har-validator": { - "version": "5.1.0", + "version": "5.1.5", "bundled": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "bundled": true + } } }, "has": { @@ -10359,7 +10608,7 @@ "bundled": true }, "hosted-git-info": { - "version": "2.8.5", + "version": "2.8.8", "bundled": true }, "http-cache-semantics": { @@ -10384,7 +10633,7 @@ } }, "https-proxy-agent": { - "version": "2.2.2", + "version": "2.2.4", "bundled": true, "requires": { "agent-base": "^4.3.0", @@ -10458,10 +10707,6 @@ "validate-npm-package-name": "^3.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "bundled": true - }, "ip": { "version": "1.1.5", "bundled": true @@ -10475,10 +10720,10 @@ "bundled": true }, "is-ci": { - "version": "1.1.0", + "version": "1.2.1", "bundled": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.5.0" }, "dependencies": { "ci-info": { @@ -10540,7 +10785,7 @@ } }, "is-retry-allowed": { - "version": "1.1.0", + "version": "1.2.0", "bundled": true }, "is-stream": { @@ -10583,10 +10828,6 @@ "version": "0.2.3", "bundled": true }, - "json-schema-traverse": { - "version": "0.3.1", - "bundled": true - }, "json-stringify-safe": { "version": "5.0.1", "bundled": true @@ -10616,15 +10857,8 @@ "version": "1.0.0", "bundled": true }, - "lcid": { - "version": "1.0.0", - "bundled": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "libcipm": { - "version": "4.0.7", + "version": "4.0.8", "bundled": true, "requires": { "bin-links": "^1.1.2", @@ -10633,7 +10867,7 @@ "find-npm-prefix": "^1.0.2", "graceful-fs": "^4.1.11", "ini": "^1.3.5", - "lock-verify": "^2.0.2", + "lock-verify": "^2.1.0", "mkdirp": "^0.5.1", "npm-lifecycle": "^3.0.0", "npm-logical-tree": "^1.2.1", @@ -10779,7 +11013,7 @@ } }, "libnpx": { - "version": "10.2.0", + "version": "10.2.4", "bundled": true, "requires": { "dotenv": "^5.0.1", @@ -10789,15 +11023,7 @@ "update-notifier": "^2.3.0", "which": "^1.3.0", "y18n": "^4.0.0", - "yargs": "^11.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "yargs": "^14.2.3" } }, "lock-verify": { @@ -10893,14 +11119,14 @@ } }, "make-fetch-happen": { - "version": "5.0.0", + "version": "5.0.2", "bundled": true, "requires": { "agentkeepalive": "^3.4.1", "cacache": "^12.0.0", "http-cache-semantics": "^3.8.1", "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", + "https-proxy-agent": "^2.2.3", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "node-fetch-npm": "^2.0.2", @@ -10910,16 +11136,9 @@ } }, "meant": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true }, - "mem": { - "version": "1.1.0", - "bundled": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "mime-db": { "version": "1.35.0", "bundled": true @@ -10931,10 +11150,6 @@ "mime-db": "~1.35.0" } }, - "mimic-fn": { - "version": "1.2.0", - "bundled": true - }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -10943,7 +11158,7 @@ } }, "minimist": { - "version": "0.0.8", + "version": "1.2.5", "bundled": true }, "minizlib": { @@ -10980,10 +11195,16 @@ } }, "mkdirp": { - "version": "0.5.1", + "version": "0.5.5", "bundled": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true + } } }, "move-concurrently": { @@ -11022,37 +11243,24 @@ } }, "node-gyp": { - "version": "5.0.5", + "version": "5.1.0", "bundled": true, "requires": { - "env-paths": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", "tar": "^4.4.12", - "which": "1" - }, - "dependencies": { - "nopt": { - "version": "3.0.6", - "bundled": true, - "requires": { - "abbrev": "1" - } - }, - "semver": { - "version": "5.3.0", - "bundled": true - } + "which": "^1.3.1" } }, "nopt": { - "version": "4.0.1", + "version": "4.0.3", "bundled": true, "requires": { "abbrev": "1", @@ -11079,7 +11287,7 @@ } }, "npm-audit-report": { - "version": "1.3.2", + "version": "1.3.3", "bundled": true, "requires": { "cli-table3": "^0.5.0", @@ -11087,8 +11295,11 @@ } }, "npm-bundled": { - "version": "1.0.6", - "bundled": true + "version": "1.1.1", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } }, "npm-cache-filename": { "version": "1.0.2", @@ -11102,7 +11313,7 @@ } }, "npm-lifecycle": { - "version": "3.1.4", + "version": "3.1.5", "bundled": true, "requires": { "byline": "^5.0.0", @@ -11119,6 +11330,10 @@ "version": "1.2.1", "bundled": true }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, "npm-package-arg": { "version": "6.1.1", "bundled": true, @@ -11130,11 +11345,12 @@ } }, "npm-packlist": { - "version": "1.4.6", + "version": "1.4.8", "bundled": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { @@ -11147,7 +11363,7 @@ } }, "npm-profile": { - "version": "4.0.2", + "version": "4.0.4", "bundled": true, "requires": { "aproba": "^1.1.2 || 2", @@ -11156,7 +11372,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.2", + "version": "4.0.7", "bundled": true, "requires": { "JSONStream": "^1.3.4", @@ -11169,7 +11385,7 @@ }, "dependencies": { "safe-buffer": { - "version": "5.2.0", + "version": "5.2.1", "bundled": true } } @@ -11182,7 +11398,7 @@ } }, "npm-user-validate": { - "version": "1.0.0", + "version": "1.0.1", "bundled": true }, "npmlog": { @@ -11234,15 +11450,6 @@ "version": "1.0.2", "bundled": true }, - "os-locale": { - "version": "2.1.0", - "bundled": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, "os-tmpdir": { "version": "1.0.2", "bundled": true @@ -11259,24 +11466,6 @@ "version": "1.0.0", "bundled": true }, - "p-limit": { - "version": "1.2.0", - "bundled": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "bundled": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "bundled": true - }, "package-json": { "version": "4.0.1", "bundled": true, @@ -11288,7 +11477,7 @@ } }, "pacote": { - "version": "9.5.9", + "version": "9.5.12", "bundled": true, "requires": { "bluebird": "^3.5.3", @@ -11305,6 +11494,7 @@ "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", "npm-pick-manifest": "^3.0.0", @@ -11500,19 +11690,13 @@ "bundled": true }, "rc": { - "version": "1.2.7", + "version": "1.2.8", "bundled": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true - } } }, "read": { @@ -11543,14 +11727,14 @@ } }, "read-package-json": { - "version": "2.1.0", + "version": "2.1.1", "bundled": true, "requires": { "glob": "^7.1.1", "graceful-fs": "^4.1.2", "json-parse-better-errors": "^1.0.1", "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "npm-normalize-package-bin": "^1.0.0" } }, "read-package-tree": { @@ -11563,7 +11747,7 @@ } }, "readable-stream": { - "version": "3.4.0", + "version": "3.6.0", "bundled": true, "requires": { "inherits": "^2.0.3", @@ -11582,7 +11766,7 @@ } }, "registry-auth-token": { - "version": "3.3.2", + "version": "3.4.0", "bundled": true, "requires": { "rc": "^1.1.6", @@ -11627,7 +11811,7 @@ "bundled": true }, "require-main-filename": { - "version": "1.0.1", + "version": "2.0.0", "bundled": true }, "resolve-from": { @@ -11639,7 +11823,7 @@ "bundled": true }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "requires": { "glob": "^7.1.3" @@ -11703,24 +11887,20 @@ "version": "3.0.2", "bundled": true }, - "slash": { - "version": "1.0.0", - "bundled": true - }, "slide": { "version": "1.1.6", "bundled": true }, "smart-buffer": { - "version": "4.0.2", + "version": "4.1.0", "bundled": true }, "socks": { - "version": "2.3.2", + "version": "2.3.3", "bundled": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "4.0.2" + "ip": "1.1.5", + "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { @@ -11801,7 +11981,7 @@ } }, "spdx-license-ids": { - "version": "3.0.3", + "version": "3.0.5", "bundled": true }, "split-on-first": { @@ -11902,10 +12082,16 @@ } }, "string_decoder": { - "version": "1.2.0", + "version": "1.3.0", "bundled": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } } }, "stringify-package": { @@ -12087,6 +12273,19 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.4.0", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "bundled": true + } + } + }, "url-parse-lax": { "version": "1.0.0", "bundled": true, @@ -12174,7 +12373,7 @@ } }, "widest-line": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "requires": { "string-width": "^2.1.1" @@ -12188,20 +12387,36 @@ } }, "wrap-ansi": { - "version": "2.1.0", + "version": "5.1.0", "bundled": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, "string-width": { - "version": "1.0.2", + "version": "3.1.0", "bundled": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" } } } @@ -12236,34 +12451,93 @@ "bundled": true }, "yargs": { - "version": "11.0.0", + "version": "14.2.3", "bundled": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" }, "dependencies": { - "y18n": { - "version": "3.2.1", + "ansi-regex": { + "version": "4.1.0", "bundled": true + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "9.0.2", + "version": "15.0.1", "bundled": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "bundled": true + } } } } @@ -12301,7 +12575,7 @@ "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "integrity": "sha1-sr0pXDfj3VijvwcAN2Zjuk2c8Fw=", "dev": true, "requires": { "boolbase": "~1.0.0" @@ -12319,10 +12593,16 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=", "dev": true }, "object-assign": { @@ -12771,6 +13051,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, "parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", @@ -12831,7 +13117,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "integrity": "sha1-1i27VnlAXXLEc37FhgDp3c8G0kw=", "dev": true }, "path-root": { @@ -12872,7 +13158,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "optional": true }, "performance-now": { "version": "2.1.0", @@ -12880,37 +13167,6 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, - "phantomjs-prebuilt": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", - "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3", - "extract-zip": "^1.6.5", - "fs-extra": "^1.0.0", - "hasha": "^2.2.0", - "kew": "^0.7.0", - "progress": "^1.1.8", - "request": "^2.81.0", - "request-progress": "^2.0.1", - "which": "^1.2.10" - }, - "dependencies": { - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - } - } - }, "picomatch": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", @@ -12941,7 +13197,7 @@ "plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "integrity": "sha1-dwFr2JGdCsN3/c3QMiMolTyleBw=", "dev": true, "requires": { "ansi-colors": "^1.0.1", @@ -12979,7 +13235,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true }, "supports-color": { @@ -12996,7 +13252,7 @@ "postcss-calc": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", - "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", + "integrity": "sha1-Ntd7qwI7Dsu5eJ2E3LI8SUEUVDY=", "dev": true, "requires": { "css-unit-converter": "^1.1.1", @@ -13021,7 +13277,7 @@ "postcss-convert-values": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "integrity": "sha1-yjgT7U2g+BL51DcDWE5Enr4Ymn8=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -13040,7 +13296,7 @@ "postcss-discard-duplicates": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "integrity": "sha1-P+EzzTyCKC5VD8myORdqkge3hOs=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13049,7 +13305,7 @@ "postcss-discard-empty": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "integrity": "sha1-yMlR6fc+2UKAGUWERKAq2Qu592U=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13058,7 +13314,7 @@ "postcss-discard-overridden": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "integrity": "sha1-ZSrvipZybwKfXj4AFG7npOdV/1c=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13116,7 +13372,7 @@ "postcss-minify-font-values": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "integrity": "sha1-zUw0TM5HQ0P6xdgiBqssvLiv1aY=", "dev": true, "requires": { "postcss": "^7.0.0", @@ -13177,7 +13433,7 @@ "postcss-normalize-charset": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "integrity": "sha1-izWt067oOhNrBHHg1ZvlilAoXdQ=", "dev": true, "requires": { "postcss": "^7.0.0" @@ -13243,7 +13499,7 @@ "postcss-normalize-unicode": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "integrity": "sha1-hBvUj9zzAZrUuqdJOj02O1KuHPs=", "dev": true, "requires": { "browserslist": "^4.0.0", @@ -13254,7 +13510,7 @@ "postcss-normalize-url": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "integrity": "sha1-EOQ3+GvHx+WPe5ZS7YeNqqlfquE=", "dev": true, "requires": { "is-absolute-url": "^2.0.0", @@ -13334,7 +13590,7 @@ "postcss-unique-selectors": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "integrity": "sha1-lEaRHzKJv9ZMbWgPBzwDsfnuS6w=", "dev": true, "requires": { "alphanum-sort": "^1.0.0", @@ -13345,7 +13601,7 @@ "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "integrity": "sha1-n/giVH4okyE88cMO+lGsX9G6goE=", "dev": true }, "prelude-ls": { @@ -13382,7 +13638,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", "dev": true }, "process-nextick-args": { @@ -13400,7 +13656,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "dev": true, "optional": true, "requires": { @@ -13457,7 +13713,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", "dev": true }, "q": { @@ -13469,7 +13725,7 @@ "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "integrity": "sha1-xF6cYYAL0IfviNfiVkI73Unl0HE=", "dev": true }, "qs": { @@ -13502,7 +13758,7 @@ "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "integrity": "sha1-t3bvxZN1mE42xTey9RofCv8Noe0=", "dev": true, "requires": { "is-number": "^4.0.0", @@ -13513,7 +13769,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "integrity": "sha1-ACbjf1RU1z41bf5lZGmYZ8an8P8=", "dev": true } } @@ -13572,7 +13828,7 @@ "readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -13595,7 +13851,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -13610,7 +13866,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -13641,7 +13897,7 @@ "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "integrity": "sha1-SoVuxLVuQHfFV1icroXnpMiGmhE=", "dev": true }, "regenerate-unicode-properties": { @@ -13665,7 +13921,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3" @@ -13674,7 +13930,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -13684,7 +13940,7 @@ "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "integrity": "sha1-jRnTHPYySCtYkEn4KB+T28uk0H8=", "dev": true }, "regexpu-core": { @@ -13754,7 +14010,7 @@ "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=", "dev": true }, "repeat-string": { @@ -13793,8 +14049,9 @@ "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "integrity": "sha1-nC/KT301tZLv5Xx/ClXoEFIST+8=", "dev": true, + "optional": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -13823,19 +14080,31 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.0.1" } } } }, - "request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "throttleit": "^1.0.0" + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "require-directory": { @@ -13919,7 +14188,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", "dev": true }, "reusify": { @@ -13982,7 +14251,7 @@ "run-sequence": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", - "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", + "integrity": "sha1-HOZD2jb9jH6n4akynaM/wriJhJU=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -14079,7 +14348,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", "dev": true }, "safe-regex": { @@ -14094,15 +14363,24 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=", "dev": true }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=", "dev": true }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "seek-bzip": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", @@ -14203,7 +14481,7 @@ "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "integrity": "sha1-1rkYHBpI05cyTISHHvvPxz/AZUs=", "dev": true }, "signal-exit": { @@ -14215,7 +14493,7 @@ "signalr": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/signalr/-/signalr-2.4.0.tgz", - "integrity": "sha512-GPJHb3pcNk3IUui5/WG8lMuarEn+Vpc8wEvJ60w0KQ43W9FHnJcuNcF8dkZePr81eBslzicsRdyEunKNF7KjZQ==", + "integrity": "sha1-kq8AjmtSetSzbpT7s0DhNQh6YNI=", "requires": { "jquery": ">=1.6.4" } @@ -14232,7 +14510,7 @@ "is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "integrity": "sha1-RXSirlb3qyBolvtDHq7tBm/fjwM=", "dev": true } } @@ -14265,7 +14543,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", "dev": true, "requires": { "base": "^0.11.1", @@ -14281,7 +14559,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "dev": true, "requires": { "ms": "2.0.0" @@ -14316,7 +14594,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", "dev": true, "requires": { "define-property": "^1.0.0", @@ -14336,7 +14614,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14345,7 +14623,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -14354,7 +14632,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -14367,7 +14645,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -14387,7 +14665,7 @@ "socket.io": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", - "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "integrity": "sha1-oGnF/qvuPmshSnW0DOBlLhz7mYA=", "dev": true, "requires": { "debug": "~3.1.0", @@ -14401,7 +14679,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14424,7 +14702,7 @@ "socket.io-client": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", - "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "integrity": "sha1-3LOBA0NqtFeN2wJmOK4vIbYjZx8=", "dev": true, "requires": { "backo2": "1.0.2", @@ -14452,7 +14730,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -14469,7 +14747,7 @@ "socket.io-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", - "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "integrity": "sha1-58Yii2qh+BTmFIrqMltRqpSZ4Hc=", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -14535,7 +14813,7 @@ "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", "dev": true, "requires": { "atob": "^2.1.1", @@ -14554,7 +14832,7 @@ "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "integrity": "sha1-AI22XtzmxQ7sDF4ijhlFBh3QQ3w=", "dev": true }, "spdx-correct": { @@ -14570,13 +14848,13 @@ "spdx-exceptions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "integrity": "sha1-LqRQrudPKom/uUUZwH/Nb0EyKXc=", "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -14589,15 +14867,15 @@ "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, - "spectrum-colorpicker": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/spectrum-colorpicker/-/spectrum-colorpicker-1.8.0.tgz", - "integrity": "sha1-uSbPUALAp3hgtfg1HhwJPGUgAQc=" + "spectrum-colorpicker2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/spectrum-colorpicker2/-/spectrum-colorpicker2-2.0.3.tgz", + "integrity": "sha512-uEmzdiPqSHgJ1sJvEiwsuYAt7ep/GltjWZ7yloMDFMPcr4qQmmwX4UqlFz7C2HxmmqA51jx51FfgiF65s7R3Pg==" }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -14671,7 +14949,7 @@ "stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "integrity": "sha1-g26zyDgv4pNv6vVEYxAXzn1Ho88=", "dev": true }, "stack-trace": { @@ -14707,6 +14985,12 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -14735,7 +15019,7 @@ "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", "dev": true, "requires": { "ms": "^2.1.1" @@ -14835,6 +15119,12 @@ "strip-bom": "^2.0.0" } }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", @@ -14871,7 +15161,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "optional": true, "requires": { @@ -14905,7 +15195,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14942,6 +15232,12 @@ "util.promisify": "~1.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -14991,7 +15287,7 @@ "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, "optional": true, "requires": { @@ -15014,7 +15310,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "optional": true, "requires": { @@ -15030,7 +15326,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "optional": true, "requires": { @@ -15060,7 +15356,7 @@ "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "integrity": "sha1-adycGxdEbueakr9biEu0uRJ1BvU=", "dev": true }, "text-table": { @@ -15069,12 +15365,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -15084,7 +15374,7 @@ "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", "dev": true, "requires": { "readable-stream": "~2.3.6", @@ -15100,7 +15390,7 @@ "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15115,7 +15405,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15158,7 +15448,7 @@ "timers-ext": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "integrity": "sha1-b1ethXjgej+5+R2Th9ZWR1VeJcY=", "dev": true, "requires": { "es5-ext": "~0.10.46", @@ -15177,14 +15467,14 @@ "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, "tinymce": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.2.tgz", - "integrity": "sha512-ZRoTGG4GAsOI73QPSNkabO7nkoYw9H6cglRB44W2mMkxSiqxYi8WJlgkUphk0fDqo6ZD6r3E+NSP4UHxF2lySg==" + "version": "4.9.11", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-4.9.11.tgz", + "integrity": "sha512-nkSLsax+VY5DBRjMFnHFqPwTnlLEGHCco82FwJF2JNH6W+5/ClvNC1P4uhD5lXPDNiDykSHR0XJdEh7w/ICHzA==" }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { "os-tmpdir": "~1.0.2" @@ -15209,7 +15499,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true, "optional": true }, @@ -15242,7 +15532,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", "dev": true, "requires": { "define-property": "^2.0.2", @@ -15279,7 +15569,7 @@ "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "integrity": "sha1-U/Nto/R3g7CSWvoG/587FlKA94E=", "dev": true, "requires": { "psl": "^1.1.24", @@ -15294,6 +15584,15 @@ } } }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", @@ -15328,7 +15627,6 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -15436,7 +15734,7 @@ "ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "integrity": "sha1-n+FTahCmZKZSZqHjzPhf02MCvJw=", "dev": true }, "unbzip2-stream": { @@ -15459,7 +15757,7 @@ "underscore": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + "integrity": "sha1-BtzjSg5op7q8KbNluOdLiSUgOWE=" }, "undertaker": { "version": "1.2.1", @@ -15487,13 +15785,13 @@ "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "integrity": "sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=", "dev": true }, "unicode-match-property-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "integrity": "sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=", "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^1.0.4", @@ -15619,7 +15917,7 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", "dev": true, "requires": { "punycode": "^2.1.0" @@ -15651,7 +15949,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", "dev": true }, "useragent": { @@ -15673,7 +15971,7 @@ "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "integrity": "sha1-RA9xZaRZyaFtwUXrjnLzVocJcDA=", "dev": true, "requires": { "define-properties": "^1.1.2", @@ -15698,10 +15996,19 @@ "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -15931,6 +16238,56 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", + "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, "when": { "version": "3.7.8", "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", @@ -15940,7 +16297,7 @@ "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { "isexe": "^2.0.0" @@ -15952,6 +16309,11 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, + "wicg-inert": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.0.tgz", + "integrity": "sha512-P0ZiWaN9SxOkJbYtF/PIwmIRO8UTqTJtyl33QTQlHfAb6h15T0Dp5m7WTJ8N6UWIoj+KU5M0a8EtfRZLlHiP0Q==" + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -15992,7 +16354,7 @@ "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "integrity": "sha1-8c+E/i1ekB686U767OeF8YeiKPI=", "dev": true, "requires": { "async-limiter": "~1.0.0", @@ -16000,12 +16362,24 @@ "ultron": "~1.1.0" } }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xmlbuilder": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-12.0.0.tgz", "integrity": "sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==", "dev": true }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", @@ -16031,9 +16405,9 @@ "dev": true }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", + "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", "dev": true, "requires": { "camelcase": "^3.0.0", @@ -16048,16 +16422,17 @@ "string-width": "^1.0.2", "which-module": "^1.0.0", "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "yargs-parser": "5.0.0-security.0" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "5.0.0-security.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", + "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", "dev": true, "requires": { - "camelcase": "^3.0.0" + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" } }, "yauzl": { From 358a8ec2aff6276c30456a64b4855172cb86b7c3 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 22 Dec 2020 16:36:07 +0100 Subject: [PATCH 09/88] Initial stage of getting rid of HttpResponseException and replacing it with the usage of ValidationErrorResult --- .../Controllers/UsersControllerUnitTests.cs | 7 +- .../Controllers/AuthenticationController.cs | 10 +-- .../Controllers/CodeFileController.cs | 22 ++++-- .../Controllers/ContentController.cs | 27 +++---- .../Controllers/ContentTypeController.cs | 36 +++++---- .../Controllers/CurrentUserController.cs | 10 +-- .../Controllers/DictionaryController.cs | 36 +++------ .../ExamineManagementController.cs | 17 ++-- .../Controllers/LanguageController.cs | 23 +++--- .../Controllers/LogViewerController.cs | 22 +++--- .../Controllers/MacrosController.cs | 44 +++++------ .../Controllers/MediaController.cs | 5 +- .../Controllers/MemberController.cs | 16 ++-- .../Controllers/PackageInstallController.cs | 18 ++--- .../Controllers/UsersController.cs | 79 ++++++++++--------- 15 files changed, 180 insertions(+), 192 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs index 4f4db85e5e..bc1fc26a57 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs @@ -1,10 +1,11 @@ using AutoFixture.NUnit3; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Moq; using NUnit.Framework; using Umbraco.Core.Security; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.BackOffice.Controllers; -using Umbraco.Web.Common.Exceptions; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers { @@ -23,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers .Setup(x => x.FindByIdAsync(It.IsAny())) .ReturnsAsync(user); - Assert.ThrowsAsync(() => sut.PostUnlockUsers(userIds)); + var result = sut.PostUnlockUsers(userIds).Result as ObjectResult; + Assert.AreEqual(StatusCodes.Status400BadRequest, result.StatusCode); } - } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 36e5c2b6fe..44eb747089 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; @@ -27,7 +26,6 @@ using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; @@ -151,7 +149,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (result.Succeeded == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Errors.ToErrorMessage()); + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Errors.ToErrorMessage()); } await _signInManager.SignOutAsync(); @@ -207,7 +205,7 @@ namespace Umbraco.Web.BackOffice.Controllers else { AddModelErrors(result); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } } @@ -350,7 +348,7 @@ namespace Umbraco.Web.BackOffice.Controllers // by our angular helper because it thinks that we need to re-perform the request once we are // authorized and we don't want to return a 403 because angular will show a warning message indicating // that the user doesn't have access to perform this function, we just want to return a normal invalid message. - throw new HttpResponseException(HttpStatusCode.BadRequest); + return new ValidationErrorResult(null); } /// @@ -468,7 +466,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ModelState.IsValid == false) { - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index d1feaf11e9..a945ad3b2b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -21,7 +22,6 @@ using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; using Stylesheet = Umbraco.Core.Models.Stylesheet; using StylesheetRule = Umbraco.Web.Models.ContentEditing.StylesheetRule; @@ -84,13 +84,19 @@ namespace Umbraco.Web.BackOffice.Controllers var view = new PartialView(PartialViewType.PartialView, display.VirtualPath); view.Content = display.Content; var result = _fileService.CreatePartialView(view, display.Snippet, currentUser.Id); - return result.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(); + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); case Core.Constants.Trees.PartialViewMacros: var viewMacro = new PartialView(PartialViewType.PartialViewMacro, display.VirtualPath); viewMacro.Content = display.Content; var resultMacro = _fileService.CreatePartialViewMacro(viewMacro, display.Snippet, currentUser.Id); - return resultMacro.Success == true ? Ok() : throw HttpResponseException.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message); + if (resultMacro.Success) + return Ok(); + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(resultMacro.Exception.Message); case Core.Constants.Trees.Scripts: var script = new Script(display.VirtualPath); @@ -116,7 +122,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (string.IsNullOrWhiteSpace(parentId)) throw new ArgumentException("Value cannot be null or whitespace.", "parentId"); if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", "name"); if (name.ContainsAny(Path.GetInvalidPathChars())) { - throw HttpResponseException.CreateNotificationValidationErrorResponse(_localizedTextService.Localize("codefile/createFolderIllegalChars")); + return ValidationErrorResult.CreateNotificationValidationErrorResult(_localizedTextService.Localize("codefile/createFolderIllegalChars")); } // if the parentId is root (-1) then we just need an empty string as we are @@ -233,7 +239,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// This is a string but will be 'partialViews', 'partialViewMacros' /// Returns a list of if a correct type is sent - public IEnumerable GetSnippets(string type) + public ActionResult> GetSnippets(string type) { if (string.IsNullOrWhiteSpace(type)) throw new ArgumentException("Value cannot be null or whitespace.", "type"); @@ -252,10 +258,10 @@ namespace Umbraco.Web.BackOffice.Controllers snippets = _fileService.GetPartialViewSnippetNames(); break; default: - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(type, StatusCodes.Status404NotFound); } - return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet}); + return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet}).ToList(); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index b6dcdce14d..4b464388c2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Net; using System.Net.Mime; using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -14,7 +16,6 @@ using Umbraco.Core.Events; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.ContentEditing; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Validation; using Umbraco.Core.Persistence; @@ -24,23 +25,21 @@ using Umbraco.Core.Security; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Actions; -using Umbraco.Web.ContentApps; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Routing; -using Constants = Umbraco.Core.Constants; using Umbraco.Extensions; +using Umbraco.Web.Actions; +using Umbraco.Web.BackOffice.ActionResults; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; -using Umbraco.Web.BackOffice.ActionResults; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Common.Filters; -using Umbraco.Web.Models.Mapping; -using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.BackOffice.Authorization; -using System.Threading.Tasks; +using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.ContentApps; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Routing; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -1603,7 +1602,7 @@ namespace Umbraco.Web.BackOffice.Controllers { _logger.LogWarning("Content sorting failed, this was probably caused by an event being cancelled"); // TODO: Now you can cancel sorting, does the event messages bubble up automatically? - throw HttpResponseException.CreateValidationErrorResponse("Content sorting failed, this was probably caused by an event being cancelled"); + return new ValidationErrorResult("Content sorting failed, this was probably caused by an event being cancelled"); } return Ok(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 05e4db5daa..e6fc284498 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -21,9 +21,9 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -118,12 +118,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] - public DocumentTypeDisplay GetById(int id) + public ActionResult GetById(int id) { var ct = _contentTypeService.Get(id); if (ct == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ct, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(ct); @@ -137,12 +137,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] - public DocumentTypeDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var contentType = _contentTypeService.Get(id); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(contentType); @@ -156,16 +156,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] - public DocumentTypeDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var contentType = _contentTypeService.Get(guidUdi.Guid); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(contentType); @@ -185,7 +185,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _contentTypeService.Get(id); if (foundType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); } _contentTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -263,13 +263,13 @@ namespace Umbraco.Web.BackOffice.Controllers } [Authorize(Policy = AuthorizationPolicies.TreeAccessAnyContentOrTypes)] - public ContentPropertyDisplay GetPropertyTypeScaffold(int id) + public ActionResult GetPropertyTypeScaffold(int id) { var dataTypeDiff = _dataTypeService.GetDataType(id); if (dataTypeDiff == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dataTypeDiff, StatusCodes.Status404NotFound); } var configuration = _dataTypeService.GetDataType(id).Configuration; @@ -304,9 +304,10 @@ namespace Umbraco.Web.BackOffice.Controllers { var result = _contentTypeService.CreateContainer(parentId, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - return result - ? Ok(result.Result) //return the id - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); //return the id + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] @@ -314,9 +315,10 @@ namespace Umbraco.Web.BackOffice.Controllers { var result = _contentTypeService.RenameContainer(id, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - return result - ? Ok(result.Result) //return the id - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); //return the id + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs index d156551c26..2c64cdca49 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs @@ -22,9 +22,9 @@ using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -171,7 +171,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// This only works when the user is logged in (partially) /// [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] // TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController - public async Task PostSetInvitedUserPassword([FromBody]string newPassword) + public async Task> PostSetInvitedUserPassword([FromBody]string newPassword) { var user = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString()); if (user == null) throw new InvalidOperationException("Could not find user"); @@ -184,7 +184,7 @@ namespace Umbraco.Web.BackOffice.Controllers // so that is why it is being used here. ModelState.AddModelError("value", result.Errors.ToErrorMessage()); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } //They've successfully set their password, we can now update their user account to be approved @@ -214,7 +214,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// If the password is being reset it will return the newly reset password, otherwise will return an empty value /// - public async Task> PostChangePassword(ChangingPasswordModel data) + public async Task>> PostChangePassword(ChangingPasswordModel data) { // TODO: Why don't we inject this? Then we can just inject a logger var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger()); @@ -233,7 +233,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } // TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index c7f86e12a1..94ada7e3aa 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -1,24 +1,21 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers @@ -101,7 +98,7 @@ namespace Umbraco.Web.BackOffice.Controllers public ActionResult Create(int parentId, string key) { if (string.IsNullOrEmpty(key)) - throw HttpResponseException.CreateNotificationValidationErrorResponse("Key can not be empty."); // TODO: translate + return ValidationErrorResult.CreateNotificationValidationErrorResult("Key can not be empty."); // TODO: translate if (_localizationService.DictionaryItemExists(key)) { @@ -109,7 +106,7 @@ namespace Umbraco.Web.BackOffice.Controllers "dictionaryItem/changeKeyError", _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserCulture(_localizedTextService, _globalSettings), new Dictionary { { "0", key } }); - throw HttpResponseException.CreateNotificationValidationErrorResponse(message); + return ValidationErrorResult.CreateNotificationValidationErrorResult(message); } try @@ -130,7 +127,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { _logger.LogError(ex, "Error creating dictionary with {Name} under {ParentId}", key, parentId); - throw HttpResponseException.CreateNotificationValidationErrorResponse("Error creating dictionary item"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Error creating dictionary item"); } } @@ -141,11 +138,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// The id. /// /// - /// The . + /// The . Returns a not found response when dictionary item does not exist /// - /// - /// Returns a not found response when dictionary item does not exist - /// [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(int id) { @@ -163,11 +157,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// The id. /// /// - /// The . + /// The . Returns a not found response when dictionary item does not exist /// - /// - /// Returns a not found response when dictionary item does not exist - /// [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Guid id) { @@ -185,11 +176,8 @@ namespace Umbraco.Web.BackOffice.Controllers /// The id. /// /// - /// The . + /// The . Returns a not found response when dictionary item does not exist /// - /// - /// Returns a not found response when dictionary item does not exist - /// [DetermineAmbiguousActionByPassingParameters] public ActionResult GetById(Udi id) { @@ -213,13 +201,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The . /// - public DictionaryDisplay PostSave(DictionarySave dictionary) + public ActionResult PostSave(DictionarySave dictionary) { var dictionaryItem = _localizationService.GetDictionaryItemById(int.Parse(dictionary.Id.ToString())); if (dictionaryItem == null) - throw HttpResponseException.CreateNotificationValidationErrorResponse("Dictionary item does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Dictionary item does not exist"); var userCulture = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserCulture(_localizedTextService, _globalSettings); @@ -236,7 +224,7 @@ namespace Umbraco.Web.BackOffice.Controllers userCulture, new Dictionary { { "0", dictionary.Name } }); ModelState.AddModelError("Name", message); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } dictionaryItem.ItemKey = dictionary.Name; @@ -263,7 +251,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { _logger.LogError(ex, "Error saving dictionary with {Name} under {ParentId}", dictionary.Name, dictionary.ParentId); - throw HttpResponseException.CreateNotificationValidationErrorResponse("Something went wrong saving dictionary"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Something went wrong saving dictionary"); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index 72f07c02f3..c692f45ac2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Examine; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -10,8 +9,8 @@ using Umbraco.Core.Cache; using Umbraco.Core.IO; using Umbraco.Examine; using Umbraco.Extensions; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Search; using SearchResult = Umbraco.Web.Models.ContentEditing.SearchResult; @@ -62,14 +61,14 @@ namespace Umbraco.Web.BackOffice.Controllers return model; } - public SearchResults GetSearchResults(string searcherName, string query, int pageIndex = 0, int pageSize = 20) + public ActionResult GetSearchResults(string searcherName, string query, int pageIndex = 0, int pageSize = 20) { if (query.IsNullOrWhiteSpace()) return SearchResults.Empty(); var msg = ValidateSearcher(searcherName, out var searcher); if (!msg.IsSuccessStatusCode()) - throw new HttpResponseException(msg); + return new ValidationErrorResult(msg); // NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work. var results = searcher.CreateQuery().NativeQuery(query).Execute(maxResults: pageSize * (pageIndex + 1)); @@ -105,11 +104,11 @@ namespace Umbraco.Web.BackOffice.Controllers var validate = ValidateIndex(indexName, out var index); if (!validate.IsSuccessStatusCode()) - throw new HttpResponseException(validate); + return new ValidationErrorResult(validate); validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode()) - throw new HttpResponseException(validate); + return new ValidationErrorResult(validate); var cacheKey = "temp_indexing_op_" + indexName; var found = _runtimeCache.Get(cacheKey); @@ -130,11 +129,11 @@ namespace Umbraco.Web.BackOffice.Controllers { var validate = ValidateIndex(indexName, out var index); if (!validate.IsSuccessStatusCode()) - throw new HttpResponseException(validate); + return new ValidationErrorResult(validate); validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode()) - throw new HttpResponseException(validate); + return new ValidationErrorResult(validate); _logger.LogInformation("Rebuilding index '{IndexName}'", indexName); diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 21b205de0f..4ac9209dd9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -6,16 +6,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Editors; using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.BackOffice.Controllers @@ -97,7 +94,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (language.IsDefault) { var message = $"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted."; - throw HttpResponseException.CreateNotificationValidationErrorResponse(message); + return ValidationErrorResult.CreateNotificationValidationErrorResult(message); } // service is happy deleting a language that's fallback for another language, @@ -113,10 +110,10 @@ namespace Umbraco.Web.BackOffice.Controllers /// [Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)] [HttpPost] - public Language SaveLanguage(Language language) + public ActionResult SaveLanguage(Language language) { if (!ModelState.IsValid) - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); // this is prone to race conditions but the service will not let us proceed anyways var existingByCulture = _localizationService.GetLanguageByIsoCode(language.IsoCode); @@ -132,7 +129,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //someone is trying to create a language that already exist ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } var existingById = language.Id != default ? _localizationService.GetLanguageById(language.Id) : null; @@ -149,7 +146,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (CultureNotFoundException) { ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } // create it (creating a new language cannot create a fallback cycle) @@ -172,7 +169,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (existingById.IsDefault && !language.IsDefault) { ModelState.AddModelError("IsDefault", "Cannot un-default the default language."); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } existingById.IsDefault = language.IsDefault; @@ -187,12 +184,12 @@ namespace Umbraco.Web.BackOffice.Controllers if (!languages.ContainsKey(existingById.FallbackLanguageId.Value)) { ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist."); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } if (CreatesCycle(existingById, languages)) { ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existingById.FallbackLanguageId.Value].IsoCode} would create a circular path."); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs index 5165d3a092..d77f76a4b2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LogViewerController.cs @@ -1,13 +1,13 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Logging.Viewer; using Umbraco.Core.Models; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; namespace Umbraco.Web.BackOffice.Controllers { @@ -44,53 +44,53 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpGet] - public int GetNumberOfErrors([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) + public ActionResult GetNumberOfErrors([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size"); } return _logViewer.GetNumberOfErrors(logTimePeriod); } [HttpGet] - public LogLevelCounts GetLogLevelCounts([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) + public ActionResult GetLogLevelCounts([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size"); } return _logViewer.GetLogLevelCounts(logTimePeriod); } [HttpGet] - public IEnumerable GetMessageTemplates([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) + public ActionResult> GetMessageTemplates([FromQuery] DateTime? startDate = null,[FromQuery] DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size"); } - return _logViewer.GetMessageTemplates(logTimePeriod); + return new ActionResult>(_logViewer.GetMessageTemplates(logTimePeriod)); } [HttpGet] - public PagedResult GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromQuery(Name = "logLevels[]")]string[] logLevels = null, [FromQuery]DateTime? startDate = null, [FromQuery]DateTime? endDate = null) + public ActionResult> GetLogs(string orderDirection = "Descending", int pageNumber = 1, string filterExpression = null, [FromQuery(Name = "logLevels[]")]string[] logLevels = null, [FromQuery]DateTime? startDate = null, [FromQuery]DateTime? endDate = null) { var logTimePeriod = GetTimePeriod(startDate, endDate); //We will need to stop the request if trying to do this on a 1GB file if (CanViewLogs(logTimePeriod) == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Unable to view logs, due to size"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Unable to view logs, due to size"); } var direction = orderDirection == "Descending" ? Direction.Descending : Direction.Ascending; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs index 3ca89fa5ff..598fe15bf4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Services; +using Umbraco.Core.Services; using System; using System.Collections.Generic; using System.IO; @@ -12,14 +12,12 @@ using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; using Umbraco.Core.PropertyEditors; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Security; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Security; using Microsoft.AspNetCore.Authorization; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers @@ -74,19 +72,19 @@ namespace Umbraco.Web.BackOffice.Controllers { if (string.IsNullOrWhiteSpace(name)) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Name can not be empty"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Name can not be empty"); } var alias = name.ToSafeAlias(_shortStringHelper); if (_macroService.GetByAlias(alias) != null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Macro with this alias already exists"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists"); } if (name == null || name.Length > 255) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Name cannnot be more than 255 characters in length."); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length."); } try @@ -106,19 +104,19 @@ namespace Umbraco.Web.BackOffice.Controllers { const string errorMessage = "Error creating macro"; _logger.LogError(exception, errorMessage); - throw HttpResponseException.CreateNotificationValidationErrorResponse(errorMessage); + return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage); } } [HttpGet] [DetermineAmbiguousActionByPassingParameters] - public MacroDisplay GetById(int id) + public ActionResult GetById(int id) { var macro = _macroService.GetById(id); if (macro == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist"); } var macroDisplay = MapToDisplay(macro); @@ -128,13 +126,13 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [DetermineAmbiguousActionByPassingParameters] - public MacroDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var macro = _macroService.GetById(id); if (macro == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist"); } var macroDisplay = MapToDisplay(macro); @@ -144,16 +142,16 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [DetermineAmbiguousActionByPassingParameters] - public MacroDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist"); var macro = _macroService.GetById(guidUdi.Guid); if (macro == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist"); } var macroDisplay = MapToDisplay(macro); @@ -162,13 +160,13 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] - public OkResult DeleteById(int id) + public ActionResult DeleteById(int id) { var macro = _macroService.GetById(id); if (macro == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {id} does not exist"); } _macroService.Delete(macro); @@ -177,23 +175,23 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] - public MacroDisplay Save(MacroDisplay macroDisplay) + public ActionResult Save(MacroDisplay macroDisplay) { if (macroDisplay == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"No macro data found in request"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("No macro data found in request"); } if (macroDisplay.Name == null || macroDisplay.Name.Length > 255) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Name cannnot be more than 255 characters in length."); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Name cannnot be more than 255 characters in length."); } var macro = _macroService.GetById(int.Parse(macroDisplay.Id.ToString())); if (macro == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with id {macroDisplay.Id} does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult($"Macro with id {macroDisplay.Id} does not exist"); } if (macroDisplay.Alias != macro.Alias) @@ -202,7 +200,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (macroByAlias != null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse($"Macro with this alias already exists"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Macro with this alias already exists"); } } @@ -230,7 +228,7 @@ namespace Umbraco.Web.BackOffice.Controllers { const string errorMessage = "Error creating macro"; _logger.LogError(exception, errorMessage); - throw HttpResponseException.CreateNotificationValidationErrorResponse(errorMessage); + return ValidationErrorResult.CreateNotificationValidationErrorResult(errorMessage); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 67cd41ff29..465c1e06bf 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -36,6 +36,7 @@ using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; @@ -649,7 +650,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (_mediaService.Sort(sortedMedia) == false) { _logger.LogWarning("Media sorting failed, this was probably caused by an event being cancelled"); - throw HttpResponseException.CreateValidationErrorResponse("Media sorting failed, this was probably caused by an event being cancelled"); + return new ValidationErrorResult("Media sorting failed, this was probably caused by an event being cancelled"); } return Ok(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 267539c97f..47487d6c5b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -8,6 +8,7 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -27,13 +28,12 @@ using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ModelBinders; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers @@ -168,18 +168,18 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - public MemberDisplay GetEmpty(string contentTypeAlias = null) + public ActionResult GetEmpty(string contentTypeAlias = null) { IMember emptyContent; if (contentTypeAlias == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentTypeAlias, StatusCodes.Status404NotFound); } var contentType = _memberTypeService.Get(contentTypeAlias); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } var passwordGenerator = new PasswordGenerator(_passwordConfig); @@ -218,7 +218,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); - throw HttpResponseException.CreateValidationErrorResponse(forDisplay); + return new ValidationErrorResult(forDisplay); } //We're gonna look up the current roles now because the below code can cause @@ -241,7 +241,7 @@ namespace Umbraco.Web.BackOffice.Controllers break; default: //we don't support anything else for members - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentItem.Action, StatusCodes.Status404NotFound); } //TODO: There's 3 things saved here and we should do this all in one transaction, which we can do here by wrapping in a scope diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 961ec388f7..e9ed78cdb6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -16,14 +16,12 @@ using Umbraco.Core.Packaging; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.WebAssets; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Security; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Common.ActionsResults; namespace Umbraco.Web.BackOffice.Controllers { @@ -190,7 +188,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (installType == PackageInstallType.AlreadyInstalled) { //this package is already installed - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("packager/packageAlreadyInstalled")); } @@ -217,7 +215,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpGet] - public async Task Fetch(string packageGuid) + public async Task> Fetch(string packageGuid) { //Default path string fileName = packageGuid + ".umb"; @@ -244,7 +242,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (installType == PackageInstallType.AlreadyInstalled) { - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("packager/packageAlreadyInstalled")); } @@ -259,7 +257,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [HttpPost] - public PackageInstallModel Import(PackageInstallModel model) + public ActionResult Import(PackageInstallModel model) { var zipFile = new FileInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Packages), model.ZipFileName)); @@ -270,7 +268,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var packageMinVersion = packageInfo.UmbracoVersion; if (_umbracoVersion.Current < packageMinVersion) - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("packager/targetVersionMismatch", new[] {packageMinVersion.ToString()})); } @@ -289,7 +287,7 @@ namespace Umbraco.Web.BackOffice.Controllers //save to the installedPackages.config, this will create a new entry with a new Id if (!_packagingService.SaveInstalledPackage(packageDefinition)) - throw HttpResponseException.CreateNotificationValidationErrorResponse("Could not save the package"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Could not save the package"); model.Id = packageDefinition.Id; break; diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 9d7999b9f7..844db81c03 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -34,7 +34,6 @@ using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; @@ -121,11 +120,11 @@ namespace Umbraco.Web.BackOffice.Controllers /// Returns a list of the sizes of gravatar URLs for the user or null if the gravatar server cannot be reached /// /// - public string[] GetCurrentUserAvatarUrls() + public ActionResult GetCurrentUserAvatarUrls() { var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator); if (urls == null) - throw new HttpResponseException(HttpStatusCode.BadRequest, "Could not access Gravatar endpoint"); + return new ValidationErrorResult("Could not access Gravatar endpoint"); return urls; } @@ -141,7 +140,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (files is null) { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + return new ValidationErrorResult(files, StatusCodes.Status415UnsupportedMediaType); } var root = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads); @@ -159,7 +158,7 @@ namespace Umbraco.Web.BackOffice.Controllers return new NotFoundResult(); if (files.Count > 1) - throw HttpResponseException.CreateValidationErrorResponse("The request was not formatted correctly, only one file can be attached to the request"); + return new ValidationErrorResult("The request was not formatted correctly, only one file can be attached to the request"); //get the file info var file = files.First(); @@ -224,12 +223,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] - public UserDisplay GetById(int id) + public ActionResult GetById(int id) { var user = _userService.GetUserById(id); if (user == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(user, StatusCodes.Status404NotFound); } var result = _umbracoMapper.Map(user); return result; @@ -242,20 +241,20 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] - public IEnumerable GetByIds([FromJsonPath]int[] ids) + public ActionResult> GetByIds([FromJsonPath]int[] ids) { if (ids == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); } if (ids.Length == 0) - return Enumerable.Empty(); + return Enumerable.Empty().ToList(); var users = _userService.GetUsersById(ids); if (users == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(users, StatusCodes.Status404NotFound); } var result = _umbracoMapper.MapEnumerable(users); @@ -336,13 +335,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public async Task PostCreateUser(UserInvite userSave) + public async Task> PostCreateUser(UserInvite userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); if (ModelState.IsValid == false) { - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); } if (_securitySettings.UsernameIsEmail) @@ -361,7 +360,7 @@ namespace Umbraco.Web.BackOffice.Controllers var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { - throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result); + return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized); } //we want to create the user with the UserManager, this ensures the 'empty' (special) password @@ -372,7 +371,7 @@ namespace Umbraco.Web.BackOffice.Controllers var created = await _userManager.CreateAsync(identityUser); if (created.Succeeded == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage()); + return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage()); } string resetPassword; @@ -381,7 +380,7 @@ namespace Umbraco.Web.BackOffice.Controllers var result = await _userManager.AddPasswordAsync(identityUser, password); if (result.Succeeded == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse(created.Errors.ToErrorMessage()); + return ValidationErrorResult.CreateNotificationValidationErrorResult(created.Errors.ToErrorMessage()); } resetPassword = password; @@ -431,9 +430,9 @@ namespace Umbraco.Web.BackOffice.Controllers else { //first validate the username if we're showing it - user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue).Value; } - user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue).Value; if (!EmailSender.CanSendRequiredEmail(_globalSettings) && !_userManager.HasSendingUserInviteEventHandler) { @@ -512,18 +511,19 @@ namespace Umbraco.Web.BackOffice.Controllers return display; } - private IUser CheckUniqueEmail(string email, Func extraCheck) + private ActionResult CheckUniqueEmail(string email, Func extraCheck) { var user = _userService.GetByEmail(email); if (user != null && (extraCheck == null || extraCheck(user))) { ModelState.AddModelError("Email", "A user with the email already exists"); - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); } - return user; + + return new ActionResult(user); } - private IUser CheckUniqueUsername(string username, Func extraCheck) + private ActionResult CheckUniqueUsername(string username, Func extraCheck) { var user = _userService.GetByUsername(username); if (user != null && (extraCheck == null || extraCheck(user))) @@ -531,9 +531,10 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError( _securitySettings.UsernameIsEmail ? "Email" : "Username", "A user with the username already exists"); - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); } - return user; + + return new ActionResult(user); } private async Task SendUserInviteEmailAsync(UserBasic userDisplay, string from, string fromEmail, IUser to, string message) @@ -576,28 +577,29 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - public UserDisplay PostSaveUser(UserSave userSave) + public ActionResult PostSaveUser(UserSave userSave) { if (userSave == null) throw new ArgumentNullException(nameof(userSave)); if (ModelState.IsValid == false) { - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); } var intId = userSave.Id.TryConvertTo(); if (intId.Success == false) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(intId, StatusCodes.Status404NotFound); + var found = _userService.GetUserById(intId.Result); if (found == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(found, StatusCodes.Status404NotFound); //Perform authorization here to see if the current user can actually save this user with the info being requested var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { - throw new HttpResponseException(HttpStatusCode.Unauthorized, canSaveUser.Result); + return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized); } var hasErrors = false; @@ -644,7 +646,7 @@ namespace Umbraco.Web.BackOffice.Controllers } if (hasErrors) - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); //merge the save data onto the user var user = _umbracoMapper.Map(userSave, found); @@ -662,25 +664,25 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public async Task> PostChangePassword(ChangingPasswordModel changingPasswordModel) + public async Task>> PostChangePassword(ChangingPasswordModel changingPasswordModel) { changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(ModelState); } var intId = changingPasswordModel.Id.TryConvertTo(); if (intId.Success == false) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(intId, StatusCodes.Status404NotFound); } var found = _userService.GetUserById(intId.Result); if (found == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(found, StatusCodes.Status404NotFound); } // TODO: Why don't we inject this? Then we can just inject a logger @@ -699,7 +701,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } @@ -713,7 +715,7 @@ namespace Umbraco.Web.BackOffice.Controllers var tryGetCurrentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId(); if (tryGetCurrentUserId && userIds.Contains(tryGetCurrentUserId.Result)) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("The current user cannot disable itself"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("The current user cannot disable itself"); } var users = _userService.GetUsersById(userIds).ToArray(); @@ -780,8 +782,8 @@ namespace Umbraco.Web.BackOffice.Controllers var unlockResult = await _userManager.SetLockoutEndDateAsync(user, DateTimeOffset.Now); if (unlockResult.Succeeded == false) { - throw HttpResponseException.CreateValidationErrorResponse( - string.Format("Could not unlock for user {0} - error {1}", u, unlockResult.Errors.ToErrorMessage())); + return new ValidationErrorResult( + $"Could not unlock for user {u} - error {unlockResult.Errors.ToErrorMessage()}"); } if (userIds.Length == 1) @@ -857,6 +859,5 @@ namespace Umbraco.Web.BackOffice.Controllers [DataMember(Name = "userStates")] public IDictionary UserStates { get; set; } } - } } From b20ce5a92ea8adcc2b134679ca0c7aae8225495f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 09:50:05 +1100 Subject: [PATCH 10/88] A bunch of cleanup for DI namespaces, ext methods and dist cache classes --- .../Cache/DistributedCacheBinderComponent.cs | 22 -- .../Composing/CollectionBuilderBase.cs | 3 +- .../Composing/CompositionExtensions.cs | 3 +- .../Composing/TypeCollectionBuilderBase.cs | 1 + .../ServiceCollectionExtensions.cs | 9 +- .../ServiceProviderExtensions.cs | 8 +- .../UmbracoBuilder.Collections.cs} | 20 +- .../DependencyInjection/UmbracoBuilder.cs | 29 +- src/Umbraco.Core/IO/FileSystems.cs | 1 + .../Sync/IBatchedDatabaseServerMessenger.cs | 6 +- .../Sync/IDatabaseServerMessenger.cs | 7 - src/Umbraco.Core/Sync/IServerMessenger.cs | 10 +- src/Umbraco.Core/Sync/IServerRegistrar.cs | 4 +- .../ExamineLuceneComposer.cs | 1 - .../BatchedDatabaseServerMessenger.cs | 28 +- ...abaseServerMessengerNotificationHandler.cs | 93 +++++ .../Cache/DistributedCacheBinderComposer.cs | 20 -- ...aseServerRegistrarAndMessengerComponent.cs | 121 ------- .../Compose/NotificationsComposer.cs | 3 +- .../CompositionExtensions/FileSystems.cs | 1 + .../CompositionExtensions/Installer.cs | 1 - .../CompositionExtensions.cs | 335 ------------------ .../UmbracoBuilder.Collections.cs | 96 +++++ .../UmbracoBuilder.CoreServices.cs} | 37 +- .../UmbracoBuilder.DistributedCache.cs | 138 ++++++++ .../UmbracoBuilder.Uniques.cs | 157 ++++++++ .../InstructionProcessTask.cs | 4 +- .../Logging/Serilog/SerilogComposer.cs | 1 - .../Logging/Viewer/LogViewerComposer.cs | 1 + .../Migrations/MigrationBuilder.cs | 1 + .../NoopPublishedSnapshotRebuilder.cs | 12 - .../Scoping/IScopeProvider.cs | 4 +- .../Search/ExamineComposer.cs | 3 +- .../Sync/DatabaseServerMessenger.cs | 157 ++++---- .../Sync/ServerMessengerBase.cs | 4 +- .../WebAssets/WebAssetsComposer.cs | 1 - .../Compose/ModelsBuilderComposer.cs | 1 - .../UmbracoBuilderExtensions.cs | 1 - src/Umbraco.Tests.Integration/RuntimeTests.cs | 47 +-- .../Testing/IntegrationTestComposer.cs | 10 +- .../Scoping/ScopedRepositoryTests.cs | 2 + .../Services/ContentEventsTests.cs | 2 + .../TestHelpers/BaseUsingSqlSyntax.cs | 1 - .../TestHelpers/TestHelper.cs | 1 + .../DistributedCache/DistributedCacheTests.cs | 14 +- .../InstructionProcessTaskTests.cs | 4 +- .../Cache/DistributedCacheBinderTests.cs | 1 - .../PublishedContentLanguageVariantTests.cs | 1 + .../PublishedContentSnapshotTestBase.cs | 2 +- .../PublishedContent/PublishedContentTests.cs | 1 + .../PublishedContent/PublishedMediaTests.cs | 1 + .../Routing/RenderRouteHandlerTests.cs | 1 + ...oviderWithHideTopLevelNodeFromPathTests.cs | 2 +- .../Routing/UrlRoutingTestBase.cs | 2 +- .../Routing/UrlsProviderWithDomainsTests.cs | 2 +- .../Routing/UrlsWithNestedDomains.cs | 2 +- .../Scoping/ScopeEventDispatcherTests.cs | 1 - .../Scoping/ScopedNuCacheTests.cs | 1 + src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 3 + src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 1 + .../TestHelpers/TestWithDatabaseBase.cs | 1 + src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 1 + .../UmbracoExamine/ExamineBaseTest.cs | 2 +- .../AuthenticationControllerTests.cs | 2 +- .../Runtime/BackOfficeComposer.cs | 1 - .../UmbracoBuilderExtensions.cs | 7 + .../Extensions/LinkGeneratorExtensions.cs | 3 +- .../UmbracoCoreServiceCollectionExtensions.cs | 2 +- .../Profiler/WebProfilerComposer.cs | 1 - .../Runtime/AspNetCoreBootFailedComposer.cs | 1 - .../RuntimeMinification/SmidgeComposer.cs | 3 +- .../Runtime/WebsiteComposer.cs | 1 - src/Umbraco.Web/UmbracoBuilderExtensions.cs | 1 - 74 files changed, 737 insertions(+), 737 deletions(-) delete mode 100644 src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs rename src/Umbraco.Core/{ => DependencyInjection}/ServiceCollectionExtensions.cs (92%) rename src/Umbraco.Core/{ => DependencyInjection}/ServiceProviderExtensions.cs (96%) rename src/Umbraco.Core/{CompositionExtensions.cs => DependencyInjection/UmbracoBuilder.Collections.cs} (93%) delete mode 100644 src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs create mode 100644 src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs delete mode 100644 src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs delete mode 100644 src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs delete mode 100644 src/Umbraco.Infrastructure/CompositionExtensions.cs create mode 100644 src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs rename src/Umbraco.Infrastructure/{Runtime/CoreInitialServices.cs => DependencyInjection/UmbracoBuilder.CoreServices.cs} (95%) create mode 100644 src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs create mode 100644 src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs delete mode 100644 src/Umbraco.Infrastructure/Migrations/PostMigrations/NoopPublishedSnapshotRebuilder.cs diff --git a/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs b/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs deleted file mode 100644 index 31e876892e..0000000000 --- a/src/Umbraco.Core/Cache/DistributedCacheBinderComponent.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Cache -{ - public class DistributedCacheBinderComponent : IComponent - { - private readonly IDistributedCacheBinder _binder; - - public DistributedCacheBinderComponent(IDistributedCacheBinder distributedCacheBinder) - { - _binder = distributedCacheBinder; - } - - public void Initialize() - { - _binder.BindEvents(); - } - - public void Terminate() - { } - } -} diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 1c4de6cd8e..14089ba924 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Core.Composing { diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs index e4e02443eb..d7b143df38 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -1,5 +1,4 @@ -using System; -using Umbraco.Core; +using System; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Web.PublishedCache; diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs index edbf554a2c..9229a95cc3 100644 --- a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Core.Composing { diff --git a/src/Umbraco.Core/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs similarity index 92% rename from src/Umbraco.Core/ServiceCollectionExtensions.cs rename to src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index d1c89ea17e..51ff6b705f 100644 --- a/src/Umbraco.Core/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,9 +1,10 @@ -using System; +using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Umbraco.Core; using Umbraco.Core.Composing; -namespace Umbraco.Core +namespace Umbraco.Core.DependencyInjection { public static class ServiceCollectionExtensions { @@ -21,7 +22,7 @@ namespace Umbraco.Core where TImplementing : class, TService1, TService2 { services.AddUnique(); - services.AddUnique(factory => (TImplementing) factory.GetRequiredService()); + services.AddUnique(factory => (TImplementing)factory.GetRequiredService()); } public static void AddUnique(this IServiceCollection services) @@ -48,7 +49,7 @@ namespace Umbraco.Core /// public static void AddUnique(this IServiceCollection services, TService instance) where TService : class - => services.Replace(ServiceDescriptor.Singleton(instance)); + => services.Replace(ServiceDescriptor.Singleton(instance)); public static IServiceCollection AddLazySupport(this IServiceCollection services) { diff --git a/src/Umbraco.Core/ServiceProviderExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs similarity index 96% rename from src/Umbraco.Core/ServiceProviderExtensions.cs rename to src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs index e0d3da2c03..a1cc779500 100644 --- a/src/Umbraco.Core/ServiceProviderExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.Composing; -namespace Umbraco.Core +namespace Umbraco.Core.DependencyInjection { /// /// Provides extension methods to the class. diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs similarity index 93% rename from src/Umbraco.Core/CompositionExtensions.cs rename to src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 58aac7b811..35d8ba1025 100644 --- a/src/Umbraco.Core/CompositionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,4 +1,3 @@ -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Core.HealthCheck; using Umbraco.Core.Manifest; @@ -11,18 +10,17 @@ using Umbraco.Web.Routing; using Umbraco.Web.Sections; using Umbraco.Web.Tour; -namespace Umbraco.Core +namespace Umbraco.Core.DependencyInjection { - public static partial class CompositionExtensions + /// + /// Extension methods for + /// + public static partial class UmbracoBuilderExtensions { - - #region Collection Builders - /// /// Gets the actions collection builder. /// /// The builder. - /// public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -30,7 +28,6 @@ namespace Umbraco.Core /// Gets the content apps collection builder. /// /// The builder. - /// public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -38,7 +35,6 @@ namespace Umbraco.Core /// Gets the content finders collection builder. /// /// The builder. - /// public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -46,7 +42,6 @@ namespace Umbraco.Core /// Gets the editor validators collection builder. /// /// The builder. - /// public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -90,7 +85,6 @@ namespace Umbraco.Core public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// /// Gets the backoffice dashboards collection builder. /// @@ -109,15 +103,11 @@ namespace Umbraco.Core .Add() .Add(); - /// /// Gets the content finders collection builder. /// /// The builder. - /// public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - - #endregion } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index d56712cdcf..1c05695429 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -14,17 +14,26 @@ namespace Umbraco.Core.DependencyInjection { public class UmbracoBuilder : IUmbracoBuilder { - public IServiceCollection Services { get; } - public IConfiguration Config { get; } - public TypeLoader TypeLoader { get; } - public ILoggerFactory BuilderLoggerFactory { get; } - private readonly Dictionary _builders = new Dictionary(); + public IServiceCollection Services { get; } + + public IConfiguration Config { get; } + + public TypeLoader TypeLoader { get; } + + public ILoggerFactory BuilderLoggerFactory { get; } + + /// + /// Initializes a new instance of the class. + /// public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader) : this(services, config, typeLoader, NullLoggerFactory.Instance) { } + /// + /// Initializes a new instance of the class. + /// public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader, ILoggerFactory loggerFactory) { Services = services; @@ -43,10 +52,12 @@ namespace Umbraco.Core.DependencyInjection public TBuilder WithCollectionBuilder() where TBuilder : ICollectionBuilder, new() { - var typeOfBuilder = typeof(TBuilder); + Type typeOfBuilder = typeof(TBuilder); - if (_builders.TryGetValue(typeOfBuilder, out var o)) + if (_builders.TryGetValue(typeOfBuilder, out ICollectionBuilder o)) + { return (TBuilder)o; + } var builder = new TBuilder(); _builders[typeOfBuilder] = builder; @@ -55,8 +66,10 @@ namespace Umbraco.Core.DependencyInjection public void Build() { - foreach (var builder in _builders.Values) + foreach (ICollectionBuilder builder in _builders.Values) + { builder.RegisterWith(Services); + } _builders.Clear(); } diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index b078172213..62f46edce4 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Core.Hosting; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Core.IO { diff --git a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs index 560b7fb235..02859ff6f0 100644 --- a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs @@ -3,10 +3,10 @@ namespace Umbraco.Core.Sync /// /// An implementation that works by storing messages in the database. /// - public interface IBatchedDatabaseServerMessenger : IDatabaseServerMessenger + public interface IBatchedDatabaseServerMessenger : IServerMessenger { + // TODO: We only ever use IBatchedDatabaseServerMessenger so just combine these interfaces + void FlushBatch(); - DatabaseServerMessengerCallbacks Callbacks { get; } - void Startup(); } } diff --git a/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs deleted file mode 100644 index a49cfdd023..0000000000 --- a/src/Umbraco.Core/Sync/IDatabaseServerMessenger.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Sync -{ - public interface IDatabaseServerMessenger: IServerMessenger - { - void Sync(); - } -} diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index b8300b2d6d..a6c5b5d755 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -1,15 +1,19 @@ -using System; -using System.Collections.Generic; +using System; using Umbraco.Core.Cache; namespace Umbraco.Core.Sync { /// - /// Broadcasts distributed cache notifications to all servers of a load balanced environment. + /// Transmits distributed cache notifications for all servers of a load balanced environment. /// /// Also ensures that the notification is processed on the local environment. public interface IServerMessenger { + /// + /// Called to synchronize a server with queued notifications + /// + void Sync(); + /// /// Notifies the distributed cache, for a specified . /// diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs index 780f865ad7..7e63b6b170 100644 --- a/src/Umbraco.Core/Sync/IServerRegistrar.cs +++ b/src/Umbraco.Core/Sync/IServerRegistrar.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Sync { @@ -10,7 +10,7 @@ namespace Umbraco.Core.Sync /// /// Gets the server registrations. /// - IEnumerable Registrations { get; } + IEnumerable Registrations { get; } // TODO: This isn't even used anymore, this whole interface can probably go away /// /// Gets the role of the current server in the application environment. diff --git a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs index e2f2460d58..05315b05fe 100644 --- a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs +++ b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs @@ -1,5 +1,4 @@ using System.Runtime.InteropServices; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index 187eced6e4..bafb537db6 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -25,13 +25,11 @@ namespace Umbraco.Web /// public class BatchedDatabaseServerMessenger : DatabaseServerMessenger, IBatchedDatabaseServerMessenger { - private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IRequestCache _requestCache; private readonly IRequestAccessor _requestAccessor; public BatchedDatabaseServerMessenger( IMainDom mainDom, - IUmbracoDatabaseFactory databaseFactory, IScopeProvider scopeProvider, IProfilingLogger proflog, ILogger logger, @@ -42,34 +40,12 @@ namespace Umbraco.Web IRequestCache requestCache, IRequestAccessor requestAccessor, IOptions globalSettings) - : base(mainDom, scopeProvider, databaseFactory, proflog, logger, serverRegistrar, true, callbacks, hostingEnvironment, cacheRefreshers, globalSettings) + : base(mainDom, scopeProvider, proflog, logger, serverRegistrar, true, callbacks, hostingEnvironment, cacheRefreshers, globalSettings) { - _databaseFactory = databaseFactory; _requestCache = requestCache; _requestAccessor = requestAccessor; } - // invoked by DatabaseServerRegistrarAndMessengerComponent - public void Startup() - { - _requestAccessor.EndRequest += UmbracoModule_EndRequest; - - if (_databaseFactory.CanConnect == false) - { - Logger.LogWarning("Cannot connect to the database, distributed calls will not be enabled for this server."); - } - else - { - Boot(); - } - } - - private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) - { - // will clear the batch - will remain in HttpContext though - that's ok - FlushBatch(); - } - protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { var idsA = ids?.ToArray(); diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs new file mode 100644 index 0000000000..b0921f7698 --- /dev/null +++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs @@ -0,0 +1,93 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Umbraco.Core.Events; +using Umbraco.Core.Persistence; +using Umbraco.Core.Sync; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.Routing; + +namespace Umbraco.Infrastructure.Cache +{ + /// + /// Ensures that distributed cache events are setup and the is initialized + /// + public sealed class DatabaseServerMessengerNotificationHandler : INotificationHandler + { + private readonly IBatchedDatabaseServerMessenger _messenger; + private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoDatabaseFactory _databaseFactory; + private readonly IDistributedCacheBinder _distributedCacheBinder; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public DatabaseServerMessengerNotificationHandler( + IServerMessenger serverMessenger, + IRequestAccessor requestAccessor, + IUmbracoDatabaseFactory databaseFactory, + IDistributedCacheBinder distributedCacheBinder, + ILogger logger) + { + _requestAccessor = requestAccessor; + _databaseFactory = databaseFactory; + _distributedCacheBinder = distributedCacheBinder; + _logger = logger; + _messenger = serverMessenger as IBatchedDatabaseServerMessenger; + } + + /// + public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + { + // The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services. + // The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL + // being available (via IRequestAccessor), which can only be retrieved within an HTTP request (unless it's explicitly configured). + // Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available + // for the hosted services to use when the HTTP request is not available. + _requestAccessor.RouteAttempt += EnsureApplicationUrlOnce; + _requestAccessor.EndRequest += UmbracoModule_EndRequest; + + Startup(); + + return Task.CompletedTask; + } + + private void Startup() + { + if (_databaseFactory.CanConnect == false) + { + _logger.LogWarning("Cannot connect to the database, distributed calls will not be enabled for this server."); + } + else + { + _distributedCacheBinder.BindEvents(); + + // Sync on startup, this will run through the messenger's initialization sequence + _messenger?.Sync(); + } + } + + // TODO: I don't really know or think that the Application Url plays a role anymore with the DB dist cache, + // this might be really old stuff + private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e) + { + if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest) + { + _requestAccessor.RouteAttempt -= EnsureApplicationUrlOnce; + EnsureApplicationUrl(); + } + } + + // By retrieving the application URL within the context of a request (as we are here in responding + // to the IRequestAccessor's RouteAttempt event), we'll get it from the HTTP context and save it for + // future requests that may not be within an HTTP request (e.g. from hosted services). + private void EnsureApplicationUrl() => _requestAccessor.GetApplicationUrl(); + + /// + /// Clear the batch on end request + /// + private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.FlushBatch(); + } +} diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs deleted file mode 100644 index 7279eaf10c..0000000000 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinderComposer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Cache -{ - /// - /// Installs listeners on service events in order to refresh our caches. - /// - [ComposeBefore(typeof(ICoreComposer))] // runs before every other IUmbracoCoreComponent! - public sealed class DistributedCacheBinderComposer : ComponentComposer, ICoreComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs b/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs deleted file mode 100644 index 8d2a2e19cc..0000000000 --- a/src/Umbraco.Infrastructure/Compose/DatabaseServerRegistrarAndMessengerComponent.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Core.Services.Changes; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; -using Umbraco.Web.Search; - -namespace Umbraco.Web.Compose -{ - /// - /// Ensures that servers are automatically registered in the database, when using the database server registrar. - /// - /// - /// At the moment servers are automatically registered upon first request and then on every - /// request but not more than once per (configurable) period. This really is "for information & debug" purposes so - /// we can look at the table and see what servers are registered - but the info is not used anywhere. - /// Should we actually want to use this, we would need a better and more deterministic way of figuring - /// out the "server address" ie the address to which server-to-server requests should be sent - because it - /// probably is not the "current request address" - especially in multi-domains configurations. - /// - // during Initialize / Startup, we end up checking Examine, which needs to be initialized beforehand - // TODO: should not be a strong dependency on "examine" but on an "indexing component" - [ComposeAfter(typeof(ExamineComposer))] - - public sealed class DatabaseServerRegistrarAndMessengerComposer : ComponentComposer, ICoreComposer - { - public static DatabaseServerMessengerCallbacks GetCallbacks(IServiceProvider factory) - { - return new DatabaseServerMessengerCallbacks - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] - { - //rebuild the xml cache file if the server is not synced - () => - { - var publishedSnapshotService = factory.GetRequiredService(); - - // rebuild the published snapshot caches entirely, if the server is not synced - // this is equivalent to DistributedCache RefreshAll... but local only - // (we really should have a way to reuse RefreshAll... locally) - // note: refresh all content & media caches does refresh content types too - publishedSnapshotService.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); - publishedSnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); - publishedSnapshotService.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); - }, - - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - () => - { - var indexRebuilder = factory.GetRequiredService(); - indexRebuilder.RebuildIndexes(false, 5000); - } - } - }; - } - - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - - builder.SetDatabaseServerMessengerCallbacks(GetCallbacks); - builder.SetServerMessenger(); - } - } - - public sealed class DatabaseServerRegistrarAndMessengerComponent : IComponent - { - private readonly IBatchedDatabaseServerMessenger _messenger; - private readonly IRequestAccessor _requestAccessor; - - public DatabaseServerRegistrarAndMessengerComponent( - IServerMessenger serverMessenger, - IRequestAccessor requestAccessor) - { - _requestAccessor = requestAccessor; - _messenger = serverMessenger as IBatchedDatabaseServerMessenger; - } - - public void Initialize() - { - // The scheduled tasks - TouchServerTask and InstructionProcessTask - run as .NET Core hosted services. - // The former (as well as other hosted services that run outside of an HTTP request context) depends on the application URL - // being available (via IRequestAccessor), which can only be retrieved within an HTTP request (unless it's explicitly configured). - // Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available - // for the hosted services to use when the HTTP request is not available. - _requestAccessor.RouteAttempt += EnsureApplicationUrlOnce; - - // Must come last, as it references some _variables - _messenger?.Startup(); - } - - public void Terminate() - { } - - private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e) - { - if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest) - { - _requestAccessor.RouteAttempt -= EnsureApplicationUrlOnce; - EnsureApplicationUrl(); - } - } - - private void EnsureApplicationUrl() - { - // By retrieving the application URL within the context of a request (as we are here in responding - // to the IRequestAccessor's RouteAttempt event), we'll get it from the HTTP context and save it for - // future requests that may not be within an HTTP request (e.g. from hosted services). - _requestAccessor.GetApplicationUrl(); - } - } -} diff --git a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs index 0fee815560..79066dedd7 100644 --- a/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs +++ b/src/Umbraco.Infrastructure/Compose/NotificationsComposer.cs @@ -1,5 +1,4 @@ -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; namespace Umbraco.Web.Compose diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs index f098cfaf57..4c8caa89b8 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Strings; +using Umbraco.Infrastructure.DependencyInjection; namespace Umbraco.Core.Composing.CompositionExtensions { diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs index 31b3133e4d..dc3b1e2481 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Web.Install; diff --git a/src/Umbraco.Infrastructure/CompositionExtensions.cs b/src/Umbraco.Infrastructure/CompositionExtensions.cs deleted file mode 100644 index 703c35e06b..0000000000 --- a/src/Umbraco.Infrastructure/CompositionExtensions.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Dictionary; -using Umbraco.Core.IO; -using Umbraco.Core.Logging.Viewer; -using Umbraco.Core.Manifest; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.PackageActions; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Strings; -using Umbraco.Core.Sync; -using Umbraco.Web.Media.EmbedProviders; -using Umbraco.Web.Search; - -namespace Umbraco.Core -{ - /// - /// Provides extension methods to the class. - /// - public static partial class CompositionExtensions - { - #region Collection Builders - - /// - /// Gets the cache refreshers collection builder. - /// - /// The builder. - public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the mappers collection builder. - /// - /// The builder. - public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the package actions collection builder. - /// - /// The builder. - internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the data editor collection builder. - /// - /// The builder. - public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the data value reference factory collection builder. - /// - /// The builder. - public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the property value converters collection builder. - /// - /// The builder. - public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the url segment providers collection builder. - /// - /// The builder. - public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the validators collection builder. - /// - /// The builder. - internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the manifest filter collection builder. - /// - /// The builder. - public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the backoffice OEmbed Providers collection builder. - /// - /// The builder. - public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the back office searchable tree collection builder - /// - /// - /// - public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - #endregion - - #region Uniques - - /// - /// Sets the culture dictionary factory. - /// - /// The type of the factory. - /// The builder. - public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder) - where T : class, ICultureDictionaryFactory - { - builder.Services.AddUnique(); - } - - /// - /// Sets the culture dictionary factory. - /// - /// The builder. - /// A function creating a culture dictionary factory. - public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the culture dictionary factory. - /// - /// The builder. - /// A factory. - public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the published content model factory. - /// - /// The type of the factory. - /// The builder. - public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder) - where T : class, IPublishedModelFactory - { - builder.Services.AddUnique(); - } - - /// - /// Sets the published content model factory. - /// - /// The builder. - /// A function creating a published content model factory. - public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the published content model factory. - /// - /// The builder. - /// A published content model factory. - public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the server registrar. - /// - /// The type of the server registrar. - /// The builder. - public static void SetServerRegistrar(this IUmbracoBuilder builder) - where T : class, IServerRegistrar - { - builder.Services.AddUnique(); - } - - /// - /// Sets the server registrar. - /// - /// The builder. - /// A function creating a server registrar. - public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the server registrar. - /// - /// The builder. - /// A server registrar. - public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRegistrar registrar) - { - builder.Services.AddUnique(registrar); - } - - /// - /// Sets the server messenger. - /// - /// The type of the server registrar. - /// The builder. - public static void SetServerMessenger(this IUmbracoBuilder builder) - where T : class, IServerMessenger - { - builder.Services.AddUnique(); - } - - /// - /// Sets the server messenger. - /// - /// The builder. - /// A function creating a server messenger. - public static void SetServerMessenger(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the server messenger. - /// - /// The builder. - /// A server messenger. - public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar) - { - builder.Services.AddUnique(registrar); - } - - /// - /// Sets the database server messenger options. - /// - /// The builder. - /// A function creating the options. - /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. - public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the database server messenger options. - /// - /// The builder. - /// Options. - /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. - public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options) - { - builder.Services.AddUnique(options); - } - - /// - /// Sets the short string helper. - /// - /// The type of the short string helper. - /// The builder. - public static void SetShortStringHelper(this IUmbracoBuilder builder) - where T : class, IShortStringHelper - { - builder.Services.AddUnique(); - } - - /// - /// Sets the short string helper. - /// - /// The builder. - /// A function creating a short string helper. - public static void SetShortStringHelper(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the short string helper. - /// - /// A builder. - /// A short string helper. - public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper) - { - builder.Services.AddUnique(helper); - } - - /// - /// Sets the underlying media filesystem. - /// - /// A builder. - /// A filesystem factory. - /// - /// Using this helper will ensure that your IFileSystem implementation is wrapped by the ShadowWrapper - /// - public static void SetMediaFileSystem(this IUmbracoBuilder builder, Func filesystemFactory) - => builder.Services.AddUnique(factory => - { - var fileSystems = factory.GetRequiredService(); - return fileSystems.GetFileSystem(filesystemFactory(factory)); - }); - - /// - /// Sets the log viewer. - /// - /// The type of the log viewer. - /// The builder. - public static void SetLogViewer(this IUmbracoBuilder builder) - where T : class, ILogViewer - { - builder.Services.AddUnique(); - } - - /// - /// Sets the log viewer. - /// - /// The builder. - /// A function creating a log viewer. - public static void SetLogViewer(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - } - - /// - /// Sets the log viewer. - /// - /// A builder. - /// A log viewer. - public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer) - { - builder.Services.AddUnique(viewer); - } - - #endregion - } -} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs new file mode 100644 index 0000000000..28bed7b363 --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs @@ -0,0 +1,96 @@ +using Umbraco.Core.Cache; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Manifest; +using Umbraco.Core.PackageActions; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Strings; +using Umbraco.Web.Media.EmbedProviders; +using Umbraco.Web.Search; + +namespace Umbraco.Infrastructure.DependencyInjection +{ + /// + /// Provides extension methods to the class. + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Gets the cache refreshers collection builder. + /// + /// The builder. + public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the mappers collection builder. + /// + /// The builder. + public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the package actions collection builder. + /// + /// The builder. + internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the data editor collection builder. + /// + /// The builder. + public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the data value reference factory collection builder. + /// + /// The builder. + public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the property value converters collection builder. + /// + /// The builder. + public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the url segment providers collection builder. + /// + /// The builder. + public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the validators collection builder. + /// + /// The builder. + internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the manifest filter collection builder. + /// + /// The builder. + public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the backoffice OEmbed Providers collection builder. + /// + /// The builder. + public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the back office searchable tree collection builder + /// + /// + /// + public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs similarity index 95% rename from src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 32f5e6bf36..e150f0cdba 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -1,7 +1,6 @@ using System; using Examine; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Cache; @@ -13,9 +12,7 @@ using Umbraco.Core.Dashboards; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; -using Umbraco.Core.Hosting; using Umbraco.Core.Install; -using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Media; using Umbraco.Core.Migrations; @@ -23,7 +20,6 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Packaging; -using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.PropertyEditors.ValueConverters; @@ -38,6 +34,7 @@ using Umbraco.Core.Templates; using Umbraco.Examine; using Umbraco.Infrastructure.Examine; using Umbraco.Infrastructure.Media; +using Umbraco.Infrastructure.Runtime; using Umbraco.Web; using Umbraco.Web.Actions; using Umbraco.Web.Cache; @@ -62,10 +59,30 @@ using Umbraco.Web.Templates; using Umbraco.Web.Trees; using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter; -namespace Umbraco.Infrastructure.Runtime +namespace Umbraco.Infrastructure.DependencyInjection { - public static class CoreInitialServices + public static partial class UmbracoBuilderExtensions { + + /* + * TODO: Many of these things are not "Core" services and are probably not required to run + * + * This should be split up: + * - Distributed Cache + * - BackOffice + * - Manifest + * - Property Editors + * - Packages + * - Dashboards + * - OEmbed + * - Sections + * - Content Apps + * - Health Checks + * - ETC... + * - Installation + * - Front End + */ + public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder) { builder.AddNotificationHandler(); @@ -130,7 +147,7 @@ namespace Umbraco.Infrastructure.Runtime builder.DataValueReferenceFactories(); // register a server registrar, by default it's the db registrar - builder.Services.AddUnique(f => + builder.Services.AddUnique(f => { var globalSettings = f.GetRequiredService>().Value; @@ -138,7 +155,7 @@ namespace Umbraco.Infrastructure.Runtime // even on 1 single server we can have 2 concurrent app domains var singleServer = globalSettings.DisableElectionForSingleServer; return singleServer - ? (IServerRegistrar) new SingleServerRegistrar(f.GetRequiredService()) + ? (IServerRegistrar)new SingleServerRegistrar(f.GetRequiredService()) : new DatabaseServerRegistrar( new Lazy(f.GetRequiredService)); }); @@ -183,7 +200,6 @@ namespace Umbraco.Infrastructure.Runtime // by default, register a noop factory builder.Services.AddUnique(); - // by default builder.Services.AddUnique(); builder.SetCultureDictionaryFactory(); @@ -253,10 +269,8 @@ namespace Umbraco.Infrastructure.Runtime builder.EditorValidators() .Add(() => builder.TypeLoader.GetTypes()); - builder.TourFilters(); - // replace with web implementation builder.Services.AddUnique(); // register OEmbed providers - no type scanning - all explicit opt-in of adding types @@ -338,7 +352,6 @@ namespace Umbraco.Infrastructure.Runtime // register distributed cache builder.Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService())); - builder.Services.AddScoped(); builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs new file mode 100644 index 0000000000..4970df2b87 --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -0,0 +1,138 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Events; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Sync; +using Umbraco.Infrastructure.Cache; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Search; + +namespace Umbraco.Infrastructure.DependencyInjection +{ + /// + /// Provides extension methods to the class. + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Adds distributed cache support + /// + public static IUmbracoBuilder AddDistributedCache(this IUmbracoBuilder builder) + { + // NOTE: the `DistributedCache` is registered in AddCoreInitialServices since it's a core service + + builder.SetDatabaseServerMessengerCallbacks(GetCallbacks); + builder.SetServerMessenger(); + builder.AddNotificationHandler(); + + builder.CacheRefreshers() + .Add(() => builder.TypeLoader.GetCacheRefreshers()); + + builder.Services.AddUnique(); + return builder; + } + + /// + /// Sets the server registrar. + /// + /// The type of the server registrar. + /// The builder. + public static void SetServerRegistrar(this IUmbracoBuilder builder) + where T : class, IServerRegistrar + => builder.Services.AddUnique(); + + /// + /// Sets the server registrar. + /// + /// The builder. + /// A function creating a server registrar. + public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory) + => builder.Services.AddUnique(factory); + + /// + /// Sets the server registrar. + /// + /// The builder. + /// A server registrar. + public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRegistrar registrar) + => builder.Services.AddUnique(registrar); + + /// + /// Sets the database server messenger options. + /// + /// The builder. + /// A function creating the options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerCallbacks(this IUmbracoBuilder builder, Func factory) + => builder.Services.AddUnique(factory); + + /// + /// Sets the database server messenger options. + /// + /// The builder. + /// Options. + /// Use DatabaseServerRegistrarAndMessengerComposer.GetDefaultOptions to get the options that Umbraco would use by default. + public static void SetDatabaseServerMessengerOptions(this IUmbracoBuilder builder, DatabaseServerMessengerCallbacks options) + => builder.Services.AddUnique(options); + + /// + /// Sets the server messenger. + /// + /// The type of the server registrar. + /// The builder. + public static void SetServerMessenger(this IUmbracoBuilder builder) + where T : class, IServerMessenger + => builder.Services.AddUnique(); + + /// + /// Sets the server messenger. + /// + /// The builder. + /// A function creating a server messenger. + public static void SetServerMessenger(this IUmbracoBuilder builder, Func factory) + => builder.Services.AddUnique(factory); + + /// + /// Sets the server messenger. + /// + /// The builder. + /// A server messenger. + public static void SetServerMessenger(this IUmbracoBuilder builder, IServerMessenger registrar) + => builder.Services.AddUnique(registrar); + + private static DatabaseServerMessengerCallbacks GetCallbacks(IServiceProvider factory) => new DatabaseServerMessengerCallbacks + { + // These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + // rebuild the xml cache file if the server is not synced + () => + { + IPublishedSnapshotService publishedSnapshotService = factory.GetRequiredService(); + + // rebuild the published snapshot caches entirely, if the server is not synced + // this is equivalent to DistributedCache RefreshAll... but local only + // (we really should have a way to reuse RefreshAll... locally) + // note: refresh all content & media caches does refresh content types too + publishedSnapshotService.Notify(new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }); + publishedSnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + publishedSnapshotService.Notify(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _); + }, + + // rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + () => + { + var indexRebuilder = factory.GetRequiredService(); + indexRebuilder.RebuildIndexes(false, 5000); + } + } + }; + } +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs new file mode 100644 index 0000000000..f26b4442f8 --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs @@ -0,0 +1,157 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Dictionary; +using Umbraco.Core.IO; +using Umbraco.Core.Logging.Viewer; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Strings; +using Umbraco.Core.Sync; + +namespace Umbraco.Infrastructure.DependencyInjection +{ + /// + /// Provides extension methods to the class. + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Sets the culture dictionary factory. + /// + /// The type of the factory. + /// The builder. + public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder) + where T : class, ICultureDictionaryFactory + { + builder.Services.AddUnique(); + } + + /// + /// Sets the culture dictionary factory. + /// + /// The builder. + /// A function creating a culture dictionary factory. + public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, Func factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the culture dictionary factory. + /// + /// The builder. + /// A factory. + public static void SetCultureDictionaryFactory(this IUmbracoBuilder builder, ICultureDictionaryFactory factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the published content model factory. + /// + /// The type of the factory. + /// The builder. + public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder) + where T : class, IPublishedModelFactory + { + builder.Services.AddUnique(); + } + + /// + /// Sets the published content model factory. + /// + /// The builder. + /// A function creating a published content model factory. + public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, Func factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the published content model factory. + /// + /// The builder. + /// A published content model factory. + public static void SetPublishedContentModelFactory(this IUmbracoBuilder builder, IPublishedModelFactory factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the short string helper. + /// + /// The type of the short string helper. + /// The builder. + public static void SetShortStringHelper(this IUmbracoBuilder builder) + where T : class, IShortStringHelper + { + builder.Services.AddUnique(); + } + + /// + /// Sets the short string helper. + /// + /// The builder. + /// A function creating a short string helper. + public static void SetShortStringHelper(this IUmbracoBuilder builder, Func factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the short string helper. + /// + /// A builder. + /// A short string helper. + public static void SetShortStringHelper(this IUmbracoBuilder builder, IShortStringHelper helper) + { + builder.Services.AddUnique(helper); + } + + /// + /// Sets the underlying media filesystem. + /// + /// A builder. + /// A filesystem factory. + /// + /// Using this helper will ensure that your IFileSystem implementation is wrapped by the ShadowWrapper + /// + public static void SetMediaFileSystem(this IUmbracoBuilder builder, Func filesystemFactory) + => builder.Services.AddUnique(factory => + { + var fileSystems = factory.GetRequiredService(); + return fileSystems.GetFileSystem(filesystemFactory(factory)); + }); + + /// + /// Sets the log viewer. + /// + /// The type of the log viewer. + /// The builder. + public static void SetLogViewer(this IUmbracoBuilder builder) + where T : class, ILogViewer + { + builder.Services.AddUnique(); + } + + /// + /// Sets the log viewer. + /// + /// The builder. + /// A function creating a log viewer. + public static void SetLogViewer(this IUmbracoBuilder builder, Func factory) + { + builder.Services.AddUnique(factory); + } + + /// + /// Sets the log viewer. + /// + /// A builder. + /// A log viewer. + public static void SetLogViewer(this IUmbracoBuilder builder, ILogViewer viewer) + { + builder.Services.AddUnique(viewer); + } + } +} diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index 3c291f187b..f3d970d2b0 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -17,7 +17,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration public class InstructionProcessTask : RecurringHostedServiceBase { private readonly IRuntimeState _runtimeState; - private readonly IDatabaseServerMessenger _messenger; + private readonly IServerMessenger _messenger; private readonly ILogger _logger; /// @@ -31,7 +31,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; - _messenger = messenger as IDatabaseServerMessenger ?? throw new ArgumentNullException(nameof(messenger)); + _messenger = messenger as IServerMessenger ?? throw new ArgumentNullException(nameof(messenger)); _logger = logger; } diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs index fc26f922eb..4d8046ee8c 100644 --- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs +++ b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Core.Logging.Serilog.Enrichers; diff --git a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs index aa57383541..4c419a1648 100644 --- a/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs +++ b/src/Umbraco.Infrastructure/Logging/Viewer/LogViewerComposer.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using Serilog; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; +using Umbraco.Infrastructure.DependencyInjection; namespace Umbraco.Core.Logging.Viewer { diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs index 9fc5b1b277..22961dea5a 100644 --- a/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs +++ b/src/Umbraco.Infrastructure/Migrations/MigrationBuilder.cs @@ -1,5 +1,6 @@ using System; using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Core.Migrations { diff --git a/src/Umbraco.Infrastructure/Migrations/PostMigrations/NoopPublishedSnapshotRebuilder.cs b/src/Umbraco.Infrastructure/Migrations/PostMigrations/NoopPublishedSnapshotRebuilder.cs deleted file mode 100644 index cf53f161a4..0000000000 --- a/src/Umbraco.Infrastructure/Migrations/PostMigrations/NoopPublishedSnapshotRebuilder.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Umbraco.Core.Migrations.PostMigrations -{ - /// - /// Implements in Umbraco.Core (doing nothing). - /// - public class NoopPublishedSnapshotRebuilder : IPublishedSnapshotRebuilder - { - /// - public void Rebuild() - { } - } -} diff --git a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs index dce6658f16..712b90affa 100644 --- a/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/IScopeProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Umbraco.Core.Events; using Umbraco.Core.Persistence; @@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping /// Provides scopes. /// public interface IScopeProvider - { + { /// /// Creates an ambient scope. /// diff --git a/src/Umbraco.Infrastructure/Search/ExamineComposer.cs b/src/Umbraco.Infrastructure/Search/ExamineComposer.cs index 683ed48ecd..c961aa6e72 100644 --- a/src/Umbraco.Infrastructure/Search/ExamineComposer.cs +++ b/src/Umbraco.Infrastructure/Search/ExamineComposer.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Core.Models; diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 856772148f..11e1596529 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -23,15 +23,20 @@ namespace Umbraco.Core.Sync /// /// An that works by storing messages in the database. /// - // - // this messenger writes ALL instructions to the database, - // but only processes instructions coming from remote servers, - // thus ensuring that instructions run only once - // - public class DatabaseServerMessenger : ServerMessengerBase, IDatabaseServerMessenger + public abstract class DatabaseServerMessenger : ServerMessengerBase { + // TODO: This class is never used directly, only BatchedDatabaseServerMessenger is used so + // this could/should be combined with that or made abstract and/or just cleaned up. + + // TODO: This class needs to be split into a service/repo for DB access + + /* + * this messenger writes ALL instructions to the database, + * but only processes instructions coming from remote servers, + * thus ensuring that instructions run only once + */ + private readonly IMainDom _mainDom; - private readonly IUmbracoDatabaseFactory _umbracoDatabaseFactory; private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); private readonly IProfilingLogger _profilingLogger; @@ -43,22 +48,28 @@ namespace Umbraco.Core.Sync private int _lastId = -1; private DateTime _lastSync; private DateTime _lastPruned; - private bool _initialized; + private readonly Lazy _initialized; private bool _syncing; private bool _released; - public DatabaseServerMessengerCallbacks Callbacks { get; } - - public GlobalSettings GlobalSettings { get; } - + /// + /// Initializes a new instance of the class. + /// public DatabaseServerMessenger( - IMainDom mainDom, IScopeProvider scopeProvider, IUmbracoDatabaseFactory umbracoDatabaseFactory, IProfilingLogger proflog, ILogger logger, IServerRegistrar serverRegistrar, - bool distributedEnabled, DatabaseServerMessengerCallbacks callbacks, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, IOptions globalSettings) + IMainDom mainDom, + IScopeProvider scopeProvider, + IProfilingLogger proflog, + ILogger logger, + IServerRegistrar serverRegistrar, + bool distributedEnabled, + DatabaseServerMessengerCallbacks callbacks, + IHostingEnvironment hostingEnvironment, + CacheRefresherCollection cacheRefreshers, + IOptions globalSettings) : base(distributedEnabled) { ScopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); _mainDom = mainDom; - _umbracoDatabaseFactory = umbracoDatabaseFactory; _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); _serverRegistrar = serverRegistrar; _hostingEnvironment = hostingEnvironment; @@ -76,24 +87,28 @@ namespace Umbraco.Core.Sync + " [P" + Process.GetCurrentProcess().Id // eg 1234 + "/D" + AppDomain.CurrentDomain.Id // eg 22 + "] " + Guid.NewGuid().ToString("N").ToUpper(); // make it truly unique + + _initialized = new Lazy(EnsureInitialized); } + public DatabaseServerMessengerCallbacks Callbacks { get; } + + public GlobalSettings GlobalSettings { get; } + protected ILogger Logger { get; } protected IScopeProvider ScopeProvider { get; } - protected Sql Sql() => _umbracoDatabaseFactory.SqlContext.Sql(); + protected Sql Sql() => ScopeProvider.SqlContext.Sql(); private string DistCacheFilePath => _distCacheFilePath.Value; #region Messenger + // we don't care if there's servers listed or not, + // if distributed call is enabled we will make the call protected override bool RequiresDistributed(ICacheRefresher refresher, MessageType dispatchType) - { - // we don't care if there's servers listed or not, - // if distributed call is enabled we will make the call - return _initialized && DistributedEnabled; - } + => _initialized.Value && DistributedEnabled; protected override void DeliverRemote( ICacheRefresher refresher, @@ -104,7 +119,9 @@ namespace Umbraco.Core.Sync var idsA = ids?.ToArray(); if (GetArrayType(idsA, out var idType) == false) + { throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids)); + } var instructions = RefreshInstruction.GetInstructions(refresher, messageType, idsA, idType, json); @@ -130,17 +147,12 @@ namespace Umbraco.Core.Sync /// /// Boots the messenger. /// - /// - /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. - /// Callers MUST ensure thread-safety. - /// - protected void Boot() + private bool EnsureInitialized() { // weight:10, must release *before* the published snapshot service, because once released // the service will *not* be able to properly handle our notifications anymore const int weight = 10; - var registered = _mainDom.Register( () => { @@ -154,7 +166,7 @@ namespace Umbraco.Core.Sync // properly releasing MainDom - a timeout here means that one refresher // is taking too much time processing, however when it's done we will // not update lastId and stop everything - var idle =_syncIdle.WaitOne(5000); + var idle = _syncIdle.WaitOne(5000); if (idle == false) { Logger.LogWarning("The wait lock timed out, application is shutting down. The current instruction batch will be re-processed."); @@ -163,15 +175,16 @@ namespace Umbraco.Core.Sync weight); if (registered == false) - return; + { + return false; + } ReadLastSynced(); // get _lastId - using (var scope = ScopeProvider.CreateScope()) + + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { EnsureInstructions(scope.Database); // reset _lastId if instructions are missing - Initialize(scope.Database); // boot - - scope.Complete(); + return Initialize(scope.Database); // boot } } @@ -182,14 +195,19 @@ namespace Umbraco.Core.Sync /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// Callers MUST ensure thread-safety. /// - private void Initialize(IUmbracoDatabase database) + private bool Initialize(IUmbracoDatabase database) { lock (_locko) { - if (_released) return; + if (_released) + { + return false; + } var coldboot = false; - if (_lastId < 0) // never synced before + + // never synced before + if (_lastId < 0) { // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. @@ -201,12 +219,12 @@ namespace Umbraco.Core.Sync } else { - //check for how many instructions there are to process, each row contains a count of the number of instructions contained in each - //row so we will sum these numbers to get the actual count. - var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); + // check for how many instructions there are to process, each row contains a count of the number of instructions contained in each + // row so we will sum these numbers to get the actual count. + var count = database.ExecuteScalar("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new { lastId = _lastId }); if (count > GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount) { - //too many instructions, proceed to cold boot + // too many instructions, proceed to cold boot Logger.LogWarning( "The instruction count ({InstructionCount}) exceeds the specified MaxProcessingInstructionCount ({MaxProcessingInstructionCount})." + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" @@ -224,38 +242,55 @@ namespace Umbraco.Core.Sync // when doing it before, some instructions might run twice - not an issue var maxId = database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); - //if there is a max currently, or if we've never synced + // if there is a max currently, or if we've never synced if (maxId > 0 || _lastId < 0) + { SaveLastSynced(maxId); + } // execute initializing callbacks if (Callbacks.InitializingCallbacks != null) + { foreach (var callback in Callbacks.InitializingCallbacks) + { callback(); + } + } } - _initialized = true; + return true; } } /// /// Synchronize the server (throttled). /// - public void Sync() + public override void Sync() { + if (!_initialized.Value) + { + return; + } + lock (_locko) { if (_syncing) + { return; + } - //Don't continue if we are released + // Don't continue if we are released if (_released) + { return; + } if ((DateTime.UtcNow - _lastSync) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenSyncOperations) + { return; + } - //Set our flag and the lock to be in it's original state (i.e. it can be awaited) + // Set our flag and the lock to be in it's original state (i.e. it can be awaited) _syncing = true; _syncIdle.Reset(); _lastSync = DateTime.UtcNow; @@ -268,7 +303,7 @@ namespace Umbraco.Core.Sync { ProcessDatabaseInstructions(scope.Database); - //Check for pruning throttling + // Check for pruning throttling if (_released || (DateTime.UtcNow - _lastPruned) <= GlobalSettings.DatabaseServerMessenger.TimeBetweenPruneOperations) { scope.Complete(); @@ -292,7 +327,7 @@ namespace Umbraco.Core.Sync { lock (_locko) { - //We must reset our flag and signal any waiting locks + // We must reset our flag and signal any waiting locks _syncing = false; } @@ -306,9 +341,6 @@ namespace Umbraco.Core.Sync /// /// Thread safety: this is NOT thread safe. Because it is NOT meant to run multi-threaded. /// - /// - /// Returns the number of processed instructions - /// private void ProcessDatabaseInstructions(IUmbracoDatabase database) { // NOTE @@ -324,7 +356,7 @@ namespace Umbraco.Core.Sync .Where(dto => dto.Id > _lastId) .OrderBy(dto => dto.Id); - //only retrieve the top 100 (just in case there's tons) + // only retrieve the top 100 (just in case there's tons) // even though MaxProcessingInstructionCount is by default 1000 we still don't want to process that many // rows in one request thread since each row can contain a ton of instructions (until 7.5.5 in which case // a row can only contain MaxProcessingInstructionCount) @@ -337,15 +369,15 @@ namespace Umbraco.Core.Sync var lastId = 0; - //tracks which ones have already been processed to avoid duplicates + // tracks which ones have already been processed to avoid duplicates var processed = new HashSet(); - //It would have been nice to do this in a Query instead of Fetch using a data reader to save + // It would have been nice to do this in a Query instead of Fetch using a data reader to save // some memory however we cannot do that because inside of this loop the cache refreshers are also // performing some lookups which cannot be done with an active reader open foreach (var dto in database.Fetch(topSql)) { - //If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot + // If this flag gets set it means we're shutting down! In this case, we need to exit asap and cannot // continue processing anything otherwise we'll hold up the app domain shutdown if (_released) { @@ -377,10 +409,10 @@ namespace Umbraco.Core.Sync var instructionBatch = GetAllInstructions(jsonA); - //process as per-normal + // process as per-normal var success = ProcessDatabaseInstructions(instructionBatch, dto, processed, ref lastId); - //if they couldn't be all processed (i.e. we're shutting down) then exit + // if they couldn't be all processed (i.e. we're shutting down) then exit if (success == false) { Logger.LogInformation("The current batch of instructions was not processed, app is shutting down"); @@ -425,11 +457,11 @@ namespace Umbraco.Core.Sync //} catch (Exception ex) { - Logger.LogError( - ex, - "DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored", - dto.Id, - dto.Instructions); + Logger.LogError( + ex, + "DISTRIBUTED CACHE IS NOT UPDATED. Failed to execute instructions ({DtoId}: '{DtoInstructions}'). Instruction is being skipped/ignored", + dto.Id, + dto.Instructions); //we cannot throw here because this invalid instruction will just keep getting processed over and over and errors // will be thrown over and over. The only thing we can do is ignore and move on. @@ -509,7 +541,8 @@ namespace Umbraco.Core.Sync /// private void ReadLastSynced() { - if (File.Exists(DistCacheFilePath) == false) return; + if (File.Exists(DistCacheFilePath) == false) + return; var content = File.ReadAllText(DistCacheFilePath); if (int.TryParse(content, out var last)) diff --git a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs index f2918ffe96..150f3428a7 100644 --- a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs +++ b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; @@ -346,6 +346,8 @@ namespace Umbraco.Core.Sync DeliverRemote(refresher, messageType, idsA); } + public abstract void Sync(); + //protected virtual void Deliver(ICacheRefresher refresher, object payload) //{ // if (servers == null) throw new ArgumentNullException("servers"); diff --git a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs index af59c27ef2..5db66fdf78 100644 --- a/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs +++ b/src/Umbraco.Infrastructure/WebAssets/WebAssetsComposer.cs @@ -1,4 +1,3 @@ -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index ca597a607b..94237ccf3d 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -1,7 +1,6 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Configuration; -using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded.Building; diff --git a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs index 719d014296..6f6e0d0c0e 100644 --- a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Scoping; diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 9b09f7c562..394884a0db 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -10,6 +11,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; using Umbraco.Web.Common.DependencyInjection; @@ -42,16 +44,15 @@ namespace Umbraco.Tests.Integration { var testHelper = new TestHelper(); - var hostBuilder = new HostBuilder() + IHostBuilder hostBuilder = new HostBuilder() .ConfigureServices((hostContext, services) => { - var webHostEnvironment = testHelper.GetWebHostEnvironment(); + IWebHostEnvironment webHostEnvironment = testHelper.GetWebHostEnvironment(); services.AddSingleton(testHelper.DbProviderFactoryCreator); services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); // Add it! - - var typeLoader = services.AddTypeLoader( + TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, webHostEnvironment, testHelper.GetHostingEnvironment(), @@ -60,9 +61,13 @@ namespace Umbraco.Tests.Integration hostContext.Configuration, testHelper.Profiler); - var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, + var builder = new UmbracoBuilder( + services, + hostContext.Configuration, + typeLoader, testHelper.ConsoleLoggerFactory); - builder.Services.AddUnique(AppCaches.NoCache); + + builder.Services.AddUnique(AppCaches.NoCache); builder.AddConfiguration() .AddUmbracoCore() .Build(); @@ -70,15 +75,14 @@ namespace Umbraco.Tests.Integration services.AddRouting(); // LinkGenerator }); - var host = await hostBuilder.StartAsync(); + IHost host = await hostBuilder.StartAsync(); var app = new ApplicationBuilder(host.Services); app.UseUmbracoCore(); - // assert results - var runtimeState = app.ApplicationServices.GetRequiredService(); - var mainDom = app.ApplicationServices.GetRequiredService(); + IRuntimeState runtimeState = app.ApplicationServices.GetRequiredService(); + IMainDom mainDom = app.ApplicationServices.GetRequiredService(); Assert.IsTrue(mainDom.IsMainDom); Assert.IsNull(runtimeState.BootFailedException); @@ -97,10 +101,7 @@ namespace Umbraco.Tests.Integration IsComposed = true; } - public static void Reset() - { - IsComposed = false; - } + public static void Reset() => IsComposed = false; public static bool IsComposed { get; private set; } } @@ -108,24 +109,16 @@ namespace Umbraco.Tests.Integration public class MyComponent : IComponent { public static bool IsInit { get; private set; } + public static bool IsTerminated { get; private set; } private readonly ILogger _logger; - public MyComponent(ILogger logger) - { - _logger = logger; - } + public MyComponent(ILogger logger) => _logger = logger; - public void Initialize() - { - IsInit = true; - } + public void Initialize() => IsInit = true; - public void Terminate() - { - IsTerminated = true; - } + public void Terminate() => IsTerminated = true; public static void Reset() { @@ -134,6 +127,4 @@ namespace Umbraco.Tests.Integration } } } - - } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 39d74f8869..1aeaec1bca 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using System; using System.Collections.Generic; @@ -22,10 +22,10 @@ using Umbraco.Core.Sync; using Umbraco.Core.WebAssets; using Umbraco.Examine; using Umbraco.Tests.TestHelpers.Stubs; -using Umbraco.Web.Compose; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.Scheduling; using Umbraco.Web.Search; +using Umbraco.Infrastructure.Cache; namespace Umbraco.Tests.Integration.Testing { @@ -38,11 +38,12 @@ namespace Umbraco.Tests.Integration.Testing /// public class IntegrationTestComposer : ComponentComposer { + // TODO: Kill this and only enable using ext methods what we need (first we need to kill composers) + public override void Compose(IUmbracoBuilder builder) { base.Compose(builder); - builder.Components().Remove(); builder.Services.AddUnique(); builder.Services.AddUnique(factory => Mock.Of()); @@ -65,7 +66,6 @@ namespace Umbraco.Tests.Integration.Testing /// Used to register a replacement for where the file sources are the ones within the netcore project so /// we don't need to copy files /// - /// private ILocalizedTextService GetLocalizedTextService(IServiceProvider factory) { var globalSettings = factory.GetRequiredService>(); @@ -153,6 +153,8 @@ namespace Umbraco.Tests.Integration.Testing { } + + public void Sync() { } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 11f4095f53..556dedee14 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -335,6 +335,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Scoping : base(false) { } + public override void Sync() { } + protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { throw new NotImplementedException(); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs index 80bf464c31..6b5af1e1e2 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs @@ -2171,6 +2171,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services public LocalServerMessenger() : base(false) { } + public override void Sync() { } + protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { } diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs index 76f96ead05..d6db3c09f6 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using NPoco; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence; diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 0b0f3120d0..40e70d9b5d 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Diagnostics; using Umbraco.Core.Hosting; using Umbraco.Core.IO; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs index 1023e47dfa..6211711202 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs @@ -127,12 +127,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache internal class TestServerMessenger : IServerMessenger { // Used for tests - public List IntIdsRefreshed = new List(); - public List GuidIdsRefreshed = new List(); - public List IntIdsRemoved = new List(); - public List PayloadsRemoved = new List(); - public List PayloadsRefreshed = new List(); - public int CountOfFullRefreshes = 0; + public List IntIdsRefreshed { get; } = new List(); + public List GuidIdsRefreshed { get; } = new List(); + public List IntIdsRemoved { get; } = new List(); + public List PayloadsRemoved { get; } = new List(); + public List PayloadsRefreshed { get; } = new List(); + public int CountOfFullRefreshes { get; private set; } = 0; public void PerformRefresh(ICacheRefresher refresher, TPayload[] payload) { @@ -156,6 +156,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) => GuidIdsRefreshed.AddRange(guidIds); public void PerformRefreshAll(ICacheRefresher refresher) => CountOfFullRefreshes++; + + public void Sync() { } } internal class TestServerRegistrar : IServerRegistrar diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs index 6ea56792e2..5187f83375 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTaskTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe [TestFixture] public class InstructionProcessTaskTests { - private Mock _mockDatabaseServerMessenger; + private Mock _mockDatabaseServerMessenger; [TestCase(RuntimeLevel.Boot)] [TestCase(RuntimeLevel.Install)] @@ -45,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe var mockLogger = new Mock>(); - _mockDatabaseServerMessenger = new Mock(); + _mockDatabaseServerMessenger = new Mock(); var settings = new GlobalSettings(); diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs index 1800abc8a8..f18aacf18b 100644 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using System; using System.Linq; using System.Threading; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; using Umbraco.Core.Models; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs index edcd199463..7392537e39 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentLanguageVariantTests.cs @@ -6,6 +6,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index e4b3721892..3387f424dd 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -6,9 +6,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 797b1c2be9..af0dab8e14 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index 9262c72dfa..912d1e4995 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -25,6 +25,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Tests.Common; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.PublishedContent { diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 9332dc894a..a8d017e3cb 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -26,6 +26,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.Runtime; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.Routing { diff --git a/src/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs b/src/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs index 50b1597cca..de8ddfd201 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderWithHideTopLevelNodeFromPathTests.cs @@ -1,10 +1,10 @@ using NUnit.Framework; using Microsoft.Extensions.Logging; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Tests.Common; using Umbraco.Tests.Testing; using Umbraco.Web.Routing; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.Routing { diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs index 4c658379cd..649f63f09e 100644 --- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs +++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs @@ -2,8 +2,8 @@ using System.Linq; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index aaf3b0de96..a18d12351f 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -5,8 +5,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index af2ffad6e5..46d67eb9bd 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using Microsoft.Extensions.Logging; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -13,6 +12,7 @@ using Umbraco.Tests.Common; using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Web; using Umbraco.Web.Routing; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.Routing { diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index e663996d60..b1851694bc 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; using Umbraco.Core.IO; diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 76a947f63a..4f424f4bb0 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 6814210cc4..51c306a864 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -299,6 +300,8 @@ namespace Umbraco.Tests.Scoping : base(false) { } + public override void Sync() { } + protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { throw new NotImplementedException(); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index cab74e22e4..103d361fc5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -6,8 +6,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index e91c2fdf4d..bea5deb10c 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -38,6 +38,7 @@ using Umbraco.Tests.Common.Builders; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 8c7b9a00e2..302e1198a8 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -32,6 +32,7 @@ using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Web.WebApi; using Umbraco.Tests.Common; using Umbraco.Core.Security; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 288a309374..4da5ba7189 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -43,6 +43,7 @@ using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs index 59b453cc5b..61b5141856 100644 --- a/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/ExamineBaseTest.cs @@ -2,9 +2,9 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Strings; diff --git a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs index cdc2bfed00..52e1c3a5a3 100644 --- a/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs +++ b/src/Umbraco.Tests/Web/Controllers/AuthenticationControllerTests.cs @@ -12,7 +12,6 @@ using System.Web.Http; using Moq; using Newtonsoft.Json; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Web.Composing; using Umbraco.Core.Configuration; @@ -37,6 +36,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Persistance.SqlCe; using Umbraco.Web.Routing; +using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.Web.Controllers { diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs index e9bf69f2c0..d933d00d68 100644 --- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs +++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs @@ -1,7 +1,6 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 39a3eb3dce..1d11f32916 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -31,9 +31,11 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Runtime; using Umbraco.Extensions; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.HostedServices; using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Infrastructure.Runtime; +using Umbraco.Web.Cache; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.DependencyInjection; @@ -50,6 +52,9 @@ namespace Umbraco.Web.Common.DependencyInjection /// public static class UmbracoBuilderExtensions { + /// + /// Creates an and registers basic Umbraco services + /// public static IUmbracoBuilder AddUmbraco( this IServiceCollection services, IWebHostEnvironment webHostEnvironment, @@ -65,6 +70,8 @@ namespace Umbraco.Web.Common.DependencyInjection throw new ArgumentNullException(nameof(config)); } + // TODO: Should some/all of these registrations be moved directly into UmbracoBuilder? + IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config); var loggingDir = tempHostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.LogFiles); diff --git a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs index f2babdb07c..6bfa402154 100644 --- a/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/LinkGeneratorExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; @@ -9,7 +9,6 @@ using Umbraco.Web.Common.Install; using Umbraco.Core.Hosting; using System.Linq.Expressions; using Umbraco.Web.Common.Controllers; -using System.Linq; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index f42fc274f0..4161cafd91 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -7,10 +7,10 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; using Serilog.Extensions.Hosting; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Runtime; diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs index 3c00b0d3bc..eac3e058c2 100644 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs @@ -1,4 +1,3 @@ -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs index a23f880e7e..758125b425 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs @@ -1,4 +1,3 @@ -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Web.Common.Middleware; diff --git a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs index 1058192034..9a097f688b 100644 --- a/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs +++ b/src/Umbraco.Web.Common/RuntimeMinification/SmidgeComposer.cs @@ -1,6 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Smidge.FileProcessors; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Core.Runtime; diff --git a/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs b/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs index a40b29aea2..2a4b85a0df 100644 --- a/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs +++ b/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs @@ -1,4 +1,3 @@ -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; using Umbraco.Extensions; diff --git a/src/Umbraco.Web/UmbracoBuilderExtensions.cs b/src/Umbraco.Web/UmbracoBuilderExtensions.cs index bb6fe29c93..d9eea6b5ea 100644 --- a/src/Umbraco.Web/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web/UmbracoBuilderExtensions.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; using Umbraco.Core.DependencyInjection; using Umbraco.Web.Routing; From 1f6297ad6bb75b0b2dd74d2808885e234465592c Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 11:46:17 +1100 Subject: [PATCH 11/88] Moves some services 'up' to Core, moves core DI registrations 'up' to UmbracoBuilder, moves Composing ext to DependencyInjection namespaces as UmbracoBuilder ext --- .../ServiceCollectionExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilder.cs | 111 ++++++++++++ .../EmailNotificationMethod.cs | 1 + src/Umbraco.Core/{ => Mail}/IEmailSender.cs | 4 +- src/Umbraco.Core/{ => Mail}/ISmsSender.cs | 4 +- .../Mail/NotImplementedEmailSender.cs | 12 ++ .../Mail}/NotImplementedSmsSender.cs | 4 +- .../{ => PublishedCache}/ITagQuery.cs | 0 ...uginsManifestWatcherNotificationHandler.cs | 3 +- .../Runtime/EssentialDirectoryCreator.cs | 3 +- .../Runtime/MainDomSemaphoreLock.cs | 6 +- .../{Implement => }/DashboardService.cs | 0 .../Services}/InstallationService.cs | 4 +- src/Umbraco.Core/Services/UpgradeService.cs | 5 +- .../CompositionExtensions/Installer.cs | 36 ---- .../UmbracoBuilder.CoreServices.cs | 166 +++++------------- .../UmbracoBuilder.DistributedCache.cs | 18 ++ .../UmbracoBuilder.FileSystems.cs} | 16 +- .../UmbracoBuilder.Installer.cs | 36 ++++ .../UmbracoBuilder.MappingProfiles.cs} | 12 +- .../UmbracoBuilder.Repositories.cs} | 11 +- .../UmbracoBuilder.Services.cs} | 27 ++- src/Umbraco.Infrastructure/EmailSender.cs | 1 + .../Manifest/ManifestParser.cs | 2 +- .../Media/ImageDimensionExtractor.cs | 11 +- .../Packaging/PackageInstallation.cs | 13 +- .../Services/Implement/NotificationService.cs | 1 + src/Umbraco.Tests.Integration/RuntimeTests.cs | 1 - .../Umbraco.Core/IO/FileSystemsTests.cs | 14 +- .../TestHelpers/TestHelper.cs | 1 + src/Umbraco.Tests/TestHelpers/TestHelper.cs | 1 + src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 8 +- .../Controllers/AuthenticationController.cs | 1 + .../Controllers/UsersController.cs | 1 + .../Trees/PartialViewsTreeController.cs | 5 +- .../UmbracoBuilderExtensions.cs | 54 +----- .../Runtime/AspNetCoreComposer.cs | 4 +- 37 files changed, 301 insertions(+), 298 deletions(-) rename src/Umbraco.Core/{ => Mail}/IEmailSender.cs (78%) rename src/Umbraco.Core/{ => Mail}/ISmsSender.cs (84%) create mode 100644 src/Umbraco.Core/Mail/NotImplementedEmailSender.cs rename src/{Umbraco.Infrastructure => Umbraco.Core/Mail}/NotImplementedSmsSender.cs (90%) rename src/Umbraco.Core/{ => PublishedCache}/ITagQuery.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Runtime/AppPluginsManifestWatcherNotificationHandler.cs (96%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Runtime/EssentialDirectoryCreator.cs (96%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Runtime/MainDomSemaphoreLock.cs (92%) rename src/Umbraco.Core/Services/{Implement => }/DashboardService.cs (100%) rename src/{Umbraco.Infrastructure/Services/Implement/Implement => Umbraco.Core/Services}/InstallationService.cs (87%) delete mode 100644 src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs rename src/Umbraco.Infrastructure/{Composing/CompositionExtensions/FileSystems.cs => DependencyInjection/UmbracoBuilder.FileSystems.cs} (87%) create mode 100644 src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs rename src/Umbraco.Infrastructure/{Composing/CompositionExtensions/CoreMappingProfiles.cs => DependencyInjection/UmbracoBuilder.MappingProfiles.cs} (79%) rename src/Umbraco.Infrastructure/{Composing/CompositionExtensions/Repositories.cs => DependencyInjection/UmbracoBuilder.Repositories.cs} (91%) rename src/Umbraco.Infrastructure/{Composing/CompositionExtensions/Services.cs => DependencyInjection/UmbracoBuilder.Services.cs} (89%) diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index 51ff6b705f..97e70da9be 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.DependencyInjection where TService : class => services.Replace(ServiceDescriptor.Singleton(instance)); - public static IServiceCollection AddLazySupport(this IServiceCollection services) + internal static IServiceCollection AddLazySupport(this IServiceCollection services) { services.Replace(ServiceDescriptor.Transient(typeof(Lazy<>), typeof(LazyResolve<>))); return services; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 1c05695429..11b035bb73 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -3,12 +3,36 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Grid; +using Umbraco.Core.Dictionary; using Umbraco.Core.Events; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Mail; +using Umbraco.Core.Manifest; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Runtime; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Sync; +using Umbraco.Web; +using Umbraco.Web.Cache; +using Umbraco.Web.Editors; +using Umbraco.Web.Features; +using Umbraco.Web.Install; +using Umbraco.Web.Models.PublishedContent; +using Umbraco.Web.Routing; +using Umbraco.Web.Services; +using Umbraco.Web.Templates; namespace Umbraco.Core.DependencyInjection { @@ -79,6 +103,93 @@ namespace Umbraco.Core.DependencyInjection // Register as singleton to allow injection everywhere. Services.AddSingleton(p => p.GetService); Services.AddSingleton(); + + Services.AddLazySupport(); + + Services.AddUnique(); + + Services.AddUnique(factory => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return new IOHelperLinux(hostingEnvironment); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return new IOHelperOSX(hostingEnvironment); + } + + return new IOHelperWindows(hostingEnvironment); + }); + + Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); + Services.AddUnique(factory => factory.GetRequiredService().RequestCache); + Services.AddUnique(); + Services.AddUnique(); + + this.AddNotificationHandler(); + + Services.AddSingleton(); + this.AddNotificationHandler(); + + Services.AddUnique(); + + // by default, register a noop factory + Services.AddUnique(); + + Services.AddUnique(); + Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); + + Services.AddUnique(); + + Services.AddUnique(); + + // will be injected in controllers when needed to invoke rest endpoints on Our + Services.AddUnique(); + Services.AddUnique(); + + // Grid config is not a real config file as we know them + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + + // register properties fallback + Services.AddUnique(); + + Services.AddUnique(); + + // register published router + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + + // register distributed cache + Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService())); + + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); } } } diff --git a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index d29f3ccb0b..ad92886ecd 100644 --- a/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.HealthCheck; +using Umbraco.Core.Mail; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Infrastructure.HealthCheck; diff --git a/src/Umbraco.Core/IEmailSender.cs b/src/Umbraco.Core/Mail/IEmailSender.cs similarity index 78% rename from src/Umbraco.Core/IEmailSender.cs rename to src/Umbraco.Core/Mail/IEmailSender.cs index aab944e04d..3862d0e717 100644 --- a/src/Umbraco.Core/IEmailSender.cs +++ b/src/Umbraco.Core/Mail/IEmailSender.cs @@ -1,7 +1,7 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Umbraco.Core.Models; -namespace Umbraco.Core +namespace Umbraco.Core.Mail { /// /// Simple abstraction to send an email message diff --git a/src/Umbraco.Core/ISmsSender.cs b/src/Umbraco.Core/Mail/ISmsSender.cs similarity index 84% rename from src/Umbraco.Core/ISmsSender.cs rename to src/Umbraco.Core/Mail/ISmsSender.cs index f296a2ea9b..a2ff054c48 100644 --- a/src/Umbraco.Core/ISmsSender.cs +++ b/src/Umbraco.Core/Mail/ISmsSender.cs @@ -1,6 +1,6 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace Umbraco.Core +namespace Umbraco.Core.Mail { /// /// Service to send an SMS diff --git a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs new file mode 100644 index 0000000000..bb8d787cbf --- /dev/null +++ b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Mail +{ + internal class NotImplementedEmailSender : IEmailSender + { + public Task SendAsync(EmailMessage message) + => throw new NotImplementedException("To send an Email ensure IEmailSender is implemented with a custom implementation"); + } +} diff --git a/src/Umbraco.Infrastructure/NotImplementedSmsSender.cs b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs similarity index 90% rename from src/Umbraco.Infrastructure/NotImplementedSmsSender.cs rename to src/Umbraco.Core/Mail/NotImplementedSmsSender.cs index ffc33373d0..16c3d04711 100644 --- a/src/Umbraco.Infrastructure/NotImplementedSmsSender.cs +++ b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Threading.Tasks; -namespace Umbraco.Core +namespace Umbraco.Core.Mail { /// /// An that throws diff --git a/src/Umbraco.Core/ITagQuery.cs b/src/Umbraco.Core/PublishedCache/ITagQuery.cs similarity index 100% rename from src/Umbraco.Core/ITagQuery.cs rename to src/Umbraco.Core/PublishedCache/ITagQuery.cs diff --git a/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs similarity index 96% rename from src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs rename to src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs index b67f41136a..5f219ac51f 100644 --- a/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs +++ b/src/Umbraco.Core/Runtime/AppPluginsManifestWatcherNotificationHandler.cs @@ -2,12 +2,11 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.Manifest; -namespace Umbraco.Infrastructure.Runtime +namespace Umbraco.Core.Runtime { /// /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes. diff --git a/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs similarity index 96% rename from src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs rename to src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index 5543662464..92ad5808b3 100644 --- a/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -1,13 +1,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Options; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.IO; -namespace Umbraco.Infrastructure.Runtime +namespace Umbraco.Core.Runtime { public class EssentialDirectoryCreator : INotificationHandler { diff --git a/src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs similarity index 92% rename from src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs rename to src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs index eed13f5060..74dea6742c 100644 --- a/src/Umbraco.Infrastructure/Runtime/MainDomSemaphoreLock.cs +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -29,7 +29,7 @@ namespace Umbraco.Core.Runtime _logger = logger; } - //WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + // WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread public Task ListenAsync() => _signal.WaitOneAsync(); public Task AcquireLockAsync(int millisecondsTimeout) @@ -44,7 +44,7 @@ namespace Umbraco.Core.Runtime // if more than 1 instance reach that point, one will get the lock // and the other one will timeout, which is accepted - //This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + // This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. try { _lockRelease = _systemLock.Lock(millisecondsTimeout); diff --git a/src/Umbraco.Core/Services/Implement/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs similarity index 100% rename from src/Umbraco.Core/Services/Implement/DashboardService.cs rename to src/Umbraco.Core/Services/DashboardService.cs diff --git a/src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs b/src/Umbraco.Core/Services/InstallationService.cs similarity index 87% rename from src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs rename to src/Umbraco.Core/Services/InstallationService.cs index a1f74e0862..6e283a61d7 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/Implement/InstallationService.cs +++ b/src/Umbraco.Core/Services/InstallationService.cs @@ -1,8 +1,8 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; -namespace Umbraco.Core.Services.Implement +namespace Umbraco.Core.Services { public class InstallationService : IInstallationService { diff --git a/src/Umbraco.Core/Services/UpgradeService.cs b/src/Umbraco.Core/Services/UpgradeService.cs index ead5270b41..b36eac7789 100644 --- a/src/Umbraco.Core/Services/UpgradeService.cs +++ b/src/Umbraco.Core/Services/UpgradeService.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Semver; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Services; -namespace Umbraco.Core +namespace Umbraco.Core.Services { public class UpgradeService : IUpgradeService { diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs deleted file mode 100644 index dc3b1e2481..0000000000 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Installer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Web.Install; -using Umbraco.Web.Install.InstallSteps; -using Umbraco.Web.Install.Models; - -namespace Umbraco.Web.Composing.CompositionExtensions -{ - public static class Installer - { - public static IUmbracoBuilder ComposeInstaller(this IUmbracoBuilder builder) - { - // register the installer steps - - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - // TODO: Add these back once we have a compatible Starter kit - // composition.Services.AddScoped(); - // composition.Services.AddScoped(); - // composition.Services.AddScoped(); - - builder.Services.AddScoped(); - - builder.Services.AddTransient(); - builder.Services.AddUnique(); - - return builder; - } - } -} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index e150f0cdba..b4fd03350a 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -1,18 +1,16 @@ -using System; +using System.Runtime.InteropServices; using Examine; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Dashboards; using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Dictionary; -using Umbraco.Core.Events; +using Umbraco.Core.Hosting; using Umbraco.Core.Install; +using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Media; using Umbraco.Core.Migrations; @@ -20,16 +18,14 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Packaging; +using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.PropertyEditors.ValueConverters; +using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; -using Umbraco.Core.Security; using Umbraco.Core.Serialization; -using Umbraco.Core.Services; -using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; -using Umbraco.Core.Sync; using Umbraco.Core.Templates; using Umbraco.Examine; using Umbraco.Infrastructure.Examine; @@ -37,10 +33,8 @@ using Umbraco.Infrastructure.Media; using Umbraco.Infrastructure.Runtime; using Umbraco.Web; using Umbraco.Web.Actions; -using Umbraco.Web.Cache; using Umbraco.Web.ContentApps; using Umbraco.Web.Editors; -using Umbraco.Web.Features; using Umbraco.Web.HealthCheck; using Umbraco.Web.HealthCheck.NotificationMethods; using Umbraco.Web.Install; @@ -54,8 +48,6 @@ using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Search; using Umbraco.Web.Sections; -using Umbraco.Web.Services; -using Umbraco.Web.Templates; using Umbraco.Web.Trees; using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter; @@ -66,7 +58,7 @@ namespace Umbraco.Infrastructure.DependencyInjection /* * TODO: Many of these things are not "Core" services and are probably not required to run - * + * * This should be split up: * - Distributed Cache * - BackOffice @@ -83,19 +75,25 @@ namespace Umbraco.Infrastructure.DependencyInjection * - Front End */ + /// + /// Adds all core Umbraco services required to run which may be replaced later in the pipeline + /// public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder) { - builder.AddNotificationHandler(); + builder.AddMainDom(); - builder.Services.AddSingleton(); - builder.AddNotificationHandler(); + builder.Services.AddUnique(); + builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase()); + builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); + builder.Services.AddUnique(); + builder.Services.AddUnique(); // composers builder - .ComposeRepositories() - .ComposeServices() - .ComposeCoreMappingProfiles() - .ComposeFileSystems(); + .AddRepositories() + .AddServices() + .AddCoreMappingProfiles() + .AddFileSystems(); // register persistence mappers - required by database factory so needs to be done here // means the only place the collection can be modified is in a runtime - afterwards it @@ -110,7 +108,6 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); // register database builder // *not* a singleton, don't want to keep it around @@ -146,41 +143,6 @@ namespace Umbraco.Infrastructure.DependencyInjection // references to media item/s builder.DataValueReferenceFactories(); - // register a server registrar, by default it's the db registrar - builder.Services.AddUnique(f => - { - var globalSettings = f.GetRequiredService>().Value; - - // TODO: we still register the full IServerMessenger because - // even on 1 single server we can have 2 concurrent app domains - var singleServer = globalSettings.DisableElectionForSingleServer; - return singleServer - ? (IServerRegistrar)new SingleServerRegistrar(f.GetRequiredService()) - : new DatabaseServerRegistrar( - new Lazy(f.GetRequiredService)); - }); - - // by default we'll use the database server messenger with default options (no callbacks), - // this will be overridden by the db thing in the corresponding components in the web - // project - builder.Services.AddUnique(factory - => new DatabaseServerMessenger( - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService>(), - factory.GetRequiredService(), - true, - new DatabaseServerMessengerCallbacks(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService>() - )); - - builder.CacheRefreshers() - .Add(() => builder.TypeLoader.GetCacheRefreshers()); - builder.PackageActions() .Add(() => builder.TypeLoader.GetPackageActions()); @@ -197,43 +159,20 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(factory => new MigrationBuilder(factory)); - // by default, register a noop factory - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.SetCultureDictionaryFactory(); - builder.Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); - builder.Services.AddUnique(); - // register the published snapshot accessor - the "current" published snapshot is in the umbraco context builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); - // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards builder.Dashboards() .Add(builder.TypeLoader.GetTypes()); - // will be injected in controllers when needed to invoke rest endpoints on Our - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - // Grid config is not a real config file as we know them - builder.Services.AddUnique(); - // Config manipulator builder.Services.AddUnique(); - // register the umbraco context factory - // composition.Services.AddUnique(); - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); @@ -254,15 +193,8 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.MediaUrlProviders() .Append(); - builder.Services.AddUnique(); - - // register properties fallback - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Actions() .Add(() => builder.TypeLoader.GetTypes()); @@ -311,9 +243,6 @@ namespace Umbraco.Infrastructure.DependencyInjection .Append() .Append(); - // register published router - builder.Services.AddUnique(); - // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); builder.HealthChecks() @@ -325,12 +254,14 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); builder.ContentFinders() + // all built-in finders in the correct order, // devs can then modify this list on application startup .Append() .Append() .Append() - //.Append() // disabled, this is an odd finder + + // .Append() // disabled, this is an odd finder .Append() .Append(); @@ -339,26 +270,13 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.SearchableTrees() .Add(() => builder.TypeLoader.GetTypes()); - // replace some services - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); + // replace builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddUnique(); - // register distributed cache - builder.Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService())); - builder.Services.AddScoped(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); builder.Services.AddScoped(factory => { @@ -366,14 +284,6 @@ namespace Umbraco.Infrastructure.DependencyInjection return new PublishedContentQuery(umbCtx.UmbracoContext.PublishedSnapshot, factory.GetRequiredService(), factory.GetRequiredService()); }); - builder.Services.AddUnique(); - - // register the http context and umbraco context accessors - // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when - // we have no http context, eg when booting Umbraco or in background threads, so instead - // let's use an hybrid accessor that can fall back to a ThreadStatic context. - builder.Services.AddUnique(); - // register accessors for cultures builder.Services.AddUnique(); @@ -389,17 +299,33 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); - builder.Services.AddUnique(factory => new LegacyPasswordSecurity()); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddUnique(); return builder; } + + private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + + builder.Services.AddUnique(factory => + { + var globalSettings = factory.GetRequiredService>().Value; + var connectionStrings = factory.GetRequiredService>().Value; + var hostingEnvironment = factory.GetRequiredService(); + + var dbCreator = factory.GetRequiredService(); + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var loggerFactory = factory.GetRequiredService(); + + return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false + ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment) + : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); + }); + + return builder; + } } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs index 4970df2b87..23e927df97 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -1,8 +1,11 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Core; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; +using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Sync; using Umbraco.Infrastructure.Cache; @@ -29,6 +32,21 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.SetServerMessenger(); builder.AddNotificationHandler(); + // TODO: We don't need server registrar anymore + // register a server registrar, by default it's the db registrar + builder.Services.AddUnique(f => + { + var globalSettings = f.GetRequiredService>().Value; + + // TODO: we still register the full IServerMessenger because + // even on 1 single server we can have 2 concurrent app domains + var singleServer = globalSettings.DisableElectionForSingleServer; + return singleServer + ? (IServerRegistrar)new SingleServerRegistrar(f.GetRequiredService()) + : new DatabaseServerRegistrar( + new Lazy(f.GetRequiredService)); + }); + builder.CacheRefreshers() .Add(() => builder.TypeLoader.GetCacheRefreshers()); diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs similarity index 87% rename from src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs index 4c8caa89b8..5c61fd2c60 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/FileSystems.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.FileSystems.cs @@ -1,17 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; -using Umbraco.Core.Strings; -using Umbraco.Infrastructure.DependencyInjection; -namespace Umbraco.Core.Composing.CompositionExtensions +namespace Umbraco.Infrastructure.DependencyInjection { - internal static class FileSystems + public static partial class UmbracoBuilderExtensions { /* * HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM @@ -35,16 +33,16 @@ namespace Umbraco.Core.Composing.CompositionExtensions * */ - public static IUmbracoBuilder ComposeFileSystems(this IUmbracoBuilder builder) + internal static IUmbracoBuilder AddFileSystems(this IUmbracoBuilder builder) { // register FileSystems, which manages all filesystems // it needs to be registered (not only the interface) because it provides additional // functionality eg for scoping, and is injected in the scope provider - whereas the // interface is really for end-users to get access to filesystems. - builder.Services.AddUnique(factory => factory.CreateInstance(factory)); + builder.Services.AddUnique(factory => factory.CreateInstance(factory)); // register IFileSystems, which gives access too all filesystems - builder.Services.AddUnique(factory => factory.GetRequiredService()); + builder.Services.AddUnique(factory => factory.GetRequiredService()); // register the scheme for media paths builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs new file mode 100644 index 0000000000..bcdead6fd0 --- /dev/null +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core.DependencyInjection; +using Umbraco.Web.Install; +using Umbraco.Web.Install.InstallSteps; +using Umbraco.Web.Install.Models; + +namespace Umbraco.Infrastructure.DependencyInjection +{ + public static partial class UmbracoBuilderExtensions + { + /// + /// Adds the services for the Umbraco installer + /// + public static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder) + { + // register the installer steps + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // TODO: Add these back once we have a compatible Starter kit + // composition.Services.AddScoped(); + // composition.Services.AddScoped(); + // composition.Services.AddScoped(); + builder.Services.AddScoped(); + + builder.Services.AddTransient(); + builder.Services.AddUnique(); + + return builder; + } + } +} diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs similarity index 79% rename from src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs index c700938534..4974a043b1 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs @@ -4,17 +4,14 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Security; using Umbraco.Web.Models.Mapping; -namespace Umbraco.Core.Composing.CompositionExtensions - +namespace Umbraco.Infrastructure.DependencyInjection { - public static class CoreMappingProfiles + public static partial class UmbracoBuilderExtensions { /// /// Registers the core Umbraco mapper definitions /// - /// - /// - public static IUmbracoBuilder ComposeCoreMappingProfiles(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddCoreMappingProfiles(this IUmbracoBuilder builder) { builder.Services.AddUnique(); @@ -34,8 +31,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions .Add() .Add() .Add() - .Add() - ; + .Add(); builder.Services.AddTransient(); builder.Services.AddTransient(); diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs similarity index 91% rename from src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 05b7371d15..1e32eddb5c 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -1,15 +1,18 @@ -using Umbraco.Core.DependencyInjection; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; -namespace Umbraco.Core.Composing.CompositionExtensions +namespace Umbraco.Infrastructure.DependencyInjection { /// /// Composes repositories. /// - internal static class Repositories + public static partial class UmbracoBuilderExtensions { - public static IUmbracoBuilder ComposeRepositories(this IUmbracoBuilder builder) + /// + /// Adds the Umbraco repositories + /// + internal static IUmbracoBuilder AddRepositories(this IUmbracoBuilder builder) { // repositories builder.Services.AddUnique(); diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs similarity index 89% rename from src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs rename to src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs index b027a99c67..918bdcb941 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/Services.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Services.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.IO; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -15,16 +16,15 @@ using Umbraco.Core.Routing; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; -namespace Umbraco.Core.Composing.CompositionExtensions +namespace Umbraco.Infrastructure.DependencyInjection { - internal static class Services + public static partial class UmbracoBuilderExtensions { - public static IUmbracoBuilder ComposeServices(this IUmbracoBuilder builder) + /// + /// Adds Umbraco services + /// + internal static IUmbracoBuilder AddServices(this IUmbracoBuilder builder) { - // register a transient messages factory, which will be replaced by the web - // boot manager when running in a web context - builder.Services.AddUnique(); - // register the service context builder.Services.AddUnique(); @@ -59,7 +59,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.Services.AddTransient(SourcesFactory); + builder.Services.AddTransient(SourcesFactory); builder.Services.AddUnique(factory => new LocalizedTextService( factory.GetRequiredService>(), factory.GetRequiredService>())); @@ -82,9 +82,6 @@ namespace Umbraco.Core.Composing.CompositionExtensions /// /// Creates an instance of PackagesRepository for either the ICreatedPackagesRepository or the IInstalledPackagesRepository /// - /// - /// - /// private static PackagesRepository CreatePackageRepository(IServiceProvider factory, string packageRepoFileName) => new PackagesRepository( factory.GetRequiredService(), @@ -106,9 +103,9 @@ namespace Umbraco.Core.Composing.CompositionExtensions { var hostingEnvironment = container.GetRequiredService(); var globalSettings = container.GetRequiredService>().Value; - var mainLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(globalSettings.UmbracoPath , "config","lang"))); + var mainLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(globalSettings.UmbracoPath, "config", "lang"))); var appPlugins = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins)); - var configLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(Constants.SystemDirectories.Config ,"lang"))); + var configLangFolder = new DirectoryInfo(hostingEnvironment.MapPathContentRoot(WebPath.Combine(Constants.SystemDirectories.Config, "lang"))); var pluginLangFolders = appPlugins.Exists == false ? Enumerable.Empty() @@ -117,7 +114,7 @@ namespace Umbraco.Core.Composing.CompositionExtensions .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly)) .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false)); - //user defined langs that overwrite the default, these should not be used by plugin creators + // user defined langs that overwrite the default, these should not be used by plugin creators var userLangFolders = configLangFolder.Exists == false ? Enumerable.Empty() : configLangFolder diff --git a/src/Umbraco.Infrastructure/EmailSender.cs b/src/Umbraco.Infrastructure/EmailSender.cs index 539adcb971..4c377f1ff1 100644 --- a/src/Umbraco.Infrastructure/EmailSender.cs +++ b/src/Umbraco.Infrastructure/EmailSender.cs @@ -7,6 +7,7 @@ using MimeKit; using MimeKit.Text; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; +using Umbraco.Core.Mail; using Umbraco.Core.Models; using SmtpClient = MailKit.Net.Smtp.SmtpClient; diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs index 15ddcebb7e..d134010104 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs b/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs index 538c559cf6..ad5155e6d2 100644 --- a/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs +++ b/src/Umbraco.Infrastructure/Media/ImageDimensionExtractor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Drawing; using System.IO; using Umbraco.Core; @@ -18,7 +18,7 @@ namespace Umbraco.Web.Media /// use potentially large amounts of memory. public ImageSize GetDimensions(Stream stream) { - //Try to load with exif + // Try to load with exif try { if (ExifImageDimensionExtractor.TryGetDimensions(stream, out var width, out var height)) @@ -28,12 +28,13 @@ namespace Umbraco.Web.Media } catch { - //We will just swallow, just means we can't read exif data, we don't want to log an error either + // We will just swallow, just means we can't read exif data, we don't want to log an error either } - //we have no choice but to try to read in via GDI + // we have no choice but to try to read in via GDI try { + // TODO: We should be using ImageSharp for this using (var image = Image.FromStream(stream)) { var fileWidth = image.Width; @@ -43,7 +44,7 @@ namespace Umbraco.Web.Media } catch (Exception) { - //We will just swallow, just means we can't read via GDI, we don't want to log an error either + // We will just swallow, just means we can't read via GDI, we don't want to log an error either } return new ImageSize(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize); diff --git a/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs index 4970fc302e..df8a58fc8d 100644 --- a/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs +++ b/src/Umbraco.Infrastructure/Packaging/PackageInstallation.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -17,16 +17,11 @@ namespace Umbraco.Core.Packaging private readonly IPackageActionRunner _packageActionRunner; private readonly DirectoryInfo _applicationRootFolder; + /// - /// Constructor + /// Initializes a new instance of the class. /// - /// - /// - /// - /// - /// - public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner, - IHostingEnvironment hostingEnvironment) + public PackageInstallation(PackageDataInstallation packageDataInstallation, PackageFileInstallation packageFileInstallation, CompiledPackageXmlParser parser, IPackageActionRunner packageActionRunner, IHostingEnvironment hostingEnvironment) { _packageExtraction = new PackageExtraction(); _packageFileInstallation = packageFileInstallation ?? throw new ArgumentNullException(nameof(packageFileInstallation)); diff --git a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs index 3e99ca9b4b..653a878ab9 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; +using Umbraco.Core.Mail; namespace Umbraco.Core.Services.Implement { diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index fc2844db24..394884a0db 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -70,7 +70,6 @@ namespace Umbraco.Tests.Integration builder.Services.AddUnique(AppCaches.NoCache); builder.AddConfiguration() .AddUmbracoCore() - .AddNuCache() .Build(); services.AddRouting(); // LinkGenerator diff --git a/src/Umbraco.Tests.Integration/Umbraco.Core/IO/FileSystemsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Core/IO/FileSystemsTests.cs index 4474b95584..314a50ce75 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Core/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Core/IO/FileSystemsTests.cs @@ -1,27 +1,17 @@ -using System; +using System; using System.IO; using System.Text; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Composing.CompositionExtensions; -using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; -using Umbraco.Core.Services; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; -using FileSystems = Umbraco.Core.IO.FileSystems; namespace Umbraco.Tests.IO { [TestFixture] - [UmbracoTest()] + [UmbracoTest] public class FileSystemsTests : UmbracoIntegrationTest { [Test] diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index 40e70d9b5d..b23ccc9080 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -23,6 +23,7 @@ using Umbraco.Core.Diagnostics; using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Mail; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.PublishedContent; diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index bea5deb10c..8c89905c97 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -39,6 +39,7 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Mail; namespace Umbraco.Tests.TestHelpers { diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 4da5ba7189..433ef1de02 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -18,7 +18,6 @@ using Serilog; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; @@ -28,6 +27,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; +using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Mapping; using Umbraco.Core.Media; @@ -383,7 +383,7 @@ namespace Umbraco.Tests.Testing if (configure == false) return; Builder - .ComposeCoreMappingProfiles(); + .AddCoreMappingProfiles(); } protected virtual TypeLoader GetTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IHostingEnvironment hostingEnvironment, ILogger logger, IProfilingLogger profilingLogger, UmbracoTestOptions.TypeLoader option) @@ -452,7 +452,7 @@ namespace Umbraco.Tests.Testing if (withApplication == false) return; // default Datalayer/Repositories/SQL/Database/etc... - Builder.ComposeRepositories(); + Builder.AddRepositories(); Builder.Services.AddUnique(); @@ -497,7 +497,7 @@ namespace Umbraco.Tests.Testing => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService())); Builder.Services.AddUnique(factory => (IScopeAccessor)factory.GetRequiredService()); - Builder.ComposeServices(); + Builder.AddServices(); // composition root is doing weird things, fix Builder.Services.AddUnique(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 36e5c2b6fe..502ffbcba2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Mail; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 9d7999b9f7..fca8c49004 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.IO; +using Umbraco.Core.Mail; using Umbraco.Core.Mapping; using Umbraco.Core.Media; using Umbraco.Core.Models; diff --git a/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs index b648bd797f..a5707968ee 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PartialViewsTreeController.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Umbraco.Core.IO; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Composing; -using Umbraco.Web.Mvc; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 1d11f32916..f3109ac2da 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -105,12 +105,16 @@ namespace Umbraco.Web.Common.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - builder.Services.AddLazySupport(); + // Add ASP.NET specific services + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddHostedService(factory => factory.GetRequiredService()); // Add supported databases builder.AddUmbracoSqlCeSupport(); builder.AddUmbracoSqlServerSupport(); + // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( DbProviderFactories.GetFactory, factory.GetServices(), @@ -118,54 +122,6 @@ namespace Umbraco.Web.Common.DependencyInjection factory.GetServices() )); - builder.Services.AddUnique(factory => - { - var globalSettings = factory.GetRequiredService>().Value; - var connectionStrings = factory.GetRequiredService>().Value; - var hostingEnvironment = factory.GetRequiredService(); - - var dbCreator = factory.GetRequiredService(); - var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - var loggerFactory = factory.GetRequiredService(); - - return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false - ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment) - : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); - }); - - builder.Services.AddUnique(factory => - { - var hostingEnvironment = factory.GetRequiredService(); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return new IOHelperLinux(hostingEnvironment); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return new IOHelperOSX(hostingEnvironment); - } - - return new IOHelperWindows(hostingEnvironment); - } - - ); - builder.Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); - builder.Services.AddUnique(factory => factory.GetRequiredService().RequestCache); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase()); - builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddHostedService(factory => factory.GetRequiredService()); - builder.AddCoreInitialServices(); builder.AddComposers(); diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 49dcf5a6db..42352d19f2 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -20,11 +20,11 @@ using Umbraco.Web.Common.Profiler; using Umbraco.Web.Common.Routing; using Umbraco.Web.Common.Security; using Umbraco.Web.Common.Templates; -using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Macros; using Umbraco.Web.Security; using Umbraco.Web.Templates; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Infrastructure.DependencyInjection; namespace Umbraco.Web.Common.Runtime { @@ -75,7 +75,7 @@ namespace Umbraco.Web.Common.Runtime builder.Services.AddUnique(); //register the install components - builder.ComposeInstaller(); + builder.AddInstaller(); var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList(); builder.WithCollectionBuilder() From e785ac28a342cdc48b7c01c84385385037be6a58 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 14:29:26 +1100 Subject: [PATCH 12/88] Moves more services and registrations "up", removes AspNetCoreComposer logic and moves to AddWebComponents --- src/Umbraco.Core/Cache/DistributedCache.cs | 10 +- .../UmbracoBuilder.Collections.cs | 186 ++++++++++++++++-- .../UmbracoBuilder.Composers.cs | 26 +++ .../UmbracoBuilder.Configuration.cs | 59 ++++++ .../UmbracoBuilder.Events.cs | 4 +- .../DependencyInjection/UmbracoBuilder.cs | 5 + .../Media/IImageDimensionExtractor.cs | 0 .../Media/ImageSize.cs | 0 .../Media/UploadAutoFillProperties.cs | 0 .../PropertyEditorCollection.cs | 4 +- .../TextStringValueConverter.cs | 0 ...rter.cs => SimpleTinyMceValueConverter.cs} | 4 +- .../TextStringValueConverter.cs | 45 ----- src/Umbraco.Core/Trees/ISearchableTree.cs | 4 +- .../Trees}/SearchableApplicationTree.cs | 4 +- .../Trees}/SearchableTreeCollection.cs | 4 +- .../Trees}/SearchableTreeCollectionBuilder.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../BatchedDatabaseServerMessenger.cs | 5 +- ...abaseServerMessengerNotificationHandler.cs | 4 +- .../UmbracoBuilder.Collections.cs | 73 +------ .../UmbracoBuilder.CoreServices.cs | 122 +----------- .../UmbracoBuilder.DistributedCache.cs | 7 +- .../UmbracoBuilder.Installer.cs | 2 +- .../RteMacroRenderingValueConverter.cs | 4 +- .../Search/UmbracoTreeSearcher.cs | 2 +- .../UmbracoTestServerTestBase.cs | 1 - .../Scoping/ScopedRepositoryTests.cs | 17 +- .../Services/ContentEventsTests.cs | 5 - .../ContentTypeServiceVariantsTests.cs | 2 - .../Services/TrackRelationsTests.cs | 2 - .../PublishedContentTestBase.cs | 2 +- .../Controllers/EntityController.cs | 1 + .../UmbracoBuilderExtensions.cs | 4 +- .../Trees/ContentTreeController.cs | 1 + .../Trees/ContentTypeTreeController.cs | 1 + .../Trees/DataTypeTreeController.cs | 1 + .../Trees/MediaTreeController.cs | 1 + .../Trees/MediaTypeTreeController.cs | 1 + .../Trees/MemberTreeController.cs | 1 + .../Trees/MemberTypeTreeController.cs | 1 + .../Trees/TemplatesTreeController.cs | 1 + .../Trees/TreeControllerBase.cs | 1 + .../UmbracoBuilderExtensions.cs | 130 +++++++----- .../Runtime/AspNetCoreBootFailedComposer.cs | 17 -- .../Runtime/AspNetCoreComposer.cs | 58 ------ .../UmbracoBuilderExtensions.cs | 3 +- 47 files changed, 402 insertions(+), 429 deletions(-) create mode 100644 src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs create mode 100644 src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs rename src/{Umbraco.Infrastructure => Umbraco.Core}/Media/IImageDimensionExtractor.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Media/ImageSize.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/Media/UploadAutoFillProperties.cs (100%) rename src/{Umbraco.Infrastructure/PropertyEditors/ValueConverters => Umbraco.Core/PropertyEditors}/TextStringValueConverter.cs (100%) rename src/Umbraco.Core/PropertyEditors/ValueConverters/{TinyMceValueConverter.cs => SimpleTinyMceValueConverter.cs} (95%) delete mode 100644 src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs rename src/{Umbraco.Infrastructure/Search => Umbraco.Core/Trees}/SearchableApplicationTree.cs (92%) rename src/{Umbraco.Infrastructure/Search => Umbraco.Core/Trees}/SearchableTreeCollection.cs (97%) rename src/{Umbraco.Infrastructure/Search => Umbraco.Core/Trees}/SearchableTreeCollectionBuilder.cs (81%) delete mode 100644 src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs diff --git a/src/Umbraco.Core/Cache/DistributedCache.cs b/src/Umbraco.Core/Cache/DistributedCache.cs index 698e97c610..569ff47724 100644 --- a/src/Umbraco.Core/Cache/DistributedCache.cs +++ b/src/Umbraco.Core/Cache/DistributedCache.cs @@ -164,9 +164,15 @@ namespace Umbraco.Web.Cache #endregion // helper method to get an ICacheRefresher by its unique identifier - private ICacheRefresher GetRefresherById(Guid refresherGuid) + private ICacheRefresher GetRefresherById(Guid refresherGuid) { - return _cacheRefreshers[refresherGuid]; + ICacheRefresher refresher = _cacheRefreshers[refresherGuid]; + if (refresher == null) + { + throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}"); + } + + return refresher; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 35d8ba1025..f6dc6fd6ff 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,11 +1,21 @@ +using System.Security.Cryptography; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; using Umbraco.Core.HealthCheck; using Umbraco.Core.Manifest; +using Umbraco.Core.PackageActions; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.PropertyEditors.Validators; +using Umbraco.Core.Strings; +using Umbraco.Core.Trees; using Umbraco.Web.Actions; using Umbraco.Web.ContentApps; using Umbraco.Web.Dashboards; using Umbraco.Web.Editors; +using Umbraco.Web.HealthCheck; +using Umbraco.Web.HealthCheck.NotificationMethods; +using Umbraco.Web.Media.EmbedProviders; using Umbraco.Web.Routing; using Umbraco.Web.Sections; using Umbraco.Web.Tour; @@ -17,6 +27,97 @@ namespace Umbraco.Core.DependencyInjection /// public static partial class UmbracoBuilderExtensions { + /// + /// Adds all core collection builders + /// + internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) + { + builder.CacheRefreshers().Add(() => builder.TypeLoader.GetCacheRefreshers()); + builder.DataEditors().Add(() => builder.TypeLoader.GetDataEditors()); + builder.Actions().Add(() => builder.TypeLoader.GetTypes()); + // register known content apps + builder.ContentApps() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + // all built-in finders in the correct order, + // devs can then modify this list on application startup + builder.ContentFinders() + .Append() + .Append() + .Append() + /*.Append() // disabled, this is an odd finder */ + .Append() + .Append(); + builder.EditorValidators().Add(() => builder.TypeLoader.GetTypes()); + builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes()); + builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes()); + builder.TourFilters(); + builder.UrlProviders() + .Append() + .Append(); + builder.MediaUrlProviders() + .Append(); + // register back office sections in the order we want them rendered + builder.Sections() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + builder.Components(); + // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards + builder.Dashboards() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(builder.TypeLoader.GetTypes()); + builder.PackageActions().Add(() => builder.TypeLoader.GetPackageActions()); + builder.DataValueReferenceFactories(); + builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes()); + builder.UrlSegmentProviders().Append(); + builder.ManifestValueValidators() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); + builder.ManifestFilters(); + builder.MediaUrlGenerators(); + // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable + builder.OEmbedProviders() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + builder.SearchableTrees().Add(() => builder.TypeLoader.GetTypes()); + } + /// /// Gets the actions collection builder. /// @@ -52,6 +153,9 @@ namespace Umbraco.Core.DependencyInjection public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + public static HealthCheckNotificationMethodCollectionBuilder HealthCheckNotificationMethods(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + /// /// Gets the TourFilters collection builder. /// @@ -90,18 +194,63 @@ namespace Umbraco.Core.DependencyInjection /// /// The builder. public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); + => builder.WithCollectionBuilder(); + + /// + /// Gets the cache refreshers collection builder. + /// + /// The builder. + public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the package actions collection builder. + /// + /// The builder. + internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the data editor collection builder. + /// + /// The builder. + public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the data value reference factory collection builder. + /// + /// The builder. + public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the property value converters collection builder. + /// + /// The builder. + public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the url segment providers collection builder. + /// + /// The builder. + public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the validators collection builder. + /// + /// The builder. + internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the manifest filter collection builder. + /// + /// The builder. + public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); /// /// Gets the content finders collection builder. @@ -109,5 +258,18 @@ namespace Umbraco.Core.DependencyInjection /// The builder. public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + + /// + /// Gets the backoffice OEmbed Providers collection builder. + /// + /// The builder. + public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the back office searchable tree collection builder + /// + public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs new file mode 100644 index 0000000000..3479bd74d6 --- /dev/null +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Composing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Umbraco.Core.DependencyInjection +{ + /// + /// Extension methods for + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Adds Umbraco composers for plugins + /// + public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) + { + IEnumerable composerTypes = builder.TypeLoader.GetTypes(); + IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); + + return builder; + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs new file mode 100644 index 0000000000..1733536908 --- /dev/null +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Configuration.Models.Validation; + +namespace Umbraco.Core.DependencyInjection +{ + /// + /// Extension methods for + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Add Umbraco configuration services and options + /// + public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) + { + // Register configuration validators. + builder.Services.AddSingleton, ContentSettingsValidator>(); + builder.Services.AddSingleton, GlobalSettingsValidator>(); + builder.Services.AddSingleton, HealthChecksSettingsValidator>(); + builder.Services.AddSingleton, RequestHandlerSettingsValidator>(); + + // Register configuration sections. + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigActiveDirectory)); + builder.Services.Configure(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigContent)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigCoreDebug)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExceptionFilter)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHealthChecks)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHosting)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigImaging)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExamine)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigKeepAlive)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigLogging)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigMemberPassword)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigNuCache)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRequestHandler)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntime)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigSecurity)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTours)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting)); + builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins)); + + return builder; + } + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index a21ae74976..d5090de01b 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core.Events; namespace Umbraco.Core.DependencyInjection @@ -23,7 +24,8 @@ namespace Umbraco.Core.DependencyInjection where TNotification : INotification { // Register the handler as transient. This ensures that anything can be injected into it. - builder.Services.AddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); + // TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 + builder.Services.TryAddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); return builder; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 11b035bb73..32eed6d78d 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -20,6 +20,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Runtime; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -130,6 +131,7 @@ namespace Umbraco.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); + this.AddAllCoreCollectionBuilders(); this.AddNotificationHandler(); Services.AddSingleton(); @@ -190,6 +192,9 @@ namespace Umbraco.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); } } } diff --git a/src/Umbraco.Infrastructure/Media/IImageDimensionExtractor.cs b/src/Umbraco.Core/Media/IImageDimensionExtractor.cs similarity index 100% rename from src/Umbraco.Infrastructure/Media/IImageDimensionExtractor.cs rename to src/Umbraco.Core/Media/IImageDimensionExtractor.cs diff --git a/src/Umbraco.Infrastructure/Media/ImageSize.cs b/src/Umbraco.Core/Media/ImageSize.cs similarity index 100% rename from src/Umbraco.Infrastructure/Media/ImageSize.cs rename to src/Umbraco.Core/Media/ImageSize.cs diff --git a/src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs similarity index 100% rename from src/Umbraco.Infrastructure/Media/UploadAutoFillProperties.cs rename to src/Umbraco.Core/Media/UploadAutoFillProperties.cs diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index 2149ece02a..a3c02aeb0d 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -1,11 +1,9 @@ -using System.Linq; +using System.Linq; using Umbraco.Core.Composing; using Umbraco.Core.Manifest; namespace Umbraco.Core.PropertyEditors { - - public class PropertyEditorCollection : BuilderCollectionBase { public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs similarity index 100% rename from src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/TextStringValueConverter.cs rename to src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs similarity index 95% rename from src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs rename to src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs index 51471f6da7..64ecba5c7c 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; @@ -8,7 +8,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. /// [DefaultPropertyValueConverter] - public class TinyMceValueConverter : PropertyValueConverterBase + public class SimpleTinyMceValueConverter : PropertyValueConverterBase { public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs deleted file mode 100644 index 7caa9a90cc..0000000000 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TextStringValueConverter.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Linq; -using Umbraco.Core.Models.PublishedContent; - -namespace Umbraco.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class TextStringValueConverter : PropertyValueConverterBase - { - private static readonly string[] PropertyTypeAliases = - { - Constants.PropertyEditors.Aliases.TextBox, - Constants.PropertyEditors.Aliases.TextArea - }; - - public override bool IsConverter(IPublishedPropertyType propertyType) - => PropertyTypeAliases.Contains(propertyType.EditorAlias); - - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); - - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) - { - // in xml a string is: string - // in the database a string is: string - // default value is: null - return source; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter ?? string.Empty; - } - - public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter; - } - } -} diff --git a/src/Umbraco.Core/Trees/ISearchableTree.cs b/src/Umbraco.Core/Trees/ISearchableTree.cs index 3d82d548c8..8f31c2c6ab 100644 --- a/src/Umbraco.Core/Trees/ISearchableTree.cs +++ b/src/Umbraco.Core/Trees/ISearchableTree.cs @@ -1,8 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Composing; using Umbraco.Web.Models.ContentEditing; -namespace Umbraco.Web.Trees +namespace Umbraco.Core.Trees { public interface ISearchableTree : IDiscoverable { diff --git a/src/Umbraco.Infrastructure/Search/SearchableApplicationTree.cs b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs similarity index 92% rename from src/Umbraco.Infrastructure/Search/SearchableApplicationTree.cs rename to src/Umbraco.Core/Trees/SearchableApplicationTree.cs index 346f106b84..ad6fb7f43f 100644 --- a/src/Umbraco.Infrastructure/Search/SearchableApplicationTree.cs +++ b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs @@ -1,6 +1,4 @@ -using Umbraco.Web.Trees; - -namespace Umbraco.Web.Search +namespace Umbraco.Core.Trees { public class SearchableApplicationTree { diff --git a/src/Umbraco.Infrastructure/Search/SearchableTreeCollection.cs b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs similarity index 97% rename from src/Umbraco.Infrastructure/Search/SearchableTreeCollection.cs rename to src/Umbraco.Core/Trees/SearchableTreeCollection.cs index 5c251a857d..e562b763d4 100644 --- a/src/Umbraco.Infrastructure/Search/SearchableTreeCollection.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -6,7 +6,7 @@ using Umbraco.Core.Composing; using Umbraco.Web.Services; using Umbraco.Web.Trees; -namespace Umbraco.Web.Search +namespace Umbraco.Core.Trees { public class SearchableTreeCollection : BuilderCollectionBase { diff --git a/src/Umbraco.Infrastructure/Search/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs similarity index 81% rename from src/Umbraco.Infrastructure/Search/SearchableTreeCollectionBuilder.cs rename to src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs index a5e940b4a6..7922cf36ad 100644 --- a/src/Umbraco.Infrastructure/Search/SearchableTreeCollectionBuilder.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs @@ -1,8 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Composing; -using Umbraco.Web.Trees; -namespace Umbraco.Web.Search +namespace Umbraco.Core.Trees { public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 24b2fae683..263621aed7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index bafb537db6..131aac23f7 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -9,11 +9,9 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; -using Umbraco.Web.Routing; namespace Umbraco.Web { @@ -28,6 +26,9 @@ namespace Umbraco.Web private readonly IRequestCache _requestCache; private readonly IRequestAccessor _requestAccessor; + /// + /// Initializes a new instance of the class. + /// public BatchedDatabaseServerMessenger( IMainDom mainDom, IScopeProvider scopeProvider, diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs index b0921f7698..43527b5486 100644 --- a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs @@ -47,7 +47,7 @@ namespace Umbraco.Infrastructure.Cache // Hence we hook up a one-off task on an HTTP request to ensure this is retrieved, which caches the value and makes it available // for the hosted services to use when the HTTP request is not available. _requestAccessor.RouteAttempt += EnsureApplicationUrlOnce; - _requestAccessor.EndRequest += UmbracoModule_EndRequest; + _requestAccessor.EndRequest += EndRequest; Startup(); @@ -88,6 +88,6 @@ namespace Umbraco.Infrastructure.Cache /// /// Clear the batch on end request /// - private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.FlushBatch(); + private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.FlushBatch(); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs index 28bed7b363..4da9c93fb3 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs @@ -5,8 +5,8 @@ using Umbraco.Core.PackageActions; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; +using Umbraco.Core.Trees; using Umbraco.Web.Media.EmbedProviders; -using Umbraco.Web.Search; namespace Umbraco.Infrastructure.DependencyInjection { @@ -15,82 +15,11 @@ namespace Umbraco.Infrastructure.DependencyInjection /// public static partial class UmbracoBuilderExtensions { - /// - /// Gets the cache refreshers collection builder. - /// - /// The builder. - public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the mappers collection builder. /// /// The builder. public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - - /// - /// Gets the package actions collection builder. - /// - /// The builder. - internal static PackageActionCollectionBuilder PackageActions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the data editor collection builder. - /// - /// The builder. - public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the data value reference factory collection builder. - /// - /// The builder. - public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the property value converters collection builder. - /// - /// The builder. - public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the url segment providers collection builder. - /// - /// The builder. - public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the validators collection builder. - /// - /// The builder. - internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the manifest filter collection builder. - /// - /// The builder. - public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the backoffice OEmbed Providers collection builder. - /// - /// The builder. - public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the back office searchable tree collection builder - /// - /// - /// - public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); } } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index b4fd03350a..b5c76aad83 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -27,6 +27,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Serialization; using Umbraco.Core.Strings; using Umbraco.Core.Templates; +using Umbraco.Core.Trees; using Umbraco.Examine; using Umbraco.Infrastructure.Examine; using Umbraco.Infrastructure.Media; @@ -49,7 +50,6 @@ using Umbraco.Web.Routing; using Umbraco.Web.Search; using Umbraco.Web.Sections; using Umbraco.Web.Trees; -using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter; namespace Umbraco.Infrastructure.DependencyInjection { @@ -116,47 +116,18 @@ namespace Umbraco.Infrastructure.DependencyInjection // register manifest parser, will be injected in collection builders where needed builder.Services.AddUnique(); - // register our predefined validators - builder.ManifestValueValidators() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); - // register the manifest filter collection builder (collection is empty by default) builder.ManifestFilters(); - // properties and parameters derive from data editors - builder.DataEditors() - .Add(() => builder.TypeLoader.GetDataEditors()); - builder.MediaUrlGenerators() .Add() .Add(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - // Used to determine if a datatype/editor should be storing/tracking - // references to media item/s - builder.DataValueReferenceFactories(); - - builder.PackageActions() - .Add(() => builder.TypeLoader.GetPackageActions()); - - builder.PropertyValueConverters() - .Append(builder.TypeLoader.GetTypes()); - builder.Services.AddUnique(); builder.Services.AddUnique(factory => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetRequiredService>().Value))); - builder.UrlSegmentProviders() - .Append(); - builder.Services.AddUnique(factory => new MigrationBuilder(factory)); builder.Services.AddUnique(); @@ -166,10 +137,6 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); - // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards - builder.Dashboards() - .Add(builder.TypeLoader.GetTypes()); - // Config manipulator builder.Services.AddUnique(); @@ -177,99 +144,22 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); // both TinyMceValueConverter (in Core) and RteMacroRenderingValueConverter (in Web) will be - // discovered when CoreBootManager configures the converters. We HAVE to remove one of them - // here because there cannot be two converters for one property editor - and we want the full - // RteMacroRenderingValueConverter that converts macros, etc. So remove TinyMceValueConverter. - // (the limited one, defined in Core, is there for tests) - same for others + // discovered when CoreBootManager configures the converters. We will remove the basic one defined + // in core so that the more enhanced version is active. builder.PropertyValueConverters() - .Remove() - .Remove() - .Remove(); - - builder.UrlProviders() - .Append() - .Append(); - - builder.MediaUrlProviders() - .Append(); + .Remove(); builder.Services.AddUnique(); - builder.Actions() - .Add(() => builder.TypeLoader.GetTypes()); - - builder.EditorValidators() - .Add(() => builder.TypeLoader.GetTypes()); - - builder.TourFilters(); - builder.Services.AddUnique(); - // register OEmbed providers - no type scanning - all explicit opt-in of adding types - // note: IEmbedProvider is not IDiscoverable - think about it if going for type scanning - builder.OEmbedProviders() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - - // register back office sections in the order we want them rendered - builder.Sections() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - - // register known content apps - builder.ContentApps() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); - builder.HealthChecks() - .Add(() => builder.TypeLoader.GetTypes()); - - builder.WithCollectionBuilder() - .Add(() => builder.TypeLoader.GetTypes()); - + builder.Services.AddUnique(); - builder.ContentFinders() - - // all built-in finders in the correct order, - // devs can then modify this list on application startup - .Append() - .Append() - .Append() - - // .Append() // disabled, this is an odd finder - .Append() - .Append(); - builder.Services.AddScoped(); - builder.SearchableTrees() - .Add(() => builder.TypeLoader.GetTypes()); - // replace builder.Services.AddUnique(); @@ -303,6 +193,8 @@ namespace Umbraco.Infrastructure.DependencyInjection builder.Services.AddUnique(); + builder.AddInstaller(); + return builder; } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs index 23e927df97..8be46d38fb 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -26,7 +26,7 @@ namespace Umbraco.Infrastructure.DependencyInjection /// public static IUmbracoBuilder AddDistributedCache(this IUmbracoBuilder builder) { - // NOTE: the `DistributedCache` is registered in AddCoreInitialServices since it's a core service + // NOTE: the `DistributedCache` is registered in UmbracoBuilder since it's a core service builder.SetDatabaseServerMessengerCallbacks(GetCallbacks); builder.SetServerMessenger(); @@ -34,7 +34,7 @@ namespace Umbraco.Infrastructure.DependencyInjection // TODO: We don't need server registrar anymore // register a server registrar, by default it's the db registrar - builder.Services.AddUnique(f => + builder.Services.AddUnique(f => { var globalSettings = f.GetRequiredService>().Value; @@ -47,9 +47,6 @@ namespace Umbraco.Infrastructure.DependencyInjection new Lazy(f.GetRequiredService)); }); - builder.CacheRefreshers() - .Add(() => builder.TypeLoader.GetCacheRefreshers()); - builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs index bcdead6fd0..e5bdd844d8 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Installer.cs @@ -11,7 +11,7 @@ namespace Umbraco.Infrastructure.DependencyInjection /// /// Adds the services for the Umbraco installer /// - public static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder) + internal static IUmbracoBuilder AddInstaller(this IUmbracoBuilder builder) { // register the installer steps builder.Services.AddScoped(); diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs index 7fe202ed4c..d0713b46ff 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/RteMacroRenderingValueConverter.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text; using HtmlAgilityPack; using Umbraco.Core; @@ -16,7 +16,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters /// used dynamically. /// [DefaultPropertyValueConverter] - public class RteMacroRenderingValueConverter : TinyMceValueConverter + public class RteMacroRenderingValueConverter : SimpleTinyMceValueConverter { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IMacroRenderer _macroRenderer; diff --git a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs index ecaf7354ca..a2955dfef5 100644 --- a/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Infrastructure/Search/UmbracoTreeSearcher.cs @@ -8,11 +8,11 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Examine; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Routing; -using Umbraco.Web.Trees; namespace Umbraco.Web.Search { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index ef3735032e..017b0ddb45 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -154,7 +154,6 @@ namespace Umbraco.Tests.Integration.TestServerTest mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() - .AddNuCache() .Build(); } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 556dedee14..e42fd7dbf1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -14,7 +11,6 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; -using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -29,15 +25,12 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Scoping private DistributedCacheBinder _distributedCacheBinder; private IUserService UserService => GetRequiredService(); - private ILocalizationService LocalizationService => GetRequiredService(); - private IServerMessenger ServerMessenger => GetRequiredService(); - private CacheRefresherCollection CacheRefresherCollection => GetRequiredService(); - protected override void CustomTestSetup(IUmbracoBuilder builder) - { - builder.AddNuCache(); - builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IServerMessenger), typeof(LocalServerMessenger))); - } + private ILocalizationService LocalizationService => GetRequiredService(); + + private IServerMessenger ServerMessenger { get; } = new LocalServerMessenger(); + + private CacheRefresherCollection CacheRefresherCollection => GetRequiredService(); protected override AppCaches GetAppCaches() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs index 6b5af1e1e2..44459550d2 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs @@ -5,16 +5,13 @@ using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Sync; -using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; -using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.Cache; namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services @@ -54,8 +51,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services ContentTypeService.Save(_contentType); } - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddNuCache(); - [TearDown] public void TearDownTest() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 010f680ad1..b52609ce89 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -38,8 +38,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services private ILocalizationService LocalizationService => GetRequiredService(); - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddNuCache(); - protected override void BeforeHostStart(IHost host) { base.BeforeHostStart(host); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs index e4095c1d33..e6a8104355 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs @@ -19,8 +19,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services Boot = true)] public class TrackRelationsTests : UmbracoIntegrationTestWithContent { - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddNuCache(); - private IMediaTypeService MediaTypeService => GetRequiredService(); private IMediaService MediaService => GetRequiredService(); diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs index aa8fc26bd8..ec8e55e2bf 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestBase.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.PublishedContent Builder.WithCollectionBuilder() .Clear() .Append() - .Append() + .Append() .Append(); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index b2a6300cc9..219ca694e8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -31,6 +31,7 @@ using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Routing; using Umbraco.Web.Security; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Controllers { diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index e8c6171f01..30e6bdcbc7 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,6 +2,7 @@ using System; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Security; @@ -32,8 +33,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection .AddWebServer() .AddPreviewSupport() .AddHostedServices() - .AddHttpClients() - .AddNuCache(); + .AddDistributedCache(); /// /// Adds Umbraco back office authentication requirements diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 16dd446d49..404ebfdb3a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -22,6 +22,7 @@ using Microsoft.Extensions.Options; using Umbraco.Web.Trees; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index 8b5286bdd2..25c48b94bd 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Web.Actions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index ab2bfdb8d4..30389fb1be 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -16,6 +16,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index ece4013d0b..d284624999 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -22,6 +22,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index cd64e23067..424a0b2451 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -15,6 +15,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 4ebd8f7cc5..378a90da83 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -22,6 +22,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index be400bef39..8a8fe7b11e 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Attributes; diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs index 361875a41b..eb08dbe629 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Extensions; using Umbraco.Web.Actions; using Umbraco.Web.BackOffice.Filters; diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index e469cd4a25..ad4266e5e5 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; +using Umbraco.Core.Trees; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Filters; diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index f3109ac2da..29e3820637 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; @@ -25,22 +26,39 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Diagnostics; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Runtime; +using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.HostedServices; using Umbraco.Infrastructure.HostedServices.ServerRegistration; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Infrastructure.Runtime; +using Umbraco.Net; using Umbraco.Web.Cache; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.DependencyInjection; +using Umbraco.Web.Common.Install; +using Umbraco.Web.Common.Lifetime; +using Umbraco.Web.Common.Macros; +using Umbraco.Web.Common.Middleware; +using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Common.Profiler; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Common.Security; +using Umbraco.Web.Common.Templates; +using Umbraco.Web.Macros; +using Umbraco.Web.Security; using Umbraco.Web.Telemetry; +using Umbraco.Web.Templates; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.Common.DependencyInjection @@ -123,62 +141,13 @@ namespace Umbraco.Web.Common.DependencyInjection )); builder.AddCoreInitialServices(); + + // TODO: This should be a separate call to opt-in to plugins builder.AddComposers(); return builder; } - /// - /// Adds Umbraco composers for plugins - /// - public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) - { - IEnumerable composerTypes = builder.TypeLoader.GetTypes(); - IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); - new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); - - return builder; - } - - /// - /// Add Umbraco configuration services and options - /// - public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) - { - // Register configuration validators. - builder.Services.AddSingleton, ContentSettingsValidator>(); - builder.Services.AddSingleton, GlobalSettingsValidator>(); - builder.Services.AddSingleton, HealthChecksSettingsValidator>(); - builder.Services.AddSingleton, RequestHandlerSettingsValidator>(); - - // Register configuration sections. - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigActiveDirectory)); - builder.Services.Configure(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigContent)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigCoreDebug)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExceptionFilter)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHealthChecks)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHosting)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigImaging)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExamine)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigKeepAlive)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigLogging)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigMemberPassword)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigNuCache)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRequestHandler)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntime)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigSecurity)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTours)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting)); - builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins)); - - return builder; - } - /// /// Add Umbraco hosted services /// @@ -195,8 +164,7 @@ namespace Umbraco.Web.Common.DependencyInjection return builder; } - // TODO: Not sure this needs to exist and/or be public? - public static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) + private static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) { builder.Services.AddHttpClient(); return builder; @@ -241,6 +209,9 @@ namespace Umbraco.Web.Common.DependencyInjection return builder; } + /// + /// Adds all web based services required for Umbraco to run + /// public static IUmbracoBuilder AddWebComponents(this IUmbracoBuilder builder) { // Add service session @@ -257,6 +228,59 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.AddUmbracoImageSharp(builder.Config); + // AspNetCore specific services + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + // Our own netcore implementations + builder.Services.AddMultipleUnique(); + + builder.Services.AddUnique(); + + // The umbraco request lifetime + builder.Services.AddMultipleUnique(); + + // Password hasher + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + builder.Services.AddTransient(); + builder.Services.AddUnique(); + + builder.Services.AddMultipleUnique(); + + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + // register the umbraco context factory + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList(); + builder.WithCollectionBuilder() + .Add(umbracoApiControllerTypes); + + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + builder.AddNuCache(); + builder.AddHttpClients(); + return builder; } diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs deleted file mode 100644 index 758125b425..0000000000 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreBootFailedComposer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Web.Common.Middleware; - -namespace Umbraco.Web.Common.Runtime -{ - /// - /// Executes if the boot fails to ensure critical services are registered - /// - public class AspNetCoreBootFailedComposer : IComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 42352d19f2..34a8b7583a 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -35,63 +35,5 @@ namespace Umbraco.Web.Common.Runtime [ComposeBefore(typeof(ICoreComposer))] public class AspNetCoreComposer : ComponentComposer, IComposer { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - - // AspNetCore specific services - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - // Our own netcore implementations - builder.Services.AddMultipleUnique(); - - builder.Services.AddUnique(); - - // The umbraco request lifetime - builder.Services.AddMultipleUnique(); - - // Password hasher - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddTransient(); - builder.Services.AddUnique(); - - builder.Services.AddMultipleUnique(); - - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - // register the umbraco context factory - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - //register the install components - builder.AddInstaller(); - - var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList(); - builder.WithCollectionBuilder() - .Add(umbracoApiControllerTypes); - - - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - } } } diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index f495c09d3c..5762a5fb69 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; @@ -35,7 +36,7 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddScoped(); builder.Services.AddSingleton(); - builder.AddNuCache(); + builder.AddDistributedCache(); return builder; } From 307ef4c1e070193aae9c40dec562dbe371eef8ef Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 14:44:42 +1100 Subject: [PATCH 13/88] Removes IBatchedDatabaseServerMessenger, renames methods of IServerMessenger --- src/Umbraco.Core/Cache/DistributedCache.cs | 16 +++++++------- .../Sync/DatabaseServerMessengerCallbacks.cs | 4 ++-- .../Sync/IBatchedDatabaseServerMessenger.cs | 12 ----------- src/Umbraco.Core/Sync/IServerMessenger.cs | 21 ++++++++++++------- .../BatchedDatabaseServerMessenger.cs | 7 ++++--- ...abaseServerMessengerNotificationHandler.cs | 6 +++--- .../HostedServices/ScheduledPublishing.cs | 4 ++-- .../Sync/ServerMessengerBase.cs | 21 ++++++++++--------- .../Testing/IntegrationTestComposer.cs | 18 +++++++++------- .../Scoping/ScopedRepositoryTests.cs | 2 ++ .../Services/ContentEventsTests.cs | 2 ++ .../DistributedCache/DistributedCacheTests.cs | 18 +++++++++------- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 4 +++- 13 files changed, 70 insertions(+), 65 deletions(-) delete mode 100644 src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs diff --git a/src/Umbraco.Core/Cache/DistributedCache.cs b/src/Umbraco.Core/Cache/DistributedCache.cs index 569ff47724..7ad9f9569f 100644 --- a/src/Umbraco.Core/Cache/DistributedCache.cs +++ b/src/Umbraco.Core/Cache/DistributedCache.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return; - _serverMessenger.PerformRefresh( + _serverMessenger.QueueRefresh( GetRefresherById(refresherGuid), getNumericId, instances); @@ -61,7 +61,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || id == default(int)) return; - _serverMessenger.PerformRefresh( + _serverMessenger.QueueRefresh( GetRefresherById(refresherGuid), id); } @@ -75,7 +75,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || id == Guid.Empty) return; - _serverMessenger.PerformRefresh( + _serverMessenger.QueueRefresh( GetRefresherById(refresherGuid), id); } @@ -86,7 +86,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || payload == null) return; - _serverMessenger.PerformRefresh( + _serverMessenger.QueueRefresh( GetRefresherById(refresherGuid), payload); } @@ -97,7 +97,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || payloads == null) return; - _serverMessenger.PerformRefresh( + _serverMessenger.QueueRefresh( GetRefresherById(refresherGuid), payloads.ToArray()); } @@ -125,7 +125,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty) return; - _serverMessenger.PerformRefreshAll( + _serverMessenger.QueueRefreshAll( GetRefresherById(refresherGuid)); } @@ -138,7 +138,7 @@ namespace Umbraco.Web.Cache { if (refresherGuid == Guid.Empty || id == default(int)) return; - _serverMessenger.PerformRemove( + _serverMessenger.QueueRemove( GetRefresherById(refresherGuid), id); } @@ -155,7 +155,7 @@ namespace Umbraco.Web.Cache /// public void Remove(Guid refresherGuid, Func getNumericId, params T[] instances) { - _serverMessenger.PerformRemove( + _serverMessenger.QueueRemove( GetRefresherById(refresherGuid), getNumericId, instances); diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs index 7438762295..f7dafac7a6 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerCallbacks.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core.Sync { /// - /// Holds a list of callbacks associated with implementations of . + /// Holds a list of callbacks associated with implementations of . /// public class DatabaseServerMessengerCallbacks { diff --git a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs deleted file mode 100644 index 02859ff6f0..0000000000 --- a/src/Umbraco.Core/Sync/IBatchedDatabaseServerMessenger.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Umbraco.Core.Sync -{ - /// - /// An implementation that works by storing messages in the database. - /// - public interface IBatchedDatabaseServerMessenger : IServerMessenger - { - // TODO: We only ever use IBatchedDatabaseServerMessenger so just combine these interfaces - - void FlushBatch(); - } -} diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index a6c5b5d755..df2d665057 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -14,12 +14,17 @@ namespace Umbraco.Core.Sync /// void Sync(); + /// + /// Called to send/commit the queued messages created with the Perform methods + /// + void SendMessages(); + /// /// Notifies the distributed cache, for a specified . /// /// The ICacheRefresher. /// The notification content. - void PerformRefresh(ICacheRefresher refresher, TPayload[] payload); + void QueueRefresh(ICacheRefresher refresher, TPayload[] payload); /// /// Notifies the distributed cache of specified item invalidation, for a specified . @@ -28,7 +33,7 @@ namespace Umbraco.Core.Sync /// The ICacheRefresher. /// A function returning the unique identifier of items. /// The invalidated items. - void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances); + void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances); /// /// Notifies the distributed cache of specified item invalidation, for a specified . @@ -37,7 +42,7 @@ namespace Umbraco.Core.Sync /// The ICacheRefresher. /// A function returning the unique identifier of items. /// The invalidated items. - void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances); + void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances); /// /// Notifies all servers of specified items removal, for a specified . @@ -46,33 +51,33 @@ namespace Umbraco.Core.Sync /// The ICacheRefresher. /// A function returning the unique identifier of items. /// The removed items. - void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances); + void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances); /// /// Notifies all servers of specified items removal, for a specified . /// /// The ICacheRefresher. /// The unique identifiers of the removed items. - void PerformRemove(ICacheRefresher refresher, params int[] numericIds); + void QueueRemove(ICacheRefresher refresher, params int[] numericIds); /// /// Notifies all servers of specified items invalidation, for a specified . /// /// The ICacheRefresher. /// The unique identifiers of the invalidated items. - void PerformRefresh(ICacheRefresher refresher, params int[] numericIds); + void QueueRefresh(ICacheRefresher refresher, params int[] numericIds); /// /// Notifies all servers of specified items invalidation, for a specified . /// /// The ICacheRefresher. /// The unique identifiers of the invalidated items. - void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds); + void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds); /// /// Notifies all servers of a global invalidation for a specified . /// /// The ICacheRefresher. - void PerformRefreshAll(ICacheRefresher refresher); + void QueueRefreshAll(ICacheRefresher refresher); } } diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index 131aac23f7..caba831aff 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web /// /// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls /// - public class BatchedDatabaseServerMessenger : DatabaseServerMessenger, IBatchedDatabaseServerMessenger + public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { private readonly IRequestCache _requestCache; private readonly IRequestAccessor _requestAccessor; @@ -58,7 +58,7 @@ namespace Umbraco.Web BatchMessage(refresher, messageType, idsA, arrayType, json); } - public void FlushBatch() + public override void SendMessages() { var batch = GetBatch(false); if (batch == null) return; @@ -66,13 +66,14 @@ namespace Umbraco.Web var instructions = batch.SelectMany(x => x.Instructions).ToArray(); batch.Clear(); - //Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount + // Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount using (var scope = ScopeProvider.CreateScope()) { foreach (var instructionsBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) { WriteInstructions(scope, instructionsBatch); } + scope.Complete(); } diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs index 43527b5486..c8d8c81ab1 100644 --- a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs @@ -15,7 +15,7 @@ namespace Umbraco.Infrastructure.Cache /// public sealed class DatabaseServerMessengerNotificationHandler : INotificationHandler { - private readonly IBatchedDatabaseServerMessenger _messenger; + private readonly IServerMessenger _messenger; private readonly IRequestAccessor _requestAccessor; private readonly IUmbracoDatabaseFactory _databaseFactory; private readonly IDistributedCacheBinder _distributedCacheBinder; @@ -35,7 +35,7 @@ namespace Umbraco.Infrastructure.Cache _databaseFactory = databaseFactory; _distributedCacheBinder = distributedCacheBinder; _logger = logger; - _messenger = serverMessenger as IBatchedDatabaseServerMessenger; + _messenger = serverMessenger; } /// @@ -88,6 +88,6 @@ namespace Umbraco.Infrastructure.Cache /// /// Clear the batch on end request /// - private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.FlushBatch(); + private void EndRequest(object sender, UmbracoRequestEventArgs e) => _messenger?.SendMessages(); } } diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index 4c235255c2..bd73310c29 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -123,9 +123,9 @@ namespace Umbraco.Infrastructure.HostedServices finally { // If running on a temp context, we have to flush the messenger - if (contextReference.IsRoot && _serverMessenger is IBatchedDatabaseServerMessenger m) + if (contextReference.IsRoot) { - m.FlushBatch(); + _serverMessenger.SendMessages(); } } } diff --git a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs index 150f3428a7..dfba90291b 100644 --- a/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs +++ b/src/Umbraco.Infrastructure/Sync/ServerMessengerBase.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Sync #region IServerMessenger - public void PerformRefresh(ICacheRefresher refresher, TPayload[] payload) + public void QueueRefresh(ICacheRefresher refresher, TPayload[] payload) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); if (payload == null) throw new ArgumentNullException(nameof(payload)); @@ -72,7 +72,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RefreshByJson, json: jsonPayload); } - public void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) + public void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -83,7 +83,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RefreshByInstance, getId, instances); } - public void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) + public void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -94,7 +94,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RefreshByInstance, getId, instances); } - public void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) + public void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -105,7 +105,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RemoveByInstance, getId, instances); } - public void PerformRemove(ICacheRefresher refresher, params int[] numericIds) + public void QueueRemove(ICacheRefresher refresher, params int[] numericIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -114,7 +114,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RemoveById, numericIds.Cast()); } - public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) + public void QueueRefresh(ICacheRefresher refresher, params int[] numericIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -123,7 +123,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RefreshById, numericIds.Cast()); } - public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) + public void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -132,7 +132,7 @@ namespace Umbraco.Core.Sync Deliver(refresher, MessageType.RefreshById, guidIds.Cast()); } - public void PerformRefreshAll(ICacheRefresher refresher) + public void QueueRefreshAll(ICacheRefresher refresher) { if (refresher == null) throw new ArgumentNullException(nameof(refresher)); @@ -346,8 +346,6 @@ namespace Umbraco.Core.Sync DeliverRemote(refresher, messageType, idsA); } - public abstract void Sync(); - //protected virtual void Deliver(ICacheRefresher refresher, object payload) //{ // if (servers == null) throw new ArgumentNullException("servers"); @@ -367,5 +365,8 @@ namespace Umbraco.Core.Sync //} #endregion + + public abstract void Sync(); + public abstract void SendMessages(); } } diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index 1aeaec1bca..842a2a8a34 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -114,47 +114,49 @@ namespace Umbraco.Tests.Integration.Testing public NoopServerMessenger() { } - public void PerformRefresh(ICacheRefresher refresher, TPayload[] payload) + public void QueueRefresh(ICacheRefresher refresher, TPayload[] payload) { } - public void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) + public void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) { } - public void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) + public void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) { } - public void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) + public void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) { } - public void PerformRemove(ICacheRefresher refresher, params int[] numericIds) + public void QueueRemove(ICacheRefresher refresher, params int[] numericIds) { } - public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) + public void QueueRefresh(ICacheRefresher refresher, params int[] numericIds) { } - public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) + public void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds) { } - public void PerformRefreshAll(ICacheRefresher refresher) + public void QueueRefreshAll(ICacheRefresher refresher) { } public void Sync() { } + + public void SendMessages() { } } } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index e42fd7dbf1..0a1e46274b 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -328,6 +328,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Scoping : base(false) { } + public override void SendMessages() { } + public override void Sync() { } protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs index 44459550d2..68ec7fcf5f 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs @@ -2166,6 +2166,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services public LocalServerMessenger() : base(false) { } + public override void SendMessages() { } + public override void Sync() { } protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs index 6211711202..706ca94e71 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs @@ -134,30 +134,32 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache public List PayloadsRefreshed { get; } = new List(); public int CountOfFullRefreshes { get; private set; } = 0; - public void PerformRefresh(ICacheRefresher refresher, TPayload[] payload) + public void QueueRefresh(ICacheRefresher refresher, TPayload[] payload) { // doing nothing } public void PerformRefresh(ICacheRefresher refresher, string jsonPayload) => PayloadsRefreshed.Add(jsonPayload); - public void PerformRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) => IntIdsRefreshed.AddRange(instances.Select(getNumericId)); + public void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances) => IntIdsRefreshed.AddRange(instances.Select(getNumericId)); - public void PerformRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) => GuidIdsRefreshed.AddRange(instances.Select(getGuidId)); + public void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances) => GuidIdsRefreshed.AddRange(instances.Select(getGuidId)); public void PerformRemove(ICacheRefresher refresher, string jsonPayload) => PayloadsRemoved.Add(jsonPayload); - public void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) => IntIdsRemoved.AddRange(instances.Select(getNumericId)); + public void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) => IntIdsRemoved.AddRange(instances.Select(getNumericId)); - public void PerformRemove(ICacheRefresher refresher, params int[] numericIds) => IntIdsRemoved.AddRange(numericIds); + public void QueueRemove(ICacheRefresher refresher, params int[] numericIds) => IntIdsRemoved.AddRange(numericIds); - public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) => IntIdsRefreshed.AddRange(numericIds); + public void QueueRefresh(ICacheRefresher refresher, params int[] numericIds) => IntIdsRefreshed.AddRange(numericIds); - public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) => GuidIdsRefreshed.AddRange(guidIds); + public void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds) => GuidIdsRefreshed.AddRange(guidIds); - public void PerformRefreshAll(ICacheRefresher refresher) => CountOfFullRefreshes++; + public void QueueRefreshAll(ICacheRefresher refresher) => CountOfFullRefreshes++; public void Sync() { } + + public void SendMessages() { } } internal class TestServerRegistrar : IServerRegistrar diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index 51c306a864..b4009d6f3e 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Xml; using Microsoft.Extensions.DependencyInjection; @@ -300,6 +300,8 @@ namespace Umbraco.Tests.Scoping : base(false) { } + public override void SendMessages() { } + public override void Sync() { } protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) From 91486bbede4dc15c949704141b4a34d612301ed6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 16:35:59 +1100 Subject: [PATCH 14/88] Removes IServerRegistrar, new IServerRoleAccessor, removes more composers, moves more to ext, --- .../UmbracoBuilder.Composers.cs | 5 +- .../DependencyInjection/UmbracoBuilder.cs | 21 ++++++++ src/Umbraco.Core/Diagnostics/NoopMarchal.cs | 9 ++++ .../IUmbracoApplicationLifetime.cs | 11 ++-- .../IUmbracoApplicationLifetimeManager.cs | 8 +++ .../NoopApplicationShutdownRegistry.cs | 8 +++ .../NoopUmbracoApplicationLifetimeManager.cs | 7 +++ .../InstallSteps/StarterKitInstallStep.cs | 2 +- .../{VoidProfiler.cs => NoopProfiler.cs} | 4 +- src/Umbraco.Core/Manifest/ManifestWatcher.cs | 2 +- .../Services/IServerRegistrationService.cs | 10 +--- .../Sync/DatabaseServerRegistrar.cs | 43 ---------------- .../Sync/ElectedServerRoleAccessor.cs | 29 +++++++++++ src/Umbraco.Core/Sync/IServerRegistrar.cs | 21 -------- src/Umbraco.Core/Sync/IServerRoleAccessor.cs | 15 ++++++ .../Sync/SingleServerRegistrar.cs | 44 ---------------- .../Sync/SingleServerRoleAccessor.cs | 19 +++++++ .../BatchedDatabaseServerMessenger.cs | 2 +- .../UmbracoBuilder.CoreServices.cs | 51 +++++++------------ .../UmbracoBuilder.DistributedCache.cs | 25 ++------- .../HostedServices/HealthCheckNotifier.cs | 6 +-- .../HostedServices/KeepAlive.cs | 6 +-- .../HostedServices/LogScrubber.cs | 6 +-- .../HostedServices/ScheduledPublishing.cs | 6 +-- .../ServerRegistration/TouchServerTask.cs | 4 +- .../InstallSteps/StarterKitDownloadStep.cs | 2 +- .../Enrichers/ThreadAbortExceptionEnricher.cs | 4 +- .../Logging/Serilog/SerilogComposer.cs | 21 -------- .../Implement/ServerRegistrationService.cs | 35 ++++++------- .../Sync/DatabaseServerMessenger.cs | 6 +-- .../Compose/ModelsBuilderComponent.cs | 2 +- .../UmbracoBuilderExtensions.cs} | 50 +++++++++--------- .../Implementations/TestHelper.cs | 2 +- src/Umbraco.Tests.Integration/RuntimeTests.cs | 6 ++- .../UmbracoBuilderExtensions.cs | 27 ---------- .../UmbracoTestServerTestBase.cs | 4 +- .../Testing/UmbracoIntegrationTest.cs | 16 +++--- .../DistributedCache/DistributedCacheTests.cs | 6 +-- .../HealthCheckNotifierTests.cs | 4 +- .../HostedServices/KeepAliveTests.cs | 4 +- .../HostedServices/LogScrubberTests.cs | 4 +- .../ScheduledPublishingTests.cs | 4 +- .../TouchServerTaskTests.cs | 5 +- .../Scoping/ScopedNuCacheTests.cs | 4 +- src/Umbraco.Tests/Scoping/ScopedXmlTests.cs | 2 +- .../Controllers/PackageInstallController.cs | 1 - .../AspNetCoreApplicationShutdownRegistry.cs | 17 +++---- .../AspNetCoreUmbracoApplicationLifetime.cs | 9 ++-- .../UmbracoBuilderExtensions.cs | 21 ++++---- .../Install/InstallApiController.cs | 2 +- .../Profiler/WebProfilerComponent.cs | 4 +- .../Runtime/AspNetCoreComponent.cs | 4 +- .../AspNetUmbracoApplicationLifetime.cs | 2 +- src/Umbraco.Web/UmbracoApplicationBase.cs | 4 +- 54 files changed, 273 insertions(+), 363 deletions(-) create mode 100644 src/Umbraco.Core/Diagnostics/NoopMarchal.cs rename src/Umbraco.Core/{Net => Hosting}/IUmbracoApplicationLifetime.cs (73%) create mode 100644 src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs create mode 100644 src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs create mode 100644 src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs rename src/Umbraco.Core/Logging/{VoidProfiler.cs => NoopProfiler.cs} (89%) delete mode 100644 src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs create mode 100644 src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs delete mode 100644 src/Umbraco.Core/Sync/IServerRegistrar.cs create mode 100644 src/Umbraco.Core/Sync/IServerRoleAccessor.cs delete mode 100644 src/Umbraco.Core/Sync/SingleServerRegistrar.cs create mode 100644 src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs delete mode 100644 src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs rename src/Umbraco.Tests.Integration/{Testing/IntegrationTestComposer.cs => DependencyInjection/UmbracoBuilderExtensions.cs} (78%) delete mode 100644 src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs index 3479bd74d6..5bd2fe9e8c 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Composing; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; +using Umbraco.Core.Composing; namespace Umbraco.Core.DependencyInjection { @@ -16,6 +15,8 @@ namespace Umbraco.Core.DependencyInjection /// public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) { + // TODO: Should have a better name + IEnumerable composerTypes = builder.TypeLoader.GetTypes(); IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 32eed6d78d..96f01d111a 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -8,10 +8,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Diagnostics; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; using Umbraco.Core.Hosting; @@ -107,6 +110,14 @@ namespace Umbraco.Core.DependencyInjection Services.AddLazySupport(); + // Adds no-op registrations as many core services require these dependencies but these + // dependencies cannot be fulfilled in the Core project + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(factory => @@ -195,6 +206,16 @@ namespace Umbraco.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); + + // register a server registrar, by default it's the db registrar + Services.AddUnique(f => + { + GlobalSettings globalSettings = f.GetRequiredService>().Value; + var singleServer = globalSettings.DisableElectionForSingleServer; + return singleServer + ? (IServerRoleAccessor)new SingleServerRoleAccessor() + : new ElectedServerRoleAccessor(f.GetRequiredService()); + }); } } } diff --git a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs new file mode 100644 index 0000000000..09629f9595 --- /dev/null +++ b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Core.Diagnostics +{ + internal class NoopMarchal : IMarchal + { + public IntPtr GetExceptionPointers() => IntPtr.Zero; + } +} diff --git a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs similarity index 73% rename from src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs rename to src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs index a032720d46..a4368a2634 100644 --- a/src/Umbraco.Core/Net/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs @@ -1,25 +1,20 @@ using System; -namespace Umbraco.Net +namespace Umbraco.Core.Hosting { - // TODO: This shouldn't be in this namespace? public interface IUmbracoApplicationLifetime { /// /// A value indicating whether the application is restarting after the current request. /// bool IsRestarting { get; } + /// /// Terminates the current application. The application restarts the next time a request is received for it. /// void Restart(); + // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications event EventHandler ApplicationInit; } - - - public interface IUmbracoApplicationLifetimeManager - { - void InvokeApplicationInit(); - } } diff --git a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs new file mode 100644 index 0000000000..778edc24dd --- /dev/null +++ b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Hosting +{ + // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications + public interface IUmbracoApplicationLifetimeManager + { + void InvokeApplicationInit(); + } +} diff --git a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs new file mode 100644 index 0000000000..3ffef04410 --- /dev/null +++ b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Hosting +{ + internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry + { + public void RegisterObject(IRegisteredObject registeredObject) { } + public void UnregisterObject(IRegisteredObject registeredObject) { } + } +} diff --git a/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs b/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs new file mode 100644 index 0000000000..7833fd1224 --- /dev/null +++ b/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Hosting +{ + internal class NoopUmbracoApplicationLifetimeManager : IUmbracoApplicationLifetimeManager + { + public void InvokeApplicationInit() { } + } +} diff --git a/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs b/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs index e3cd56c5c1..4866c472e6 100644 --- a/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/StarterKitInstallStep.cs @@ -2,9 +2,9 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Core.Services; -using Umbraco.Net; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Install.InstallSteps diff --git a/src/Umbraco.Core/Logging/VoidProfiler.cs b/src/Umbraco.Core/Logging/NoopProfiler.cs similarity index 89% rename from src/Umbraco.Core/Logging/VoidProfiler.cs rename to src/Umbraco.Core/Logging/NoopProfiler.cs index d771fd7630..e7b43e5e2d 100644 --- a/src/Umbraco.Core/Logging/VoidProfiler.cs +++ b/src/Umbraco.Core/Logging/NoopProfiler.cs @@ -1,8 +1,8 @@ -using System; +using System; namespace Umbraco.Core.Logging { - public class VoidProfiler : IProfiler + public class NoopProfiler : IProfiler { private readonly VoidDisposable _disposable = new VoidDisposable(); diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs index e74393a179..b6cd82b31f 100644 --- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs +++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; -using Umbraco.Net; +using Umbraco.Core.Hosting; namespace Umbraco.Core.Manifest { diff --git a/src/Umbraco.Core/Services/IServerRegistrationService.cs b/src/Umbraco.Core/Services/IServerRegistrationService.cs index 62bb68eb14..f0246dd287 100644 --- a/src/Umbraco.Core/Services/IServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/IServerRegistrationService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Sync; @@ -11,9 +11,8 @@ namespace Umbraco.Core.Services /// Touches a server to mark it as active; deactivate stale servers. /// /// The server URL. - /// The server unique identity. /// The time after which a server is considered stale. - void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout); + void TouchServer(string serverAddress, TimeSpan staleTimeout); /// /// Deactivates a server. @@ -38,11 +37,6 @@ namespace Umbraco.Core.Services /// from the database. IEnumerable GetActiveServers(bool refresh = false); - /// - /// Gets the current server identity. - /// - string CurrentServerIdentity { get; } - /// /// Gets the role of the current server. /// diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs deleted file mode 100644 index f361eb7a67..0000000000 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrar.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Services; - -namespace Umbraco.Core.Sync -{ - /// - /// A registrar that stores registered server nodes in the database. - /// - /// - /// This is the default registrar which determines a server's role by using a master election process. - /// The master election process doesn't occur until just after startup so this election process doesn't really affect the primary startup phase. - /// - public sealed class DatabaseServerRegistrar : IServerRegistrar - { - private readonly Lazy _registrationService; - - /// - /// Initializes a new instance of the class. - /// - /// The registration service. - /// Some options. - public DatabaseServerRegistrar(Lazy registrationService) - { - _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService)); - } - - /// - /// Gets the registered servers. - /// - public IEnumerable Registrations => _registrationService.Value.GetActiveServers(); - - /// - /// Gets the role of the current server in the application environment. - /// - public ServerRole GetCurrentServerRole() - { - var service = _registrationService.Value; - return service.GetCurrentServerRole(); - } - - } -} diff --git a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs new file mode 100644 index 0000000000..e4accd046b --- /dev/null +++ b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs @@ -0,0 +1,29 @@ +using System; +using Umbraco.Core.Services; + +namespace Umbraco.Core.Sync +{ + /// + /// Gets the current server's based on active servers registered with + /// + /// + /// This is the default service which determines a server's role by using a master election process. + /// The master election process doesn't occur until just after startup so this election process doesn't really affect the primary startup phase. + /// + public sealed class ElectedServerRoleAccessor : IServerRoleAccessor + { + private readonly IServerRegistrationService _registrationService; + + /// + /// Initializes a new instance of the class. + /// + /// The registration service. + /// Some options. + public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService)); + + /// + /// Gets the role of the current server in the application environment. + /// + public ServerRole CurrentServerRole => _registrationService.GetCurrentServerRole(); + } +} diff --git a/src/Umbraco.Core/Sync/IServerRegistrar.cs b/src/Umbraco.Core/Sync/IServerRegistrar.cs deleted file mode 100644 index 7e63b6b170..0000000000 --- a/src/Umbraco.Core/Sync/IServerRegistrar.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace Umbraco.Core.Sync -{ - /// - /// Provides server registrations to the distributed cache. - /// - public interface IServerRegistrar - { - /// - /// Gets the server registrations. - /// - IEnumerable Registrations { get; } // TODO: This isn't even used anymore, this whole interface can probably go away - - /// - /// Gets the role of the current server in the application environment. - /// - ServerRole GetCurrentServerRole(); - - } -} diff --git a/src/Umbraco.Core/Sync/IServerRoleAccessor.cs b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs new file mode 100644 index 0000000000..b23acbac7c --- /dev/null +++ b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Sync +{ + /// + /// Gets the current server's + /// + public interface IServerRoleAccessor + { + /// + /// Gets the role of the current server in the application environment. + /// + ServerRole CurrentServerRole { get; } + } +} diff --git a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs deleted file mode 100644 index fe03e195b2..0000000000 --- a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Web; - -namespace Umbraco.Core.Sync -{ - /// - /// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance - /// - /// - /// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the - /// which by default is done with master election by a database query. The master election process doesn't occur until just after startup - /// so this micro optimization doesn't really affect the primary startup phase. - /// - public class SingleServerRegistrar : IServerRegistrar - { - private readonly IRequestAccessor _requestAccessor; - private readonly Lazy _registrations; - - public IEnumerable Registrations => _registrations.Value; - - public SingleServerRegistrar(IRequestAccessor requestAccessor) - { - _requestAccessor = requestAccessor; - _registrations = new Lazy(() => new IServerAddress[] { new ServerAddressImpl(_requestAccessor.GetApplicationUrl().ToString()) }); - } - - public ServerRole GetCurrentServerRole() - { - return ServerRole.Single; - } - - - private class ServerAddressImpl : IServerAddress - { - public ServerAddressImpl(string serverAddress) - { - ServerAddress = serverAddress; - } - - public string ServerAddress { get; } - } - } -} diff --git a/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs new file mode 100644 index 0000000000..65b9559522 --- /dev/null +++ b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Umbraco.Web; + +namespace Umbraco.Core.Sync +{ + /// + /// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance + /// + /// + /// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the + /// which by default is done with master election by a database query. The master election process doesn't occur until just after startup + /// so this micro optimization doesn't really affect the primary startup phase. + /// + public class SingleServerRoleAccessor : IServerRoleAccessor + { + public ServerRole CurrentServerRole => ServerRole.Single; + } +} diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs index caba831aff..6900354202 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web IScopeProvider scopeProvider, IProfilingLogger proflog, ILogger logger, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, DatabaseServerMessengerCallbacks callbacks, IHostingEnvironment hostingEnvironment, CacheRefresherCollection cacheRefreshers, diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index b5c76aad83..94c1e3dcfa 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -6,10 +6,10 @@ using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Dashboards; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; using Umbraco.Core.Install; +using Umbraco.Core.Logging.Serilog.Enrichers; using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Media; @@ -19,28 +19,22 @@ using Umbraco.Core.Migrations.PostMigrations; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Packaging; using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.PropertyEditors.Validators; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Serialization; using Umbraco.Core.Strings; using Umbraco.Core.Templates; -using Umbraco.Core.Trees; using Umbraco.Examine; using Umbraco.Infrastructure.Examine; +using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Infrastructure.Media; using Umbraco.Infrastructure.Runtime; using Umbraco.Web; -using Umbraco.Web.Actions; -using Umbraco.Web.ContentApps; -using Umbraco.Web.Editors; using Umbraco.Web.HealthCheck; using Umbraco.Web.HealthCheck.NotificationMethods; using Umbraco.Web.Install; using Umbraco.Web.Media; -using Umbraco.Web.Media.EmbedProviders; using Umbraco.Web.Migrations.PostMigrations; using Umbraco.Web.Models.PublishedContent; using Umbraco.Web.PropertyEditors; @@ -48,39 +42,20 @@ using Umbraco.Web.PropertyEditors.ValueConverters; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Search; -using Umbraco.Web.Sections; using Umbraco.Web.Trees; namespace Umbraco.Infrastructure.DependencyInjection { public static partial class UmbracoBuilderExtensions { - - /* - * TODO: Many of these things are not "Core" services and are probably not required to run - * - * This should be split up: - * - Distributed Cache - * - BackOffice - * - Manifest - * - Property Editors - * - Packages - * - Dashboards - * - OEmbed - * - Sections - * - Content Apps - * - Health Checks - * - ETC... - * - Installation - * - Front End - */ - /// /// Adds all core Umbraco services required to run which may be replaced later in the pipeline /// public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder) { - builder.AddMainDom(); + builder + .AddMainDom() + .AddLogging(); builder.Services.AddUnique(); builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase()); @@ -155,7 +130,7 @@ namespace Umbraco.Infrastructure.DependencyInjection // register *all* checks, except those marked [HideFromTypeFinder] of course builder.Services.AddUnique(); - + builder.Services.AddUnique(); builder.Services.AddScoped(); @@ -198,10 +173,20 @@ namespace Umbraco.Infrastructure.DependencyInjection return builder; } + /// + /// Adds logging requirements for Umbraco + /// + private static IUmbracoBuilder AddLogging(this IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + return builder; + } + private static IUmbracoBuilder AddMainDom(this IUmbracoBuilder builder) { - builder.Services.AddUnique(); - builder.Services.AddUnique(factory => { var globalSettings = factory.GetRequiredService>().Value; diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs index 8be46d38fb..3ad7556c92 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -26,27 +26,10 @@ namespace Umbraco.Infrastructure.DependencyInjection /// public static IUmbracoBuilder AddDistributedCache(this IUmbracoBuilder builder) { - // NOTE: the `DistributedCache` is registered in UmbracoBuilder since it's a core service - builder.SetDatabaseServerMessengerCallbacks(GetCallbacks); builder.SetServerMessenger(); builder.AddNotificationHandler(); - // TODO: We don't need server registrar anymore - // register a server registrar, by default it's the db registrar - builder.Services.AddUnique(f => - { - var globalSettings = f.GetRequiredService>().Value; - - // TODO: we still register the full IServerMessenger because - // even on 1 single server we can have 2 concurrent app domains - var singleServer = globalSettings.DisableElectionForSingleServer; - return singleServer - ? (IServerRegistrar)new SingleServerRegistrar(f.GetRequiredService()) - : new DatabaseServerRegistrar( - new Lazy(f.GetRequiredService)); - }); - builder.Services.AddUnique(); return builder; } @@ -57,15 +40,15 @@ namespace Umbraco.Infrastructure.DependencyInjection /// The type of the server registrar. /// The builder. public static void SetServerRegistrar(this IUmbracoBuilder builder) - where T : class, IServerRegistrar - => builder.Services.AddUnique(); + where T : class, IServerRoleAccessor + => builder.Services.AddUnique(); /// /// Sets the server registrar. /// /// The builder. /// A function creating a server registrar. - public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory) + public static void SetServerRegistrar(this IUmbracoBuilder builder, Func factory) => builder.Services.AddUnique(factory); /// @@ -73,7 +56,7 @@ namespace Umbraco.Infrastructure.DependencyInjection /// /// The builder. /// A server registrar. - public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRegistrar registrar) + public static void SetServerRegistrar(this IUmbracoBuilder builder, IServerRoleAccessor registrar) => builder.Services.AddUnique(registrar); /// diff --git a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs index cd89ebc046..c1412d4169 100644 --- a/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs +++ b/src/Umbraco.Infrastructure/HostedServices/HealthCheckNotifier.cs @@ -30,7 +30,7 @@ namespace Umbraco.Infrastructure.HostedServices private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IRuntimeState _runtimeState; - private readonly IServerRegistrar _serverRegistrar; + private readonly IServerRoleAccessor _serverRegistrar; private readonly IMainDom _mainDom; private readonly IScopeProvider _scopeProvider; private readonly ILogger _logger; @@ -54,7 +54,7 @@ namespace Umbraco.Infrastructure.HostedServices HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IRuntimeState runtimeState, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, IMainDom mainDom, IScopeProvider scopeProvider, ILogger logger, @@ -87,7 +87,7 @@ namespace Umbraco.Infrastructure.HostedServices return; } - switch (_serverRegistrar.GetCurrentServerRole()) + switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); diff --git a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs index 6a56b6f98e..0ec237c6d6 100644 --- a/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs +++ b/src/Umbraco.Infrastructure/HostedServices/KeepAlive.cs @@ -24,7 +24,7 @@ namespace Umbraco.Infrastructure.HostedServices private readonly KeepAliveSettings _keepAliveSettings; private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; - private readonly IServerRegistrar _serverRegistrar; + private readonly IServerRoleAccessor _serverRegistrar; private readonly IHttpClientFactory _httpClientFactory; /// @@ -43,7 +43,7 @@ namespace Umbraco.Infrastructure.HostedServices IOptions keepAliveSettings, ILogger logger, IProfilingLogger profilingLogger, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, IHttpClientFactory httpClientFactory) : base(TimeSpan.FromMinutes(5), DefaultDelay) { @@ -64,7 +64,7 @@ namespace Umbraco.Infrastructure.HostedServices } // Don't run on replicas nor unknown role servers - switch (_serverRegistrar.GetCurrentServerRole()) + switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); diff --git a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs index ca87d3e84e..c933ee2470 100644 --- a/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs +++ b/src/Umbraco.Infrastructure/HostedServices/LogScrubber.cs @@ -23,7 +23,7 @@ namespace Umbraco.Infrastructure.HostedServices public class LogScrubber : RecurringHostedServiceBase { private readonly IMainDom _mainDom; - private readonly IServerRegistrar _serverRegistrar; + private readonly IServerRoleAccessor _serverRegistrar; private readonly IAuditService _auditService; private readonly LoggingSettings _settings; private readonly IProfilingLogger _profilingLogger; @@ -42,7 +42,7 @@ namespace Umbraco.Infrastructure.HostedServices /// The profiling logger. public LogScrubber( IMainDom mainDom, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, IAuditService auditService, IOptions settings, IScopeProvider scopeProvider, @@ -61,7 +61,7 @@ namespace Umbraco.Infrastructure.HostedServices internal override Task PerformExecuteAsync(object state) { - switch (_serverRegistrar.GetCurrentServerRole()) + switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); diff --git a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs index bd73310c29..b42de1add5 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ScheduledPublishing.cs @@ -27,7 +27,7 @@ namespace Umbraco.Infrastructure.HostedServices private readonly IRuntimeState _runtimeState; private readonly IServerMessenger _serverMessenger; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; - private readonly IServerRegistrar _serverRegistrar; + private readonly IServerRoleAccessor _serverRegistrar; private readonly IUmbracoContextFactory _umbracoContextFactory; /// @@ -44,7 +44,7 @@ namespace Umbraco.Infrastructure.HostedServices public ScheduledPublishing( IRuntimeState runtimeState, IMainDom mainDom, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, IContentService contentService, IUmbracoContextFactory umbracoContextFactory, ILogger logger, @@ -69,7 +69,7 @@ namespace Umbraco.Infrastructure.HostedServices return Task.CompletedTask; } - switch (_serverRegistrar.GetCurrentServerRole()) + switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs index 25e975582d..69f9280fc0 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTask.cs @@ -57,9 +57,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration try { - // TouchServer uses a proper unit of work etc underneath so even in a - // background task it is safe to call it without dealing with any scope. - _serverRegistrationService.TouchServer(serverAddress, _serverRegistrationService.CurrentServerIdentity, _globalSettings.DatabaseServerRegistrar.StaleServerTimeout); + _serverRegistrationService.TouchServer(serverAddress, _globalSettings.DatabaseServerRegistrar.StaleServerTimeout); } catch (Exception ex) { diff --git a/src/Umbraco.Infrastructure/Install/InstallSteps/StarterKitDownloadStep.cs b/src/Umbraco.Infrastructure/Install/InstallSteps/StarterKitDownloadStep.cs index 77385eb2fa..8bc5bcfdff 100644 --- a/src/Umbraco.Infrastructure/Install/InstallSteps/StarterKitDownloadStep.cs +++ b/src/Umbraco.Infrastructure/Install/InstallSteps/StarterKitDownloadStep.cs @@ -6,9 +6,9 @@ using Umbraco.Core.Services; using Umbraco.Core.Configuration; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Security; -using Umbraco.Net; using Umbraco.Web.Install.Models; using Umbraco.Web.Security; +using Umbraco.Core.Hosting; namespace Umbraco.Web.Install.InstallSteps { diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs index 8428b60fde..a85e52cffe 100644 --- a/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs +++ b/src/Umbraco.Infrastructure/Logging/Serilog/Enrichers/ThreadAbortExceptionEnricher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using System.Threading; using Microsoft.Extensions.Options; @@ -55,6 +55,7 @@ namespace Umbraco.Infrastructure.Logging.Serilog.Enrichers logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message)); } else + { try { var dumped = MiniDump.Dump(_marchal, _hostingEnvironment, withException: true); @@ -68,6 +69,7 @@ namespace Umbraco.Infrastructure.Logging.Serilog.Enrichers message = "Failed to create a minidump. " + ex; logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ThreadAbortExceptionInfo", message)); } + } } private static bool IsTimeoutThreadAbortException(Exception exception) diff --git a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs b/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs deleted file mode 100644 index 4d8046ee8c..0000000000 --- a/src/Umbraco.Infrastructure/Logging/Serilog/SerilogComposer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging.Serilog.Enrichers; -using Umbraco.Infrastructure.Logging.Serilog.Enrichers; - -namespace Umbraco.Infrastructure.Logging.Serilog -{ - public class SerilogComposer : ICoreComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs index 145bf54aaf..14197762c6 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ServerRegistrationService.cs @@ -1,8 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; -using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.Models; @@ -26,11 +25,12 @@ namespace Umbraco.Core.Services.Implement /// /// Initializes a new instance of the class. /// - /// A UnitOfWork provider. - /// A logger factory - /// - public ServerRegistrationService(IScopeProvider scopeProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IServerRegistrationRepository serverRegistrationRepository, IHostingEnvironment hostingEnvironment) + public ServerRegistrationService( + IScopeProvider scopeProvider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IServerRegistrationRepository serverRegistrationRepository, + IHostingEnvironment hostingEnvironment) : base(scopeProvider, loggerFactory, eventMessagesFactory) { _serverRegistrationRepository = serverRegistrationRepository; @@ -41,10 +41,10 @@ namespace Umbraco.Core.Services.Implement /// Touches a server to mark it as active; deactivate stale servers. /// /// The server URL. - /// The server unique identity. /// The time after which a server is considered stale. - public void TouchServer(string serverAddress, string serverIdentity, TimeSpan staleTimeout) + public void TouchServer(string serverAddress, TimeSpan staleTimeout) { + var serverIdentity = GetCurrentServerIdentity(); using (var scope = ScopeProvider.CreateScope()) { scope.WriteLock(Constants.Locks.Servers); @@ -144,19 +144,16 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets the local server identity. - /// - public string CurrentServerIdentity => NetworkHelper.MachineName // eg DOMAIN\SERVER - + "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT; - /// /// Gets the role of the current server. /// /// The role of the current server. - public ServerRole GetCurrentServerRole() - { - return _currentServerRole; - } + public ServerRole GetCurrentServerRole() => _currentServerRole; + + /// + /// Gets the local server identity. + /// + private string GetCurrentServerIdentity() => NetworkHelper.MachineName // eg DOMAIN\SERVER + + "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT; } } diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 11e1596529..26b1de5080 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Sync private readonly ManualResetEvent _syncIdle; private readonly object _locko = new object(); private readonly IProfilingLogger _profilingLogger; - private readonly IServerRegistrar _serverRegistrar; + private readonly IServerRoleAccessor _serverRegistrar; private readonly IHostingEnvironment _hostingEnvironment; private readonly CacheRefresherCollection _cacheRefreshers; @@ -60,7 +60,7 @@ namespace Umbraco.Core.Sync IScopeProvider scopeProvider, IProfilingLogger proflog, ILogger logger, - IServerRegistrar serverRegistrar, + IServerRoleAccessor serverRegistrar, bool distributedEnabled, DatabaseServerMessengerCallbacks callbacks, IHostingEnvironment hostingEnvironment, @@ -312,7 +312,7 @@ namespace Umbraco.Core.Sync _lastPruned = _lastSync; - switch (_serverRegistrar.GetCurrentServerRole()) + switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Single: case ServerRole.Master: diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index fb39007bd0..7afb166069 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -6,13 +6,13 @@ using Microsoft.Extensions.Options; using Umbraco.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.ModelsBuilder.Embedded.BackOffice; -using Umbraco.Net; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.WebAssets; diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs similarity index 78% rename from src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs rename to src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 842a2a8a34..88e0e9f502 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,72 +1,70 @@ -using Moq; -using NUnit.Framework; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Logging; +using Umbraco.Core.Runtime; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Sync; using Umbraco.Core.WebAssets; using Umbraco.Examine; +using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.PublishedCache.NuCache; -using Umbraco.Web.Scheduling; using Umbraco.Web.Search; -using Umbraco.Infrastructure.Cache; -namespace Umbraco.Tests.Integration.Testing +namespace Umbraco.Tests.Integration.DependencyInjection { /// /// This is used to replace certain services that are normally registered from our Core / Infrastructure that /// we do not want active within integration tests /// - /// - /// This is a IUserComposer so that it runs after all core composers - /// - public class IntegrationTestComposer : ComponentComposer + public static class UmbracoBuilderExtensions { - // TODO: Kill this and only enable using ext methods what we need (first we need to kill composers) - - public override void Compose(IUmbracoBuilder builder) + /// + /// Uses/Replaces services with testing services + /// + public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper) { - base.Compose(builder); + builder.Services.AddUnique(AppCaches.NoCache); + builder.Services.AddUnique(Mock.Of()); + builder.Services.AddUnique(testHelper.MainDom); builder.Services.AddUnique(); - builder.Services.AddUnique(factory => Mock.Of()); + builder.Services.AddUnique(factory => Mock.Of()); // we don't want persisted nucache files in tests builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }); - #if IS_WINDOWS +#if IS_WINDOWS // ensure all lucene indexes are using RAM directory (no file system) builder.Services.AddUnique(); - #endif +#endif // replace this service so that it can lookup the correct file locations - builder.Services.AddUnique(GetLocalizedTextService); + builder.Services.AddUnique(GetLocalizedTextService); builder.Services.AddUnique(); builder.Services.AddUnique(); + + return builder; } /// /// Used to register a replacement for where the file sources are the ones within the netcore project so /// we don't need to copy files /// - private ILocalizedTextService GetLocalizedTextService(IServiceProvider factory) + private static ILocalizedTextService GetLocalizedTextService(IServiceProvider factory) { var globalSettings = factory.GetRequiredService>(); var loggerFactory = factory.GetRequiredService(); @@ -77,7 +75,7 @@ namespace Umbraco.Tests.Integration.Testing { // get the src folder var currFolder = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); - while(!currFolder.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)) + while (!currFolder.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase)) { currFolder = currFolder.Parent; } @@ -98,8 +96,8 @@ namespace Umbraco.Tests.Integration.Testing // replace the default so there is no background index rebuilder private class TestBackgroundIndexRebuilder : BackgroundIndexRebuilder { - public TestBackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger profilingLogger , ILoggerFactory loggerFactory, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) - : base(mainDom, profilingLogger , loggerFactory, hostingEnvironment, indexRebuilder) + public TestBackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger profilingLogger, ILoggerFactory loggerFactory, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder) + : base(mainDom, profilingLogger, loggerFactory, hostingEnvironment, indexRebuilder) { } diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs index fd9ffe5d26..9c2da39076 100644 --- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs +++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.Integration.Implementations public ILoggerFactory ConsoleLoggerFactory { get; private set; } public IProfilingLogger ProfilingLogger { get; private set; } - public IProfiler Profiler { get; } = new VoidProfiler(); + public IProfiler Profiler { get; } = new NoopProfiler(); public IHttpContextAccessor GetHttpContextAccessor() => _httpContextAccessor; diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 394884a0db..502936a04a 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -37,10 +37,10 @@ namespace Umbraco.Tests.Integration } /// - /// Calling AddUmbracoCore to configure the container and UseUmbracoCore to start the runtime + /// This will boot up umbraco with components enabled to show they initialize and shutdown /// [Test] - public async Task UseUmbracoCore() + public async Task Start_And_Stop_Umbraco_With_Components_Enabled() { var testHelper = new TestHelper(); @@ -70,6 +70,8 @@ namespace Umbraco.Tests.Integration builder.Services.AddUnique(AppCaches.NoCache); builder.AddConfiguration() .AddUmbracoCore() + .AddWebComponents() + .AddComposers() .Build(); services.AddRouting(); // LinkGenerator diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs deleted file mode 100644 index 796f9a8669..0000000000 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Moq; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Runtime; -using Umbraco.Tests.Integration.Implementations; -using Umbraco.Web.Common.DependencyInjection; - -namespace Umbraco.Tests.Integration.TestServerTest -{ - public static class UmbracoBuilderExtensions - { - /// - /// Uses a test version of Umbraco Core with a test IRuntime - /// - public static IUmbracoBuilder AddTestCore(this IUmbracoBuilder builder, TestHelper testHelper) - { - builder.AddUmbracoCore(); - - builder.Services.AddUnique(AppCaches.NoCache); - builder.Services.AddUnique(Mock.Of()); - builder.Services.AddUnique(testHelper.MainDom); - - return builder; - } - } -} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 017b0ddb45..33c1c28e48 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; +using Umbraco.Tests.Integration.DependencyInjection; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -135,7 +136,7 @@ namespace Umbraco.Tests.Integration.TestServerTest builder .AddConfiguration() - .AddTestCore(TestHelper) // This is the important one! + .AddUmbracoCore() .AddWebComponents() .AddRuntimeMinifier() .AddBackOfficeAuthentication() @@ -154,6 +155,7 @@ namespace Umbraco.Tests.Integration.TestServerTest mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() + .AddTestServices(TestHelper) // This is the important one! .Build(); } diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index d8f27d27a7..21c3cf8304 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -27,8 +27,10 @@ using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Integration.DependencyInjection; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.TestServerTest; using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.BackOffice.DependencyInjection; @@ -212,7 +214,6 @@ namespace Umbraco.Tests.Integration.Testing TestHelper.Profiler); var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory); - builder.Services.AddLogger(TestHelper.GetHostingEnvironment(), TestHelper.GetLoggingConfiguration(), Configuration); builder.AddConfiguration() @@ -222,13 +223,14 @@ namespace Umbraco.Tests.Integration.Testing builder.Services.AddUnique(Mock.Of()); builder.Services.AddUnique(TestHelper.MainDom); + //.AddTestServices(TestHelper) + builder.AddWebComponents() + .AddRuntimeMinifier() + .AddBackOfficeAuthentication() + .AddBackOfficeIdentity(); + //.AddComposers(); + services.AddSignalR(); - - builder.AddWebComponents(); - builder.AddRuntimeMinifier(); - builder.AddBackOfficeAuthentication(); - builder.AddBackOfficeIdentity(); - services.AddMvc(); builder.Build(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs index 706ca94e71..0f16da11c7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs @@ -18,7 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache { private global::Umbraco.Web.Cache.DistributedCache _distributedCache; - private IServerRegistrar ServerRegistrar { get; set; } + private IServerRoleAccessor ServerRegistrar { get; set; } private TestServerMessenger ServerMessenger { get; set; } @@ -162,14 +162,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache public void SendMessages() { } } - internal class TestServerRegistrar : IServerRegistrar + internal class TestServerRegistrar : IServerRoleAccessor { public IEnumerable Registrations => new List { new TestServerAddress("localhost") }; - public ServerRole GetCurrentServerRole() => throw new NotImplementedException(); + public ServerRole CurrentServerRole => throw new NotImplementedException(); } public class TestServerAddress : IServerAddress diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs index ffad002928..d5bd10fe3c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/HealthCheckNotifierTests.cs @@ -138,8 +138,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockRunTimeState = new Mock(); mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); - var mockServerRegistrar = new Mock(); - mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); + var mockServerRegistrar = new Mock(); + mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); var mockMainDom = new Mock(); mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs index 98164a7aac..752da01f0f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/KeepAliveTests.cs @@ -81,8 +81,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockRequestAccessor = new Mock(); mockRequestAccessor.Setup(x => x.GetApplicationUrl()).Returns(new Uri(ApplicationUrl)); - var mockServerRegistrar = new Mock(); - mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); + var mockServerRegistrar = new Mock(); + mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); var mockMainDom = new Mock(); mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs index 564b716f75..b7e2f7d80e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/LogScrubberTests.cs @@ -67,8 +67,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices MaxLogAge = TimeSpan.FromMinutes(MaxLogAgeInMinutes), }; - var mockServerRegistrar = new Mock(); - mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); + var mockServerRegistrar = new Mock(); + mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); var mockMainDom = new Mock(); mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs index fa3a609ce6..17ff9f0c5d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ScheduledPublishingTests.cs @@ -90,8 +90,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices var mockRunTimeState = new Mock(); mockRunTimeState.SetupGet(x => x.Level).Returns(runtimeLevel); - var mockServerRegistrar = new Mock(); - mockServerRegistrar.Setup(x => x.GetCurrentServerRole()).Returns(serverRole); + var mockServerRegistrar = new Mock(); + mockServerRegistrar.Setup(x => x.CurrentServerRole).Returns(serverRole); var mockMainDom = new Mock(); mockMainDom.SetupGet(x => x.IsMainDom).Returns(isMainDom); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs index 7f58f39346..d293a5b7e8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/ServerRegistration/TouchServerTaskTests.cs @@ -21,7 +21,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe private Mock _mockServerRegistrationService; private const string ApplicationUrl = "https://mysite.com/"; - private const string ServerIdentity = "Test/1"; private readonly TimeSpan _staleServerTimeout = TimeSpan.FromMinutes(2); [TestCase(RuntimeLevel.Boot)] @@ -63,8 +62,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe var mockLogger = new Mock>(); _mockServerRegistrationService = new Mock(); - _mockServerRegistrationService.SetupGet(x => x.CurrentServerIdentity).Returns(ServerIdentity); - + var settings = new GlobalSettings { DatabaseServerRegistrar = new DatabaseServerRegistrarSettings @@ -89,7 +87,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices.ServerRe .Verify( x => x.TouchServer( It.Is(y => y == ApplicationUrl), - It.Is(y => y == ServerIdentity), It.Is(y => y == _staleServerTimeout)), times); } diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 4f424f4bb0..71809d063a 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; +using Umbraco.Core.Hosting; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; @@ -19,7 +20,6 @@ using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Core.Sync; using Umbraco.Infrastructure.PublishedCache.Persistence; -using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Scoping // FIXME: and we cannot inject a DistributedCache yet // so doing all this mess Builder.Services.AddUnique(); - Builder.Services.AddUnique(f => Mock.Of()); + Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); } diff --git a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs index b4009d6f3e..af94f6b2e1 100644 --- a/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedXmlTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.Scoping // FIXME: and we cannot inject a DistributedCache yet // so doing all this mess Builder.Services.AddUnique(); - Builder.Services.AddUnique(f => Mock.Of()); + Builder.Services.AddUnique(f => Mock.Of()); Builder.WithCollectionBuilder() .Add(() => Builder.TypeLoader.GetCacheRefreshers()); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs index 961ec388f7..d1f5d36b0f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs @@ -11,7 +11,6 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Models.Packaging; -using Umbraco.Net; using Umbraco.Core.Packaging; using Umbraco.Core.Security; using Umbraco.Core.Services; diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs index 57ad83d4ba..93347ddaa0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreApplicationShutdownRegistry.cs @@ -13,10 +13,11 @@ namespace Umbraco.Web.Common.AspNetCore private readonly ConcurrentDictionary _registeredObjects = new ConcurrentDictionary(); + /// + /// Initializes a new instance of the class. + /// public AspNetCoreApplicationShutdownRegistry(IHostApplicationLifetime hostApplicationLifetime) - { - _hostApplicationLifetime = hostApplicationLifetime; - } + => _hostApplicationLifetime = hostApplicationLifetime; public void RegisterObject(IRegisteredObject registeredObject) { @@ -43,17 +44,11 @@ namespace Umbraco.Web.Common.AspNetCore { private readonly IRegisteredObject _inner; - public RegisteredObjectWrapper(IRegisteredObject inner) - { - _inner = inner; - } + public RegisteredObjectWrapper(IRegisteredObject inner) => _inner = inner; public CancellationTokenRegistration CancellationTokenRegistration { get; set; } - public void Stop(bool immediate) - { - _inner.Stop(immediate); - } + public void Stop(bool immediate) => _inner.Stop(immediate); } } } diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index f34197d23e..cdba8273a0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -1,22 +1,21 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; -using Umbraco.Net; +using Umbraco.Core.Hosting; namespace Umbraco.Web.Common.AspNetCore { public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager { - private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostApplicationLifetime _hostApplicationLifetime; - public AspNetCoreUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor, IHostApplicationLifetime hostApplicationLifetime) + public AspNetCoreUmbracoApplicationLifetime(IHostApplicationLifetime hostApplicationLifetime) { - _httpContextAccessor = httpContextAccessor; _hostApplicationLifetime = hostApplicationLifetime; } public bool IsRestarting { get; set; } + public void Restart() { IsRestarting = true; @@ -27,6 +26,8 @@ namespace Umbraco.Web.Common.AspNetCore { ApplicationInit?.Invoke(this, EventArgs.Empty); } + + // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 29e3820637..a2dde620b9 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -88,8 +88,6 @@ namespace Umbraco.Web.Common.DependencyInjection throw new ArgumentNullException(nameof(config)); } - // TODO: Should some/all of these registrations be moved directly into UmbracoBuilder? - IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config); var loggingDir = tempHostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.LogFiles); @@ -116,6 +114,9 @@ namespace Umbraco.Web.Common.DependencyInjection /// /// Adds core Umbraco services /// + /// + /// This will not add any composers/components + /// public static IUmbracoBuilder AddUmbracoCore(this IUmbracoBuilder builder) { if (builder is null) @@ -142,8 +143,9 @@ namespace Umbraco.Web.Common.DependencyInjection builder.AddCoreInitialServices(); - // TODO: This should be a separate call to opt-in to plugins - builder.AddComposers(); + // aspnet app lifetime mgmt + builder.Services.AddMultipleUnique(); + builder.Services.AddUnique(); return builder; } @@ -232,11 +234,6 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); - // Our own netcore implementations - builder.Services.AddMultipleUnique(); - - builder.Services.AddUnique(); - // The umbraco request lifetime builder.Services.AddMultipleUnique(); @@ -278,9 +275,11 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.AddUnique(); builder.Services.AddUnique(); - builder.AddNuCache(); builder.AddHttpClients(); + // TODO: Does this belong in web components?? + builder.AddNuCache(); + return builder; } @@ -364,7 +363,7 @@ namespace Umbraco.Web.Common.DependencyInjection { // should let it be null, that's how MiniProfiler is meant to work, // but our own IProfiler expects an instance so let's get one - return new VoidProfiler(); + return new NoopProfiler(); } var webProfiler = new WebProfiler(); diff --git a/src/Umbraco.Web.Common/Install/InstallApiController.cs b/src/Umbraco.Web.Common/Install/InstallApiController.cs index 8c32796ad8..6deecc2ce5 100644 --- a/src/Umbraco.Web.Common/Install/InstallApiController.cs +++ b/src/Umbraco.Web.Common/Install/InstallApiController.cs @@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; -using Umbraco.Net; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs index 0c10b7d95a..498b550c1a 100644 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Net; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.Middleware; +using Umbraco.Core.Hosting; namespace Umbraco.Web.Common.Profiler { @@ -31,7 +31,7 @@ namespace Umbraco.Web.Common.Profiler if (_profiler != null) return; // if VoidProfiler was registered, let it be known - if (profiler is VoidProfiler) + if (profiler is NoopProfiler) logger.LogInformation( "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); _profile = false; diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs index 9309bd7e38..5c7e47cf3f 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Net; +using Umbraco.Core.Hosting; using Umbraco.Web.Common.Lifetime; namespace Umbraco.Web.Common.Runtime diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index f0ff6e3cad..90261b1a5a 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -1,7 +1,7 @@ using System; using System.Threading; using System.Web; -using Umbraco.Net; +using Umbraco.Core.Hosting; namespace Umbraco.Web.AspNet { diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index bdf5b40a02..82182e26b7 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -68,10 +68,10 @@ namespace Umbraco.Web { // should let it be null, that's how MiniProfiler is meant to work, // but our own IProfiler expects an instance so let's get one - return new VoidProfiler(); + return new NoopProfiler(); } - return new VoidProfiler(); + return new NoopProfiler(); } protected UmbracoApplicationBase(ILogger logger, ILoggerFactory loggerFactory, SecuritySettings securitySettings, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IIOHelper ioHelper, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo) From cc84c866bcb2aac3187ef7a241c0edc9e44808d4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 16:44:50 +1100 Subject: [PATCH 15/88] moves file --- .../UmbracoBuilder.DistributedCache.cs | 2 +- .../{ => Sync}/BatchedDatabaseServerMessenger.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) rename src/Umbraco.Infrastructure/{ => Sync}/BatchedDatabaseServerMessenger.cs (96%) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs index 3ad7556c92..54c0feeea0 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Sync; using Umbraco.Infrastructure.Cache; -using Umbraco.Web; +using Umbraco.Core.Sync; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.Search; diff --git a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs similarity index 96% rename from src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs rename to src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs index 6900354202..c265461c99 100644 --- a/src/Umbraco.Infrastructure/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs @@ -12,8 +12,9 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; +using Umbraco.Web; -namespace Umbraco.Web +namespace Umbraco.Core.Sync { /// /// An implementation that works by storing messages in the database. @@ -61,7 +62,8 @@ namespace Umbraco.Web public override void SendMessages() { var batch = GetBatch(false); - if (batch == null) return; + if (batch == null) + return; var instructions = batch.SelectMany(x => x.Instructions).ToArray(); batch.Clear(); @@ -95,7 +97,8 @@ namespace Umbraco.Web { var key = nameof(BatchedDatabaseServerMessenger); - if (!_requestCache.IsAvailable) return null; + if (!_requestCache.IsAvailable) + return null; // no thread-safety here because it'll run in only 1 thread (request) at a time var batch = (ICollection)_requestCache.Get(key); From 1a0d961e42cf6831cb215b8f577c44fbbc738327 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 18:11:16 +1100 Subject: [PATCH 16/88] removes WebsiteComposer, BackOfficeComposer --- src/Umbraco.Core/Composing/Composers.cs | 3 +- .../ComponentRuntimeTests.cs | 76 ++++++++++ .../UmbracoBuilderExtensions.cs | 4 +- src/Umbraco.Tests.Integration/RuntimeTests.cs | 132 ------------------ .../UmbracoTestServerTestBase.cs | 1 + .../Testing/UmbracoIntegrationTest.cs | 17 +-- .../UmbracoBuilderExtensions.cs | 54 ++++++- .../Runtime/BackOfficeComposer.cs | 64 --------- .../Runtime/AspNetCoreComposer.cs | 27 ---- src/Umbraco.Web.UI.NetCore/Startup.cs | 1 + .../UmbracoBuilderExtensions.cs | 7 + .../Runtime/WebsiteComposer.cs | 24 ---- 12 files changed, 147 insertions(+), 263 deletions(-) create mode 100644 src/Umbraco.Tests.Integration/ComponentRuntimeTests.cs delete mode 100644 src/Umbraco.Tests.Integration/RuntimeTests.cs delete mode 100644 src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs delete mode 100644 src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs diff --git a/src/Umbraco.Core/Composing/Composers.cs b/src/Umbraco.Core/Composing/Composers.cs index 47f272cbf4..91c8244324 100644 --- a/src/Umbraco.Core/Composing/Composers.cs +++ b/src/Umbraco.Core/Composing/Composers.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -70,7 +70,6 @@ namespace Umbraco.Core.Composing foreach (var composer in composers) { - var componentType = composer.GetType(); composer.Compose(_builder); } } diff --git a/src/Umbraco.Tests.Integration/ComponentRuntimeTests.cs b/src/Umbraco.Tests.Integration/ComponentRuntimeTests.cs new file mode 100644 index 0000000000..ba6c6473fa --- /dev/null +++ b/src/Umbraco.Tests.Integration/ComponentRuntimeTests.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; +using Umbraco.Extensions; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; +using Umbraco.Tests.Integration.Extensions; +using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; +using Umbraco.Web.Common.DependencyInjection; + +namespace Umbraco.Tests.Integration +{ + + [TestFixture] + [UmbracoTest(Boot = true)] + public class ComponentRuntimeTests : UmbracoIntegrationTest + { + // ensure composers are added + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddComposers(); + + /// + /// This will boot up umbraco with components enabled to show they initialize and shutdown + /// + [Test] + public async Task Start_And_Stop_Umbraco_With_Components_Enabled() + { + IRuntime runtime = Services.GetRequiredService(); + IRuntimeState runtimeState = Services.GetRequiredService(); + IMainDom mainDom = Services.GetRequiredService(); + ComponentCollection components = Services.GetRequiredService(); + + MyComponent myComponent = components.OfType().First(); + + Assert.IsTrue(mainDom.IsMainDom); + Assert.IsNull(runtimeState.BootFailedException); + Assert.IsTrue(myComponent.IsInit, "The component was not initialized"); + + // force stop now + await runtime.StopAsync(CancellationToken.None); + Assert.IsTrue(myComponent.IsTerminated, "The component was not terminated"); + } + + public class MyComposer : IUserComposer + { + public void Compose(IUmbracoBuilder builder) => builder.Components().Append(); + } + + public class MyComponent : IComponent + { + public bool IsInit { get; private set; } + + public bool IsTerminated { get; private set; } + + private readonly ILogger _logger; + + public MyComponent(ILogger logger) => _logger = logger; + + public void Initialize() => IsInit = true; + + public void Terminate() => IsTerminated = true; + + } + } +} diff --git a/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs index 88e0e9f502..c1bdf19069 100644 --- a/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/DependencyInjection/UmbracoBuilderExtensions.cs @@ -34,9 +34,9 @@ namespace Umbraco.Tests.Integration.DependencyInjection /// /// Uses/Replaces services with testing services /// - public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper) + public static IUmbracoBuilder AddTestServices(this IUmbracoBuilder builder, TestHelper testHelper, AppCaches appCaches = null) { - builder.Services.AddUnique(AppCaches.NoCache); + builder.Services.AddUnique(appCaches ?? AppCaches.NoCache); builder.Services.AddUnique(Mock.Of()); builder.Services.AddUnique(testHelper.MainDom); diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs deleted file mode 100644 index 502936a04a..0000000000 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.DependencyInjection; -using Umbraco.Extensions; -using Umbraco.Infrastructure.PublishedCache.DependencyInjection; -using Umbraco.Tests.Integration.Extensions; -using Umbraco.Tests.Integration.Implementations; -using Umbraco.Web.Common.DependencyInjection; - -namespace Umbraco.Tests.Integration -{ - - [TestFixture] - public class RuntimeTests - { - [TearDown] - public void TearDown() - { - MyComponent.Reset(); - MyComposer.Reset(); - } - - [SetUp] - public void Setup() - { - MyComponent.Reset(); - MyComposer.Reset(); - } - - /// - /// This will boot up umbraco with components enabled to show they initialize and shutdown - /// - [Test] - public async Task Start_And_Stop_Umbraco_With_Components_Enabled() - { - var testHelper = new TestHelper(); - - IHostBuilder hostBuilder = new HostBuilder() - .ConfigureServices((hostContext, services) => - { - IWebHostEnvironment webHostEnvironment = testHelper.GetWebHostEnvironment(); - services.AddSingleton(testHelper.DbProviderFactoryCreator); - services.AddRequiredNetCoreServices(testHelper, webHostEnvironment); - - // Add it! - TypeLoader typeLoader = services.AddTypeLoader( - GetType().Assembly, - webHostEnvironment, - testHelper.GetHostingEnvironment(), - testHelper.ConsoleLoggerFactory, - AppCaches.NoCache, - hostContext.Configuration, - testHelper.Profiler); - - var builder = new UmbracoBuilder( - services, - hostContext.Configuration, - typeLoader, - testHelper.ConsoleLoggerFactory); - - builder.Services.AddUnique(AppCaches.NoCache); - builder.AddConfiguration() - .AddUmbracoCore() - .AddWebComponents() - .AddComposers() - .Build(); - - services.AddRouting(); // LinkGenerator - }); - - IHost host = await hostBuilder.StartAsync(); - var app = new ApplicationBuilder(host.Services); - - app.UseUmbracoCore(); - - // assert results - IRuntimeState runtimeState = app.ApplicationServices.GetRequiredService(); - IMainDom mainDom = app.ApplicationServices.GetRequiredService(); - - Assert.IsTrue(mainDom.IsMainDom); - Assert.IsNull(runtimeState.BootFailedException); - Assert.IsTrue(MyComponent.IsInit); - - await host.StopAsync(); - - Assert.IsTrue(MyComponent.IsTerminated); - } - - public class MyComposer : IUserComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Components().Append(); - IsComposed = true; - } - - public static void Reset() => IsComposed = false; - - public static bool IsComposed { get; private set; } - } - - public class MyComponent : IComponent - { - public static bool IsInit { get; private set; } - - public static bool IsTerminated { get; private set; } - - private readonly ILogger _logger; - - public MyComponent(ILogger logger) => _logger = logger; - - public void Initialize() => IsInit = true; - - public void Terminate() => IsTerminated = true; - - public static void Reset() - { - IsTerminated = false; - IsInit = false; - } - } - } -} diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 33c1c28e48..27209182fe 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -139,6 +139,7 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddUmbracoCore() .AddWebComponents() .AddRuntimeMinifier() + .AddBackOfficeCore() .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 21c3cf8304..17778f986c 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -217,25 +217,20 @@ namespace Umbraco.Tests.Integration.Testing builder.Services.AddLogger(TestHelper.GetHostingEnvironment(), TestHelper.GetLoggingConfiguration(), Configuration); builder.AddConfiguration() - .AddUmbracoCore(); - - builder.Services.AddUnique(GetAppCaches()); - builder.Services.AddUnique(Mock.Of()); - builder.Services.AddUnique(TestHelper.MainDom); - - //.AddTestServices(TestHelper) - builder.AddWebComponents() + .AddUmbracoCore() + .AddWebComponents() .AddRuntimeMinifier() .AddBackOfficeAuthentication() - .AddBackOfficeIdentity(); + .AddBackOfficeIdentity() + .AddTestServices(TestHelper, GetAppCaches()); //.AddComposers(); services.AddSignalR(); services.AddMvc(); - builder.Build(); - CustomTestSetup(builder); + + builder.Build(); } protected virtual AppCaches GetAppCaches() diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 30e6bdcbc7..fb2cbfb607 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,11 +1,21 @@ using System; +using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Services; +using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; -using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Web.BackOffice.Authorization; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.BackOffice.Middleware; +using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.BackOffice.Services; using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.DependencyInjection; @@ -25,6 +35,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection .AddUmbracoCore() .AddWebComponents() .AddRuntimeMinifier() + .AddBackOfficeCore() .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies() @@ -64,6 +75,11 @@ namespace Umbraco.Web.BackOffice.DependencyInjection }); builder.Services.ConfigureOptions(); + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + return builder; } @@ -117,5 +133,41 @@ namespace Umbraco.Web.BackOffice.DependencyInjection /// public static TreeCollectionBuilder Trees(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + + public static IUmbracoBuilder AddBackOfficeCore(this IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddUnique(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + // register back office trees + // the collection builder only accepts types inheriting from TreeControllerBase + // and will filter out those that are not attributed with TreeAttribute + var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList(); + builder.Trees() + .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); + + builder.ComposeWebMappingProfiles(); + + builder.Services.AddUnique(factory => + { + var path = "~/"; + var hostingEnvironment = factory.GetRequiredService(); + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + hostingEnvironment.MapPathContentRoot(path), + hostingEnvironment.ToAbsolute(path) + ); + }); + + builder.Services.AddUnique(); + builder.Services.AddUnique(); + + return builder; + } } } diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs deleted file mode 100644 index d933d00d68..0000000000 --- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Linq; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Core.Composing; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; -using Umbraco.Core.Services; -using Umbraco.Extensions; -using Umbraco.Web.BackOffice.Controllers; -using Umbraco.Web.BackOffice.DependencyInjection; -using Umbraco.Web.BackOffice.Filters; -using Umbraco.Web.BackOffice.Middleware; -using Umbraco.Web.BackOffice.Routing; -using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.BackOffice.Services; -using Umbraco.Web.BackOffice.Trees; -using Umbraco.Web.Common.Runtime; - -namespace Umbraco.Web.BackOffice.Runtime -{ - [ComposeBefore(typeof(ICoreComposer))] - [ComposeAfter(typeof(AspNetCoreComposer))] - public class BackOfficeComposer : IComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - builder.Services.AddUnique(); - - // register back office trees - // the collection builder only accepts types inheriting from TreeControllerBase - // and will filter out those that are not attributed with TreeAttribute - var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList(); - builder.Trees() - .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); - - builder.ComposeWebMappingProfiles(); - - builder.Services.AddUnique(factory => - { - var path = "~/"; - var hostingEnvironment = factory.GetRequiredService(); - return new PhysicalFileSystem( - factory.GetRequiredService(), - hostingEnvironment, - factory.GetRequiredService>(), - hostingEnvironment.MapPathContentRoot(path), - hostingEnvironment.ToAbsolute(path) - ); - }); - - builder.Services.AddUnique(); - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 34a8b7583a..1eda1cc23a 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -1,31 +1,4 @@ -using System.Linq; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; -using Umbraco.Core.Diagnostics; -using Umbraco.Core.Hosting; -using Umbraco.Core.Logging; -using Umbraco.Core.Security; -using Umbraco.Extensions; -using Umbraco.Net; -using Umbraco.Web.Common.AspNetCore; -using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Common.Install; -using Umbraco.Web.Common.Lifetime; -using Umbraco.Web.Common.Macros; -using Umbraco.Web.Common.Middleware; -using Umbraco.Web.Common.Profiler; -using Umbraco.Web.Common.Routing; -using Umbraco.Web.Common.Security; -using Umbraco.Web.Common.Templates; -using Umbraco.Web.Macros; -using Umbraco.Web.Security; -using Umbraco.Web.Templates; -using Umbraco.Web.Common.ModelBinders; -using Umbraco.Infrastructure.DependencyInjection; - namespace Umbraco.Web.Common.Runtime { diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 75212e3077..a09b850d98 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -46,6 +46,7 @@ namespace Umbraco.Web.UI.NetCore services.AddUmbraco(_env, _config) .AddBackOffice() .AddWebsite() + .AddComposers() .Build(); #pragma warning restore IDE0022 // Use expression body for methods diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 5762a5fb69..52f6d5df11 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,8 +2,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; +using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; +using Umbraco.Web.Website.Collections; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; using Umbraco.Web.Website.ViewEngines; @@ -20,6 +22,11 @@ namespace Umbraco.Web.Website.DependencyInjection /// public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) { + builder.Services.AddUnique(); + + builder.WithCollectionBuilder() + .Add(builder.TypeLoader.GetSurfaceControllers()); + // Set the render & plugin view engines (Super complicated, but this allows us to use the IServiceCollection // to inject dependencies into the viewEngines) builder.Services.AddTransient, RenderMvcViewOptionsSetup>(); diff --git a/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs b/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs deleted file mode 100644 index 2a4b85a0df..0000000000 --- a/src/Umbraco.Web.Website/Runtime/WebsiteComposer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Extensions; -using Umbraco.Web.Website.Routing; -using Umbraco.Web.Common.Runtime; -using Umbraco.Web.Website.Collections; - -namespace Umbraco.Web.Website.Runtime -{ - // web's initial composer composes after core's, and before all core composers - [ComposeBefore(typeof(ICoreComposer))] - [ComposeAfter(typeof(AspNetCoreComposer))] - public class WebsiteComposer : IComposer - { - public void Compose(IUmbracoBuilder builder) - { - builder.Services.AddUnique(); - - builder.WithCollectionBuilder() - .Add(builder.TypeLoader.GetSurfaceControllers()); - } - } -} - From c482b6df3d3c99e226bd1da208b188e3998fae1b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 29 Dec 2020 12:55:48 +0100 Subject: [PATCH 17/88] Another step towards getting rid of HttpResponseException --- .../Controllers/ContentController.cs | 62 +++++++------- .../Controllers/ContentTypeController.cs | 4 +- .../Controllers/ContentTypeControllerBase.cs | 28 +++---- .../Controllers/DataTypeController.cs | 59 +++++++------ .../Controllers/EntityController.cs | 84 +++++++++---------- .../Controllers/MacroRenderingController.cs | 14 ++-- .../Controllers/MediaController.cs | 67 ++++++++------- .../Controllers/MediaTypeController.cs | 52 ++++++------ .../Controllers/MemberGroupController.cs | 27 +++--- .../Controllers/MemberTypeController.cs | 45 ++++------ .../Controllers/PackageController.cs | 21 ++--- .../Controllers/RelationTypeController.cs | 30 +++---- .../Controllers/TemplateController.cs | 27 +++--- .../Install/InstallApiController.cs | 11 ++- 14 files changed, 262 insertions(+), 269 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 4b464388c2..9a9ac85c35 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -377,7 +377,7 @@ namespace Umbraco.Web.BackOffice.Controllers [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] [DetermineAmbiguousActionByPassingParameters] - public ContentItemDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi != null) @@ -385,7 +385,7 @@ namespace Umbraco.Web.BackOffice.Controllers return GetById(guidUdi.Guid); } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } /// @@ -395,12 +395,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [OutgoingEditorModelEvent] [DetermineAmbiguousActionByPassingParameters] - public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) + public ActionResult GetEmpty(string contentTypeAlias, int parentId) { var contentType = _contentTypeService.Get(contentTypeAlias); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } return GetEmpty(contentType, parentId); @@ -413,12 +413,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - public ContentItemDisplay GetEmptyByKey(Guid contentTypeKey, int parentId) + public ActionResult GetEmptyByKey(Guid contentTypeKey, int parentId) { var contentType = _contentTypeService.Get(contentTypeKey); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } return GetEmpty(contentType, parentId); @@ -442,12 +442,12 @@ namespace Umbraco.Web.BackOffice.Controllers [OutgoingEditorModelEvent] [DetermineAmbiguousActionByPassingParameters] - public ContentItemDisplay GetEmpty(int blueprintId, int parentId) + public ActionResult GetEmpty(int blueprintId, int parentId) { var blueprint = _contentService.GetBlueprintById(blueprintId); if (blueprint == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(blueprint, StatusCodes.Status404NotFound); } blueprint.Id = 0; @@ -642,7 +642,7 @@ namespace Umbraco.Web.BackOffice.Controllers return display; }); - return contentItemDisplay; + return contentItemDisplay.Value; } /// @@ -659,10 +659,10 @@ namespace Umbraco.Web.BackOffice.Controllers content => _contentService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id), MapToDisplay); - return contentItemDisplay; + return contentItemDisplay.Value; } - private async Task PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) + private async Task> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) { //Recent versions of IE/Edge may send in the full client side file path instead of just the file name. //To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all @@ -695,7 +695,7 @@ namespace Umbraco.Web.BackOffice.Controllers // add the model state to the outgoing object and throw a validation message var forDisplay = mapToDisplay(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); - throw HttpResponseException.CreateValidationErrorResponse(forDisplay); + return new ValidationErrorResult(forDisplay); } //if there's only one variant and the model state is not valid we cannot publish so change it to save @@ -852,7 +852,7 @@ namespace Umbraco.Web.BackOffice.Controllers //If the item is new and the operation was cancelled, we need to return a different // status code so the UI can handle it since it won't be able to redirect since there // is no Id to redirect to! - throw HttpResponseException.CreateValidationErrorResponse(display); + return new ValidationErrorResult(display); } } @@ -1482,7 +1482,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var notificationModel = new SimpleNotificationModel(); AddMessageForPublishStatus(new[] { publishResult }, notificationModel); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); } return Ok(); @@ -1534,7 +1534,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); } } else @@ -1544,7 +1544,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); } } @@ -1629,7 +1629,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toMove = ValidateMoveOrCopy(move); + var toMove = ValidateMoveOrCopy(move).Value; _contentService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -1651,7 +1651,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toCopy = ValidateMoveOrCopy(copy); + var toCopy = ValidateMoveOrCopy(copy).Value; var c = _contentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -1692,7 +1692,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (!unpublishResult.Success) { AddCancelMessage(content); - throw HttpResponseException.CreateValidationErrorResponse(content); + return new ValidationErrorResult(content); } else { @@ -1764,7 +1764,7 @@ namespace Umbraco.Web.BackOffice.Controllers } catch (UriFormatException) { - throw HttpResponseException.CreateValidationErrorResponse(_localizedTextService.Localize("assignDomain/invalidDomain")); + return new ValidationErrorResult(_localizedTextService.Localize("assignDomain/invalidDomain")); } } @@ -2019,25 +2019,25 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - private IContent ValidateMoveOrCopy(MoveOrCopy model) + private ActionResult ValidateMoveOrCopy(MoveOrCopy model) { if (model == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(model, StatusCodes.Status404NotFound); } var contentService = _contentService; var toMove = contentService.GetById(model.Id); if (toMove == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(toMove, StatusCodes.Status404NotFound); } if (model.ParentId < 0) { //cannot move if the content item is not allowed at the root if (toMove.ContentType.AllowedAsRoot == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("moveOrCopy/notAllowedAtRoot")); } } @@ -2046,7 +2046,7 @@ namespace Umbraco.Web.BackOffice.Controllers var parent = contentService.GetById(model.ParentId); if (parent == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(parent, StatusCodes.Status404NotFound); } var parentContentType = _contentTypeService.Get(parent.ContentTypeId); @@ -2054,19 +2054,19 @@ namespace Umbraco.Web.BackOffice.Controllers if (parentContentType.AllowedContentTypes.Select(x => x.Id).ToArray() .Any(x => x.Value == toMove.ContentType.Id) == false) { - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("moveOrCopy/notAllowedByContentType")); } // Check on paths - if ((string.Format(",{0},", parent.Path)).IndexOf(string.Format(",{0},", toMove.Id), StringComparison.Ordinal) > -1) + if ($",{parent.Path},".IndexOf($",{toMove.Id},", StringComparison.Ordinal) > -1) { - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( _localizedTextService.Localize("moveOrCopy/notAllowedByPath")); } } - return toMove; + return new ActionResult(toMove); } /// @@ -2391,7 +2391,7 @@ namespace Umbraco.Web.BackOffice.Controllers break; } - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); } [Authorize(Policy = AuthorizationPolicies.ContentPermissionProtectById)] diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index e6fc284498..e784ef744b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -223,7 +223,7 @@ namespace Umbraco.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public IActionResult GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) { - var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement) + var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement).Value .Select(x => new { contentType = x.Item1, @@ -254,7 +254,7 @@ namespace Umbraco.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableCompositionsFilter filter) { - var result = PerformGetWhereCompositionIsUsedInContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType) + var result = PerformGetWhereCompositionIsUsedInContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType).Value .Select(x => new { contentType = x diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 88a83ed217..2e1f711984 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -1,13 +1,12 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Mime; using System.Text; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Net.Http.Headers; using Umbraco.Core; using Umbraco.Core.Dictionary; using Umbraco.Core.Exceptions; @@ -16,6 +15,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; @@ -74,7 +74,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Wether the composite content types should be applicable for an element type /// - protected IEnumerable> PerformGetAvailableCompositeContentTypes(int contentTypeId, + protected ActionResult>> PerformGetAvailableCompositeContentTypes(int contentTypeId, UmbracoObjectTypes type, string[] filterContentTypes, string[] filterPropertyTypes, @@ -92,7 +92,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source = ContentTypeService.Get(contentTypeId); - if (source == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); } allContentTypes = ContentTypeService.GetAll().Cast().ToArray(); break; @@ -101,7 +101,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source =MediaTypeService.Get(contentTypeId); - if (source == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); } allContentTypes =MediaTypeService.GetAll().Cast().ToArray(); break; @@ -110,7 +110,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source = MemberTypeService.Get(contentTypeId); - if (source == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); } allContentTypes = MemberTypeService.GetAll().Cast().ToArray(); break; @@ -178,7 +178,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// Type of content Type, eg documentType or mediaType /// Id of composition content type /// - protected IEnumerable PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type) + protected ActionResult> PerformGetWhereCompositionIsUsedInContentTypes(int contentTypeId, UmbracoObjectTypes type) { var id = 0; @@ -205,7 +205,7 @@ namespace Umbraco.Web.BackOffice.Controllers } if (source == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(source, StatusCodes.Status404NotFound); id = source.Id; } @@ -416,11 +416,11 @@ namespace Umbraco.Web.BackOffice.Controllers case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(LocalizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); default: throw new ArgumentOutOfRangeException(); } @@ -457,11 +457,11 @@ namespace Umbraco.Web.BackOffice.Controllers case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(LocalizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); default: throw new ArgumentOutOfRangeException(); } @@ -558,7 +558,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (invalidCompositionException != null) { AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases); - return CreateModelStateValidationException(ctId, contentTypeSave, ct); + throw CreateModelStateValidationException(ctId, contentTypeSave, ct); } return null; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 0ffe64e251..a099953a08 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -6,6 +6,7 @@ using System.Net; using System.Net.Mime; using System.Text; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -17,10 +18,9 @@ using Umbraco.Core.Security; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; @@ -92,13 +92,14 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public DataTypeDisplay GetById(int id) + public ActionResult GetById(int id) { var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); } + return _umbracoMapper.Map(dataType); } @@ -108,13 +109,14 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public DataTypeDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); } + return _umbracoMapper.Map(dataType); } @@ -124,17 +126,18 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public DataTypeDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var dataType = _dataTypeService.GetDataType(guidUdi.Guid); if (dataType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); } + return _umbracoMapper.Map(dataType); } @@ -150,7 +153,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _dataTypeService.GetDataType(id); if (foundType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); } var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; _dataTypeService.Delete(foundType, currentUser.Id); @@ -171,12 +174,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// a DataTypeDisplay - public DataTypeDisplay GetCustomListView(string contentTypeAlias) + public ActionResult GetCustomListView(string contentTypeAlias) { var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); if (dt == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dt, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(dt); @@ -208,7 +211,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The data type id for the pre-values, -1 if it is a new data type /// - public IEnumerable GetPreValues(string editorAlias, int dataTypeId = -1) + public ActionResult> GetPreValues(string editorAlias, int dataTypeId = -1) { var propEd = _propertyEditors[editorAlias]; if (propEd == null) @@ -219,14 +222,14 @@ namespace Umbraco.Web.BackOffice.Controllers if (dataTypeId == -1) { //this is a new data type, so just return the field editors with default values - return _umbracoMapper.Map>(propEd); + return new ActionResult>(_umbracoMapper.Map>(propEd)); } //we have a data type associated var dataType = _dataTypeService.GetDataType(dataTypeId); if (dataType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); } //now, lets check if the data type has the current editor selected, if that is true @@ -235,11 +238,11 @@ namespace Umbraco.Web.BackOffice.Controllers if (dataType.EditorAlias == editorAlias) { //this is the currently assigned pre-value editor, return with values. - return _umbracoMapper.Map>(dataType); + return new ActionResult>(_umbracoMapper.Map>(dataType)); } //these are new pre-values, so just return the field editors with default values - return _umbracoMapper.Map>(propEd); + return new ActionResult>(_umbracoMapper.Map>(propEd)); } /// @@ -263,9 +266,10 @@ namespace Umbraco.Web.BackOffice.Controllers var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; var result = _dataTypeService.CreateContainer(parentId, name, currentUser.Id); - return result - ? Ok(result.Result) //return the id - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); //return the id + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } /// @@ -300,7 +304,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (DuplicateNameException ex) { ModelState.AddModelError("Name", ex.Message); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); } // map back to display model, and return @@ -335,11 +339,11 @@ namespace Umbraco.Web.BackOffice.Controllers case MoveOperationStatusType.FailedCancelledByEvent: //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); case MoveOperationStatusType.FailedNotAllowedByPath: var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); default: throw new ArgumentOutOfRangeException(); } @@ -350,9 +354,10 @@ namespace Umbraco.Web.BackOffice.Controllers var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; var result = _dataTypeService.RenameContainer(id, name, currentUser.Id); - return result - ? Ok(result.Result) - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index b2a6300cc9..2d3e3fc5f6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1,11 +1,9 @@ -using System; +using System; using System.Collections.Generic; -using System.Net; using Umbraco.Core; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; using System.Linq; -using System.Net.Http; using System.Reflection; using Microsoft.AspNetCore.Http; using Umbraco.Core.Models; @@ -19,6 +17,7 @@ using Umbraco.Core.Strings; using Umbraco.Core.Xml; using Umbraco.Extensions; using Umbraco.Web.BackOffice.ModelBinders; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Models; using Umbraco.Web.Models.TemplateQuery; using Umbraco.Web.Search; @@ -26,11 +25,9 @@ using Umbraco.Web.Services; using Umbraco.Web.Trees; using Constants = Umbraco.Core.Constants; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Routing; -using Umbraco.Web.Security; namespace Umbraco.Web.BackOffice.Controllers { @@ -207,7 +204,7 @@ namespace Umbraco.Web.BackOffice.Controllers [DetermineAmbiguousActionByPassingParameters] public IEnumerable GetPath(int id, UmbracoEntityTypes type) { - var foundContent = GetResultForId(id, type); + var foundContent = GetResultForId(id, type).Value; return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } @@ -221,7 +218,7 @@ namespace Umbraco.Web.BackOffice.Controllers [DetermineAmbiguousActionByPassingParameters] public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) { - var foundContent = GetResultForKey(id, type); + var foundContent = GetResultForKey(id, type).Value; return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); } @@ -233,14 +230,14 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetPath(Udi id, UmbracoEntityTypes type) + public ActionResult> GetPath(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; if (guidUdi != null) { - return GetPath(guidUdi.Guid, type); + return new ActionResult>(GetPath(guidUdi.Guid, type)); } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } /// @@ -254,7 +251,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var intId = _entityService.GetId(udi); if (!intId.Success) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(intId.Result, StatusCodes.Status404NotFound); UmbracoEntityTypes entityType; switch (udi.EntityType) { @@ -268,7 +265,7 @@ namespace Umbraco.Web.BackOffice.Controllers entityType = UmbracoEntityTypes.Member; break; default: - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(udi.EntityType, StatusCodes.Status404NotFound); } return GetUrl(intId.Result, entityType, culture); } @@ -356,11 +353,11 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [DetermineAmbiguousActionByPassingParameters] - public UrlAndAnchors GetUrlAndAnchors(Udi id, string culture = "*") + public ActionResult GetUrlAndAnchors(Udi id, string culture = "*") { var intId = _entityService.GetId(id); if (!intId.Success) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(intId.Result, StatusCodes.Status404NotFound); return GetUrlAndAnchors(intId.Result, culture); } @@ -395,7 +392,7 @@ namespace Umbraco.Web.BackOffice.Controllers [DetermineAmbiguousActionByPassingParameters] public EntityBasic GetById(int id, UmbracoEntityTypes type) { - return GetResultForId(id, type); + return GetResultForId(id, type).Value; } /// @@ -407,7 +404,7 @@ namespace Umbraco.Web.BackOffice.Controllers [DetermineAmbiguousActionByPassingParameters] public EntityBasic GetById(Guid id, UmbracoEntityTypes type) { - return GetResultForKey(id, type); + return GetResultForKey(id, type).Value; } /// @@ -417,14 +414,15 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public EntityBasic GetById(Udi id, UmbracoEntityTypes type) + public ActionResult GetById(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; if (guidUdi != null) { return GetResultForKey(guidUdi.Guid, type); } - throw new HttpResponseException(HttpStatusCode.NotFound); + + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } #endregion @@ -441,13 +439,14 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [HttpPost] [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) + public ActionResult> GetByIds([FromJsonPath]int[] ids, UmbracoEntityTypes type) { if (ids == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); } - return GetResultForIds(ids, type); + + return new ActionResult>(GetResultForIds(ids, type)); } /// @@ -462,13 +461,14 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [HttpPost] [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) + public ActionResult> GetByIds([FromJsonPath]Guid[] ids, UmbracoEntityTypes type) { if (ids == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); } - return GetResultForKeys(ids, type); + + return new ActionResult>(GetResultForKeys(ids, type)); } /// @@ -485,16 +485,16 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] [HttpPost] [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type) + public ActionResult> GetByIds([FromJsonPath]Udi[] ids, [FromQuery]UmbracoEntityTypes type) { if (ids == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); } if (ids.Length == 0) { - return Enumerable.Empty(); + return Enumerable.Empty().ToList(); } //all udi types will need to be the same in this list so we'll determine by the first @@ -503,10 +503,10 @@ namespace Umbraco.Web.BackOffice.Controllers var guidUdi = ids[0] as GuidUdi; if (guidUdi != null) { - return GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type); + return new ActionResult>(GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type)); } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } #endregion @@ -562,7 +562,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public PagedResult GetPagedChildren( + public ActionResult> GetPagedChildren( string id, UmbracoEntityTypes type, int pageNumber, @@ -580,13 +580,13 @@ namespace Umbraco.Web.BackOffice.Controllers if (Guid.TryParse(id, out _)) { //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(id, StatusCodes.Status404NotFound); } if (UdiParser.TryParse(id, out _)) { //Not supported currently - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(id, StatusCodes.Status404NotFound); } //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type @@ -624,7 +624,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public PagedResult GetPagedChildren( + public ActionResult> GetPagedChildren( int id, UmbracoEntityTypes type, int pageNumber, @@ -635,9 +635,9 @@ namespace Umbraco.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(pageNumber, StatusCodes.Status404NotFound); if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(pageSize, StatusCodes.Status404NotFound); var objectType = ConvertToObjectType(type); if (objectType.HasValue) @@ -724,7 +724,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - public PagedResult GetPagedDescendants( + public ActionResult> GetPagedDescendants( int id, UmbracoEntityTypes type, int pageNumber, @@ -735,9 +735,9 @@ namespace Umbraco.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(pageNumber, StatusCodes.Status404NotFound); if (pageSize <= 0) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(pageSize, StatusCodes.Status404NotFound); // re-normalize since NULL can be passed in filter = filter ?? string.Empty; @@ -972,7 +972,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - private EntityBasic GetResultForKey(Guid key, UmbracoEntityTypes entityType) + private ActionResult GetResultForKey(Guid key, UmbracoEntityTypes entityType) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -980,7 +980,7 @@ namespace Umbraco.Web.BackOffice.Controllers var found = _entityService.Get(key, objectType.Value); if (found == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(found, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(found); } @@ -1004,7 +1004,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - private EntityBasic GetResultForId(int id, UmbracoEntityTypes entityType) + private ActionResult GetResultForId(int id, UmbracoEntityTypes entityType) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) @@ -1012,7 +1012,7 @@ namespace Umbraco.Web.BackOffice.Controllers var found = _entityService.Get(id, objectType.Value); if (found == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(found, StatusCodes.Status404NotFound); } return MapEntity(found); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs index d11b89f6f0..b3d0eefad4 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; -using System.Net.Http; using System.Text; using System.Threading; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core; @@ -15,8 +15,8 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Templates; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Routing; namespace Umbraco.Web.BackOffice.Controllers @@ -63,15 +63,15 @@ namespace Umbraco.Web.BackOffice.Controllers /// Note that ALL logged in users have access to this method because editors will need to insert macros into rte (content/media/members) and it's used for /// inserting into templates/views/etc... it doesn't expose any sensitive data. /// - public IEnumerable GetMacroParameters(int macroId) + public ActionResult> GetMacroParameters(int macroId) { var macro = _macroService.GetById(macroId); if (macro == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(macro, StatusCodes.Status404NotFound); } - return _umbracoMapper.Map>(macro).OrderBy(x => x.SortOrder); + return new ActionResult>(_umbracoMapper.Map>(macro).OrderBy(x => x.SortOrder)); } /// @@ -116,7 +116,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var m = _macroService.GetByAlias(macroAlias); if (m == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(m, StatusCodes.Status404NotFound); var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var publishedContent = umbracoContext.Content.GetById(true, pageId); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 465c1e06bf..e43b40f97e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -39,7 +39,6 @@ using Umbraco.Web.BackOffice.ModelBinders; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; @@ -122,12 +121,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [OutgoingEditorModelEvent] - public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) + public ActionResult GetEmpty(string contentTypeAlias, int parentId) { var contentType = _mediaTypeService.Get(contentTypeAlias); if (contentType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); } var emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId)); @@ -214,14 +213,15 @@ namespace Umbraco.Web.BackOffice.Controllers [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.MediaPermissionPathById)] [DetermineAmbiguousActionByPassingParameters] - public MediaItemDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi != null) { return GetById(guidUdi.Guid); } - throw new HttpResponseException(HttpStatusCode.NotFound); + + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } /// @@ -378,7 +378,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] [DetermineAmbiguousActionByPassingParameters] - public PagedResult> GetChildren(Guid id, + public ActionResult>> GetChildren(Guid id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -391,7 +391,8 @@ namespace Umbraco.Web.BackOffice.Controllers { return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } - throw new HttpResponseException(HttpStatusCode.NotFound); + + return new ValidationErrorResult(entity, StatusCodes.Status404NotFound); } /// @@ -407,7 +408,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [FilterAllowedOutgoingMedia(typeof(IEnumerable>), "Items")] [DetermineAmbiguousActionByPassingParameters] - public PagedResult> GetChildren(Udi id, + public ActionResult>> GetChildren(Udi id, int pageNumber = 0, int pageSize = 0, string orderBy = "SortOrder", @@ -425,7 +426,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } #endregion @@ -454,7 +455,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); } } else @@ -464,7 +465,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //returning an object of INotificationModel will ensure that any pending // notification messages are added to the response. - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); } } @@ -486,7 +487,7 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toMove = ValidateMoveOrCopy(move); + var toMove = ValidateMoveOrCopy(move).Value; var destinationParentID = move.ParentId; var sourceParentID = toMove.ParentId; @@ -494,11 +495,11 @@ namespace Umbraco.Web.BackOffice.Controllers if (sourceParentID == destinationParentID) { - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel(new BackOfficeNotification("",_localizedTextService.Localize("media/moveToSameFolderFailed"),NotificationStyle.Error))); + return new ValidationErrorResult(new SimpleNotificationModel(new BackOfficeNotification("",_localizedTextService.Localize("media/moveToSameFolderFailed"),NotificationStyle.Error))); } if (moveResult == false) { - throw HttpResponseException.CreateValidationErrorResponse(new SimpleNotificationModel()); + return new ValidationErrorResult(new SimpleNotificationModel()); } else { @@ -513,7 +514,7 @@ namespace Umbraco.Web.BackOffice.Controllers [FileUploadCleanupFilter] [MediaItemSaveValidation] [OutgoingEditorModelEvent] - public MediaItemDisplay PostSave( + public ActionResult PostSave( [ModelBinder(typeof(MediaItemBinder))] MediaItemSave contentItem) { @@ -559,7 +560,7 @@ namespace Umbraco.Web.BackOffice.Controllers // add the model state to the outgoing object and throw validation response var forDisplay = _umbracoMapper.Map(contentItem.PersistedContent); forDisplay.Errors = ModelState.ToErrorDictionary(); - throw HttpResponseException.CreateValidationErrorResponse(forDisplay); + return new ValidationErrorResult(forDisplay); } } @@ -592,7 +593,7 @@ namespace Umbraco.Web.BackOffice.Controllers // is no Id to redirect to! if (saveStatus.Result.Result == OperationResultType.FailedCancelledByEvent && IsCreatingAction(contentItem.Action)) { - throw HttpResponseException.CreateValidationErrorResponse(display); + return new ValidationErrorResult(display); } } @@ -663,7 +664,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task> PostAddFolder(PostedFolder folder) { - var parentId = await GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true); + var parentId = GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true).Result.Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); @@ -695,7 +696,7 @@ namespace Umbraco.Web.BackOffice.Controllers } //get the string json from the request - var parentId = await GetParentIdAsIntAsync(currentFolder, validatePermissions: true); + var parentId = GetParentIdAsIntAsync(currentFolder, validatePermissions: true).Result.Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); @@ -843,7 +844,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// and if that check fails an unauthorized exception will occur /// /// - private async Task GetParentIdAsIntAsync(string parentId, bool validatePermissions) + private async Task> GetParentIdAsIntAsync(string parentId, bool validatePermissions) { int intParentId; @@ -872,7 +873,7 @@ namespace Umbraco.Web.BackOffice.Controllers } else { - throw HttpResponseException.CreateValidationErrorResponse("The request was not formatted correctly, the parentId is not an integer, Guid or UDI"); + return new ValidationErrorResult("The request was not formatted correctly, the parentId is not an integer, Guid or UDI"); } } @@ -884,12 +885,12 @@ namespace Umbraco.Web.BackOffice.Controllers var authorizationResult = await _authorizationService.AuthorizeAsync(User, new MediaPermissionsResource(_mediaService.GetById(intParentId)), requirement); if (!authorizationResult.Succeeded) { - throw new HttpResponseException( - HttpStatusCode.Forbidden, + return new ValidationErrorResult( new SimpleNotificationModel(new BackOfficeNotification( _localizedTextService.Localize("speechBubbles/operationFailedHeader"), _localizedTextService.Localize("speechBubbles/invalidUserPermissionsText"), - NotificationStyle.Warning))); + NotificationStyle.Warning)), + StatusCodes.Status403Forbidden); } } @@ -901,18 +902,18 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - private IMedia ValidateMoveOrCopy(MoveOrCopy model) + private ActionResult ValidateMoveOrCopy(MoveOrCopy model) { if (model == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(model, StatusCodes.Status404NotFound); } var toMove = _mediaService.GetById(model.Id); if (toMove == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(toMove, StatusCodes.Status404NotFound); } if (model.ParentId < 0) { @@ -923,7 +924,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedAtRoot"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); } } else @@ -931,7 +932,7 @@ namespace Umbraco.Web.BackOffice.Controllers var parent = _mediaService.GetById(model.ParentId); if (parent == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(parent, StatusCodes.Status404NotFound); } //check if the item is allowed under this one @@ -941,7 +942,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByContentType"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); } // Check on paths @@ -949,15 +950,13 @@ namespace Umbraco.Web.BackOffice.Controllers { var notificationModel = new SimpleNotificationModel(); notificationModel.AddErrorNotification(_localizedTextService.Localize("moveOrCopy/notAllowedByPath"), ""); - throw HttpResponseException.CreateValidationErrorResponse(notificationModel); + return new ValidationErrorResult(notificationModel); } } - return toMove; + return new ActionResult(toMove); } - - public PagedResult GetPagedReferences(int id, string entityType, int pageNumber = 1, int pageSize = 100) { if (pageNumber <= 0 || pageSize <= 0) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index 32dc2ef888..2446d480ef 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Dictionary; @@ -11,10 +12,9 @@ using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; using Umbraco.Web.Models.ContentEditing; @@ -78,12 +78,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - public MediaTypeDisplay GetById(int id) + public ActionResult GetById(int id) { var ct = _mediaTypeService.Get(id); if (ct == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(ct, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(ct); @@ -97,12 +97,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - public MediaTypeDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var mediaType = _mediaTypeService.Get(id); if (mediaType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(mediaType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(mediaType); @@ -116,16 +116,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] - public MediaTypeDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var mediaType = _mediaTypeService.Get(guidUdi.Guid); if (mediaType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(mediaType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(mediaType); @@ -145,7 +145,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _mediaTypeService.Get(id); if (foundType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); } _mediaTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -179,7 +179,7 @@ namespace Umbraco.Web.BackOffice.Controllers public IActionResult GetAvailableCompositeMediaTypes(GetAvailableCompositionsFilter filter) { var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType, - filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement) + filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement).Value .Select(x => new { contentType = x.Item1, @@ -200,7 +200,7 @@ namespace Umbraco.Web.BackOffice.Controllers public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableCompositionsFilter filter) { var result = - PerformGetWhereCompositionIsUsedInContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType) + PerformGetWhereCompositionIsUsedInContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType).Value .Select(x => new { contentType = x @@ -257,9 +257,10 @@ namespace Umbraco.Web.BackOffice.Controllers { var result = _mediaTypeService.CreateContainer(parentId, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - return result - ? Ok(result.Result) //return the id - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); //return the id + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] @@ -267,9 +268,10 @@ namespace Umbraco.Web.BackOffice.Controllers { var result = _mediaTypeService.RenameContainer(id, name, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - return result - ? Ok(result.Result) //return the id - : throw HttpResponseException.CreateNotificationValidationErrorResponse(result.Exception.Message); + if (result.Success) + return Ok(result.Result); //return the id + else + return ValidationErrorResult.CreateNotificationValidationErrorResult(result.Exception.Message); } [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] @@ -373,15 +375,15 @@ namespace Umbraco.Web.BackOffice.Controllers /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetAllowedChildren(Guid contentId) + public ActionResult> GetAllowedChildren(Guid contentId) { var entity = _entityService.Get(contentId); if (entity != null) { - return GetAllowedChildren(entity.Id); + return new ActionResult>(GetAllowedChildren(entity.Id)); } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(entity, StatusCodes.Status404NotFound); } /// @@ -390,7 +392,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetAllowedChildren(Udi contentId) + public ActionResult> GetAllowedChildren(Udi contentId) { var guidUdi = contentId as GuidUdi; if (guidUdi != null) @@ -398,11 +400,11 @@ namespace Umbraco.Web.BackOffice.Controllers var entity = _entityService.Get(guidUdi.Guid); if (entity != null) { - return GetAllowedChildren(entity.Id); + return new ActionResult>(GetAllowedChildren(entity.Id)); } } - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); } #endregion diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs index a7cbaf96c1..6e8c002b7d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs @@ -1,17 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; @@ -46,12 +45,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberGroupDisplay GetById(int id) + public ActionResult GetById(int id) { var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(memberGroup); @@ -65,12 +64,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberGroupDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(memberGroup); @@ -82,16 +81,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberGroupDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var memberGroup = _memberGroupService.GetById(guidUdi.Guid); if (memberGroup == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(memberGroup); @@ -110,7 +109,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); } _memberGroupService.Delete(memberGroup); @@ -129,14 +128,14 @@ namespace Umbraco.Web.BackOffice.Controllers return _umbracoMapper.Map(item); } - public MemberGroupDisplay PostSave(MemberGroupSave saveModel) + public ActionResult PostSave(MemberGroupSave saveModel) { var id = int.Parse(saveModel.Id.ToString()); var memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup(); if (memberGroup == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); } memberGroup.Name = saveModel.Name; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index e203386958..9f01987470 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -1,30 +1,21 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Net.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; -using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; using Umbraco.Core.Security; -using Umbraco.Web.BackOffice.Controllers; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers @@ -74,15 +65,15 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberTypeDisplay GetById(int id) + public ActionResult GetById(int id) { - var ct = _memberTypeService.Get(id); - if (ct == null) + var mt = _memberTypeService.Get(id); + if (mt == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(mt, StatusCodes.Status404NotFound); } - var dto =_umbracoMapper.Map(ct); + var dto =_umbracoMapper.Map(mt); return dto; } @@ -92,12 +83,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberTypeDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var memberType = _memberTypeService.Get(id); if (memberType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(memberType); @@ -110,16 +101,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public MemberTypeDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var memberType = _memberTypeService.Get(guidUdi.Guid); if (memberType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(memberType, StatusCodes.Status404NotFound); } var dto = _umbracoMapper.Map(memberType); @@ -138,7 +129,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _memberTypeService.Get(id); if (foundType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); } _memberTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -163,7 +154,7 @@ namespace Umbraco.Web.BackOffice.Controllers [FromQuery]string[] filterContentTypes, [FromQuery]string[] filterPropertyTypes) { - var result = PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes, false) + var result = PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes, false).Value .Select(x => new { contentType = x.Item1, @@ -221,7 +212,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ct.IsSensitiveProperty(foundOnContentType.Alias) && prop.IsSensitiveData == false) { //if these don't match, then we cannot continue, this user is not allowed to change this value - throw new HttpResponseException(HttpStatusCode.Forbidden); + return new ValidationErrorResult(ct, StatusCodes.Status403Forbidden); } } } @@ -230,7 +221,7 @@ namespace Umbraco.Web.BackOffice.Controllers //if it is new, then we can just verify if any property has sensitive data turned on which is not allowed if (props.Any(prop => prop.IsSensitiveData)) { - throw new HttpResponseException(HttpStatusCode.Forbidden); + return new ValidationErrorResult(props, StatusCodes.Status403Forbidden); } } } @@ -249,7 +240,5 @@ namespace Umbraco.Web.BackOffice.Controllers return display; } - - } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 7f6bfe781f..47944428d7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Semver; @@ -13,9 +14,9 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; namespace Umbraco.Web.BackOffice.Controllers { @@ -45,11 +46,11 @@ namespace Umbraco.Web.BackOffice.Controllers return _packagingService.GetAllCreatedPackages(); } - public PackageDefinition GetCreatedPackageById(int id) + public ActionResult GetCreatedPackageById(int id) { var package = _packagingService.GetCreatedPackageById(id); if (package == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(package, StatusCodes.Status404NotFound); return package; } @@ -64,14 +65,14 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public PackageDefinition PostSavePackage(PackageDefinition model) + public ActionResult PostSavePackage(PackageDefinition model) { if (ModelState.IsValid == false) - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(ModelState); //save it if (!_packagingService.SaveCreatedPackage(model)) - throw HttpResponseException.CreateNotificationValidationErrorResponse( + return ValidationErrorResult.CreateNotificationValidationErrorResult( model.Id == default ? $"A package with the name {model.Name} already exists" : $"The package with id {model.Id} was not found"); @@ -105,7 +106,7 @@ namespace Umbraco.Web.BackOffice.Controllers var fullPath = _hostingEnvironment.MapPathWebRoot(package.PackagePath); if (!System.IO.File.Exists(fullPath)) - throw HttpResponseException.CreateNotificationValidationErrorResponse("No file found for path " + package.PackagePath); + return ValidationErrorResult.CreateNotificationValidationErrorResult("No file found for path " + package.PackagePath); var fileName = Path.GetFileName(package.PackagePath); @@ -126,10 +127,10 @@ namespace Umbraco.Web.BackOffice.Controllers } - public PackageDefinition GetInstalledPackageById(int id) + public ActionResult GetInstalledPackageById(int id) { var pack = _packagingService.GetInstalledPackageById(id); - if (pack == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (pack == null) return new ValidationErrorResult(pack, StatusCodes.Status404NotFound); return pack; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index b2706babee..2a8a148555 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -12,10 +12,10 @@ using Umbraco.Core.Strings; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers @@ -50,13 +50,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// The relation type ID. /// Returns the . [DetermineAmbiguousActionByPassingParameters] - public RelationTypeDisplay GetById(int id) + public ActionResult GetById(int id) { var relationType = _relationService.GetRelationTypeById(id); if (relationType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); } var display = _umbracoMapper.Map(relationType); @@ -69,12 +69,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// The relation type ID. /// Returns the . [DetermineAmbiguousActionByPassingParameters] - public RelationTypeDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var relationType = _relationService.GetRelationTypeById(id); if (relationType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(relationType); } @@ -85,16 +85,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// The relation type ID. /// Returns the . [DetermineAmbiguousActionByPassingParameters] - public RelationTypeDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var relationType = _relationService.GetRelationTypeById(guidUdi.Guid); if (relationType == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(relationType); } @@ -144,7 +144,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type to create. /// A containing the persisted relation type's ID. - public int PostCreate(RelationTypeSave relationType) + public ActionResult PostCreate(RelationTypeSave relationType) { var relationTypePersisted = new RelationType(relationType.Name, relationType.Name.ToSafeAlias(_shortStringHelper, true), relationType.IsBidirectional, relationType.ChildObjectType, relationType.ParentObjectType); @@ -157,7 +157,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { _logger.LogError(ex, "Error creating relation type with {Name}", relationType.Name); - throw HttpResponseException.CreateNotificationValidationErrorResponse("Error creating relation type."); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Error creating relation type."); } } @@ -166,13 +166,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The relation type to update. /// A display object containing the updated relation type. - public RelationTypeDisplay PostSave(RelationTypeSave relationType) + public ActionResult PostSave(RelationTypeSave relationType) { var relationTypePersisted = _relationService.GetRelationTypeById(relationType.Key); if (relationTypePersisted == null) { - throw HttpResponseException.CreateNotificationValidationErrorResponse("Relation type does not exist"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Relation type does not exist"); } _umbracoMapper.Map(relationType, relationTypePersisted); @@ -188,7 +188,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { _logger.LogError(ex, "Error saving relation type with {Id}", relationType.Id); - throw HttpResponseException.CreateNotificationValidationErrorResponse("Something went wrong when saving the relation type"); + return ValidationErrorResult.CreateNotificationValidationErrorResult("Something went wrong when saving the relation type"); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index fe75cf5a0a..4ba9306bd0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Net; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.IO; @@ -10,10 +10,9 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.BackOffice.Filters; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; using Constants = Umbraco.Core.Constants; @@ -63,11 +62,11 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public TemplateDisplay GetById(int id) + public ActionResult GetById(int id) { var template = _fileService.GetTemplate(id); if (template == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(template, StatusCodes.Status404NotFound); return _umbracoMapper.Map(template); } @@ -79,11 +78,11 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public TemplateDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var template = _fileService.GetTemplate(id); if (template == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(template, StatusCodes.Status404NotFound); return _umbracoMapper.Map(template); } @@ -94,16 +93,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public TemplateDisplay GetById(Udi id) + public ActionResult GetById(Udi id) { var guidUdi = id as GuidUdi; if (guidUdi == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); var template = _fileService.GetTemplate(guidUdi.Guid); if (template == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(template, StatusCodes.Status404NotFound); } return _umbracoMapper.Map(template); @@ -120,7 +119,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var template = _fileService.GetTemplate(id); if (template == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(template, StatusCodes.Status404NotFound); _fileService.DeleteTemplate(template.Alias); return Ok(); @@ -167,7 +166,7 @@ namespace Umbraco.Web.BackOffice.Controllers // update var template = _fileService.GetTemplate(display.Id); if (template == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(template, StatusCodes.Status404NotFound); var changeMaster = template.MasterTemplateAlias != display.MasterTemplateAlias; var changeAlias = template.Alias != display.Alias; @@ -239,7 +238,7 @@ namespace Umbraco.Web.BackOffice.Controllers { master = _fileService.GetTemplate(display.MasterTemplateAlias); if (master == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return new ValidationErrorResult(master, StatusCodes.Status404NotFound); } // we need to pass the template name as alias to keep the template file casing consistent with templates created with content diff --git a/src/Umbraco.Web.Common/Install/InstallApiController.cs b/src/Umbraco.Web.Common/Install/InstallApiController.cs index 8c32796ad8..fdbdc0cf94 100644 --- a/src/Umbraco.Web.Common/Install/InstallApiController.cs +++ b/src/Umbraco.Web.Common/Install/InstallApiController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -10,10 +10,9 @@ using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Net; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; -using Umbraco.Web.Common.Security; using Umbraco.Web.Install; using Umbraco.Web.Install.Models; @@ -96,7 +95,7 @@ namespace Umbraco.Web.Common.Install /// /// Installs. /// - public async Task PostPerformInstall(InstallInstructions installModel) + public async Task> PostPerformInstall(InstallInstructions installModel) { if (installModel == null) throw new ArgumentNullException(nameof(installModel)); @@ -157,7 +156,7 @@ namespace Umbraco.Web.Common.Install var installException = ex as InstallException; if (installException != null) { - throw HttpResponseException.CreateValidationErrorResponse(new + return new ValidationErrorResult(new { view = installException.View, model = installException.ViewModel, @@ -165,7 +164,7 @@ namespace Umbraco.Web.Common.Install }); } - throw HttpResponseException.CreateValidationErrorResponse(new + return new ValidationErrorResult(new { step = step.Name, view = "error", From 4ce5dc8eff552d5c67c48f09259f7644335f67b9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 11:32:36 +1100 Subject: [PATCH 18/88] fixing tests --- .../DependencyInjection/UmbracoBuilder.Events.cs | 5 +++-- .../TestServerTest/UmbracoTestServerTestBase.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index d5090de01b..138fa9bf9c 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -24,8 +24,9 @@ namespace Umbraco.Core.DependencyInjection where TNotification : INotification { // Register the handler as transient. This ensures that anything can be injected into it. - // TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 - builder.Services.TryAddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); + // TODO: Waiting on feedback here for TryAddTransient https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 + // ... though this will fail tests so it's not the final answer so we'll see where that discussion goes. + builder.Services.AddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); return builder; } } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 27209182fe..5a704c2b5d 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -25,6 +25,7 @@ using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.DependencyInjection; namespace Umbraco.Tests.Integration.TestServerTest { @@ -156,6 +157,7 @@ namespace Umbraco.Tests.Integration.TestServerTest mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() + .AddWebsite() .AddTestServices(TestHelper) // This is the important one! .Build(); } From 2848f4083b2e672256bbb15ccd44042125e4d76e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 12:01:52 +1100 Subject: [PATCH 19/88] notes - why isn't this building? --- .../DependencyInjection/UmbracoBuilder.Configuration.cs | 7 ------- .../Extensions/ApplicationBuilderExtensions.cs | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 1733536908..a31a44beeb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -1,12 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 5c7c4ee713..5dee7d10e1 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -28,6 +28,9 @@ namespace Umbraco.Extensions /// public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app) { + // TODO: Should we do some checks like this to verify that the corresponding "Add" methods have been called for the + // corresponding "Use" methods? + // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Mvc/Mvc.Core/src/Builder/MvcApplicationBuilderExtensions.cs#L132 if (app == null) { throw new ArgumentNullException(nameof(app)); From 999c20a75576be4df86f0b51d109c30648884b94 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 12:22:03 +1100 Subject: [PATCH 20/88] Fixes UmbracoIntegrationTest Mapping flag and missing using in Startup --- .../Testing/UmbracoIntegrationTest.cs | 10 +++++++++- .../Umbraco.Core/Mapping/UmbracoMapperTests.cs | 2 +- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- .../Extensions/WebMappingProfiles.cs | 2 +- src/Umbraco.Web.UI.NetCore/Startup.cs | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 17778f986c..885668acca 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -25,6 +25,7 @@ using Umbraco.Core.Runtime; using Umbraco.Core.Scoping; using Umbraco.Core.Strings; using Umbraco.Extensions; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.DependencyInjection; @@ -223,7 +224,14 @@ namespace Umbraco.Tests.Integration.Testing .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddTestServices(TestHelper, GetAppCaches()); - //.AddComposers(); + + if (TestOptions.Mapper) + { + // TODO: Should these just be called from within AddUmbracoCore/AddWebComponents? + builder + .AddCoreMappingProfiles() + .AddWebMappingProfiles(); + } services.AddSignalR(); services.AddMvc(); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/UmbracoMapperTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/UmbracoMapperTests.cs index 4028889b01..244e8c8d1a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/UmbracoMapperTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Core/Mapping/UmbracoMapperTests.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Linq; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index fb2cbfb607..0d12fae687 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -149,7 +149,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection builder.Trees() .AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x))); - builder.ComposeWebMappingProfiles(); + builder.AddWebMappingProfiles(); builder.Services.AddUnique(factory => { diff --git a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs index eeb0903e88..f66c175f29 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/WebMappingProfiles.cs @@ -7,7 +7,7 @@ namespace Umbraco.Extensions { public static class WebMappingProfiles { - public static IUmbracoBuilder ComposeWebMappingProfiles(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddWebMappingProfiles(this IUmbracoBuilder builder) { builder.WithCollectionBuilder() .Add() diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index a09b850d98..46f7b2d7ae 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.BackOffice.Security; From cf9dd9bfeca897ec2dc93c552cb4f5c7e4a4bec0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 12:48:59 +1100 Subject: [PATCH 21/88] Adds notes, another hacky fix for hacky test/code --- .../Cache/DatabaseServerMessengerNotificationHandler.cs | 5 ++++- .../DependencyInjection/UmbracoBuilder.DistributedCache.cs | 5 ----- .../Umbraco.Infrastructure/Services/ContentServiceTests.cs | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs index c8d8c81ab1..5b4dd4f0ed 100644 --- a/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs +++ b/src/Umbraco.Infrastructure/Cache/DatabaseServerMessengerNotificationHandler.cs @@ -70,7 +70,10 @@ namespace Umbraco.Infrastructure.Cache } // TODO: I don't really know or think that the Application Url plays a role anymore with the DB dist cache, - // this might be really old stuff + // this might be really old stuff. I 'think' all this is doing is ensuring that the IRequestAccessor.GetApplicationUrl + // is definitely called during the first request. If that is still required, that logic doesn't belong here. That logic + // should be part of it's own service/middleware. There's also TODO notes within IRequestAccessor.GetApplicationUrl directly + // mentioning that the property doesn't belong on that service either. This should be investigated and resolved in a separate task. private void EnsureApplicationUrlOnce(object sender, RoutableAttemptEventArgs e) { if (e.Outcome == EnsureRoutableOutcome.IsRoutable || e.Outcome == EnsureRoutableOutcome.NotDocumentRequest) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs index 54c0feeea0..db1d22e86c 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.DistributedCache.cs @@ -1,15 +1,10 @@ using System; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Events; -using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Sync; using Umbraco.Infrastructure.Cache; -using Umbraco.Core.Sync; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.Search; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index c9f8dc5391..10fe206290 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -2283,7 +2283,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services // This sleep ensures the save is called on later ticks then the SetValue and SetCultureName. Therefore // we showcase the currect lack of handling dirty on variants on save. When this is implemented the sleep // helps showcase the functionality is actually working - Thread.Sleep(1); + Thread.Sleep(5); ContentService.Save(page); var versionId5 = page.VersionId; From d1d664c44952f2cdd3e2b820f86ed982ccba07c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 12:51:13 +1100 Subject: [PATCH 22/88] removes unneeded cast --- .../HostedServices/ServerRegistration/InstructionProcessTask.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs index f3d970d2b0..8b194e32ef 100644 --- a/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs +++ b/src/Umbraco.Infrastructure/HostedServices/ServerRegistration/InstructionProcessTask.cs @@ -31,7 +31,7 @@ namespace Umbraco.Infrastructure.HostedServices.ServerRegistration : base(globalSettings.Value.DatabaseServerMessenger.TimeBetweenSyncOperations, TimeSpan.FromMinutes(1)) { _runtimeState = runtimeState; - _messenger = messenger as IServerMessenger ?? throw new ArgumentNullException(nameof(messenger)); + _messenger = messenger; _logger = logger; } From cbc08fb00879315b6191c9da0688695425c8180d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 12:56:44 +1100 Subject: [PATCH 23/88] notes/cleanup --- .../Sync/BatchedDatabaseServerMessenger.cs | 40 ++++++++++--------- .../Sync/DatabaseServerMessenger.cs | 5 +-- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs index c265461c99..d1a9481f47 100644 --- a/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/BatchedDatabaseServerMessenger.cs @@ -4,14 +4,12 @@ using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Scoping; -using Umbraco.Core.Sync; using Umbraco.Web; namespace Umbraco.Core.Sync @@ -19,9 +17,6 @@ namespace Umbraco.Core.Sync /// /// An implementation that works by storing messages in the database. /// - /// - /// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls - /// public class BatchedDatabaseServerMessenger : DatabaseServerMessenger { private readonly IRequestCache _requestCache; @@ -48,37 +43,41 @@ namespace Umbraco.Core.Sync _requestAccessor = requestAccessor; } + /// protected override void DeliverRemote(ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, string json = null) { var idsA = ids?.ToArray(); - Type arrayType; - if (GetArrayType(idsA, out arrayType) == false) + if (GetArrayType(idsA, out Type arrayType) == false) + { throw new ArgumentException("All items must be of the same type, either int or Guid.", nameof(ids)); + } BatchMessage(refresher, messageType, idsA, arrayType, json); } + /// public override void SendMessages() { - var batch = GetBatch(false); + ICollection batch = GetBatch(false); if (batch == null) + { return; + } - var instructions = batch.SelectMany(x => x.Instructions).ToArray(); + RefreshInstruction[] instructions = batch.SelectMany(x => x.Instructions).ToArray(); batch.Clear(); // Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount - using (var scope = ScopeProvider.CreateScope()) + using (IScope scope = ScopeProvider.CreateScope()) { - foreach (var instructionsBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) + foreach (IEnumerable instructionsBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) { WriteInstructions(scope, instructionsBatch); } scope.Complete(); } - } private void WriteInstructions(IScope scope, IEnumerable instructions) @@ -93,12 +92,14 @@ namespace Umbraco.Core.Sync scope.Database.Insert(dto); } - protected ICollection GetBatch(bool create) + private ICollection GetBatch(bool create) { var key = nameof(BatchedDatabaseServerMessenger); if (!_requestCache.IsAvailable) + { return null; + } // no thread-safety here because it'll run in only 1 thread (request) at a time var batch = (ICollection)_requestCache.Get(key); @@ -111,26 +112,27 @@ namespace Umbraco.Core.Sync return batch; } - protected void BatchMessage( + private void BatchMessage( ICacheRefresher refresher, MessageType messageType, IEnumerable ids = null, Type idType = null, string json = null) { - var batch = GetBatch(true); - var instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); + ICollection batch = GetBatch(true); + IEnumerable instructions = RefreshInstruction.GetInstructions(refresher, messageType, ids, idType, json); // batch if we can, else write to DB immediately if (batch == null) { - //only write the json blob with a maximum count of the MaxProcessingInstructionCount - using (var scope = ScopeProvider.CreateScope()) + // only write the json blob with a maximum count of the MaxProcessingInstructionCount + using (IScope scope = ScopeProvider.CreateScope()) { - foreach (var maxBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) + foreach (IEnumerable maxBatch in instructions.InGroupsOf(GlobalSettings.DatabaseServerMessenger.MaxProcessingInstructionCount)) { WriteInstructions(scope, maxBatch); } + scope.Complete(); } } diff --git a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs index 26b1de5080..09c90461ac 100644 --- a/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Infrastructure/Sync/DatabaseServerMessenger.cs @@ -25,9 +25,6 @@ namespace Umbraco.Core.Sync /// public abstract class DatabaseServerMessenger : ServerMessengerBase { - // TODO: This class is never used directly, only BatchedDatabaseServerMessenger is used so - // this could/should be combined with that or made abstract and/or just cleaned up. - // TODO: This class needs to be split into a service/repo for DB access /* @@ -55,7 +52,7 @@ namespace Umbraco.Core.Sync /// /// Initializes a new instance of the class. /// - public DatabaseServerMessenger( + protected DatabaseServerMessenger( IMainDom mainDom, IScopeProvider scopeProvider, IProfilingLogger proflog, From d46545faa01a3a8f0a7dad086de6888ccdaf0e8e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 13:01:49 +1100 Subject: [PATCH 24/88] For now, we explicitly require AddDistributedCache in Startup so it's not called twice. --- .../DependencyInjection/UmbracoBuilderExtensions.cs | 3 +-- src/Umbraco.Web.UI.NetCore/Startup.cs | 2 ++ .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 0d12fae687..6bb75e774a 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -43,8 +43,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection .AddMvcAndRazor() .AddWebServer() .AddPreviewSupport() - .AddHostedServices() - .AddDistributedCache(); + .AddHostedServices(); /// /// Adds Umbraco back office authentication requirements diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 46f7b2d7ae..5b262098e6 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; +using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.DependencyInjection; @@ -47,6 +48,7 @@ namespace Umbraco.Web.UI.NetCore services.AddUmbraco(_env, _config) .AddBackOffice() .AddWebsite() + .AddDistributedCache() .AddComposers() .Build(); #pragma warning restore IDE0022 // Use expression body for methods diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 52f6d5df11..41292b9415 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -43,8 +43,6 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddScoped(); builder.Services.AddSingleton(); - builder.AddDistributedCache(); - return builder; } From 34427b010434e73c56a9cb5fb21060f1f6e8cc24 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 13:22:29 +1100 Subject: [PATCH 25/88] Moves AddDistributed cache back to being called implicitly, ensures notification handlers are not added twice --- .../DependencyInjection/UmbracoBuilder.Events.cs | 12 +++++++++--- .../Umbraco.Core/Events/EventAggregatorTests.cs | 1 + .../DependencyInjection/UmbracoBuilderExtensions.cs | 3 ++- src/Umbraco.Web.UI.NetCore/Startup.cs | 2 -- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 ++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index 138fa9bf9c..c24936b4fb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -24,9 +24,15 @@ namespace Umbraco.Core.DependencyInjection where TNotification : INotification { // Register the handler as transient. This ensures that anything can be injected into it. - // TODO: Waiting on feedback here for TryAddTransient https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 - // ... though this will fail tests so it's not the final answer so we'll see where that discussion goes. - builder.Services.AddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); + var descriptor = new ServiceDescriptor(typeof(INotificationHandler), typeof(TNotificationHandler), ServiceLifetime.Transient); + + // TODO: Waiting on feedback here https://github.com/umbraco/Umbraco-CMS/pull/9556/files#r548365396 about whether + // we perform this duplicate check or not. + if (!builder.Services.Contains(descriptor)) + { + builder.Services.Add(descriptor); + } + return builder; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs index 08d78af59e..cbf3d02542 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Events/EventAggregatorTests.cs @@ -13,6 +13,7 @@ using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Events { + [TestFixture] public class EventAggregatorTests { private const int A = 3; diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 6bb75e774a..0d12fae687 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -43,7 +43,8 @@ namespace Umbraco.Web.BackOffice.DependencyInjection .AddMvcAndRazor() .AddWebServer() .AddPreviewSupport() - .AddHostedServices(); + .AddHostedServices() + .AddDistributedCache(); /// /// Adds Umbraco back office authentication requirements diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 5b262098e6..46f7b2d7ae 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; -using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.DependencyInjection; @@ -48,7 +47,6 @@ namespace Umbraco.Web.UI.NetCore services.AddUmbraco(_env, _config) .AddBackOffice() .AddWebsite() - .AddDistributedCache() .AddComposers() .Build(); #pragma warning restore IDE0022 // Use expression body for methods diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 41292b9415..52f6d5df11 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -43,6 +43,8 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddScoped(); builder.Services.AddSingleton(); + builder.AddDistributedCache(); + return builder; } From a6f281789b53722a958acaff409521d0ab244bd5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 13:29:13 +1100 Subject: [PATCH 26/88] Add explicit package sources --- NuGet.Config | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/NuGet.Config b/NuGet.Config index d6c63173f8..92eaf83792 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,13 +1,15 @@  - - - - - - + + + + + + + From a126977a4e0b6296afa33b782272103b2b983f87 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 4 Jan 2021 15:43:30 +1100 Subject: [PATCH 27/88] Simplifies how view locations are determined AB#9720 --- .../UmbracoBuilderExtensions.cs | 10 +- .../ViewEngines/IPluginViewEngine.cs | 8 -- .../ViewEngines/IRenderViewEngine.cs | 8 -- .../ViewEngines/PluginMvcViewOptionsSetup.cs | 26 ----- .../PluginRazorViewEngineOptionsSetup.cs | 52 ++++++++++ .../ViewEngines/PluginViewEngine.cs | 52 ---------- ...ingViewEngineWrapperMvcViewOptionsSetup.cs | 24 +++-- .../ViewEngines/RenderMvcViewOptionsSetup.cs | 26 ----- .../RenderRazorViewEngineOptionsSetup.cs | 48 ++++++++++ .../ViewEngines/RenderViewEngine.cs | 94 ------------------- 10 files changed, 120 insertions(+), 228 deletions(-) delete mode 100644 src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs delete mode 100644 src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs delete mode 100644 src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs create mode 100644 src/Umbraco.Web.Website/ViewEngines/PluginRazorViewEngineOptionsSetup.cs delete mode 100644 src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs delete mode 100644 src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs create mode 100644 src/Umbraco.Web.Website/ViewEngines/RenderRazorViewEngineOptionsSetup.cs delete mode 100644 src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 52f6d5df11..64d7cd0205 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; @@ -27,12 +28,9 @@ namespace Umbraco.Web.Website.DependencyInjection builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetSurfaceControllers()); - // Set the render & plugin view engines (Super complicated, but this allows us to use the IServiceCollection - // to inject dependencies into the viewEngines) - builder.Services.AddTransient, RenderMvcViewOptionsSetup>(); - builder.Services.AddSingleton(); - builder.Services.AddTransient, PluginMvcViewOptionsSetup>(); - builder.Services.AddSingleton(); + // Configure MVC startup options for custom view locations + builder.Services.AddTransient, RenderRazorViewEngineOptionsSetup>(); + builder.Services.AddTransient, PluginRazorViewEngineOptionsSetup>(); // Wraps all existing view engines in a ProfilerViewEngine builder.Services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>(); diff --git a/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs deleted file mode 100644 index 4bcb7b5dc2..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/IPluginViewEngine.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Razor; - -namespace Umbraco.Web.Website.ViewEngines -{ - public interface IPluginViewEngine : IRazorViewEngine - { - } -} diff --git a/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs deleted file mode 100644 index 6f21d21494..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/IRenderViewEngine.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Razor; - -namespace Umbraco.Web.Website.ViewEngines -{ - public interface IRenderViewEngine : IRazorViewEngine - { - } -} diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs deleted file mode 100644 index cf703ddaca..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/PluginMvcViewOptionsSetup.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace Umbraco.Web.Website.ViewEngines -{ - public class PluginMvcViewOptionsSetup : IConfigureOptions - { - private readonly IPluginViewEngine _pluginViewEngine; - - public PluginMvcViewOptionsSetup(IPluginViewEngine pluginViewEngine) - { - _pluginViewEngine = pluginViewEngine ?? throw new ArgumentNullException(nameof(pluginViewEngine)); - } - - public void Configure(MvcViewOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - options.ViewEngines.Add(_pluginViewEngine); - } - } -} diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginRazorViewEngineOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/PluginRazorViewEngineOptionsSetup.cs new file mode 100644 index 0000000000..3cb6d78113 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/PluginRazorViewEngineOptionsSetup.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Options; + +namespace Umbraco.Web.Website.ViewEngines +{ + /// + /// Configure view engine locations for front-end rendering based on App_Plugins views + /// + public class PluginRazorViewEngineOptionsSetup : IConfigureOptions + { + /// + public void Configure(RazorViewEngineOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewLocationExpanders.Add(new ViewLocationExpander()); + } + + /// + /// Expands the default view locations + /// + private class ViewLocationExpander : IViewLocationExpander + { + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + string[] umbViewLocations = new string[] + { + // area view locations for the plugin folder + string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), + string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), + + // will be used when we have partial view and child action macros + string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.cshtml"), + string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.cshtml") + }; + + viewLocations = umbViewLocations.Concat(viewLocations); + + return viewLocations; + } + + // not a dynamic expander + public void PopulateValues(ViewLocationExpanderContext context) { } + } + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs deleted file mode 100644 index e0b16a351e..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Diagnostics; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Umbraco.Web.Website.ViewEngines -{ - // TODO: We don't really need to have different view engines simply to search additional places, - // we can just do ConfigureOptions on startup to add more to the - // default list so this can be totally removed/replaced with configure options logic. - - /// - /// A view engine to look into the App_Plugins folder for views for packaged controllers - /// - public class PluginViewEngine : RazorViewEngine, IPluginViewEngine - { - public PluginViewEngine( - IRazorPageFactoryProvider pageFactory, - IRazorPageActivator pageActivator, - HtmlEncoder htmlEncoder, - ILoggerFactory loggerFactory, - DiagnosticListener diagnosticListener) - : base(pageFactory, pageActivator, htmlEncoder, OverrideViewLocations(), loggerFactory, diagnosticListener) - { - } - - private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() - { - // This is definitely not doing what it used to do :P see: - // https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Web/Mvc/PluginViewEngine.cs#L23 - - AreaViewLocationFormats = - { - // set all of the area view locations to the plugin folder - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), - - // will be used when we have partial view and child action macros - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Partials/{0}.cshtml"), - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/MacroPartials/{0}.cshtml"), - // for partialsCurrent. - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/Shared/{0}.cshtml"), - }, - ViewLocationFormats = - { - string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), - } - }); - } -} diff --git a/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs index b8fbe1e527..1510975f14 100644 --- a/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs +++ b/src/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; @@ -8,15 +8,20 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Website.ViewEngines { + /// + /// Wraps all view engines with a + /// public class ProfilingViewEngineWrapperMvcViewOptionsSetup : IConfigureOptions { private readonly IProfiler _profiler; - public ProfilingViewEngineWrapperMvcViewOptionsSetup(IProfiler profiler) - { - _profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); - } + /// + /// Initializes a new instance of the class. + /// + /// The + public ProfilingViewEngineWrapperMvcViewOptionsSetup(IProfiler profiler) => _profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); + /// public void Configure(MvcViewOptions options) { if (options == null) @@ -29,13 +34,16 @@ namespace Umbraco.Web.Website.ViewEngines private void WrapViewEngines(IList viewEngines) { - if (viewEngines == null || viewEngines.Count == 0) return; + if (viewEngines == null || viewEngines.Count == 0) + { + return; + } var originalEngines = viewEngines.ToList(); viewEngines.Clear(); - foreach (var engine in originalEngines) + foreach (IViewEngine engine in originalEngines) { - var wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine, _profiler); + IViewEngine wrappedEngine = engine is ProfilingViewEngine ? engine : new ProfilingViewEngine(engine, _profiler); viewEngines.Add(wrappedEngine); } } diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs deleted file mode 100644 index d8ad58cc91..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/RenderMvcViewOptionsSetup.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace Umbraco.Web.Website.ViewEngines -{ - public class RenderMvcViewOptionsSetup : IConfigureOptions - { - private readonly IRenderViewEngine _renderViewEngine; - - public RenderMvcViewOptionsSetup(IRenderViewEngine renderViewEngine) - { - _renderViewEngine = renderViewEngine ?? throw new ArgumentNullException(nameof(renderViewEngine)); - } - - public void Configure(MvcViewOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - options.ViewEngines.Add(_renderViewEngine); - } - } -} diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderRazorViewEngineOptionsSetup.cs b/src/Umbraco.Web.Website/ViewEngines/RenderRazorViewEngineOptionsSetup.cs new file mode 100644 index 0000000000..39009d44a1 --- /dev/null +++ b/src/Umbraco.Web.Website/ViewEngines/RenderRazorViewEngineOptionsSetup.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Options; + +namespace Umbraco.Web.Website.ViewEngines +{ + /// + /// Configure view engine locations for front-end rendering + /// + public class RenderRazorViewEngineOptionsSetup : IConfigureOptions + { + /// + public void Configure(RazorViewEngineOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewLocationExpanders.Add(new ViewLocationExpander()); + } + + /// + /// Expands the default view locations + /// + private class ViewLocationExpander : IViewLocationExpander + { + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + string[] umbViewLocations = new string[] + { + "/Views/Partials/{0}.cshtml", + "/Views/MacroPartials/{0}.cshtml", + "/Views/{0}.cshtml" + }; + + viewLocations = umbViewLocations.Concat(viewLocations); + + return viewLocations; + } + + // not a dynamic expander + public void PopulateValues(ViewLocationExpanderContext context) { } + } + } +} diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs deleted file mode 100644 index 8c53255928..0000000000 --- a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core.Hosting; -using Umbraco.Extensions; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Website.ViewEngines -{ - // TODO: We don't really need to have different view engines simply to search additional places, - // we can just do ConfigureOptions on startup to add more to the - // default list so this can be totally removed/replaced with configure options logic. - - /// - /// A view engine to look into the template location specified in the config for the front-end/Rendering part of the cms, - /// this includes paths to render partial macros and media item templates. - /// - public class RenderViewEngine : RazorViewEngine, IRenderViewEngine - { - - /// - /// Initializes a new instance of the class. - /// - public RenderViewEngine( - IRazorPageFactoryProvider pageFactory, - IRazorPageActivator pageActivator, - HtmlEncoder htmlEncoder, - ILoggerFactory loggerFactory, - DiagnosticListener diagnosticListener) - : base(pageFactory, pageActivator, htmlEncoder, OverrideViewLocations(), loggerFactory, diagnosticListener) - { - } - - private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() - { - // NOTE: we will make the main view location the last to be searched since if it is the first to be searched and there is both a view and a partial - // view in both locations and the main view is rendering a partial view with the same name, we will get a stack overflow exception. - // http://issues.umbraco.org/issue/U4-1287, http://issues.umbraco.org/issue/U4-1215 - ViewLocationFormats = - { - "/Views/Partials/{0}.cshtml", - "/Views/MacroPartials/{0}.cshtml", - "/Views/{0}.cshtml" - }, - AreaViewLocationFormats = - { - "/Views/Partials/{0}.cshtml", - "/Views/MacroPartials/{0}.cshtml", - "/Views/{0}.cshtml" - } - }); - - public new ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) - { - return ShouldFindView(context, isMainPage) - ? base.FindView(context, viewName, isMainPage) - : ViewEngineResult.NotFound(viewName, Array.Empty()); - } - - /// - /// Determines if the view should be found, this is used for view lookup performance and also to ensure - /// less overlap with other user's view engines. This will return true if the Umbraco back office is rendering - /// and its a partial view or if the umbraco front-end is rendering but nothing else. - /// - /// - /// - /// - private static bool ShouldFindView(ActionContext context, bool isMainPage) - { - return true; - // TODO: Determine if this is required, i don't think it is - ////In v8, this was testing recursively into if it was a child action, but child action do not exist anymore, - ////And my best guess is that it - //context.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var umbracoToken); - //// first check if we're rendering a partial view for the back office, or surface controller, etc... - //// anything that is not ContentModel as this should only pertain to Umbraco views. - //if (!isMainPage && !(umbracoToken is ContentModel)) - // return true; - //// only find views if we're rendering the umbraco front end - //return umbracoToken is ContentModel; - } - - - } -} From eb6e5f9af283ce12a900b1e28b724dce91e62c71 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 5 Jan 2021 10:57:00 +0100 Subject: [PATCH 28/88] Removed tests that is not useful anymore --- ...ewEngineWrapperMvcViewOptionsSetupTests.cs | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs deleted file mode 100644 index ba791dd8f2..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Umbraco. -// See LICENSE for more details. - -using Microsoft.AspNetCore.Mvc; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Logging; -using Umbraco.Web.Website.ViewEngines; - -namespace Umbraco.Tests.Runtimes -{ - [TestFixture] - public class ProfilingViewEngineWrapperMvcViewOptionsSetupTests - { - private ProfilingViewEngineWrapperMvcViewOptionsSetup Sut => - new ProfilingViewEngineWrapperMvcViewOptionsSetup(Mock.Of()); - - [Test] - public void WrapViewEngines_HasEngines_WrapsAll() - { - var options = new MvcViewOptions() - { - ViewEngines = - { - Mock.Of(), - Mock.Of(), - } - }; - - Sut.Configure(options); - - Assert.That(options.ViewEngines.Count, Is.EqualTo(2)); - Assert.That(options.ViewEngines[0], Is.InstanceOf()); - Assert.That(options.ViewEngines[1], Is.InstanceOf()); - } - - [Test] - public void WrapViewEngines_HasEngines_KeepsSortOrder() - { - var options = new MvcViewOptions() - { - ViewEngines = - { - Mock.Of(), - Mock.Of(), - } - }; - - Sut.Configure(options); - - Assert.That(options.ViewEngines.Count, Is.EqualTo(2)); - Assert.That(((ProfilingViewEngine)options.ViewEngines[0]).Inner, Is.InstanceOf()); - Assert.That(((ProfilingViewEngine)options.ViewEngines[1]).Inner, Is.InstanceOf()); - } - - [Test] - public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() - { - var profiledEngine = new ProfilingViewEngine(Mock.Of(), Mock.Of()); - var options = new MvcViewOptions() - { - ViewEngines = - { - profiledEngine - } - }; - - Sut.Configure(options); - - Assert.That(options.ViewEngines[0], Is.SameAs(profiledEngine)); - } - - [Test] - public void WrapViewEngines_CollectionIsNull_DoesNotThrow() => Assert.DoesNotThrow(() => Sut.Configure(new MvcViewOptions())); - } -} From 295ab504cdd29e535d283d3e03532a65406ed8be Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Jan 2021 17:04:35 +1100 Subject: [PATCH 29/88] Splits PublishedRequest into a builder and a immutable object --- .../Routing/ContentFinderByIdPath.cs | 52 ++- .../Routing/ContentFinderByPageIdQuery.cs | 30 +- .../Routing/ContentFinderByRedirectUrl.cs | 47 ++- .../Routing/ContentFinderByUrl.cs | 56 ++- .../Routing/ContentFinderByUrlAlias.cs | 71 +++- .../Routing/ContentFinderByUrlAndTemplate.cs | 37 +- src/Umbraco.Core/Routing/IContentFinder.cs | 2 +- src/Umbraco.Core/Routing/IPublishedRequest.cs | 179 ++------- .../Routing/IPublishedRequestBuilder.cs | 165 +++++++++ src/Umbraco.Core/Routing/IPublishedRouter.cs | 43 +-- src/Umbraco.Core/Routing/PublishedRequest.cs | 334 ++++++++--------- .../Routing/PublishedRequestBuilder.cs | 194 ++++++++++ .../Routing/PublishedRequestExtensions.cs | 74 ++++ src/Umbraco.Core/Routing/PublishedRouter.cs | 347 +++++++++--------- .../Routing/UrlProviderExtensions.cs | 24 +- src/Umbraco.Core/Web/IUmbracoContext.cs | 23 +- .../Routing/ContentFinderByConfigured404.cs | 45 ++- .../PublishedContent/PublishedRouterTests.cs | 20 +- .../Routing/ContentFinderByAliasTests.cs | 8 +- .../ContentFinderByAliasWithDomainsTests.cs | 8 +- .../Routing/ContentFinderByIdTests.cs | 8 +- .../ContentFinderByPageIdQueryTests.cs | 8 +- .../ContentFinderByUrlAndTemplateTests.cs | 21 +- .../Routing/ContentFinderByUrlTests.cs | 38 +- .../ContentFinderByUrlWithDomainsTests.cs | 14 +- .../Routing/DomainsAndCulturesTests.cs | 20 +- .../Routing/GetContentUrlsTests.cs | 24 +- .../Routing/RenderRouteHandlerTests.cs | 22 +- .../Routing/UrlsWithNestedDomains.cs | 12 +- src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 19 +- .../TestHelpers/Stubs/TestLastChanceFinder.cs | 7 +- .../TestHelpers/TestWithDatabaseBase.cs | 2 + .../Web/Mvc/SurfaceControllerTests.cs | 8 +- .../Templates/TemplateRenderer.cs | 61 +-- .../UmbracoContext/UmbracoContext.cs | 92 ++--- .../Routing/UmbracoRouteValueTransformer.cs | 19 +- .../EnsurePublishedContentRequestAttribute.cs | 182 ++++----- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 43 ++- .../Mvc/UmbracoVirtualNodeRouteHandler.cs | 75 ++-- .../PublishedContentNotFoundHandler.cs | 6 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 7 +- src/Umbraco.Web/UmbracoModule.cs | 14 +- 42 files changed, 1441 insertions(+), 1020 deletions(-) create mode 100644 src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs create mode 100644 src/Umbraco.Core/Routing/PublishedRequestBuilder.cs create mode 100644 src/Umbraco.Core/Routing/PublishedRequestExtensions.cs diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 856b91e36f..414d1da871 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -1,11 +1,9 @@ -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Models.PublishedContent; using System.Globalization; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { @@ -19,13 +17,22 @@ namespace Umbraco.Web.Routing { private readonly ILogger _logger; private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly WebRoutingSettings _webRoutingSettings; - public ContentFinderByIdPath(IOptions webRoutingSettings, ILogger logger, IRequestAccessor requestAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByIdPath( + IOptions webRoutingSettings, + ILogger logger, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { _webRoutingSettings = webRoutingSettings.Value ?? throw new System.ArgumentNullException(nameof(webRoutingSettings)); _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - _requestAccessor = requestAccessor; + _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } /// @@ -33,43 +40,48 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { - - if (frequest.UmbracoContext != null && frequest.UmbracoContext.InPreviewMode == false - && _webRoutingSettings.DisableFindContentByIdPath) + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null || (umbCtx != null && umbCtx.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath)) + { return false; + } IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); var nodeId = -1; - if (path != "/") // no id if "/" + + // no id if "/" + if (path != "/") { var noSlashPath = path.Substring(1); if (int.TryParse(noSlashPath, out nodeId) == false) + { nodeId = -1; + } if (nodeId > 0) { _logger.LogDebug("Id={NodeId}", nodeId); - node = frequest.UmbracoContext.Content.GetById(nodeId); + node = umbCtx.Content.GetById(nodeId); if (node != null) { var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); - //if we have a node, check if we have a culture in the query string + // if we have a node, check if we have a culture in the query string if (!string.IsNullOrEmpty(cultureFromQuerystring)) { - //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.Culture = CultureInfo.GetCultureInfo(cultureFromQuerystring); + // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though + frequest.SetCulture(CultureInfo.GetCultureInfo(cultureFromQuerystring)); } - frequest.PublishedContent = node; - _logger.LogDebug("Found node with id={PublishedContentId}", frequest.PublishedContent.Id); + frequest.SetPublishedContent(node); + _logger.LogDebug("Found node with id={PublishedContentId}", node.Id); } else { @@ -79,7 +91,9 @@ namespace Umbraco.Web.Routing } if (nodeId == -1) + { _logger.LogDebug("Not a node id"); + } return node != null; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index 16d7a0c4cf..15698f9134 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -1,4 +1,6 @@ - + +using Umbraco.Core.Models.PublishedContent; + namespace Umbraco.Web.Routing { /// @@ -11,25 +13,37 @@ namespace Umbraco.Web.Routing public class ContentFinderByPageIdQuery : IContentFinder { private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoContextAccessor umbracoContextAccessor) { - _requestAccessor = requestAccessor; + _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } - public bool TryFindContent(IPublishedRequest frequest) + /// + public bool TryFindContent(IPublishedRequestBuilder frequest) { - int pageId; - if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out pageId)) + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) { - var doc = frequest.UmbracoContext.Content.GetById(pageId); + return false; + } + + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), out int pageId)) + { + IPublishedContent doc = umbCtx.Content.GetById(pageId); if (doc != null) { - frequest.PublishedContent = doc; + frequest.SetPublishedContent(doc); return true; } } + return false; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 895917c69d..a35135e5a3 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; namespace Umbraco.Web.Routing @@ -17,12 +19,21 @@ namespace Umbraco.Web.Routing private readonly IRedirectUrlService _redirectUrlService; private readonly ILogger _logger; private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public ContentFinderByRedirectUrl(IRedirectUrlService redirectUrlService, ILogger logger, IPublishedUrlProvider publishedUrlProvider) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByRedirectUrl( + IRedirectUrlService redirectUrlService, + ILogger logger, + IPublishedUrlProvider publishedUrlProvider, + IUmbracoContextAccessor umbracoContextAccessor) { _redirectUrlService = redirectUrlService; _logger = logger; _publishedUrlProvider = publishedUrlProvider; + _umbracoContextAccessor = umbracoContextAccessor; } /// @@ -31,15 +42,19 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { - var route = frequest.HasDomain + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + + var route = frequest.Domain != null ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) : frequest.Uri.GetAbsolutePathDecoded(); - - - var redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); + IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); if (redirectUrl == null) { @@ -47,7 +62,7 @@ namespace Umbraco.Web.Routing return false; } - var content = frequest.UmbracoContext.Content.GetById(redirectUrl.ContentId); + IPublishedContent content = umbCtx.Content.GetById(redirectUrl.ContentId); var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture); if (url.StartsWith("#")) { @@ -59,17 +74,17 @@ namespace Umbraco.Web.Routing url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content.Id, url); - frequest.SetRedirectPermanent(url); + frequest + .SetRedirectPermanent(url) - // From: http://stackoverflow.com/a/22468386/5018 - // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 - // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads - // to problems if you rename a page back to it's original name or create a new page with the original name - //frequest.Cacheability = HttpCacheability.NoCache; - frequest.CacheabilityNoCache = true; - frequest.CacheExtensions = new List { "no-store, must-revalidate" }; - frequest.Headers = new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }; + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + .SetCacheabilityNoCache(true) + .SetCacheExtensions(new List { "no-store, must-revalidate" }) + .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }); return true; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index 653e808dfe..44ae4335e2 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -14,44 +14,70 @@ namespace Umbraco.Web.Routing { private readonly ILogger _logger; - public ContentFinderByUrl(ILogger logger) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrl(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) { - _logger = logger; + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + UmbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); } + /// + /// Gets the + /// + protected IUmbracoContextAccessor UmbracoContextAccessor { get; } + /// /// Tries to find and assign an Umbraco document to a PublishedRequest. /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public virtual bool TryFindContent(IPublishedRequest frequest) + public virtual bool TryFindContent(IPublishedRequestBuilder frequest) { - string route; - if (frequest.HasDomain) - route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); - else - route = frequest.Uri.GetAbsolutePathDecoded(); + IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } - var node = FindContent(frequest, route); + string route; + if (frequest.Domain != null) + { + route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + } + else + { + route = frequest.Uri.GetAbsolutePathDecoded(); + } + + IPublishedContent node = FindContent(frequest, route); return node != null; } /// /// Tries to find an Umbraco document for a PublishedRequest and a route. /// - /// The document request. - /// The route. /// The document node, or null. - protected IPublishedContent FindContent(IPublishedRequest docreq, string route) + protected IPublishedContent FindContent(IPublishedRequestBuilder docreq, string route) { - if (docreq == null) throw new System.ArgumentNullException(nameof(docreq)); + IUmbracoContext umbCtx = UmbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return null; + } + + if (docreq == null) + { + throw new System.ArgumentNullException(nameof(docreq)); + } _logger.LogDebug("Test route {Route}", route); - var node = docreq.UmbracoContext.Content.GetByRoute(docreq.UmbracoContext.InPreviewMode, route, culture: docreq.Culture?.Name); + IPublishedContent node = umbCtx.Content.GetByRoute(umbCtx.InPreviewMode, route, culture: docreq.Culture?.Name); if (node != null) { - docreq.PublishedContent = node; + docreq.SetPublishedContent(node); _logger.LogDebug("Got content, id={NodeId}", node.Id); } else diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 24bfad914d..f7ebd6bbc5 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -20,12 +20,21 @@ namespace Umbraco.Web.Routing { private readonly IPublishedValueFallback _publishedValueFallback; private readonly IVariationContextAccessor _variationContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILogger _logger; - public ContentFinderByUrlAlias(ILogger logger, IPublishedValueFallback publishedValueFallback, IVariationContextAccessor variationContextAccessor) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAlias( + ILogger logger, + IPublishedValueFallback publishedValueFallback, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { _publishedValueFallback = publishedValueFallback; _variationContextAccessor = variationContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; _logger = logger; } @@ -34,21 +43,29 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + IPublishedContent node = null; - if (frequest.Uri.AbsolutePath != "/") // no alias if "/" + // no alias if "/" + if (frequest.Uri.AbsolutePath != "/") { - node = FindContentByAlias(frequest.UmbracoContext.Content, - frequest.HasDomain ? frequest.Domain.ContentId : 0, + node = FindContentByAlias( + umbCtx.Content, + frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture.Name, frequest.Uri.GetAbsolutePathDecoded()); if (node != null) { - frequest.PublishedContent = node; - _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, frequest.PublishedContent.Id); + frequest.SetPublishedContent(node); + _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id); } } @@ -57,7 +74,10 @@ namespace Umbraco.Web.Routing private IPublishedContent FindContentByAlias(IPublishedContentCache cache, int rootNodeId, string culture, string alias) { - if (alias == null) throw new ArgumentNullException(nameof(alias)); + if (alias == null) + { + throw new ArgumentNullException(nameof(alias)); + } // the alias may be "foo/bar" or "/foo/bar" // there may be spaces as in "/foo/bar, /foo/nil" @@ -65,7 +85,6 @@ namespace Umbraco.Web.Routing // TODO: can we normalize the values so that they contain no whitespaces, and no leading slashes? // and then the comparisons in IsMatch can be way faster - and allocate way less strings - const string propertyAlias = Constants.Conventions.Content.UrlAlias; var test1 = alias.TrimStart('/') + ","; @@ -80,38 +99,52 @@ namespace Umbraco.Web.Routing // "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + // " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + // ")]" + if (!c.HasProperty(propertyAlias)) + { + return false; + } - if (!c.HasProperty(propertyAlias)) return false; - var p = c.GetProperty(propertyAlias); + IPublishedProperty p = c.GetProperty(propertyAlias); var varies = p.PropertyType.VariesByCulture(); string v; if (varies) { - if (!c.HasCulture(culture)) return false; + if (!c.HasCulture(culture)) + { + return false; + } + v = c.Value(_publishedValueFallback, propertyAlias, culture); } else { v = c.Value(_publishedValueFallback, propertyAlias); } - if (string.IsNullOrWhiteSpace(v)) return false; - v = "," + v.Replace(" ", "") + ","; + + if (string.IsNullOrWhiteSpace(v)) + { + return false; + } + + v = "," + v.Replace(" ", string.Empty) + ","; return v.InvariantContains(a1) || v.InvariantContains(a2); } // TODO: even with Linq, what happens below has to be horribly slow // but the only solution is to entirely refactor URL providers to stop being dynamic - if (rootNodeId > 0) { - var rootNode = cache.GetById(rootNodeId); + IPublishedContent rootNode = cache.GetById(rootNodeId); return rootNode?.Descendants(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); } - foreach (var rootContent in cache.GetAtRoot()) + foreach (IPublishedContent rootContent in cache.GetAtRoot()) { - var c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); - if (c != null) return c; + IPublishedContent c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); + if (c != null) + { + return c; + } } return null; diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 8ae4e2aead..c6bd4f383d 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -1,10 +1,10 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; namespace Umbraco.Web.Routing { @@ -24,8 +24,16 @@ namespace Umbraco.Web.Routing private readonly IContentTypeService _contentTypeService; private readonly WebRoutingSettings _webRoutingSettings; - public ContentFinderByUrlAndTemplate(ILogger logger, IFileService fileService, IContentTypeService contentTypeService, IOptions webRoutingSettings) - : base(logger) + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAndTemplate( + ILogger logger, + IFileService fileService, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IOptions webRoutingSettings) + : base(logger, umbracoContextAccessor) { _logger = logger; _fileService = fileService; @@ -39,13 +47,14 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// If successful, also assigns the template. - public override bool TryFindContent(IPublishedRequest frequest) + public override bool TryFindContent(IPublishedRequestBuilder frequest) { - IPublishedContent node = null; var path = frequest.Uri.GetAbsolutePathDecoded(); - if (frequest.HasDomain) + if (frequest.Domain != null) + { path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); + } // no template if "/" if (path == "/") @@ -59,7 +68,7 @@ namespace Umbraco.Web.Routing var templateAlias = path.Substring(pos + 1); path = pos == 0 ? "/" : path.Substring(0, pos); - var template = _fileService.GetTemplate(templateAlias); + ITemplate template = _fileService.GetTemplate(templateAlias); if (template == null) { @@ -70,8 +79,8 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias); // look for node corresponding to the rest of the route - var route = frequest.HasDomain ? (frequest.Domain.ContentId + path) : path; - node = FindContent(frequest, route); // also assigns to published request + var route = frequest.Domain != null ? (frequest.Domain.ContentId + path) : path; + IPublishedContent node = FindContent(frequest, route); if (node == null) { @@ -83,12 +92,12 @@ namespace Umbraco.Web.Routing if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) { _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); - frequest.PublishedContent = null; // clear + frequest.SetPublishedContent(null); // clear return false; } // got it - frequest.TemplateModel = template; + frequest.SetTemplate(template); return true; } } diff --git a/src/Umbraco.Core/Routing/IContentFinder.cs b/src/Umbraco.Core/Routing/IContentFinder.cs index 39d31741a6..57575b3cf0 100644 --- a/src/Umbraco.Core/Routing/IContentFinder.cs +++ b/src/Umbraco.Core/Routing/IContentFinder.cs @@ -11,6 +11,6 @@ namespace Umbraco.Web.Routing /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// Optionally, can also assign the template or anything else on the document request, although that is not required. - bool TryFindContent(IPublishedRequest request); + bool TryFindContent(IPublishedRequestBuilder request); } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 51fc9ccf64..f05df351f3 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -6,29 +6,24 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { + public interface IPublishedRequest { /// - /// Gets the UmbracoContext. - /// - IUmbracoContext UmbracoContext { get; } // TODO: This should be injected and removed from here - - /// - /// Gets or sets the cleaned up Uri used for routing. + /// Gets the cleaned up inbound Uri used for routing. /// /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - Uri Uri { get; set; } + Uri Uri { get; } /// - /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. /// - bool IgnorePublishedContentCollisions { get; set; } + bool IgnorePublishedContentCollisions { get; } /// - /// Gets or sets the requested content. + /// Gets a value indicating the requested content. /// - /// Setting the requested content clears Template. - IPublishedContent PublishedContent { get; set; } + IPublishedContent PublishedContent { get; } /// /// Gets the initial requested content. @@ -38,187 +33,59 @@ namespace Umbraco.Web.Routing IPublishedContent InitialPublishedContent { get; } /// - /// Gets value indicating whether the current published content is the initial one. - /// - bool IsInitialPublishedContent { get; } - - /// - /// Gets or sets a value indicating whether the current published content has been obtained + /// Gets a value indicating whether the current published content has been obtained /// from the initial published content following internal redirections exclusively. /// /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirectPublishedContent { get; } + bool IsInternalRedirectPublishedContent { get; } // TODO: Not sure what thsi is yet /// - /// Gets a value indicating whether the content request has a content. + /// Gets the template assigned to the request (if any) /// - bool HasPublishedContent { get; } - - ITemplate TemplateModel { get; set; } + ITemplate Template { get; } /// - /// Gets the alias of the template to use to display the requested content. - /// - string TemplateAlias { get; } - - /// - /// Gets a value indicating whether the content request has a template. - /// - bool HasTemplate { get; } - - void UpdateToNotFound(); - - /// - /// Gets or sets the content request's domain. + /// Gets the content request's domain. /// /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - DomainAndUri Domain { get; set; } + DomainAndUri Domain { get; } /// - /// Gets a value indicating whether the content request has a domain. + /// Gets the content request's culture. /// - bool HasDomain { get; } + CultureInfo Culture { get; } /// - /// Gets or sets the content request's culture. - /// - CultureInfo Culture { get; set; } - - /// - /// Gets or sets a value indicating whether the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder and can also be used in - /// custom content finders or Prepared event handlers, where we want to allow developers - /// to indicate a request is 404 but not to cancel it. - bool Is404 { get; set; } - - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - bool IsRedirect { get; } - - /// - /// Gets or sets a value indicating whether the redirect is permanent. - /// - bool IsRedirectPermanent { get; } - - /// - /// Gets or sets the url to redirect to, when the content request triggers a redirect. + /// Gets the url to redirect to, when the content request triggers a redirect. /// string RedirectUrl { get; } /// - /// Gets or sets the content request http response status code. + /// Gets the content request http response status code. /// /// Does not actually set the http response status code, only registers that the response /// should use the specified code. The code will or will not be used, in due time. int ResponseStatusCode { get; } /// - /// Gets or sets the content request http response status description. + /// Gets the content request http response status description. /// /// Does not actually set the http response status description, only registers that the response /// should use the specified description. The description will or will not be used, in due time. string ResponseStatusDescription { get; } /// - /// Gets or sets a list of Extensions to append to the Response.Cache object. + /// Gets a list of Extensions to append to the Response.Cache object. /// - List CacheExtensions { get; set; } + IReadOnlyList CacheExtensions { get; } /// - /// Gets or sets a dictionary of Headers to append to the Response object. + /// Gets a dictionary of Headers to append to the Response object. /// - Dictionary Headers { get; set; } + IReadOnlyDictionary Headers { get; } - bool CacheabilityNoCache { get; set; } - - /// - /// Prepares the request. - /// - void Prepare(); - - /// - /// Triggers the Preparing event. - /// - void OnPreparing(); - - /// - /// Triggers the Prepared event. - /// - void OnPrepared(); - - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - void SetInternalRedirectPublishedContent(IPublishedContent content); - - /// - /// Indicates that the current PublishedContent is the initial one. - /// - void SetIsInitialPublishedContent(); - - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - bool TrySetTemplate(string alias); - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - void SetTemplate(ITemplate template); - - /// - /// Resets the template. - /// - void ResetTemplate(); - - /// - /// Indicates that the content request should trigger a redirect (302). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirect(string url); - - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirectPermanent(string url); - - /// - /// Indicates that the content request should trigger a redirect, with a specified status code. - /// - /// The url to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - void SetRedirect(string url, int status); - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// The description. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - void SetResponseStatus(int code, string description = null); + bool CacheabilityNoCache { get; } } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs new file mode 100644 index 0000000000..fee64fda8d --- /dev/null +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Routing +{ + /// + /// Used by to route inbound requests to Umbraco content + /// + public interface IPublishedRequestBuilder + { + /// + /// Gets the cleaned up inbound Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + Uri Uri { get; } + + /// + /// Gets the assigned (if any) + /// + DomainAndUri Domain { get; } + + /// + /// Gets the assigned (if any) + /// + CultureInfo Culture { get; } + + /// + /// Gets a value indicating whether the current published content is the initial one. + /// + bool IsInitialPublishedContent { get; } + + /// + /// Gets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + bool IsInternalRedirectPublishedContent { get; } + + /// + /// Gets the content request http response status code. + /// + int ResponseStatusCode { get; } + + /// + /// Gets the current assigned (if any) + /// + IPublishedContent PublishedContent { get; } + + /// + /// Gets the template assigned to the request (if any) + /// + ITemplate Template { get; } + + /// + /// Builds the + /// + IPublishedRequest Build(); + + /// + /// Sets the domain for the request + /// + IPublishedRequestBuilder SetDomain(DomainAndUri domain); + + /// + /// Sets the culture for the request + /// + IPublishedRequestBuilder SetCulture(CultureInfo culture); + + /// + /// Sets the found for the request + /// + IPublishedRequestBuilder SetPublishedContent(IPublishedContent content); + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); + + /// + /// Indicates that the current PublishedContent is the initial one. + /// + IPublishedRequestBuilder SetIsInitialPublishedContent(); // TODO: Required? + + /// + /// Tries to set the template to use to display the requested content. + /// + /// The alias of the template. + /// A value indicating whether a valid template with the specified alias was found. + /// + /// Successfully setting the template does refresh RenderingEngine. + /// If setting the template fails, then the previous template (if any) remains in place. + /// + bool TrySetTemplate(string alias); + + /// + /// Sets the template to use to display the requested content. + /// + /// The template. + /// Setting the template does refresh RenderingEngine. + IPublishedRequestBuilder SetTemplate(ITemplate template); + + /// + /// Resets the template. + /// + IPublishedRequestBuilder ResetTemplate(); + + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The url to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + IPublishedRequestBuilder SetRedirectPermanent(string url); + + /// + /// Indicates that the content request should trigger a redirect, with a specified status code. + /// + /// The url to redirect to. + /// The status code (300-308). + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect); + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// The description. + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + IPublishedRequestBuilder SetResponseStatus(int code, string description = null); + + IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability); + + /// + /// Sets a list of Extensions to append to the Response.Cache object. + /// + IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions); + + /// + /// Sets a dictionary of Headers to append to the Response object. + /// + IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); + + /// + /// Sets a value indicating that the requested content could not be found. + /// + /// This is set in the PublishedContentRequestBuilder and can also be used in + /// custom content finders or Prepared event handlers, where we want to allow developers + /// to indicate a request is 404 but not to cancel it. + IPublishedRequestBuilder SetIs404(bool is404); + + // TODO: This seems to be the same as is404? + //IPublishedRequestBuilder UpdateToNotFound(); + } +} diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index db9d69df20..aaccb4b4d2 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Core.Models; @@ -9,46 +9,39 @@ namespace Umbraco.Web.Routing /// public interface IPublishedRouter { - // TODO: consider this and RenderRouteHandler - move some code around? + // TODO: consider this and UmbracoRouteValueTransformer - move some code around? /// /// Creates a published request. /// - /// The current Umbraco context. - /// The (optional) request Uri. - /// A published request. - IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null); + /// The current request Uri. + /// A published request builder. + IPublishedRequestBuilder CreateRequest(Uri uri); /// /// Prepares a request for rendering. /// /// The request. /// A value indicating whether the request was successfully prepared and can be rendered. - bool PrepareRequest(IPublishedRequest request); + IPublishedRequest RouteRequest(IPublishedRequestBuilder request); /// /// Tries to route a request. /// /// The request. /// A value indicating whether the request can be routed to a document. - bool TryRouteRequest(IPublishedRequest request); + bool TryRouteRequest(IPublishedRequestBuilder request); - /// - /// Gets a template. - /// - /// The template alias - /// The template. - ITemplate GetTemplate(string alias); - - /// - /// Updates the request to "not found". - /// - /// The request. - /// - /// This method is invoked when the pipeline decides it cannot render - /// the request, for whatever reason, and wants to force it to be re-routed - /// and rendered as if no document were found (404). - /// - void UpdateRequestToNotFound(IPublishedRequest request); + // TODO: This shouldn't be required and should be handled differently during route building + ///// + ///// Updates the request to "not found". + ///// + ///// The request. + ///// + ///// This method is invoked when the pipeline decides it cannot render + ///// the request, for whatever reason, and wants to force it to be re-routed + ///// and rendered as if no document were found (404). + ///// + //void UpdateRequestToNotFound(IPublishedRequest request); } } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index bab60e49f6..b3aa37d31e 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -2,27 +2,90 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Threading; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; namespace Umbraco.Web.Routing { + public class PublishedRequest : IPublishedRequest + { + /// + /// Initializes a new instance of the class. + /// + public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int responseStatusCode, string responseStatusDescription, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) + { + Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + //IgnorePublishedContentCollisions = ignorePublishedContentCollisions; + PublishedContent = publishedContent ?? throw new ArgumentNullException(nameof(publishedContent)); + IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent; + Template = template ?? throw new ArgumentNullException(nameof(template)); + Domain = domain ?? throw new ArgumentNullException(nameof(domain)); + Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + RedirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl)); + ResponseStatusCode = responseStatusCode; + ResponseStatusDescription = responseStatusDescription ?? throw new ArgumentNullException(nameof(responseStatusDescription)); + CacheExtensions = cacheExtensions ?? throw new ArgumentNullException(nameof(cacheExtensions)); + Headers = headers ?? throw new ArgumentNullException(nameof(headers)); + CacheabilityNoCache = cacheabilityNoCache; + } + + /// + public Uri Uri { get; } + + /// + public bool IgnorePublishedContentCollisions { get; } + + /// + public IPublishedContent PublishedContent { get; } + + /// + public IPublishedContent InitialPublishedContent { get; } + + /// + public bool IsInternalRedirectPublishedContent { get; } + + /// + public ITemplate Template { get; } + + /// + public DomainAndUri Domain { get; } + + /// + public CultureInfo Culture { get; } + + /// + public string RedirectUrl { get; } + + /// + public int ResponseStatusCode { get; } + + /// + public string ResponseStatusDescription { get; } + + /// + public IReadOnlyList CacheExtensions { get; } + + /// + public IReadOnlyDictionary Headers { get; } + + /// + public bool CacheabilityNoCache { get; } + } + /// /// Represents a request for one specified Umbraco IPublishedContent to be rendered /// by one specified template, using one specified Culture and RenderingEngine. /// - public class PublishedRequest : IPublishedRequest + public class PublishedRequestOld // : IPublishedRequest { private readonly IPublishedRouter _publishedRouter; private readonly WebRoutingSettings _webRoutingSettings; private bool _readonly; // after prepared - private bool _readonlyUri; // after preparing - private Uri _uri; // clean uri, no virtual dir, no trailing slash nor .aspx, nothing private bool _is404; private DomainAndUri _domain; private CultureInfo _culture; @@ -30,12 +93,9 @@ namespace Umbraco.Web.Routing private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The published router. - /// The Umbraco context. - /// The request Uri. - public PublishedRequest(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) + public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) { UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); @@ -52,98 +112,90 @@ namespace Umbraco.Web.Routing /// Gets or sets the cleaned up Uri used for routing. /// /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri - { - get => _uri; - set - { - if (_readonlyUri) - throw new InvalidOperationException("Cannot modify Uri after Preparing has triggered."); - _uri = value; - } - } + public Uri Uri { get; } // utility for ensuring it is ok to set some properties public void EnsureWriteable() { if (_readonly) + { throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); + } } public bool CacheabilityNoCache { get; set; } - /// - /// Prepares the request. - /// - public void Prepare() - { - _publishedRouter.PrepareRequest(this); - } + ///// + ///// Prepares the request. + ///// + //public void Prepare() + //{ + // _publishedRouter.PrepareRequest(this); + //} /// /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. /// public bool IgnorePublishedContentCollisions { get; set; } - #region Events + //#region Events - /// - /// Triggers before the published content request is prepared. - /// - /// When the event triggers, no preparation has been done. It is still possible to - /// modify the request's Uri property, for example to restore its original, public-facing value - /// that might have been modified by an in-between equipment such as a load-balancer. - public static event EventHandler Preparing; + ///// + ///// Triggers before the published content request is prepared. + ///// + ///// When the event triggers, no preparation has been done. It is still possible to + ///// modify the request's Uri property, for example to restore its original, public-facing value + ///// that might have been modified by an in-between equipment such as a load-balancer. + //public static event EventHandler Preparing; - /// - /// Triggers once the published content request has been prepared, but before it is processed. - /// - /// When the event triggers, preparation is done ie domain, culture, document, template, - /// rendering engine, etc. have been setup. It is then possible to change anything, before - /// the request is actually processed and rendered by Umbraco. - public static event EventHandler Prepared; + ///// + ///// Triggers once the published content request has been prepared, but before it is processed. + ///// + ///// When the event triggers, preparation is done ie domain, culture, document, template, + ///// rendering engine, etc. have been setup. It is then possible to change anything, before + ///// the request is actually processed and rendered by Umbraco. + //public static event EventHandler Prepared; - /// - /// Triggers the Preparing event. - /// - public void OnPreparing() - { - Preparing?.Invoke(this, EventArgs.Empty); - _readonlyUri = true; - } + ///// + ///// Triggers the Preparing event. + ///// + //public void OnPreparing() + //{ + // Preparing?.Invoke(this, EventArgs.Empty); + //} - /// - /// Triggers the Prepared event. - /// - public void OnPrepared() - { - Prepared?.Invoke(this, EventArgs.Empty); + ///// + ///// Triggers the Prepared event. + ///// + //public void OnPrepared() + //{ + // Prepared?.Invoke(this, EventArgs.Empty); - if (HasPublishedContent == false) - Is404 = true; // safety + // if (HasPublishedContent == false) + // Is404 = true; // safety - _readonly = true; - } + // _readonly = true; + //} - #endregion + //#endregion #region PublishedContent - /// - /// Gets or sets the requested content. - /// - /// Setting the requested content clears Template. - public IPublishedContent PublishedContent - { - get { return _publishedContent; } - set - { - EnsureWriteable(); - _publishedContent = value; - IsInternalRedirectPublishedContent = false; - TemplateModel = null; - } - } + ///// + ///// Gets or sets the requested content. + ///// + ///// Setting the requested content clears Template. + //public IPublishedContent PublishedContent + //{ + // get { return _publishedContent; } + // set + // { + // EnsureWriteable(); + // _publishedContent = value; + // IsInternalRedirectPublishedContent = false; + // TemplateModel = null; + // } + //} /// /// Sets the requested content, following an internal redirect. @@ -153,38 +205,39 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. public void SetInternalRedirectPublishedContent(IPublishedContent content) { - if (content == null) throw new ArgumentNullException(nameof(content)); - EnsureWriteable(); + //if (content == null) + // throw new ArgumentNullException(nameof(content)); + //EnsureWriteable(); - // unless a template has been set already by the finder, - // template should be null at that point. + //// unless a template has been set already by the finder, + //// template should be null at that point. - // IsInternalRedirect if IsInitial, or already IsInternalRedirect - var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + //// IsInternalRedirect if IsInitial, or already IsInternalRedirect + //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - // redirecting to self - if (content.Id == PublishedContent.Id) // neither can be null - { - // no need to set PublishedContent, we're done - IsInternalRedirectPublishedContent = isInternalRedirect; - return; - } + //// redirecting to self + //if (content.Id == PublishedContent.Id) // neither can be null + //{ + // // no need to set PublishedContent, we're done + // IsInternalRedirectPublishedContent = isInternalRedirect; + // return; + //} - // else + //// else - // save - var template = TemplateModel; + //// save + //var template = Template; - // set published content - this resets the template, and sets IsInternalRedirect to false - PublishedContent = content; - IsInternalRedirectPublishedContent = isInternalRedirect; + //// set published content - this resets the template, and sets IsInternalRedirect to false + //PublishedContent = content; + //IsInternalRedirectPublishedContent = isInternalRedirect; - // must restore the template if it's an internal redirect & the config option is set - if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - { - // restore - TemplateModel = template; - } + //// must restore the template if it's an internal redirect & the config option is set + //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + //{ + // // restore + // TemplateModel = template; + //} } /// @@ -219,91 +272,19 @@ namespace Umbraco.Web.Routing /// apply the internal redirect or not, when content is not the initial content. public bool IsInternalRedirectPublishedContent { get; private set; } - /// - /// Gets a value indicating whether the content request has a content. - /// - public bool HasPublishedContent => PublishedContent != null; #endregion - #region Template - /// /// Gets or sets the template model to use to display the requested content. /// - public ITemplate TemplateModel { get; set; } + public ITemplate Template { get; } /// /// Gets the alias of the template to use to display the requested content. /// - public string TemplateAlias => TemplateModel?.Alias; + public string TemplateAlias => Template?.Alias; - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - public bool TrySetTemplate(string alias) - { - EnsureWriteable(); - - if (string.IsNullOrWhiteSpace(alias)) - { - TemplateModel = null; - return true; - } - - // NOTE - can we still get it with whitespaces in it due to old legacy bugs? - alias = alias.Replace(" ", ""); - - var model = _publishedRouter.GetTemplate(alias); - if (model == null) - return false; - - TemplateModel = model; - return true; - } - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - public void SetTemplate(ITemplate template) - { - EnsureWriteable(); - TemplateModel = template; - } - - /// - /// Resets the template. - /// - public void ResetTemplate() - { - EnsureWriteable(); - TemplateModel = null; - } - - /// - /// Gets a value indicating whether the content request has a template. - /// - public bool HasTemplate => TemplateModel != null; - - public void UpdateToNotFound() - { - var __readonly = _readonly; - _readonly = false; - _publishedRouter.UpdateRequestToNotFound(this); - _readonly = __readonly; - } - - #endregion - - #region Domain and Culture /// /// Gets or sets the content request's domain. @@ -341,7 +322,6 @@ namespace Umbraco.Web.Routing // note: do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionary lookup and such? - #endregion #region Status diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs new file mode 100644 index 0000000000..8167e83e6a --- /dev/null +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Routing +{ + public class PublishedRequestBuilder : IPublishedRequestBuilder + { + private readonly IFileService _fileService; + private IReadOnlyDictionary _headers; + private bool _cacheability; + private IReadOnlyList _cacheExtensions; + private IPublishedContent _internalRedirectContent; + private bool _isInitContent; + private string _redirectUrl; + private HttpStatusCode _responseStatus = HttpStatusCode.NotFound; + private string _responseDesc; + + /// + /// Initializes a new instance of the class. + /// + public PublishedRequestBuilder(IFileService fileService) + { + _fileService = fileService; + } + + /// + public Uri Uri { get; private set; } + + /// + public DomainAndUri Domain { get; private set; } + + /// + public CultureInfo Culture { get; private set; } + + /// + public ITemplate Template { get; private set; } + + /// + public bool IsInitialPublishedContent { get; private set; } + + /// + public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet + + /// + public int ResponseStatusCode => (int)_responseStatus; + + /// + public IPublishedContent PublishedContent { get; private set; } + + /// + public IPublishedRequest Build() => new PublishedRequest( + Uri, + PublishedContent, + IsInternalRedirectPublishedContent, + Template, + Domain, + Culture, + _redirectUrl, + (int)_responseStatus, + _responseDesc, + _cacheExtensions, + _headers, + _cacheability); + + /// + public IPublishedRequestBuilder ResetTemplate() + { + Template = null; + return this; + } + + /// + public IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability) + { + _cacheability = cacheability; + return this; + } + + /// + public IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions) + { + _cacheExtensions = cacheExtensions.ToList(); + return this; + } + + /// + public IPublishedRequestBuilder SetCulture(CultureInfo culture) + { + Culture = culture; + return this; + } + + /// + public IPublishedRequestBuilder SetDomain(DomainAndUri domain) + { + Domain = domain; + return this; + } + + /// + public IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers) + { + _headers = headers; + return this; + } + + /// + public IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content) + { + _internalRedirectContent = content; + return this; + } + + /// + public IPublishedRequestBuilder SetIs404(bool is404) + { + _responseStatus = HttpStatusCode.NotFound; + return this; + } + + /// + public IPublishedRequestBuilder SetIsInitialPublishedContent() + { + _isInitContent = true; + return this; + } + + /// + public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content) + { + PublishedContent = content; + return this; + } + + /// + public IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect) + { + _redirectUrl = url; + _responseStatus = (HttpStatusCode)status; + return this; + } + + /// + public IPublishedRequestBuilder SetRedirectPermanent(string url) + { + _redirectUrl = url; + _responseStatus = HttpStatusCode.Moved; + return this; + } + + /// + public IPublishedRequestBuilder SetResponseStatus(int code, string description = null) + { + _responseStatus = (HttpStatusCode)code; + _responseDesc = description; + return this; + } + + /// + public IPublishedRequestBuilder SetTemplate(ITemplate template) + { + Template = template; + return this; + } + + /// + public bool TrySetTemplate(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) + { + Template = null; + return true; + } + + // NOTE - can we still get it with whitespaces in it due to old legacy bugs? + alias = alias.Replace(" ", ""); + + ITemplate model = _fileService.GetTemplate(alias); + if (model == null) + { + return false; + } + + Template = model; + return true; + } + } +} diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs new file mode 100644 index 0000000000..f9c9d8b294 --- /dev/null +++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs @@ -0,0 +1,74 @@ +using System.Net; + +namespace Umbraco.Web.Routing +{ + public static class PublishedRequestExtensions + { + /// + /// Gets a value indicating whether the request was successfully routed + /// + public static bool Success(this IPublishedRequest publishedRequest) + => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent(); + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequestBuilder publishedRequest) => publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequest publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public static string GetTemplateAlias(this IPublishedRequest publishedRequest) => publishedRequest.Template?.Alias; + + /// + /// Gets a value indicating whether the requested content could not be found. + /// + public static bool Is404(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.NotFound; + + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequestBuilder publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the redirect is permanent. + /// + public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the current published content is the initial one. + /// + public static bool IsInitialPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.InitialPublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Domain != null; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequest publishedRequest) => publishedRequest.Domain != null; + + } +} diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 10986b941a..a7b20b84ba 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -1,18 +1,18 @@ using System; -using System.Linq; -using System.Threading; using System.Globalization; using System.IO; +using System.Linq; +using System.Threading; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; +using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; namespace Umbraco.Web.Routing { @@ -34,6 +34,7 @@ namespace Umbraco.Web.Routing private readonly IFileService _fileService; private readonly IContentTypeService _contentTypeService; private readonly IPublicAccessService _publicAccessService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; /// /// Initializes a new instance of the class. @@ -51,7 +52,8 @@ namespace Umbraco.Web.Routing IPublicAccessChecker publicAccessChecker, IFileService fileService, IContentTypeService contentTypeService, - IPublicAccessService publicAccessService) + IPublicAccessService publicAccessService, + IUmbracoContextAccessor umbracoContextAccessor) { _webRoutingSettings = webRoutingSettings.Value ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); @@ -66,38 +68,32 @@ namespace Umbraco.Web.Routing _fileService = fileService; _contentTypeService = contentTypeService; _publicAccessService = publicAccessService; + _umbracoContextAccessor = umbracoContextAccessor; } /// - public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null) - { - return new PublishedRequest(this, umbracoContext, Options.Create(_webRoutingSettings), uri ?? umbracoContext.CleanedUmbracoUrl); - } - - #region Request + public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(_fileService); /// - public bool TryRouteRequest(IPublishedRequest request) + public bool TryRouteRequest(IPublishedRequestBuilder request) { - // disabled - is it going to change the routing? - //_pcr.OnPreparing(); - FindDomain(request); - if (request.IsRedirect) return false; - if (request.HasPublishedContent) return true; + + // TODO: This was ported from v8 but how could it possibly have a redirect here? + if (request.IsRedirect()) + { + return false; + } + + // TODO: This was ported from v8 but how could it possibly have content here? + if (request.HasPublishedContent()) + { + return true; + } + FindPublishedContent(request); - if (request.IsRedirect) return false; - // don't handle anything - we just want to ensure that we find the content - //HandlePublishedContent(); - //FindTemplate(); - //FollowExternalRedirect(); - //HandleWildcardDomains(); - - // disabled - we just want to ensure that we find the content - //_pcr.OnPrepared(); - - return request.HasPublishedContent; + return request.Build().Success(); } private void SetVariationContext(string culture) @@ -108,33 +104,20 @@ namespace Umbraco.Web.Routing } /// - public bool PrepareRequest(IPublishedRequest request) + public IPublishedRequest RouteRequest(IPublishedRequestBuilder request) { - // note - at that point the original legacy module did something do handle IIS custom 404 errors - // ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support - // "directory URLs" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain - // to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk. - // - // to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors - // so that they point to a non-existing page eg /redirect-404.aspx - // TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means. - - // trigger the Preparing event - at that point anything can still be changed - // the idea is that it is possible to change the uri - // - request.OnPreparing(); - - //find domain + // find domain FindDomain(request); // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting - if (request.IsRedirect) + if (request.IsRedirect()) { - return false; + return request.Build(); } // set the culture on the thread - once, so it's set when running document lookups + // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); @@ -154,10 +137,10 @@ namespace Umbraco.Web.Routing Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); - // trigger the Prepared event - at that point it is still possible to change about anything - // even though the request might be flagged for redirection - we'll redirect _after_ the event - // also, OnPrepared() will make the PublishedRequest readonly, so nothing can change - request.OnPrepared(); + //// trigger the Prepared event - at that point it is still possible to change about anything + //// even though the request might be flagged for redirection - we'll redirect _after_ the event + //// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change + //request.OnPrepared(); // we don't take care of anything so if the content has changed, it's up to the user // to find out the appropriate template @@ -177,80 +160,68 @@ namespace Umbraco.Web.Routing /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. /// - public bool ConfigureRequest(IPublishedRequest frequest) + internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest) { - if (frequest.HasPublishedContent == false) + if (!frequest.HasPublishedContent()) { - return false; + return frequest.Build(); } // set the culture on the thread -- again, 'cos it might have changed in the event handler + // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; SetVariationContext(frequest.Culture.Name); - // if request has been flagged to redirect, or has no content to display, - // then return - whoever called us is in charge of actually redirecting - if (frequest.IsRedirect || frequest.HasPublishedContent == false) - { - return false; - } - - // we may be 404 _and_ have a content - - // can't go beyond that point without a PublishedContent to render - // it's ok not to have a template, in order to give MVC a chance to hijack routes - return true; + return frequest.Build(); } - /// - public void UpdateRequestToNotFound(IPublishedRequest request) - { - // clear content - var content = request.PublishedContent; - request.PublishedContent = null; + // TODO: This shouldn't be required and should be handled differently during route building + ///// + //public void UpdateRequestToNotFound(IPublishedRequest request) + //{ + // // clear content + // var content = request.PublishedContent; + // request.PublishedContent = null; - HandlePublishedContent(request); // will go 404 - FindTemplate(request); + // HandlePublishedContent(request); // will go 404 + // FindTemplate(request); - // if request has been flagged to redirect then return - // whoever called us is in charge of redirecting - if (request.IsRedirect) - return; + // // if request has been flagged to redirect then return + // // whoever called us is in charge of redirecting + // if (request.IsRedirect) + // { + // return; + // } - if (request.HasPublishedContent == false) - { - // means the engine could not find a proper document to handle 404 - // restore the saved content so we know it exists - request.PublishedContent = content; - return; - } + // if (request.HasPublishedContent == false) + // { + // // means the engine could not find a proper document to handle 404 + // // restore the saved content so we know it exists + // request.PublishedContent = content; + // return; + // } - if (request.HasTemplate == false) - { - // means we may have a document, but we have no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much either - return; - } - } - - #endregion - - #region Domain + // if (request.HasTemplate == false) + // { + // // means we may have a document, but we have no template + // // at that point there isn't much we can do and there is no point returning + // // to Mvc since Mvc can't do much either + // return; + // } + //} /// /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. /// /// A value indicating whether a domain was found. - internal bool FindDomain(IPublishedRequest request) + internal bool FindDomain(IPublishedRequestBuilder request) { const string tracePrefix = "FindDomain: "; // note - we are not handling schemes nor ports here. - _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); - var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains; + IDomainCache domainsCache = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains; var domains = domainsCache.GetAll(includeWildcards: false).ToList(); // determines whether a domain corresponds to a published document, since some @@ -260,15 +231,19 @@ namespace Umbraco.Web.Routing bool IsPublishedContentDomain(Domain domain) { // just get it from content cache - optimize there, not here - var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId); + IPublishedContent domainDocument = _umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId); // not published - at all if (domainDocument == null) + { return false; + } // invariant - always published if (!domainDocument.ContentType.VariesByCulture()) + { return true; + } // variant, ensure that the culture corresponding to the domain's language is published return domainDocument.Cultures.ContainsKey(domain.Culture.Name); @@ -279,7 +254,7 @@ namespace Umbraco.Web.Routing var defaultCulture = domainsCache.DefaultCulture; // try to find a domain matching the current request - var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + DomainAndUri domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); // handle domain - always has a contentId and a culture if (domainAndUri != null) @@ -287,8 +262,9 @@ namespace Umbraco.Web.Routing // matching an existing domain _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); - request.Domain = domainAndUri; - request.Culture = domainAndUri.Culture; + request + .SetDomain(domainAndUri) + .SetCulture(domainAndUri.Culture); // canonical? not implemented at the moment // if (...) @@ -302,7 +278,7 @@ namespace Umbraco.Web.Routing // not matching any existing domain _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); - request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture); + request.SetCulture(defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture)); } _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name); @@ -313,22 +289,24 @@ namespace Umbraco.Web.Routing /// /// Looks for wildcard domains in the path and updates Culture accordingly. /// - internal void HandleWildcardDomains(IPublishedRequest request) + internal void HandleWildcardDomains(IPublishedRequestBuilder request) { const string tracePrefix = "HandleWildcardDomains: "; - if (request.HasPublishedContent == false) + if (request.PublishedContent == null) + { return; + } var nodePath = request.PublishedContent.Path; _logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); - var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null; - var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); + var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; + Domain domain = DomainUtilities.FindWildcardDomainInPath(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) { - request.Culture = domain.Culture; + request.SetCulture(domain.Culture); _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name); } else @@ -337,10 +315,6 @@ namespace Umbraco.Web.Routing } } - #endregion - - #region Rendering engine - internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) { if (directory == null || directory.Exists == false) @@ -359,21 +333,10 @@ namespace Umbraco.Web.Routing return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); } - #endregion - - #region Document and template - - /// - public ITemplate GetTemplate(string alias) - { - return _fileService.GetTemplate(alias); - } - /// /// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly. /// - /// A value indicating whether a document and template were found. - private void FindPublishedContentAndTemplate(IPublishedRequest request) + private void FindPublishedContentAndTemplate(IPublishedRequestBuilder request) { _logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath); @@ -383,8 +346,10 @@ namespace Umbraco.Web.Routing // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting // -- do not process anything any further -- - if (request.IsRedirect) + if (request.IsRedirect()) + { return; + } // not handling umbracoRedirect here but after LookupDocument2 // so internal redirect, 404, etc has precedence over redirect @@ -403,7 +368,7 @@ namespace Umbraco.Web.Routing /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. /// /// There is no finder collection. - internal void FindPublishedContent(IPublishedRequest request) + internal void FindPublishedContent(IPublishedRequestBuilder request) { const string tracePrefix = "FindPublishedContent: "; @@ -413,9 +378,9 @@ namespace Umbraco.Web.Routing using (_profilingLogger.DebugDuration( $"{tracePrefix}Begin finders", - $"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}")) + $"{tracePrefix}End finders")) { - //iterate but return on first one that finds it + // iterate but return on first one that finds it var found = _contentFinders.Any(finder => { _logger.LogDebug("Finder {ContentFinderType}", finder.GetType().FullName); @@ -435,7 +400,7 @@ namespace Umbraco.Web.Routing /// Handles "not found", internal redirects, access validation... /// things that must be handled in one place because they can create loops /// - private void HandlePublishedContent(IPublishedRequest request) + private void HandlePublishedContent(IPublishedRequestBuilder request) { // because these might loop, we have to have some sort of infinite loop detection int i = 0, j = 0; @@ -445,9 +410,9 @@ namespace Umbraco.Web.Routing _logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i); // handle not found - if (request.HasPublishedContent == false) + if (request.PublishedContent == null) { - request.Is404 = true; + request.SetIs404(true); _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); // if it fails then give up, there isn't much more that we can do @@ -464,23 +429,28 @@ namespace Umbraco.Web.Routing j = 0; while (FollowInternalRedirects(request) && j++ < maxLoop) { } - if (j == maxLoop) // we're running out of control + + // we're running out of control + if (j == maxLoop) + { break; + } // ensure access - if (request.HasPublishedContent) + if (request.PublishedContent != null) + { EnsurePublishedContentAccess(request); + } // loop while we don't have page, ie the redirect or access // got us to nowhere and now we need to run the notFoundLookup again // as long as it's not running out of control ie infinite loop of some sort - - } while (request.HasPublishedContent == false && i++ < maxLoop); + } while (request.PublishedContent == null && i++ < maxLoop); if (i == maxLoop || j == maxLoop) { _logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); - request.PublishedContent = null; + request.SetPublishedContent(null); } _logger.LogDebug("HandlePublishedContent: End"); @@ -494,14 +464,18 @@ namespace Umbraco.Web.Routing /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. /// As per legacy, if the redirect does not work, we just ignore it. /// - private bool FollowInternalRedirects(IPublishedRequest request) + private bool FollowInternalRedirects(IPublishedRequestBuilder request) { if (request.PublishedContent == null) + { throw new InvalidOperationException("There is no PublishedContent."); + } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + { return false; + } var redirect = false; var valid = false; @@ -512,29 +486,31 @@ namespace Umbraco.Web.Routing { // try and get the redirect node from a legacy integer ID valid = true; - internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId); + internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(internalRedirectId); } else { - var udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); + GuidUdi udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId != null) { // try and get the redirect node from a UDI Guid valid = true; - internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); + internalRedirectNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid); } } if (valid == false) { // bad redirect - log and display the current page (legacy behavior) - _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", + _logger.LogDebug( + "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()); } if (internalRedirectNode == null) { - _logger.LogDebug("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", + _logger.LogDebug( + "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId).GetSourceValue()); } else if (internalRedirectId == request.PublishedContent.Id) @@ -556,20 +532,22 @@ namespace Umbraco.Web.Routing /// Ensures that access to current node is permitted. /// /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. - private void EnsurePublishedContentAccess(IPublishedRequest request) + private void EnsurePublishedContentAccess(IPublishedRequestBuilder request) { if (request.PublishedContent == null) + { throw new InvalidOperationException("There is no PublishedContent."); + } var path = request.PublishedContent.Path; - var publicAccessAttempt = _publicAccessService.IsProtected(path); + Attempt publicAccessAttempt = _publicAccessService.IsProtected(path); if (publicAccessAttempt) { _logger.LogDebug("EnsurePublishedContentAccess: Page is protected, check for access"); - var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); + PublicAccessStatus status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id); switch (status) { case PublicAccessStatus.NotLoggedIn: @@ -599,24 +577,26 @@ namespace Umbraco.Web.Routing } } - private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId) + private void SetPublishedContentAsOtherPage(IPublishedRequestBuilder request, int errorPageId) { if (errorPageId != request.PublishedContent.Id) - request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId); + { + request.SetPublishedContent(_umbracoContextAccessor.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId)); + } } /// /// Finds a template for the current node, if any. /// - private void FindTemplate(IPublishedRequest request) + private void FindTemplate(IPublishedRequestBuilder request) { + // TODO: We've removed the event, might need to re-add? // NOTE: at the moment there is only 1 way to find a template, and then ppl must // use the Prepared event to change the template if they wish. Should we also // implement an ITemplateFinder logic? - if (request.PublishedContent == null) { - request.TemplateModel = null; + request.SetTemplate(null); return; } @@ -626,6 +606,7 @@ namespace Umbraco.Web.Routing // + optionally, apply the alternate template on internal redirects var useAltTemplate = request.IsInitialPublishedContent || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); + var altTemplate = useAltTemplate ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) : null; @@ -635,21 +616,18 @@ namespace Umbraco.Web.Routing // we don't have an alternate template specified. use the current one if there's one already, // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), // else lookup the template id on the document then lookup the template with that id. - - if (request.HasTemplate) + if (request.HasTemplate()) { _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); return; } - // TODO: When we remove the need for a database for templates, then this id should be irrelevant, - // not sure how were going to do this nicely. - // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! // if the template isn't assigned to the document type we should log a warning and return 404 - var templateId = request.PublishedContent.TemplateId; - request.TemplateModel = GetTemplateModel(templateId); + ITemplate template = GetTemplate(templateId); + request.SetTemplate(template); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } else { @@ -658,9 +636,11 @@ namespace Umbraco.Web.Routing // so /path/to/page/template1?altTemplate=template2 will use template2 // ignore if the alias does not match - just trace - - if (request.HasTemplate) + if (request.HasTemplate()) + { _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); + } + _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings @@ -672,11 +652,11 @@ namespace Umbraco.Web.Routing altTemplate)) { // allowed, use - var template = _fileService.GetTemplate(altTemplate); + ITemplate template = _fileService.GetTemplate(altTemplate); if (template != null) { - request.TemplateModel = template; + request.SetTemplate(template); _logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } else @@ -690,11 +670,13 @@ namespace Umbraco.Web.Routing // no allowed, back to default var templateId = request.PublishedContent.TemplateId; - request.TemplateModel = GetTemplateModel(templateId); + ITemplate template = GetTemplate(templateId); + request.SetTemplate(template); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } - if (request.HasTemplate == false) + if (!request.HasTemplate()) { _logger.LogDebug("FindTemplate: No template was found."); @@ -707,13 +689,9 @@ namespace Umbraco.Web.Routing // // so, don't set _pcr.Document to null here } - else - { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias); - } } - private ITemplate GetTemplateModel(int? templateId) + private ITemplate GetTemplate(int? templateId) { if (templateId.HasValue == false || templateId.Value == default) { @@ -724,11 +702,16 @@ namespace Umbraco.Web.Routing _logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId); if (templateId == null) + { throw new InvalidOperationException("The template is not set, the page cannot render."); + } - var template = _fileService.GetTemplate(templateId.Value); + ITemplate template = _fileService.GetTemplate(templateId.Value); if (template == null) + { throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); + } + _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); return template; } @@ -737,13 +720,18 @@ namespace Umbraco.Web.Routing /// Follows external redirection through umbracoRedirect document property. /// /// As per legacy, if the redirect does not work, we just ignore it. - private void FollowExternalRedirect(IPublishedRequest request) + private void FollowExternalRedirect(IPublishedRequestBuilder request) { - if (request.HasPublishedContent == false) return; + if (request.PublishedContent == null) + { + return; + } // don't try to find a redirect if the property doesn't exist if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) + { return; + } var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; @@ -754,14 +742,17 @@ namespace Umbraco.Web.Routing else { // might be a UDI instead of an int Id - var redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); + GuidUdi redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi != null) + { redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); + } } - if (redirectUrl != "#") - request.SetRedirect(redirectUrl); - } - #endregion + if (redirectUrl != "#") + { + request.SetRedirect(redirectUrl); + } + } } } diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 5c00b14af3..19d65b8f3a 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -175,35 +175,43 @@ namespace Umbraco.Web.Routing { // test for collisions on the 'main' URL var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); - if (uri.IsAbsoluteUri == false) uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); + if (uri.IsAbsoluteUri == false) + { + uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); + } + uri = uriUtility.UriToUmbraco(uri); - var pcr = publishedRouter.CreateRequest(umbracoContext, uri); + IPublishedRequestBuilder pcr = publishedRouter.CreateRequest(uri); publishedRouter.TryRouteRequest(pcr); urlInfo = null; - if (pcr.HasPublishedContent == false) + if (pcr.PublishedContent == null) { urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return true; } - if (pcr.IgnorePublishedContentCollisions) - return false; + // TODO: What is this? + //if (pcr.IgnorePublishedContentCollisions) + //{ + // return false; + //} if (pcr.PublishedContent.Id != content.Id) { - var o = pcr.PublishedContent; + IPublishedContent o = pcr.PublishedContent; var l = new List(); while (o != null) { l.Add(o.Name(variationContextAccessor)); o = o.Parent; } + l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); + urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); return true; } diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index c034997d7e..3bbcb43dca 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -9,10 +9,13 @@ namespace Umbraco.Web public interface IUmbracoContext : IDisposable { /// - /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this + /// Gets the DateTime this instance was created. + /// + /// + /// Used internally for performance calculations, the ObjectCreated DateTime is set as soon as this /// object is instantiated which in the web site is created during the BeginRequest phase. /// We can then determine complete rendering time from that. - /// + /// DateTime ObjectCreated { get; } /// @@ -46,16 +49,16 @@ namespace Umbraco.Web /// IDomainCache Domains { get; } - /// - /// Gets/sets the PublishedRequest object - /// - // TODO: Can we refactor this and not expose this mutable object here? Instead just expose IPublishedContent? A bunch of stuff would need to change but would be better + ///// + ///// Gets or sets the PublishedRequest object + ///// + //// TODO: Can we refactor this? The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx IPublishedRequest PublishedRequest { get; set; } /// /// Gets the variation context accessor. /// - IVariationContextAccessor VariationContextAccessor { get; } + IVariationContextAccessor VariationContextAccessor { get; } // TODO: Does this need to be a property, it can be injected when needed /// /// Gets a value indicating whether the request has debugging enabled @@ -64,10 +67,14 @@ namespace Umbraco.Web bool IsDebug { get; } /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) + /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// bool InPreviewMode { get; } + /// + /// Forces the context into preview + /// + /// A instance to be disposed to exit the preview context IDisposable ForcedPreview(bool preview); } } diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 8f68ec0a64..75fc80015a 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -1,8 +1,8 @@ using System.Globalization; using System.Linq; using Examine; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.PublishedContent; @@ -19,17 +19,23 @@ namespace Umbraco.Web.Routing private readonly IEntityService _entityService; private readonly ContentSettings _contentSettings; private readonly IExamineManager _examineManager; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + /// + /// Initializes a new instance of the class. + /// public ContentFinderByConfigured404( ILogger logger, IEntityService entityService, IOptions contentConfigSettings, - IExamineManager examineManager) + IExamineManager examineManager, + IUmbracoContextAccessor umbracoContextAccessor) { _logger = logger; _entityService = entityService; _contentSettings = contentConfigSettings.Value; _examineManager = examineManager; + _umbracoContextAccessor = umbracoContextAccessor; } /// @@ -37,13 +43,19 @@ namespace Umbraco.Web.Routing /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public bool TryFindContent(IPublishedRequest frequest) + public bool TryFindContent(IPublishedRequestBuilder frequest) { + IUmbracoContext umbCtx = _umbracoContextAccessor.UmbracoContext; + if (umbCtx == null) + { + return false; + } + _logger.LogDebug("Looking for a page to handle 404."); // try to find a culture as best as we can - var errorCulture = CultureInfo.CurrentUICulture; - if (frequest.HasDomain) + CultureInfo errorCulture = CultureInfo.CurrentUICulture; + if (frequest.Domain != null) { errorCulture = frequest.Domain.Culture; } @@ -55,22 +67,29 @@ namespace Umbraco.Web.Routing while (pos > 1) { route = route.Substring(0, pos); - node = frequest.UmbracoContext.Content.GetByRoute(route, culture: frequest?.Culture?.Name); - if (node != null) break; + node = umbCtx.Content.GetByRoute(route, culture: frequest?.Culture?.Name); + if (node != null) + { + break; + } + pos = route.LastIndexOf('/'); } + if (node != null) { - var d = DomainUtilities.FindWildcardDomainInPath(frequest.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), node.Path, null); + Domain d = DomainUtilities.FindWildcardDomainInPath(umbCtx.PublishedSnapshot.Domains.GetAll(true), node.Path, null); if (d != null) + { errorCulture = d.Culture; + } } } var error404 = NotFoundHandlerHelper.GetCurrentNotFoundPageId( _contentSettings.Error404Collection.ToArray(), _entityService, - new PublishedContentQuery(frequest.UmbracoContext.PublishedSnapshot, frequest.UmbracoContext.VariationContextAccessor, _examineManager), + new PublishedContentQuery(umbCtx.PublishedSnapshot, umbCtx.VariationContextAccessor, _examineManager), errorCulture); IPublishedContent content = null; @@ -79,7 +98,7 @@ namespace Umbraco.Web.Routing { _logger.LogDebug("Got id={ErrorNodeId}.", error404.Value); - content = frequest.UmbracoContext.Content.GetById(error404.Value); + content = umbCtx.Content.GetById(error404.Value); _logger.LogDebug(content == null ? "Could not find content with that id." @@ -90,8 +109,10 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Got nothing."); } - frequest.PublishedContent = content; - frequest.Is404 = true; + frequest + .SetPublishedContent(content) + .SetIs404(true); + return content != null; } } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 83924f3315..3621580dcb 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.ObjectModel; using System.Globalization; @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.TestHelpers; +using Umbraco.Web.Routing; namespace Umbraco.Tests.PublishedContent { @@ -18,27 +19,28 @@ namespace Umbraco.Tests.PublishedContent public void ConfigureRequest_Returns_False_Without_HasPublishedContent() { var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var result = publishedRouter.ConfigureRequest(request); - Assert.IsFalse(result); + Assert.IsFalse(result.Success()); } [Test] public void ConfigureRequest_Returns_False_When_IsRedirect() { var umbracoContext = GetUmbracoContext("/test"); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var content = GetPublishedContentMock(); - request.PublishedContent = content.Object; - request.Culture = new CultureInfo("en-AU"); + request.SetPublishedContent(content.Object); + request.SetCulture(new CultureInfo("en-AU")); request.SetRedirect("/hello"); var result = publishedRouter.ConfigureRequest(request); - Assert.IsFalse(result); + Assert.IsFalse(result.Success()); } + private Mock GetPublishedContentMock() { var pc = new Mock(); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 3e3f6163bf..34051c96bd 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Moq; using NUnit.Framework; @@ -48,10 +48,10 @@ namespace Umbraco.Tests.Routing public void Lookup_By_Url_Alias(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var lookup = - new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor); + new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 9af04cfb18..5a390f667c 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Logging; using Moq; @@ -57,15 +57,15 @@ namespace Umbraco.Tests.Routing //SetDomains1(); var umbracoContext = GetUmbracoContext(inputUrl); - var publishedRouter = CreatePublishedRouter(); - var request = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain publishedRouter.FindDomain(request); if (expectedNode > 0) Assert.AreEqual(expectedCulture, request.Culture.Name); - var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor); + var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(request); if (expectedNode > 0) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index deb5ac30bf..0ed3161caf 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -18,10 +18,10 @@ namespace Umbraco.Tests.Routing public void Lookup_By_Id(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService()); + var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs index 405572334c..24872a128e 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -18,12 +18,12 @@ namespace Umbraco.Tests.Routing { var umbracoContext = GetUmbracoContext(urlAsString); var httpContext = GetHttpContextFactory(urlAsString).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var mockRequestAccessor = new Mock(); mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")).Returns(httpContext.Request.QueryString["umbPageID"]); - var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object); + var lookup = new ContentFinderByPageIdQuery(mockRequestAccessor.Object, GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index b849b100ea..82b433a1a0 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; @@ -32,17 +32,24 @@ namespace Umbraco.Tests.Routing var template1 = CreateTemplate("test"); var template2 = CreateTemplate("blah"); var umbracoContext = GetUmbracoContext(urlAsString, template1.Id, globalSettings: globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var reqBuilder = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); - var lookup = new ContentFinderByUrlAndTemplate(LoggerFactory.CreateLogger(), ServiceContext.FileService, ServiceContext.ContentTypeService, Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); + var lookup = new ContentFinderByUrlAndTemplate( + LoggerFactory.CreateLogger(), + ServiceContext.FileService, + ServiceContext.ContentTypeService, + GetUmbracoContextAccessor(umbracoContext), + Microsoft.Extensions.Options.Options.Create(webRoutingSettings)); - var result = lookup.TryFindContent(frequest); + var result = lookup.TryFindContent(reqBuilder); + + IPublishedRequest frequest = reqBuilder.Build(); Assert.IsTrue(result); Assert.IsNotNull(frequest.PublishedContent); - Assert.IsNotNull(frequest.TemplateAlias); - Assert.AreEqual("blah".ToUpperInvariant(), frequest.TemplateAlias.ToUpperInvariant()); + Assert.IsNotNull(frequest.GetTemplateAlias()); + Assert.AreEqual("blah".ToUpperInvariant(), frequest.GetTemplateAlias().ToUpperInvariant()); } } } diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 6a0b8c3f3b..807cf729ef 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using NUnit.Framework; using Umbraco.Core.Configuration.Models; @@ -6,6 +6,8 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web.Routing; using Microsoft.Extensions.Logging; +using Umbraco.Web; +using Moq; namespace Umbraco.Tests.Routing { @@ -30,9 +32,9 @@ namespace Umbraco.Tests.Routing var snapshotService = CreatePublishedSnapshotService(globalSettings); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings, snapshotService: snapshotService); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsTrue(globalSettings.HideTopLevelNodeFromPath); @@ -64,9 +66,9 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsFalse(globalSettings.HideTopLevelNodeFromPath); @@ -88,9 +90,9 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -114,10 +116,10 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.Domain = new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/")); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/"))); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -142,10 +144,10 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.Domain = new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå")); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå"))); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 8428f44a8b..84f86f1e09 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -133,13 +133,13 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); @@ -174,14 +174,14 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(expectedId, frequest.PublishedContent.Id); diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index c51ca27b8c..4d5111df08 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Microsoft.Extensions.Logging; using Umbraco.Core.Models; @@ -268,15 +268,15 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); @@ -316,14 +316,14 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); // find document - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); // apply wildcard domain @@ -369,8 +369,8 @@ namespace Umbraco.Tests.Routing var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); - var publishedRouter = CreatePublishedRouter(Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); @@ -378,7 +378,7 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(expectedCulture, frequest.Culture.Name); - var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); Assert.IsTrue(result); diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs index e08873ac04..31bbd06ade 100644 --- a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Moq; @@ -63,8 +63,10 @@ namespace Umbraco.Tests.Routing content.Path = "-1,1046"; var umbContext = GetUmbracoContext("http://localhost:8000"); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + GetUmbracoContextAccessor(umbContext), + Factory, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbContext)) })); var urls = content.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, @@ -88,7 +90,7 @@ namespace Umbraco.Tests.Routing content.Published = true; var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -102,8 +104,10 @@ namespace Umbraco.Tests.Routing Mock.Of() ); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + Factory, + contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); var urls = content.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, @@ -134,7 +138,7 @@ namespace Umbraco.Tests.Routing child.Published = true; var umbContext = GetUmbracoContext("http://localhost:8000"); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(_requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -147,8 +151,10 @@ namespace Umbraco.Tests.Routing Mock.Of() ); - var publishedRouter = CreatePublishedRouter(Factory, - contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger()) })); + var publishedRouter = CreatePublishedRouter( + umbracoContextAccessor, + Factory, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); var urls = child.GetContentUrls(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index a8d017e3cb..63e8180aff 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Web.Mvc; using System.Web.Routing; @@ -91,15 +91,15 @@ namespace Umbraco.Tests.Routing var routeData = new RouteData { Route = route }; var umbracoContext = GetUmbracoContext(url, template.Id, routeData); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.Content.GetById(1174); - frequest.TemplateModel = template; + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetPublishedContent(umbracoContext.Content.GetById(1174)); + frequest.SetTemplate(template); var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var handler = new RenderRouteHandler(umbracoContext, new TestControllerFactory(umbracoContextAccessor, Mock.Of>()), ShortStringHelper); - handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest); + handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest.Build()); Assert.AreEqual("RenderMvc", routeData.Values["controller"].ToString()); //the route action will still be the one we've asked for because our RenderActionInvoker is the thing that decides // if the action matches. @@ -129,10 +129,10 @@ namespace Umbraco.Tests.Routing var routeData = new RouteData() { Route = route }; var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData, true); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); - frequest.PublishedContent = umbracoContext.Content.GetById(1172); - frequest.TemplateModel = template; + var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + frequest.SetPublishedContent(umbracoContext.Content.GetById(1172)); + frequest.SetTemplate(template); var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var type = new AutoPublishedContentType(Guid.NewGuid(), 22, "CustomDocument", new PublishedPropertyType[] { }); @@ -149,7 +149,7 @@ namespace Umbraco.Tests.Routing Factory.GetRequiredService()); }), ShortStringHelper); - handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest); + handler.GetHandlerForRoute(httpContext.Request.RequestContext, frequest.Build()); Assert.AreEqual("CustomDocument", routeData.Values["controller"].ToString()); Assert.AreEqual( //global::umbraco.cms.helpers.Casing.SafeAlias(template.Alias), diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index 46d67eb9bd..cdc62b1a35 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Routing // get the nice URL for 100111 var umbracoContext = GetUmbracoContext(url, 9999, globalSettings: globalSettings); - var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); + var umbracoContextAccessor = GetUmbracoContextAccessor(umbracoContext); var urlProvider = new DefaultUrlProvider( Microsoft.Extensions.Options.Options.Create(requestHandlerSettings), LoggerFactory.CreateLogger(), @@ -59,14 +59,14 @@ namespace Umbraco.Tests.Routing Assert.AreEqual("10011/1001-1-1", cachedRoutes[100111]); // route a rogue URL - var publishedRouter = CreatePublishedRouter(); - var frequest = publishedRouter.CreateRequest(umbracoContext); + var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); + var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); publishedRouter.FindDomain(frequest); - Assert.IsTrue(frequest.HasDomain); + Assert.IsTrue(frequest.HasDomain()); // check that it's been routed - var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger()); + var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); Assert.IsTrue(result); Assert.AreEqual(100111, frequest.PublishedContent.Id); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index 103d361fc5..e7ccc01acb 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using Microsoft.Extensions.DependencyInjection; @@ -93,15 +93,14 @@ namespace Umbraco.Tests.TestHelpers "; } - internal PublishedRouter CreatePublishedRouter(IServiceProvider container = null, ContentFinderCollection contentFinders = null) + internal PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, IServiceProvider container = null, ContentFinderCollection contentFinders = null) { var webRoutingSettings = new WebRoutingSettings(); - return CreatePublishedRouter(webRoutingSettings, container ?? Factory, contentFinders); + return CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings, container ?? Factory, contentFinders); } - internal static PublishedRouter CreatePublishedRouter(WebRoutingSettings webRoutingSettings, IServiceProvider container = null, ContentFinderCollection contentFinders = null) - { - return new PublishedRouter( + internal static PublishedRouter CreatePublishedRouter(IUmbracoContextAccessor umbracoContextAccessor, WebRoutingSettings webRoutingSettings, IServiceProvider container = null, ContentFinderCollection contentFinders = null) + => new PublishedRouter( Microsoft.Extensions.Options.Options.Create(webRoutingSettings), contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), @@ -111,11 +110,11 @@ namespace Umbraco.Tests.TestHelpers Mock.Of(), Mock.Of(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService()?? Current.Factory.GetRequiredService(), - container?.GetRequiredService()?? Current.Factory.GetRequiredService(), + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - container?.GetRequiredService() ?? Current.Factory.GetRequiredService() + container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), + umbracoContextAccessor ); - } } } diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs index 48517f85dd..dfbca2763c 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestLastChanceFinder.cs @@ -1,12 +1,9 @@ -using Umbraco.Web.Routing; +using Umbraco.Web.Routing; namespace Umbraco.Tests.TestHelpers.Stubs { internal class TestLastChanceFinder : IContentLastChanceFinder { - public bool TryFindContent(IPublishedRequest frequest) - { - return false; - } + public bool TryFindContent(IPublishedRequestBuilder frequest) => false; } } diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 302e1198a8..b47da98709 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -350,6 +350,8 @@ namespace Umbraco.Tests.TestHelpers } } + protected IUmbracoContextAccessor GetUmbracoContextAccessor(IUmbracoContext ctx) => new TestUmbracoContextAccessor(ctx); + protected IUmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, GlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null) { // ensure we have a PublishedCachesService diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 6afc75e931..3e5e799dba 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -151,13 +151,13 @@ namespace Umbraco.Tests.Web.Mvc var content = Mock.Of(publishedContent => publishedContent.Id == 12345); var webRoutingSettings = new WebRoutingSettings(); - var publishedRouter = BaseWebTest.CreatePublishedRouter(webRoutingSettings); - var frequest = publishedRouter.CreateRequest(umbracoContext, new Uri("http://localhost/test")); - frequest.PublishedContent = content; + var publishedRouter = BaseWebTest.CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings); + var frequest = publishedRouter.CreateRequest(new Uri("http://localhost/test")); + frequest.SetPublishedContent(content); var routeDefinition = new RouteDefinition { - PublishedRequest = frequest + PublishedRequest = frequest.Build() }; var routeData = new RouteData(); diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index 6cd514033f..654eb8f8d7 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -65,12 +65,13 @@ namespace Umbraco.Web.Common.Templates if (writer == null) throw new ArgumentNullException(nameof(writer)); var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + // instantiate a request and process // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL, though this isn't going to matter // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. - var contentRequest = _publishedRouter.CreateRequest(umbracoContext); + var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - var doc = contentRequest.UmbracoContext.Content.GetById(pageId); + var doc = umbracoContext.Content.GetById(pageId); if (doc == null) { @@ -78,32 +79,37 @@ namespace Umbraco.Web.Common.Templates return; } - //in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the - //execution of a front-end rendered page. In this case set the culture to the default. - //set the culture to the same as is currently rendering + // in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the + // execution of a front-end rendered page. In this case set the culture to the default. + // set the culture to the same as is currently rendering if (umbracoContext.PublishedRequest == null) { var defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault(); - contentRequest.Culture = defaultLanguage == null + + requestBuilder.SetCulture(defaultLanguage == null ? CultureInfo.CurrentUICulture - : defaultLanguage.CultureInfo; + : defaultLanguage.CultureInfo); } else { - contentRequest.Culture = umbracoContext.PublishedRequest.Culture; + requestBuilder.SetCulture(umbracoContext.PublishedRequest.Culture); } - //set the doc that was found by id - contentRequest.PublishedContent = doc; - //set the template, either based on the AltTemplate found or the standard template of the doc + // set the doc that was found by id + requestBuilder.SetPublishedContent(doc); + + // set the template, either based on the AltTemplate found or the standard template of the doc var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue ? doc.TemplateId : altTemplateId.Value; - if (templateId.HasValue) - contentRequest.TemplateModel = _fileService.GetTemplate(templateId.Value); - //if there is not template then exit - if (contentRequest.HasTemplate == false) + if (templateId.HasValue) + { + requestBuilder.SetTemplate(_fileService.GetTemplate(templateId.Value)); + } + + // if there is not template then exit + if (requestBuilder.HasTemplate() == false) { if (altTemplateId.HasValue == false) { @@ -113,24 +119,27 @@ namespace Umbraco.Web.Common.Templates { writer.Write("", altTemplateId); } + return; } - //First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these - //after this page has rendered. - SaveExistingItems(out var oldPublishedRequest); + // First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these + // after this page has rendered. + SaveExistingItems(out IPublishedRequest oldPublishedRequest); + + IPublishedRequest contentRequest = requestBuilder.Build(); try { - //set the new items on context objects for this templates execution + // set the new items on context objects for this templates execution SetNewItemsOnContextObjects(contentRequest); - //Render the template + // Render the template ExecuteTemplateRendering(writer, contentRequest); } finally { - //restore items on context objects to continuing rendering the parent template + // restore items on context objects to continuing rendering the parent template RestoreItems(oldPublishedRequest); } @@ -140,11 +149,11 @@ namespace Umbraco.Web.Common.Templates { var httpContext = _httpContextAccessor.GetRequiredHttpContext(); - var viewResult = _viewEngine.GetView(null, $"~/Views/{request.TemplateAlias}.cshtml", false); + var viewResult = _viewEngine.GetView(null, $"~/Views/{request.GetTemplateAlias()}.cshtml", false); if (viewResult.Success == false) { - throw new InvalidOperationException($"A view with the name {request.TemplateAlias} could not be found"); + throw new InvalidOperationException($"A view with the name {request.GetTemplateAlias()} could not be found"); } var modelMetadataProvider = httpContext.RequestServices.GetRequiredService(); @@ -175,7 +184,7 @@ namespace Umbraco.Web.Common.Templates private void SetNewItemsOnContextObjects(IPublishedRequest request) { - //now, set the new ones for this page execution + // now, set the new ones for this page execution _umbracoContextAccessor.UmbracoContext.PublishedRequest = request; } @@ -184,8 +193,8 @@ namespace Umbraco.Web.Common.Templates /// private void SaveExistingItems(out IPublishedRequest oldPublishedRequest) { - //Many objects require that these legacy items are in the http context items... before we render this template we need to first - //save the values in them so that we can re-set them after we render so the rest of the execution works as per normal + // Many objects require that these legacy items are in the http context items... before we render this template we need to first + // save the values in them so that we can re-set them after we render so the rest of the execution works as per normal oldPublishedRequest = _umbracoContextAccessor.UmbracoContext.PublishedRequest; } diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index 2d22bc5a90..76a5823801 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -62,105 +62,83 @@ namespace Umbraco.Web // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 - // OriginalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"); CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); } - /// - /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this - /// object is instantiated which in the web site is created during the BeginRequest phase. - /// We can then determine complete rendering time from that. - /// + /// public DateTime ObjectCreated { get; } /// - /// This is used internally for debugging and also used to define anything required to distinguish this request from another. + /// Gets the context Id /// - public Guid UmbracoRequestId { get; } + /// + /// Used internally for debugging and also used to define anything required to distinguish this request from another. + /// + internal Guid UmbracoRequestId { get; } - /// - /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. - /// + /// public Uri OriginalRequestUrl { get; } - /// - /// Gets the cleaned up url that is handled by Umbraco. - /// - /// That is, lowercase, no trailing slash after path, no .aspx... + /// public Uri CleanedUmbracoUrl { get; } - /// - /// Gets the published snapshot. - /// + /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; - /// - /// Gets the published content cache. - /// + /// public IPublishedContentCache Content => PublishedSnapshot.Content; - /// - /// Gets the published media cache. - /// + /// public IPublishedMediaCache Media => PublishedSnapshot.Media; - /// - /// Gets the domains cache. - /// + /// public IDomainCache Domains => PublishedSnapshot.Domains; - /// - /// Gets/sets the PublishedRequest object - /// + /// public IPublishedRequest PublishedRequest { get; set; } - /// - /// Gets the variation context accessor. - /// + /// public IVariationContextAccessor VariationContextAccessor { get; } - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug - { - get - { - //NOTE: the request can be null during app startup! - return _hostingEnvironment.IsDebugMode + /// + public bool IsDebug => // NOTE: the request can be null during app startup! + _hostingEnvironment.IsDebugMode && (string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebugshowtrace")) == false || string.IsNullOrEmpty(_requestAccessor.GetRequestValue("umbdebug")) == false || string.IsNullOrEmpty(_cookieManager.GetCookieValue("UMB-DEBUG")) == false); - } - } - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// + /// public bool InPreviewMode { get { - if (_previewing.HasValue == false) DetectPreviewMode(); + if (_previewing.HasValue == false) + { + DetectPreviewMode(); + } + return _previewing ?? false; } private set => _previewing = value; } - public string PreviewToken + internal string PreviewToken { get { - if (_previewing.HasValue == false) DetectPreviewMode(); + if (_previewing.HasValue == false) + { + DetectPreviewMode(); + } + return _previewToken; } } private void DetectPreviewMode() { - var requestUrl = _requestAccessor.GetRequestUrl(); + Uri requestUrl = _requestAccessor.GetRequestUrl(); if (requestUrl != null && requestUrl.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false && _backofficeSecurity.CurrentUser != null) @@ -172,15 +150,17 @@ namespace Umbraco.Web _previewing = _previewToken.IsNullOrWhiteSpace() == false; } - // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, - // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper - // default 'preview' mode - somehow we have to force it. and that could be recursive. + /// public IDisposable ForcedPreview(bool preview) { + // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, + // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper + // default 'preview' mode - somehow we have to force it. and that could be recursive. InPreviewMode = preview; return PublishedSnapshot.ForcedPreview(preview, orig => InPreviewMode = orig); } + /// protected override void DisposeResources() { // DisposableObject ensures that this runs only once @@ -189,7 +169,9 @@ namespace Umbraco.Web // (but don't create caches just to dispose them) // context is not multi-threaded if (_publishedSnapshot.IsValueCreated) + { _publishedSnapshot.Value.Dispose(); + } } } } diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 731c0320d6..7b0fed7991 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -16,13 +14,10 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.Routing; -using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Website.Controllers; @@ -143,12 +138,12 @@ namespace Umbraco.Web.Website.Routing // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action - if (request.HasTemplate) + if (request.HasTemplate()) { // the template Alias should always be already saved with a safe name. // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - customActionName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); + customActionName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); } // creates the default route definition which maps to the 'UmbracoController' controller @@ -229,12 +224,12 @@ namespace Umbraco.Web.Website.Routing // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current url - IPublishedRequest request = _publishedRouter.CreateRequest(umbracoContext); + IPublishedRequestBuilder requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - // TODO: This is ugly with the re-assignment to umbraco context also because IPublishedRequest is mutable - publishedRequest = umbracoContext.PublishedRequest = request; - bool prepared = _publishedRouter.PrepareRequest(request); - return prepared && request.HasPublishedContent; + // TODO: This is ugly with the re-assignment to umbraco context + publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); + + return publishedRequest.Success() && publishedRequest.HasPublishedContent(); // // HandleHttpResponseStatus returns a value indicating that the request should // // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index 3ca7e861ac..8d044ea8dd 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -22,107 +22,109 @@ namespace Umbraco.Web.Mvc /// public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute { - private readonly string _dataTokenName; - private IUmbracoContextAccessor _umbracoContextAccessor; - private readonly int? _contentId; - private IPublishedContentQuery _publishedContentQuery; + // TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task) - /// - /// Constructor - can be used for testing - /// - /// - /// - public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _contentId = contentId; - } + //private readonly string _dataTokenName; + //private IUmbracoContextAccessor _umbracoContextAccessor; + //private readonly int? _contentId; + //private IPublishedContentQuery _publishedContentQuery; - /// - /// A constructor used to set an explicit content Id to the PublishedRequest that will be created - /// - /// - public EnsurePublishedContentRequestAttribute(int contentId) - { - _contentId = contentId; - } + ///// + ///// Constructor - can be used for testing + ///// + ///// + ///// + //public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, int contentId) + //{ + // _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + // _contentId = contentId; + //} - /// - /// A constructor used to set the data token key name that contains a reference to a PublishedContent instance - /// - /// - public EnsurePublishedContentRequestAttribute(string dataTokenName) - { - _dataTokenName = dataTokenName; - } + ///// + ///// A constructor used to set an explicit content Id to the PublishedRequest that will be created + ///// + ///// + //public EnsurePublishedContentRequestAttribute(int contentId) + //{ + // _contentId = contentId; + //} - /// - /// Constructor - can be used for testing - /// - /// - /// - public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _dataTokenName = dataTokenName; - } + ///// + ///// A constructor used to set the data token key name that contains a reference to a PublishedContent instance + ///// + ///// + //public EnsurePublishedContentRequestAttribute(string dataTokenName) + //{ + // _dataTokenName = dataTokenName; + //} - /// - /// Exposes the UmbracoContext - /// - protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; + ///// + ///// Constructor - can be used for testing + ///// + ///// + ///// + //public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) + //{ + // _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + // _dataTokenName = dataTokenName; + //} - // TODO: try lazy property injection? - private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); + ///// + ///// Exposes the UmbracoContext + ///// + //protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; - private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService()); + //// TODO: try lazy property injection? + //private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); - public override void OnActionExecuted(ActionExecutedContext filterContext) - { - base.OnActionExecuted(filterContext); + //private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService()); - // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything - if (Current.UmbracoContext.PublishedRequest != null) - { - return; - } + //public override void OnActionExecuted(ActionExecutedContext filterContext) + //{ + // base.OnActionExecuted(filterContext); - Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); - ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); - } + // // First we need to check if the published content request has been set, if it has we're going to ignore this and not actually do anything + // if (Current.UmbracoContext.PublishedRequest != null) + // { + // return; + // } - /// - /// This assigns the published content to the request, developers can override this to specify - /// any other custom attributes required. - /// - /// - /// - protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext) - { - if (_contentId.HasValue) - { - var content = PublishedContentQuery.Content(_contentId.Value); - if (content == null) - { - throw new InvalidOperationException("Could not resolve content with id " + _contentId); - } - request.PublishedContent = content; - } - else if (_dataTokenName.IsNullOrWhiteSpace() == false) - { - var result = filterContext.RouteData.DataTokens[_dataTokenName]; - if (result == null) - { - throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); - } - if (result is IPublishedContent == false) - { - throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); - } - request.PublishedContent = (IPublishedContent)result; - } + // Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); + // ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); + //} - PublishedRouter.PrepareRequest(request); - } + ///// + ///// This assigns the published content to the request, developers can override this to specify + ///// any other custom attributes required. + ///// + ///// + ///// + //protected virtual void ConfigurePublishedContentRequest(IPublishedRequest request, ActionExecutedContext filterContext) + //{ + // if (_contentId.HasValue) + // { + // var content = PublishedContentQuery.Content(_contentId.Value); + // if (content == null) + // { + // throw new InvalidOperationException("Could not resolve content with id " + _contentId); + // } + // request.PublishedContent = content; + // } + // else if (_dataTokenName.IsNullOrWhiteSpace() == false) + // { + // var result = filterContext.RouteData.DataTokens[_dataTokenName]; + // if (result == null) + // { + // throw new InvalidOperationException("No data token could be found with the name " + _dataTokenName); + // } + // if (result is IPublishedContent == false) + // { + // throw new InvalidOperationException("The data token resolved with name " + _dataTokenName + " was not an instance of " + typeof(IPublishedContent)); + // } + // request.PublishedContent = (IPublishedContent)result; + // } + + // PublishedRouter.PrepareRequest(request); + //} } } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 3ca0931585..d690fb579b 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -219,7 +219,7 @@ namespace Umbraco.Web.Mvc var defaultControllerType = Current.DefaultRenderMvcControllerType; var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - //creates the default route definition which maps to the 'UmbracoController' controller + // creates the default route definition which maps to the 'UmbracoController' controller var def = new RouteDefinition { ControllerName = defaultControllerName, @@ -229,28 +229,28 @@ namespace Umbraco.Web.Mvc HasHijackedRoute = false }; - //check that a template is defined), if it doesn't and there is a hijacked route it will just route + // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action - if (request.HasTemplate) + if (request.HasTemplate()) { - //the template Alias should always be already saved with a safe name. - //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed + // the template Alias should always be already saved with a safe name. + // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); + var templateName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); def.ActionName = templateName; } - //check if there's a custom controller assigned, base on the document type alias. + // check if there's a custom controller assigned, base on the document type alias. var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.ContentType.Alias); - //check if that controller exists + // check if that controller exists if (controllerType != null) { - //ensure the controller is of type IRenderMvcController and ControllerBase + // ensure the controller is of type IRenderMvcController and ControllerBase if (TypeHelper.IsTypeAssignableFrom(controllerType) && TypeHelper.IsTypeAssignableFrom(controllerType)) { - //set the controller and name to the custom one + // set the controller and name to the custom one def.ControllerType = controllerType; def.ControllerName = ControllerExtensions.GetControllerName(controllerType); if (def.ControllerName != defaultControllerName) @@ -266,12 +266,12 @@ namespace Umbraco.Web.Mvc typeof(IRenderController).FullName, typeof(ControllerBase).FullName); - //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults + // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. } } - //store the route definition + // store the route definition requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return def; @@ -284,15 +284,19 @@ namespace Umbraco.Web.Mvc // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (request.HasPublishedContent == false) + if (request.HasPublishedContent() == false) + { // means the builder could not find a proper document to handle 404 return new PublishedContentNotFoundHandler(); + } - if (request.HasTemplate == false) + if (request.HasTemplate() == false) + { // means the engine could find a proper document, but the document has no template // at that point there isn't much we can do and there is no point returning // to Mvc since Mvc can't do much return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); + } return null; } @@ -300,8 +304,6 @@ namespace Umbraco.Web.Mvc /// /// this will determine the controller and set the values in the route data /// - /// - /// internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, IPublishedRequest request) { if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); @@ -309,7 +311,7 @@ namespace Umbraco.Web.Mvc var routeDef = GetUmbracoRouteDefinition(requestContext, request); - //Need to check for a special case if there is form data being posted back to an Umbraco URL + // Need to check for a special case if there is form data being posted back to an Umbraco URL var postedInfo = GetFormInfo(requestContext); if (postedInfo != null) { @@ -321,10 +323,11 @@ namespace Umbraco.Web.Mvc // if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, // for example for json rendering in headless. - if ((request.HasTemplate == false && Features.Disabled.DisableTemplates == false) - && routeDef.HasHijackedRoute == false) + if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false) { - request.UpdateToNotFound(); // request will go 404 + + // TODO: Handle this differently + // request.UpdateToNotFound(); // request will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index dc922d9fd2..9d5449f340 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -7,11 +7,14 @@ using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Core; using Umbraco.Web.Composing; +using System; namespace Umbraco.Web.Mvc { public abstract class UmbracoVirtualNodeRouteHandler : IRouteHandler { + // TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task) + // TODO: try lazy property injection? private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); @@ -46,54 +49,56 @@ namespace Umbraco.Web.Mvc public IHttpHandler GetHttpHandler(RequestContext requestContext) { - var umbracoContext = GetUmbracoContext(requestContext); + throw new NotImplementedException(); - var found = FindContent(requestContext, umbracoContext); - if (found == null) return new NotFoundHandler(); + // var umbracoContext = GetUmbracoContext(requestContext); - var request = PublishedRouter.CreateRequest(umbracoContext); - request.PublishedContent = found; - umbracoContext.PublishedRequest = request; + // var found = FindContent(requestContext, umbracoContext); + // if (found == null) return new NotFoundHandler(); - // allows inheritors to change the published content request - PreparePublishedContentRequest(umbracoContext.PublishedRequest); + // var request = PublishedRouter.CreateRequest(umbracoContext); + // request.PublishedContent = found; + // umbracoContext.PublishedRequest = request; - // create the render model - var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); + // // allows inheritors to change the published content request + // PreparePublishedContentRequest(umbracoContext.PublishedRequest); - // assigns the required tokens to the request - //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); - //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest); - //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); + // // create the render model + // var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); - //// this is used just for a flag that this is an umbraco custom route - //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); + // // assigns the required tokens to the request + // //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, renderModel); + // //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, umbracoContext.PublishedRequest); + // //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbracoContext); - // Here we need to detect if a SurfaceController has posted - var formInfo = RenderRouteHandler.GetFormInfo(requestContext); - if (formInfo != null) - { - var def = new RouteDefinition - { - ActionName = requestContext.RouteData.GetRequiredString("action"), - ControllerName = requestContext.RouteData.GetRequiredString("controller"), - PublishedRequest = umbracoContext.PublishedRequest - }; + // //// this is used just for a flag that this is an umbraco custom route + // //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); - // set the special data token to the current route definition - requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + // // Here we need to detect if a SurfaceController has posted + // var formInfo = RenderRouteHandler.GetFormInfo(requestContext); + // if (formInfo != null) + // { + // var def = new RouteDefinition + // { + // ActionName = requestContext.RouteData.GetRequiredString("action"), + // ControllerName = requestContext.RouteData.GetRequiredString("controller"), + // PublishedRequest = umbracoContext.PublishedRequest + // }; - return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); - } + // // set the special data token to the current route definition + // requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; - return new MvcHandler(requestContext); + // return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); + // } + + // return new MvcHandler(requestContext); } protected abstract IPublishedContent FindContent(RequestContext requestContext, IUmbracoContext umbracoContext); - protected virtual void PreparePublishedContentRequest(IPublishedRequest request) - { - PublishedRouter.PrepareRequest(request); - } + //protected virtual void PreparePublishedContentRequest(IPublishedRequest request) + //{ + // PublishedRouter.PrepareRequest(request); + //} } } diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs index 0045cf33dc..28bae7bced 100644 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; using Umbraco.Web.Composing; namespace Umbraco.Web.Routing @@ -31,9 +31,9 @@ namespace Umbraco.Web.Routing var frequest = Current.UmbracoContext.PublishedRequest; var reason = "Cannot render the page at URL '{0}'."; - if (frequest.HasPublishedContent == false) + if (frequest.HasPublishedContent() == false) reason = "No umbraco document matches the URL '{0}'."; - else if (frequest.HasTemplate == false) + else if (frequest.HasTemplate() == false) reason = "No template exists to render the document at URL '{0}'."; response.Write("

Page not found

"); diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 5c7468ce95..d0c2fd5de7 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -135,16 +135,15 @@ namespace Umbraco.Web // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL - var request = _publishedRouter.CreateRequest(umbracoContext); - umbracoContext.PublishedRequest = request; - _publishedRouter.PrepareRequest(request); + var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) return; - if (request.HasPublishedContent == false) + if (request.HasPublishedContent() == false) httpContext.RemapHandler(new PublishedContentNotFoundHandler()); else RewriteToUmbracoHandler(httpContext, request); diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 932861a89c..b2b3d4c5a8 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -51,8 +51,8 @@ namespace Umbraco.Web var response = context.Response; logger.LogDebug("Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", - pcr.IsRedirect ? (pcr.IsRedirectPermanent ? "permanent" : "redirect") : "none", - pcr.Is404 ? "true" : "false", + pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", + pcr.Is404() ? "true" : "false", pcr.ResponseStatusCode); if(pcr.CacheabilityNoCache) @@ -64,15 +64,15 @@ namespace Umbraco.Web foreach (var header in pcr.Headers) response.AppendHeader(header.Key, header.Value); - if (pcr.IsRedirect) + if (pcr.IsRedirect()) { - if (pcr.IsRedirectPermanent) + if (pcr.IsRedirectPermanent()) response.RedirectPermanent(pcr.RedirectUrl, false); // do not end response else response.Redirect(pcr.RedirectUrl, false); // do not end response end = true; } - else if (pcr.Is404) + else if (pcr.Is404()) { response.StatusCode = 404; response.TrySkipIisCustomErrors = /*Current.Configs.WebRouting().TrySkipIisCustomErrors; TODO introduce from config*/ false; @@ -90,7 +90,7 @@ namespace Umbraco.Web //if (pcr.IsRedirect) // response.End(); // end response -- kills the thread and does not return! - if (pcr.IsRedirect == false) return end; + if (pcr.IsRedirect() == false) return end; response.Flush(); // bypass everything and directly execute EndRequest event -- but returns From f5bd53b223113bb814c2dfb2d05d9906f340c1f4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Jan 2021 17:31:46 +1100 Subject: [PATCH 30/88] removes the weird IsInitialPublishedContent --- src/Umbraco.Core/Routing/IPublishedRequest.cs | 7 ------- .../Routing/IPublishedRequestBuilder.cs | 10 ---------- src/Umbraco.Core/Routing/PublishedRequest.cs | 5 +---- .../Routing/PublishedRequestBuilder.cs | 11 ----------- .../Routing/PublishedRequestExtensions.cs | 5 ----- src/Umbraco.Core/Routing/PublishedRouter.cs | 19 ++++++++++--------- .../Routing/UmbracoRouteValueTransformer.cs | 9 ++++----- 7 files changed, 15 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index f05df351f3..4ffe565489 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -25,13 +25,6 @@ namespace Umbraco.Web.Routing ///
IPublishedContent PublishedContent { get; } - /// - /// Gets the initial requested content. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - IPublishedContent InitialPublishedContent { get; } - /// /// Gets a value indicating whether the current published content has been obtained /// from the initial published content following internal redirections exclusively. diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index fee64fda8d..f94972412f 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -28,11 +28,6 @@ namespace Umbraco.Web.Routing /// CultureInfo Culture { get; } - /// - /// Gets a value indicating whether the current published content is the initial one. - /// - bool IsInitialPublishedContent { get; } - /// /// Gets a value indicating whether the current published content has been obtained /// from the initial published content following internal redirections exclusively. @@ -84,11 +79,6 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); - /// - /// Indicates that the current PublishedContent is the initial one. - /// - IPublishedRequestBuilder SetIsInitialPublishedContent(); // TODO: Required? - /// /// Tries to set the template to use to display the requested content. /// diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index b3aa37d31e..135a756600 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -76,10 +76,7 @@ namespace Umbraco.Web.Routing public bool CacheabilityNoCache { get; } } - /// - /// Represents a request for one specified Umbraco IPublishedContent to be rendered - /// by one specified template, using one specified Culture and RenderingEngine. - /// + // TODO: Kill this, but we need to port all of it's functionality public class PublishedRequestOld // : IPublishedRequest { private readonly IPublishedRouter _publishedRouter; diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 8167e83e6a..6441fcabf7 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -16,7 +16,6 @@ namespace Umbraco.Web.Routing private bool _cacheability; private IReadOnlyList _cacheExtensions; private IPublishedContent _internalRedirectContent; - private bool _isInitContent; private string _redirectUrl; private HttpStatusCode _responseStatus = HttpStatusCode.NotFound; private string _responseDesc; @@ -41,9 +40,6 @@ namespace Umbraco.Web.Routing /// public ITemplate Template { get; private set; } - /// - public bool IsInitialPublishedContent { get; private set; } - /// public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet @@ -124,13 +120,6 @@ namespace Umbraco.Web.Routing return this; } - /// - public IPublishedRequestBuilder SetIsInitialPublishedContent() - { - _isInitContent = true; - return this; - } - /// public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content) { diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs index f9c9d8b294..b3eb21ed16 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs @@ -55,11 +55,6 @@ namespace Umbraco.Web.Routing /// public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; - /// - /// Gets a value indicating whether the current published content is the initial one. - /// - public static bool IsInitialPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.InitialPublishedContent != null; - /// /// Gets a value indicating whether the content request has a domain. /// diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index a7b20b84ba..7f4bc6f22f 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -318,13 +318,15 @@ namespace Umbraco.Web.Routing internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) { if (directory == null || directory.Exists == false) + { return false; + } var pos = alias.IndexOf('/'); if (pos > 0) { // recurse - var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); + DirectoryInfo subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); alias = alias.Substring(pos + 1); return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); } @@ -351,6 +353,8 @@ namespace Umbraco.Web.Routing return; } + var foundContentByFinders = request.HasPublishedContent(); + // not handling umbracoRedirect here but after LookupDocument2 // so internal redirect, 404, etc has precedence over redirect @@ -358,7 +362,7 @@ namespace Umbraco.Web.Routing HandlePublishedContent(request); // find a template - FindTemplate(request); + FindTemplate(request, foundContentByFinders); // handle umbracoRedirect FollowExternalRedirect(request); @@ -375,7 +379,6 @@ namespace Umbraco.Web.Routing // look for the document // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template // some finders may implement caching - using (_profilingLogger.DebugDuration( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders")) @@ -387,10 +390,6 @@ namespace Umbraco.Web.Routing return finder.TryFindContent(request); }); } - - // indicate that the published content (if any) we have at the moment is the - // one that was found by the standard finders before anything else took place. - request.SetIsInitialPublishedContent(); } /// @@ -588,7 +587,9 @@ namespace Umbraco.Web.Routing /// /// Finds a template for the current node, if any. /// - private void FindTemplate(IPublishedRequestBuilder request) + /// The request builder. + /// If the content was found by the finders, before anything such as 404, redirect... took place. + private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders) { // TODO: We've removed the event, might need to re-add? // NOTE: at the moment there is only 1 way to find a template, and then ppl must @@ -604,7 +605,7 @@ namespace Umbraco.Web.Routing // only if the published content is the initial once, else the alternate template // does not apply // + optionally, apply the alternate template on internal redirects - var useAltTemplate = request.IsInitialPublishedContent + var useAltTemplate = contentFoundByFinders || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); var altTemplate = useAltTemplate diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 7b0fed7991..f087a6203e 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -218,15 +218,14 @@ namespace Umbraco.Web.Website.Routing // ok, process - // note: requestModule.UmbracoRewrite also did some stripping of &umbPage - // from the querystring... that was in v3.x to fix some issues with pre-forms - // auth. Paul Sterling confirmed in Jan. 2013 that we can get rid of it. - // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current url IPublishedRequestBuilder requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - // TODO: This is ugly with the re-assignment to umbraco context + // TODO: This is ugly with the re-assignment to umbraco context but at least its now + // an immutable object. The only way to make this better would be to have a RouteRequest + // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. + // Maybe could be a one-time Set method instead? publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); return publishedRequest.Success() && publishedRequest.HasPublishedContent(); From 88615cfa52dae5cbc527dca64eee23a83115c5ea Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 6 Jan 2021 08:49:02 +0100 Subject: [PATCH 31/88] Fix tests by change return type to ActionResult --- src/Umbraco.Web.BackOffice/Controllers/ContentController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 1b8662722b..5f1d283b14 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -39,7 +39,6 @@ using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Routing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -652,14 +651,14 @@ namespace Umbraco.Web.BackOffice.Controllers [FileUploadCleanupFilter] [ContentSaveValidation] [OutgoingEditorModelEvent] - public async Task PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) + public async Task> PostSave([ModelBinder(typeof(ContentItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = await PostSaveInternal( contentItem, content => _contentService.Save(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id), MapToDisplay); - return contentItemDisplay.Value; + return contentItemDisplay; } private async Task> PostSaveInternal(ContentItemSave contentItem, Func saveMethod, Func mapToDisplay) From 333479666cb49a2f4faff81aa5babe4392a79bf6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Jan 2021 20:03:49 +1100 Subject: [PATCH 32/88] removes ResponseStatusDescription and others that aren't used, ports the not found handler, ports redirects, headers, etc... --- src/Umbraco.Core/Routing/IPublishedRequest.cs | 9 +-- .../Routing/IPublishedRequestBuilder.cs | 23 +----- src/Umbraco.Core/Routing/PublishedRequest.cs | 23 ++---- .../Routing/PublishedRequestBuilder.cs | 30 ++------ .../Routing/PublishedRequestExtensions.cs | 28 +++++++ src/Umbraco.Core/Routing/PublishedRouter.cs | 8 +- .../Routing/UmbracoRouteResult.cs | 20 +++++ .../Routing/ContentFinderByConfigured404.cs | 2 +- .../ModelBinders/ContentModelBinderTests.cs | 8 +- .../Controllers/SurfaceControllerTests.cs | 5 +- .../PublishedContentNotFoundResult.cs | 60 +++++++++++++++ .../PublishedRequestFilterAttribute.cs | 77 +++++++++++++++++++ .../Controllers/RenderController.cs | 66 +++++++++++++++- .../ModelBinders/ContentModelBinder.cs | 2 +- .../Routing/UmbracoRouteValues.cs | 8 +- .../RedirectToUmbracoPageResult.cs | 3 +- .../Controllers/SurfaceController.cs | 2 +- .../UmbracoBuilderExtensions.cs | 2 - ...racoWebsiteApplicationBuilderExtensions.cs | 3 - .../Routing/NoContentRoutes.cs | 59 -------------- .../Routing/UmbracoRouteValueTransformer.cs | 48 ++++++++---- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 35 +++++---- .../PublishedContentNotFoundHandler.cs | 52 ------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/UmbracoInjectedModule.cs | 48 ++---------- src/Umbraco.Web/UmbracoModule.cs | 60 --------------- 26 files changed, 351 insertions(+), 331 deletions(-) create mode 100644 src/Umbraco.Core/Routing/UmbracoRouteResult.cs create mode 100644 src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs create mode 100644 src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs delete mode 100644 src/Umbraco.Web.Website/Routing/NoContentRoutes.cs delete mode 100644 src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 4ffe565489..971a8a2f98 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -60,14 +60,7 @@ namespace Umbraco.Web.Routing /// /// Does not actually set the http response status code, only registers that the response /// should use the specified code. The code will or will not be used, in due time. - int ResponseStatusCode { get; } - - /// - /// Gets the content request http response status description. - /// - /// Does not actually set the http response status description, only registers that the response - /// should use the specified description. The description will or will not be used, in due time. - string ResponseStatusDescription { get; } + int? ResponseStatusCode { get; } /// /// Gets a list of Extensions to append to the Response.Cache object. diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index f94972412f..38c685b096 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing /// /// Gets the content request http response status code. /// - int ResponseStatusCode { get; } + int? ResponseStatusCode { get; } /// /// Gets the current assigned (if any) @@ -77,7 +77,7 @@ namespace Umbraco.Web.Routing /// The requested content. /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will /// preserve or reset the template, if any. - IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); + IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); // TODO: Need to figure this one out /// /// Tries to set the template to use to display the requested content. @@ -97,11 +97,6 @@ namespace Umbraco.Web.Routing /// Setting the template does refresh RenderingEngine. IPublishedRequestBuilder SetTemplate(ITemplate template); - /// - /// Resets the template. - /// - IPublishedRequestBuilder ResetTemplate(); - /// /// Indicates that the content request should trigger a permanent redirect (301). /// @@ -123,11 +118,10 @@ namespace Umbraco.Web.Routing /// Sets the http response status code, along with an optional associated description. /// /// The http status code. - /// The description. /// Does not actually set the http response status code and description, only registers that /// the response should use the specified code and description. The code and description will or will /// not be used, in due time. - IPublishedRequestBuilder SetResponseStatus(int code, string description = null); + IPublishedRequestBuilder SetResponseStatus(int code); IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability); @@ -140,16 +134,5 @@ namespace Umbraco.Web.Routing /// Sets a dictionary of Headers to append to the Response object. /// IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); - - /// - /// Sets a value indicating that the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder and can also be used in - /// custom content finders or Prepared event handlers, where we want to allow developers - /// to indicate a request is 404 but not to cancel it. - IPublishedRequestBuilder SetIs404(bool is404); - - // TODO: This seems to be the same as is404? - //IPublishedRequestBuilder UpdateToNotFound(); } } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 135a756600..fbc72247b1 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -16,20 +16,19 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int responseStatusCode, string responseStatusDescription, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) + public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); //IgnorePublishedContentCollisions = ignorePublishedContentCollisions; - PublishedContent = publishedContent ?? throw new ArgumentNullException(nameof(publishedContent)); + PublishedContent = publishedContent; IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent; - Template = template ?? throw new ArgumentNullException(nameof(template)); - Domain = domain ?? throw new ArgumentNullException(nameof(domain)); + Template = template; + Domain = domain; Culture = culture ?? throw new ArgumentNullException(nameof(culture)); - RedirectUrl = redirectUrl ?? throw new ArgumentNullException(nameof(redirectUrl)); + RedirectUrl = redirectUrl; ResponseStatusCode = responseStatusCode; - ResponseStatusDescription = responseStatusDescription ?? throw new ArgumentNullException(nameof(responseStatusDescription)); - CacheExtensions = cacheExtensions ?? throw new ArgumentNullException(nameof(cacheExtensions)); - Headers = headers ?? throw new ArgumentNullException(nameof(headers)); + CacheExtensions = cacheExtensions; + Headers = headers; CacheabilityNoCache = cacheabilityNoCache; } @@ -42,9 +41,6 @@ namespace Umbraco.Web.Routing /// public IPublishedContent PublishedContent { get; } - /// - public IPublishedContent InitialPublishedContent { get; } - /// public bool IsInternalRedirectPublishedContent { get; } @@ -61,10 +57,7 @@ namespace Umbraco.Web.Routing public string RedirectUrl { get; } /// - public int ResponseStatusCode { get; } - - /// - public string ResponseStatusDescription { get; } + public int? ResponseStatusCode { get; } /// public IReadOnlyList CacheExtensions { get; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 6441fcabf7..9ff0ce5dce 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -17,19 +17,19 @@ namespace Umbraco.Web.Routing private IReadOnlyList _cacheExtensions; private IPublishedContent _internalRedirectContent; private string _redirectUrl; - private HttpStatusCode _responseStatus = HttpStatusCode.NotFound; - private string _responseDesc; + private HttpStatusCode? _responseStatus; /// /// Initializes a new instance of the class. /// - public PublishedRequestBuilder(IFileService fileService) + public PublishedRequestBuilder(Uri uri, IFileService fileService) { + Uri = uri; _fileService = fileService; } /// - public Uri Uri { get; private set; } + public Uri Uri { get; } /// public DomainAndUri Domain { get; private set; } @@ -44,7 +44,7 @@ namespace Umbraco.Web.Routing public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet /// - public int ResponseStatusCode => (int)_responseStatus; + public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null; /// public IPublishedContent PublishedContent { get; private set; } @@ -58,19 +58,11 @@ namespace Umbraco.Web.Routing Domain, Culture, _redirectUrl, - (int)_responseStatus, - _responseDesc, + _responseStatus.HasValue ? (int?)_responseStatus : null, _cacheExtensions, _headers, _cacheability); - /// - public IPublishedRequestBuilder ResetTemplate() - { - Template = null; - return this; - } - /// public IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability) { @@ -113,13 +105,6 @@ namespace Umbraco.Web.Routing return this; } - /// - public IPublishedRequestBuilder SetIs404(bool is404) - { - _responseStatus = HttpStatusCode.NotFound; - return this; - } - /// public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content) { @@ -144,10 +129,9 @@ namespace Umbraco.Web.Routing } /// - public IPublishedRequestBuilder SetResponseStatus(int code, string description = null) + public IPublishedRequestBuilder SetResponseStatus(int code) { _responseStatus = (HttpStatusCode)code; - _responseDesc = description; return this; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs index b3eb21ed16..2a4e4323f0 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs @@ -2,14 +2,42 @@ using System.Net; namespace Umbraco.Web.Routing { + public static class PublishedRequestExtensions { + /// + /// Gets the + /// + public static UmbracoRouteResult GetRouteResult(this IPublishedRequest publishedRequest) + { + if (publishedRequest.IsRedirect()) + { + return UmbracoRouteResult.Redirect; + } + + if (!publishedRequest.HasPublishedContent()) + { + return UmbracoRouteResult.NotFound; + } + + return UmbracoRouteResult.Success; + } + /// /// Gets a value indicating whether the request was successfully routed /// public static bool Success(this IPublishedRequest publishedRequest) => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent(); + /// + /// Sets the response status to be 404 not found + /// + public static IPublishedRequestBuilder SetIs404(this IPublishedRequestBuilder publishedRequest) + { + publishedRequest.SetResponseStatus((int)HttpStatusCode.NotFound); + return publishedRequest; + } + /// /// Gets a value indicating whether the content request has a content. /// diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 7f4bc6f22f..15dab49bad 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -72,7 +72,7 @@ namespace Umbraco.Web.Routing } /// - public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(_fileService); + public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(uri, _fileService); /// public bool TryRouteRequest(IPublishedRequestBuilder request) @@ -106,6 +106,10 @@ namespace Umbraco.Web.Routing /// public IPublishedRequest RouteRequest(IPublishedRequestBuilder request) { + //// trigger the Preparing event - at that point anything can still be changed + //// the idea is that it is possible to change the uri + //request.OnPreparing(); + // find domain FindDomain(request); @@ -411,7 +415,7 @@ namespace Umbraco.Web.Routing // handle not found if (request.PublishedContent == null) { - request.SetIs404(true); + request.SetIs404(); _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); // if it fails then give up, there isn't much more that we can do diff --git a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs new file mode 100644 index 0000000000..9f42f372ac --- /dev/null +++ b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Web.Routing +{ + public enum UmbracoRouteResult + { + /// + /// Routing was successful and a content item was matched + /// + Success, + + /// + /// A redirection took place + /// + Redirect, + + /// + /// Nothing matched + /// + NotFound + } +} diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 75fc80015a..231a68df58 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Routing frequest .SetPublishedContent(content) - .SetIs404(true); + .SetIs404(); return content != null; } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs index f539d25152..d62497b173 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -12,9 +12,11 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; +using Umbraco.Web.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { @@ -211,9 +213,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders /// private ModelBindingContext CreateBindingContextForUmbracoRequest(Type modelType, IPublishedContent publishedContent) { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(publishedContent); + IPublishedRequest publishedRequest = builder.Build(); + var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); - routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedRequest)); { } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index 31603eb8c0..07f8118ad0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -162,8 +162,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); IPublishedContent content = Mock.Of(publishedContent => publishedContent.Id == 12345); + var builder = new PublishedRequestBuilder(umbracoContext.CleanedUmbracoUrl, Mock.Of()); + builder.SetPublishedContent(content); + IPublishedRequest publishedRequest = builder.Build(); - var routeDefinition = new UmbracoRouteValues(content); + var routeDefinition = new UmbracoRouteValues(publishedRequest); var routeData = new RouteData(); routeData.Values.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); diff --git a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs new file mode 100644 index 0000000000..3e90a40f09 --- /dev/null +++ b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs @@ -0,0 +1,60 @@ +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.ActionsResults +{ + /// + /// Returns the Umbraco not found result + /// + public class PublishedContentNotFoundResult : IActionResult + { + private readonly IUmbracoContext _umbracoContext; + private readonly string _message; + + /// + /// Initializes a new instance of the class. + /// + public PublishedContentNotFoundResult(IUmbracoContext umbracoContext, string message = null) + { + _umbracoContext = umbracoContext; + _message = message; + } + + /// + public async Task ExecuteResultAsync(ActionContext context) + { + HttpResponse response = context.HttpContext.Response; + + response.Clear(); + + response.StatusCode = StatusCodes.Status404NotFound; + + IPublishedRequest frequest = _umbracoContext.PublishedRequest; + var reason = "Cannot render the page at URL '{0}'."; + if (frequest.HasPublishedContent() == false) + { + reason = "No umbraco document matches the URL '{0}'."; + } + else if (frequest.HasTemplate() == false) + { + reason = "No template exists to render the document at URL '{0}'."; + } + + await response.WriteAsync("

Page not found

"); + await response.WriteAsync("

"); + await response.WriteAsync(string.Format(reason, WebUtility.HtmlEncode(_umbracoContext.OriginalRequestUrl.PathAndQuery))); + await response.WriteAsync("

"); + if (string.IsNullOrWhiteSpace(_message) == false) + { + await response.WriteAsync("

" + _message + "

"); + } + + await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); + await response.WriteAsync("

This page is intentionally left ugly ;-)

"); + await response.WriteAsync(""); + } + } +} diff --git a/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs new file mode 100644 index 0000000000..61e21df4cb --- /dev/null +++ b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Controllers +{ + /// + /// Deals with custom headers for the umbraco request + /// + internal class PublishedRequestFilterAttribute : ResultFilterAttribute + { + /// + /// Gets the + /// + protected UmbracoRouteValues GetUmbracoRouteValues(ResultExecutingContext context) + { + if (!context.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) + { + throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); + } + + return (UmbracoRouteValues)def; + } + + /// + /// Deals with custom headers for the umbraco request + /// + public override void OnResultExecuting(ResultExecutingContext context) + { + UmbracoRouteValues routeVals = GetUmbracoRouteValues(context); + IPublishedRequest pcr = routeVals.PublishedRequest; + + // now we can deal with headers, etc... + if (pcr.ResponseStatusCode.HasValue) + { + // set status code -- even for redirects + context.HttpContext.Response.StatusCode = pcr.ResponseStatusCode.Value; + } + + AddCacheControlHeaders(context, pcr); + + if (pcr.Headers != null) + { + foreach (KeyValuePair header in pcr.Headers) + { + context.HttpContext.Response.Headers.Append(header.Key, header.Value); + } + } + } + + private void AddCacheControlHeaders(ResultExecutingContext context, IPublishedRequest pcr) + { + var cacheControlHeaders = new List(); + + if (pcr.CacheabilityNoCache) + { + cacheControlHeaders.Add("no-cache"); + } + + if (pcr.CacheExtensions != null) + { + foreach (var cacheExtension in pcr.CacheExtensions) + { + cacheControlHeaders.Add(cacheExtension); + } + } + + if (cacheControlHeaders.Count > 0) + { + context.HttpContext.Response.Headers["Cache-Control"] = string.Join(", ", cacheControlHeaders); + } + } + } +} diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 099dfd59cd..7f6d61de98 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -1,9 +1,11 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Logging; -using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; @@ -11,30 +13,50 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Common.Controllers { - /// /// Represents the default front-end rendering controller. /// [ModelBindingException] + [PublishedRequestFilter] public class RenderController : UmbracoController, IRenderController { private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; private UmbracoRouteValues _umbracoRouteValues; /// /// Initializes a new instance of the class. /// - public RenderController(ILogger logger, ICompositeViewEngine compositeViewEngine) + public RenderController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) { _logger = logger; _compositeViewEngine = compositeViewEngine; + _umbracoContextAccessor = umbracoContextAccessor; } /// /// Gets the current content item. /// - protected IPublishedContent CurrentPage => UmbracoRouteValues.PublishedContent; + protected IPublishedContent CurrentPage + { + get + { + if (!UmbracoRouteValues.PublishedRequest.HasPublishedContent()) + { + // This will never be accessed this way since the controller will handle redirects and not founds + // before this can be accessed but we need to be explicit. + throw new InvalidOperationException("There is no published content found in the request"); + } + + return UmbracoRouteValues.PublishedRequest.PublishedContent; + } + } + + /// + /// Gets the umbraco context + /// + protected IUmbracoContext UmbracoContext => _umbracoContextAccessor.UmbracoContext; /// /// Gets the @@ -95,5 +117,41 @@ namespace Umbraco.Web.Common.Controllers /// The default action to render the front-end view. /// public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); + + /// + /// Before the controller executes we will handle redirects and not founds + /// + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest; + + _logger.LogDebug( + "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", + pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", + pcr.Is404() ? "true" : "false", + pcr.ResponseStatusCode); + + UmbracoRouteResult routeStatus = pcr.GetRouteResult(); + switch (routeStatus) + { + case UmbracoRouteResult.Redirect: + + // set the redirect result and do not call next to short circuit + context.Result = pcr.IsRedirectPermanent() + ? RedirectPermanent(pcr.RedirectUrl) + : Redirect(pcr.RedirectUrl); + break; + case UmbracoRouteResult.NotFound: + + // set the redirect result and do not call next to short circuit + context.Result = new PublishedContentNotFoundResult(UmbracoContext); + break; + case UmbracoRouteResult.Success: + default: + // continue normally + await next(); + break; + } + } } } diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs index d747a4ff86..7bdf1b13af 100644 --- a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Common.ModelBinders return Task.CompletedTask; } - BindModel(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType); + BindModel(bindingContext, umbracoRouteValues.PublishedRequest.PublishedContent, bindingContext.ModelType); return Task.CompletedTask; } diff --git a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs index 2ab047a757..8622bc689e 100644 --- a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs +++ b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Routing /// Initializes a new instance of the class. ///
public UmbracoRouteValues( - IPublishedContent publishedContent, + IPublishedRequest publishedRequest, string controllerName = null, Type controllerType = null, string actionName = DefaultActionName, @@ -29,7 +29,7 @@ namespace Umbraco.Web.Common.Routing { ControllerName = controllerName ?? ControllerExtensions.GetControllerName(); ControllerType = controllerType ?? typeof(RenderController); - PublishedContent = publishedContent; + PublishedRequest = publishedRequest; HasHijackedRoute = hasHijackedRoute; ActionName = actionName; TemplateName = templateName; @@ -56,9 +56,9 @@ namespace Umbraco.Web.Common.Routing public Type ControllerType { get; } /// - /// Gets the + /// Gets the /// - public IPublishedContent PublishedContent { get; } + public IPublishedRequest PublishedRequest { get; } /// /// Gets a value indicating whether the current request has a hijacked route/user controller routed for it diff --git a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs index 93b3a5bb0d..d8816c48a9 100644 --- a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Specialized; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -12,6 +12,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Website.ActionResults { + /// /// Redirects to an Umbraco page by Id or Entity /// diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs index 390da69453..3a6a7a3507 100644 --- a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs +++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Website.Controllers } var routeDef = routeDefAttempt.Result; - return routeDef.PublishedContent; + return routeDef.PublishedRequest.PublishedContent; } } diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 64d7cd0205..a94ee5e678 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -23,8 +23,6 @@ namespace Umbraco.Web.Website.DependencyInjection /// public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) { - builder.Services.AddUnique(); - builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetSurfaceControllers()); diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs index 5ceb5e523f..36e5ff9214 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs @@ -37,9 +37,6 @@ namespace Umbraco.Extensions { app.UseEndpoints(endpoints => { - NoContentRoutes noContentRoutes = app.ApplicationServices.GetRequiredService(); - noContentRoutes.CreateRoutes(endpoints); - endpoints.MapDynamicControllerRoute("/{**slug}"); }); diff --git a/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs b/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs deleted file mode 100644 index f2f2e8dfe3..0000000000 --- a/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Web.Common.Routing; - -namespace Umbraco.Web.Website.Routing -{ - /// - /// Creates route for the no content page - /// - public class NoContentRoutes : IAreaRoutes - { - private readonly IRuntimeState _runtimeState; - private readonly string _umbracoPathSegment; - - /// - /// Initializes a new instance of the class. - /// - public NoContentRoutes( - IOptions globalSettings, - IHostingEnvironment hostingEnvironment, - IRuntimeState runtimeState) - { - _runtimeState = runtimeState; - _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - } - - /// - public void CreateRoutes(IEndpointRouteBuilder endpoints) - { - switch (_runtimeState.Level) - { - case RuntimeLevel.Install: - break; - case RuntimeLevel.Upgrade: - break; - case RuntimeLevel.Run: - - // TODO: I don't really think this is working AFAIK the code has just been migrated but it's not really enabled - // yet. Our route handler needs to be aware that there is no content and redirect there. Though, this could all be - // managed directly in UmbracoRouteValueTransformer. Else it could actually do a 'redirect' but that would need to be - // an internal rewrite. - endpoints.MapControllerRoute( - Constants.Web.NoContentRouteName, // named consistently - _umbracoPathSegment + "/UmbNoContent", - new { controller = "RenderNoContent", action = "Index" }); - break; - case RuntimeLevel.BootFailed: - case RuntimeLevel.Unknown: - case RuntimeLevel.Boot: - break; - } - } - } -} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index f087a6203e..dc72777fa8 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -93,14 +93,19 @@ namespace Umbraco.Web.Website.Routing return values; } - bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); - if (!routed) + // Check if there is no existing content and return the no content controller + if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent()) { - return values; - // TODO: Deal with it not being routable, perhaps this should be an enum result? + values["controller"] = ControllerExtensions.GetControllerName(); + values["action"] = nameof(RenderNoContentController.Index); + + return await Task.FromResult(values); } + RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); + UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest); + values["controller"] = routeDef.ControllerName; if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) { @@ -134,7 +139,6 @@ namespace Umbraco.Web.Website.Routing var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); string customActionName = null; - var customControllerName = request.PublishedContent.ContentType.Alias; // never null // check that a template is defined), if it doesn't and there is a hijacked route it will just route // to the index Action @@ -143,17 +147,31 @@ namespace Umbraco.Web.Website.Routing // the template Alias should always be already saved with a safe name. // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed // with the action name attribute. - customActionName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); + customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper); } // creates the default route definition which maps to the 'UmbracoController' controller var def = new UmbracoRouteValues( - request.PublishedContent, + request, defaultControllerName, defaultControllerType, templateName: customActionName); - IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, def.ActionName); + var customControllerName = request.PublishedContent?.ContentType.Alias; + if (customControllerName != null) + { + def = DetermineHijackedRoute(def, customControllerName, customActionName, request); + } + + // store the route definition + values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); + + return def; + } + + private UmbracoRouteValues DetermineHijackedRoute(UmbracoRouteValues routeValues, string customControllerName, string customActionName, IPublishedRequest request) + { + IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, routeValues.ActionName); // check if there's a custom controller assigned, base on the document type alias. var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(customControllerName)).ToList(); @@ -170,11 +188,12 @@ namespace Umbraco.Web.Website.Routing // now check if the custom action matches var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName)); - def = new UmbracoRouteValues( - request.PublishedContent, + // it's a hijacked route with a custom controller, so return the the values + return new UmbracoRouteValues( + request, controllerDescriptor.ControllerName, controllerDescriptor.ControllerTypeInfo, - customActionExists ? customActionName : def.ActionName, + customActionExists ? customActionName : routeValues.ActionName, customActionName, true); // Hijacked = true } @@ -192,10 +211,7 @@ namespace Umbraco.Web.Website.Routing } } - // store the route definition - values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); - - return def; + return routeValues; } /// @@ -228,7 +244,7 @@ namespace Umbraco.Web.Website.Routing // Maybe could be a one-time Set method instead? publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); - return publishedRequest.Success() && publishedRequest.HasPublishedContent(); + return publishedRequest.Success(); // // HandleHttpResponseStatus returns a value indicating that the request should // // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index d690fb579b..f4520d2af9 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -284,19 +284,22 @@ namespace Umbraco.Web.Mvc // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (request.HasPublishedContent() == false) - { - // means the builder could not find a proper document to handle 404 - return new PublishedContentNotFoundHandler(); - } - if (request.HasTemplate() == false) - { - // means the engine could find a proper document, but the document has no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much - return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); - } + // TODO: Handle this differently in netcore.... + + //if (request.HasPublishedContent() == false) + //{ + // // means the builder could not find a proper document to handle 404 + // return new PublishedContentNotFoundHandler(); + //} + + //if (request.HasTemplate() == false) + //{ + // // means the engine could find a proper document, but the document has no template + // // at that point there isn't much we can do and there is no point returning + // // to Mvc since Mvc can't do much + // return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); + //} return null; } @@ -318,6 +321,7 @@ namespace Umbraco.Web.Mvc return HandlePostedValues(requestContext, postedInfo); } + // TODO: Surely this check is part of the PublishedRouter? // Here we need to check if there is no hijacked route and no template assigned, // if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler. @@ -326,13 +330,14 @@ namespace Umbraco.Web.Mvc if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false) { - // TODO: Handle this differently + // TODO: Handle this differently in netcore.... + // request.UpdateToNotFound(); // request will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger)) - return null; + //if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger)) + // return null; var handler = GetHandlerOnMissingTemplate(request); diff --git a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs b/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs deleted file mode 100644 index 28bae7bced..0000000000 --- a/src/Umbraco.Web/Routing/PublishedContentNotFoundHandler.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Web; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Routing -{ - /// - /// Gets executed when no document can be found in Umbraco - /// - internal class PublishedContentNotFoundHandler : IHttpHandler - { - private readonly string _message; - - public PublishedContentNotFoundHandler() - { } - - public PublishedContentNotFoundHandler(string message) - { - _message = message; - } - - public void ProcessRequest(HttpContext context) - { - WriteOutput(context); - } - - internal void WriteOutput(HttpContext context) - { - var response = context.Response; - - response.Clear(); - - var frequest = Current.UmbracoContext.PublishedRequest; - var reason = "Cannot render the page at URL '{0}'."; - if (frequest.HasPublishedContent() == false) - reason = "No umbraco document matches the URL '{0}'."; - else if (frequest.HasTemplate() == false) - reason = "No template exists to render the document at URL '{0}'."; - - response.Write("

Page not found

"); - response.Write("

"); - response.Write(string.Format(reason, HttpUtility.HtmlEncode(Current.UmbracoContext.OriginalRequestUrl.PathAndQuery))); - response.Write("

"); - if (string.IsNullOrWhiteSpace(_message) == false) - response.Write("

" + _message + "

"); - response.Write("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); - response.Write("

This page is intentionally left ugly ;-)

"); - response.Write(""); - } - - public bool IsReusable => false; - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b37b766a7d..c9ea1b1198 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -233,7 +233,6 @@ - diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index d0c2fd5de7..308e7dd48e 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -138,15 +138,15 @@ namespace Umbraco.Web var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); + // NOTE: This has been ported to netcore // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) - return; - - if (request.HasPublishedContent() == false) - httpContext.RemapHandler(new PublishedContentNotFoundHandler()); - else - RewriteToUmbracoHandler(httpContext, request); + //if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) + // return; + //if (request.HasPublishedContent() == false) + // httpContext.RemapHandler(new PublishedContentNotFoundHandler()); + //else + // RewriteToUmbracoHandler(httpContext, request); } #endregion @@ -257,40 +257,6 @@ namespace Umbraco.Web urlRouting.PostResolveRequestCache(context); } - /// - /// Rewrites to the Umbraco handler - we always send the request via our MVC rendering engine, this will deal with - /// requests destined for webforms. - /// - /// - /// - private void RewriteToUmbracoHandler(HttpContextBase context, IPublishedRequest pcr) - { - // NOTE: we do not want to use TransferRequest even though many docs say it is better with IIS7, turns out this is - // not what we need. The purpose of TransferRequest is to ensure that .net processes all of the rules for the newly - // rewritten URL, but this is not what we want! - // read: http://forums.iis.net/t/1146511.aspx - - var query = pcr.Uri.Query.TrimStart('?'); - - // GlobalSettings.Path has already been through IOHelper.ResolveUrl() so it begins with / and vdir (if any) - var rewritePath = _globalSettings.GetBackOfficePath(_hostingEnvironment).TrimEnd('/') + "/RenderMvc"; - // rewrite the path to the path of the handler (i.e. /umbraco/RenderMvc) - context.RewritePath(rewritePath, "", query, false); - - //if it is MVC we need to do something special, we are not using TransferRequest as this will - //require us to rewrite the path with query strings and then re-parse the query strings, this would - //also mean that we need to handle IIS 7 vs pre-IIS 7 differently. Instead we are just going to create - //an instance of the UrlRoutingModule and call it's PostResolveRequestCache method. This does: - // * Looks up the route based on the new rewritten URL - // * Creates the RequestContext with all route parameters and then executes the correct handler that matches the route - //we also cannot re-create this functionality because the setter for the HttpContext.Request.RequestContext is internal - //so really, this is pretty much the only way without using Server.TransferRequest and if we did that, we'd have to rethink - //a bunch of things! - var urlRouting = new UrlRoutingModule(); - urlRouting.PostResolveRequestCache(context); - } - - #endregion diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index b2b3d4c5a8..2f9c6d518a 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -41,65 +41,5 @@ namespace Umbraco.Web { EndRequest?.Invoke(sender, args); } - - // returns a value indicating whether redirection took place and the request has - // been completed - because we don't want to Response.End() here to terminate - // everything properly. - internal static bool HandleHttpResponseStatus(HttpContextBase context, IPublishedRequest pcr, ILogger logger) - { - var end = false; - var response = context.Response; - - logger.LogDebug("Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", - pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", - pcr.Is404() ? "true" : "false", - pcr.ResponseStatusCode); - - if(pcr.CacheabilityNoCache) - response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache); - - foreach (var cacheExtension in pcr.CacheExtensions) - response.Cache.AppendCacheExtension(cacheExtension); - - foreach (var header in pcr.Headers) - response.AppendHeader(header.Key, header.Value); - - if (pcr.IsRedirect()) - { - if (pcr.IsRedirectPermanent()) - response.RedirectPermanent(pcr.RedirectUrl, false); // do not end response - else - response.Redirect(pcr.RedirectUrl, false); // do not end response - end = true; - } - else if (pcr.Is404()) - { - response.StatusCode = 404; - response.TrySkipIisCustomErrors = /*Current.Configs.WebRouting().TrySkipIisCustomErrors; TODO introduce from config*/ false; - - if (response.TrySkipIisCustomErrors == false) - logger.LogWarning("Status code is 404 yet TrySkipIisCustomErrors is false - IIS will take over."); - } - - if (pcr.ResponseStatusCode > 0) - { - // set status code -- even for redirects - response.StatusCode = pcr.ResponseStatusCode; - response.StatusDescription = pcr.ResponseStatusDescription; - } - //if (pcr.IsRedirect) - // response.End(); // end response -- kills the thread and does not return! - - if (pcr.IsRedirect() == false) return end; - - response.Flush(); - // bypass everything and directly execute EndRequest event -- but returns - context.ApplicationInstance.CompleteRequest(); - // though some say that .CompleteRequest() does not properly shutdown the response - // and the request will hang until the whole code has run... would need to test? - logger.LogDebug("Response status: redirecting, complete request now."); - - return end; - } } } From 8373e98eff3436cebba3fc846a0026ab06350335 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Jan 2021 20:37:36 +1100 Subject: [PATCH 33/88] Fix tests --- src/Umbraco.Core/Routing/PublishedRequest.cs | 2 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 22 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index fbc72247b1..545b86f4d9 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent; Template = template; Domain = domain; - Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + Culture = culture; RedirectUrl = redirectUrl; ResponseStatusCode = responseStatusCode; CacheExtensions = cacheExtensions; diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 15dab49bad..2f5fc1b9fe 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -96,11 +96,15 @@ namespace Umbraco.Web.Routing return request.Build().Success(); } - private void SetVariationContext(string culture) + private void SetVariationContext(CultureInfo culture) { - var variationContext = _variationContextAccessor.VariationContext; - if (variationContext != null && variationContext.Culture == culture) return; - _variationContextAccessor.VariationContext = new VariationContext(culture); + VariationContext variationContext = _variationContextAccessor.VariationContext; + if (variationContext != null && variationContext.Culture == culture?.Name) + { + return; + } + + _variationContextAccessor.VariationContext = new VariationContext(culture?.Name); } /// @@ -123,7 +127,7 @@ namespace Umbraco.Web.Routing // set the culture on the thread - once, so it's set when running document lookups // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; - SetVariationContext(request.Culture.Name); + SetVariationContext(request.Culture); // find the published content if it's not assigned. This could be manually assigned with a custom route handler, or // with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method @@ -139,7 +143,7 @@ namespace Umbraco.Web.Routing // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; - SetVariationContext(request.Culture.Name); + SetVariationContext(request.Culture); //// trigger the Prepared event - at that point it is still possible to change about anything //// even though the request might be flagged for redirection - we'll redirect _after_ the event @@ -171,12 +175,14 @@ namespace Umbraco.Web.Routing return frequest.Build(); } + var result = frequest.Build(); + // set the culture on the thread -- again, 'cos it might have changed in the event handler // TODO: Set this on HttpContext! Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; - SetVariationContext(frequest.Culture.Name); + SetVariationContext(result.Culture); - return frequest.Build(); + return result; } // TODO: This shouldn't be required and should be handled differently during route building From 5b5fe626bb1530e877b1d5f0dbcdeea478a80260 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Jan 2021 22:05:23 +1100 Subject: [PATCH 34/88] cleanup, notes --- .../HttpContextVariationContextAccessor.cs | 7 ++-- src/Umbraco.Core/Routing/PublishedRouter.cs | 17 +++++----- src/Umbraco.Core/UmbracoContextReference.cs | 34 +++++++++---------- src/Umbraco.Core/Web/IUmbracoContext.cs | 2 +- .../Cache/DistributedCacheBinder.cs | 2 +- .../UmbracoContext/UmbracoContextFactory.cs | 14 ++++---- src/Umbraco.Web/UmbracoContextFactory.cs | 4 ++- 7 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs index 09604281dc..9410a4f611 100644 --- a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Cache; +using Umbraco.Core.Cache; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models.PublishedContent @@ -14,10 +14,7 @@ namespace Umbraco.Web.Models.PublishedContent /// /// Initializes a new instance of the class. /// - public HttpContextVariationContextAccessor(IRequestCache requestCache) - { - _requestCache = requestCache; - } + public HttpContextVariationContextAccessor(IRequestCache requestCache) => _requestCache = requestCache; /// public VariationContext VariationContext diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 2f5fc1b9fe..b4818cc947 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -98,6 +98,10 @@ namespace Umbraco.Web.Routing private void SetVariationContext(CultureInfo culture) { + // set the culture on the thread - once, so it's set when running document lookups + // TODO: Set this on HttpContext! + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture; + VariationContext variationContext = _variationContextAccessor.VariationContext; if (variationContext != null && variationContext.Culture == culture?.Name) { @@ -124,9 +128,7 @@ namespace Umbraco.Web.Routing return request.Build(); } - // set the culture on the thread - once, so it's set when running document lookups - // TODO: Set this on HttpContext! - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; + // set the culture SetVariationContext(request.Culture); // find the published content if it's not assigned. This could be manually assigned with a custom route handler, or @@ -141,8 +143,7 @@ namespace Umbraco.Web.Routing // handle wildcard domains HandleWildcardDomains(request); - // set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; + // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain SetVariationContext(request.Culture); //// trigger the Prepared event - at that point it is still possible to change about anything @@ -175,11 +176,9 @@ namespace Umbraco.Web.Routing return frequest.Build(); } - var result = frequest.Build(); + IPublishedRequest result = frequest.Build(); - // set the culture on the thread -- again, 'cos it might have changed in the event handler - // TODO: Set this on HttpContext! - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture; + // set the culture -- again, 'cos it might have changed in the event handler SetVariationContext(result.Culture); return result; diff --git a/src/Umbraco.Core/UmbracoContextReference.cs b/src/Umbraco.Core/UmbracoContextReference.cs index 96404dc1ba..c253c2f007 100644 --- a/src/Umbraco.Core/UmbracoContextReference.cs +++ b/src/Umbraco.Core/UmbracoContextReference.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Web { @@ -12,10 +12,10 @@ namespace Umbraco.Web /// it disposes the and clears the /// . /// - public class UmbracoContextReference : IDisposable //fixme - should we inherit from DisposableObjectSlim? + public class UmbracoContextReference : IDisposable { private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private bool _disposed; + private bool _disposedValue; /// /// Initializes a new instance of the class. @@ -36,25 +36,25 @@ namespace Umbraco.Web /// /// Gets a value indicating whether the reference is a root reference. /// - /// - /// - /// public bool IsRoot { get; } - /// - public void Dispose() + protected virtual void Dispose(bool disposing) { - if (_disposed) - return; - _disposed = true; - - if (IsRoot) + if (!_disposedValue) { - UmbracoContext.Dispose(); - _umbracoContextAccessor.UmbracoContext = null; - } + if (disposing) + { + if (IsRoot) + { + UmbracoContext.Dispose(); + _umbracoContextAccessor.UmbracoContext = null; + } + } - GC.SuppressFinalize(this); + _disposedValue = true; + } } + + public void Dispose() => Dispose(disposing: true); } } diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index 3bbcb43dca..ad964305d7 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web /// /// Gets the variation context accessor. /// - IVariationContextAccessor VariationContextAccessor { get; } // TODO: Does this need to be a property, it can be injected when needed + IVariationContextAccessor VariationContextAccessor { get; } // TODO: This shouldn't expose the accessor should it? IUmbracoContext is basically the accessor to the VariationContext since IUmbracoContextFactory currently creates it? /// /// Gets a value indicating whether the request has debugging enabled diff --git a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs index f345f40bd7..67987915ac 100644 --- a/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs +++ b/src/Umbraco.Infrastructure/Cache/DistributedCacheBinder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs index 9dd4939c3d..ac0d776e71 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs @@ -1,17 +1,10 @@ using System; -using System.IO; -using System.Text; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Security; -using Umbraco.Core.Services; -using Umbraco.Web.Common.Security; using Umbraco.Web.PublishedCache; -using Umbraco.Web.Security; namespace Umbraco.Web { @@ -61,6 +54,13 @@ namespace Umbraco.Web private IUmbracoContext CreateUmbracoContext() { + // TODO: It is strange having the IVariationContextAccessor initialized here and piggy backing off of IUmbracoContext. + // There's no particular reason that IVariationContextAccessor needs to exist as part of IUmbracoContext. + // Making this change however basically means that anywhere EnsureUmbracoContext is called, the IVariationContextAccessor + // would most likely need to be initialized too. This can easily happen in middleware for each request, however + // EnsureUmbracoContext is called for running on background threads too and it would be annoying to have to also ensure + // IVariationContextAccessor. Hrm. + // make sure we have a variation context if (_variationContextAccessor.VariationContext == null) { diff --git a/src/Umbraco.Web/UmbracoContextFactory.cs b/src/Umbraco.Web/UmbracoContextFactory.cs index fda8026762..c65860e3e5 100644 --- a/src/Umbraco.Web/UmbracoContextFactory.cs +++ b/src/Umbraco.Web/UmbracoContextFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; using Umbraco.Core.Configuration; @@ -11,6 +11,8 @@ using Umbraco.Web.Security; namespace Umbraco.Web { + // NOTE: This has been migrated to netcore + /// /// Creates and manages instances. /// From dec0ab87daa680948c8569a39ea7f3dec39be785 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 7 Jan 2021 23:14:26 +1100 Subject: [PATCH 35/88] Gets IsInternalRedirect working and documented, adds unit tests for the builder --- .../Routing/ContentFinderByRedirectUrl.cs | 2 +- src/Umbraco.Core/Routing/IPublishedRequest.cs | 2 +- .../Routing/IPublishedRequestBuilder.cs | 16 +- src/Umbraco.Core/Routing/PublishedRequest.cs | 390 +---------------- .../Routing/PublishedRequestBuilder.cs | 43 +- .../Routing/PublishedRequestOld.cs | 391 ++++++++++++++++++ src/Umbraco.Core/Routing/PublishedRouter.cs | 31 +- src/Umbraco.Core/Web/IUmbracoContext.cs | 9 +- .../Routing/PublishedRequestBuilderTests.cs | 97 +++++ 9 files changed, 566 insertions(+), 415 deletions(-) create mode 100644 src/Umbraco.Core/Routing/PublishedRequestOld.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index a35135e5a3..b887ff11be 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.Routing // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads // to problems if you rename a page back to it's original name or create a new page with the original name - .SetCacheabilityNoCache(true) + .SetNoCacheHeader(true) .SetCacheExtensions(new List { "no-store, must-revalidate" }) .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }); diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 971a8a2f98..8bfb49d9ac 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing /// /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirectPublishedContent { get; } // TODO: Not sure what thsi is yet + bool IsInternalRedirect { get; } /// /// Gets the template assigned to the request (if any) diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index 38c685b096..706315795e 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Routing /// /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirectPublishedContent { get; } + bool IsInternalRedirect { get; } /// /// Gets the content request http response status code. @@ -57,7 +57,7 @@ namespace Umbraco.Web.Routing IPublishedRequest Build(); /// - /// Sets the domain for the request + /// Sets the domain for the request which also sets the culture /// IPublishedRequestBuilder SetDomain(DomainAndUri domain); @@ -69,15 +69,15 @@ namespace Umbraco.Web.Routing /// /// Sets the found for the request /// + /// Setting the content clears the template and redirect IPublishedRequestBuilder SetPublishedContent(IPublishedContent content); /// /// Sets the requested content, following an internal redirect. /// /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content); // TODO: Need to figure this one out + /// Since this sets the content, it will clear the template + IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content); /// /// Tries to set the template to use to display the requested content. @@ -123,7 +123,11 @@ namespace Umbraco.Web.Routing /// not be used, in due time. IPublishedRequestBuilder SetResponseStatus(int code); - IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability); + /// + /// Sets the no-cache value to the Cache-Control header + /// + /// True to set the header, false to not set it + IPublishedRequestBuilder SetNoCacheHeader(bool setHeader); /// /// Sets a list of Extensions to append to the Response.Cache object. diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 545b86f4d9..bc8450177e 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Threading; -using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -16,12 +13,13 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, /*bool ignorePublishedContentCollisions, */IPublishedContent publishedContent, bool isInternalRedirectPublishedContent, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) + public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + // TODO: What is this? //IgnorePublishedContentCollisions = ignorePublishedContentCollisions; PublishedContent = publishedContent; - IsInternalRedirectPublishedContent = isInternalRedirectPublishedContent; + IsInternalRedirect = isInternalRedirect; Template = template; Domain = domain; Culture = culture; @@ -42,7 +40,7 @@ namespace Umbraco.Web.Routing public IPublishedContent PublishedContent { get; } /// - public bool IsInternalRedirectPublishedContent { get; } + public bool IsInternalRedirect { get; } /// public ITemplate Template { get; } @@ -68,384 +66,4 @@ namespace Umbraco.Web.Routing /// public bool CacheabilityNoCache { get; } } - - // TODO: Kill this, but we need to port all of it's functionality - public class PublishedRequestOld // : IPublishedRequest - { - private readonly IPublishedRouter _publishedRouter; - private readonly WebRoutingSettings _webRoutingSettings; - - private bool _readonly; // after prepared - private bool _is404; - private DomainAndUri _domain; - private CultureInfo _culture; - private IPublishedContent _publishedContent; - private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc - - /// - /// Initializes a new instance of the class. - /// - public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) - { - UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); - _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); - _webRoutingSettings = webRoutingSettings.Value; - Uri = uri ?? umbracoContext.CleanedUmbracoUrl; - } - - /// - /// Gets the UmbracoContext. - /// - public IUmbracoContext UmbracoContext { get; } - - /// - /// Gets or sets the cleaned up Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri { get; } - - // utility for ensuring it is ok to set some properties - public void EnsureWriteable() - { - if (_readonly) - { - throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); - } - } - - public bool CacheabilityNoCache { get; set; } - - ///// - ///// Prepares the request. - ///// - //public void Prepare() - //{ - // _publishedRouter.PrepareRequest(this); - //} - - /// - /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. - /// - public bool IgnorePublishedContentCollisions { get; set; } - - //#region Events - - ///// - ///// Triggers before the published content request is prepared. - ///// - ///// When the event triggers, no preparation has been done. It is still possible to - ///// modify the request's Uri property, for example to restore its original, public-facing value - ///// that might have been modified by an in-between equipment such as a load-balancer. - //public static event EventHandler Preparing; - - ///// - ///// Triggers once the published content request has been prepared, but before it is processed. - ///// - ///// When the event triggers, preparation is done ie domain, culture, document, template, - ///// rendering engine, etc. have been setup. It is then possible to change anything, before - ///// the request is actually processed and rendered by Umbraco. - //public static event EventHandler Prepared; - - ///// - ///// Triggers the Preparing event. - ///// - //public void OnPreparing() - //{ - // Preparing?.Invoke(this, EventArgs.Empty); - //} - - ///// - ///// Triggers the Prepared event. - ///// - //public void OnPrepared() - //{ - // Prepared?.Invoke(this, EventArgs.Empty); - - // if (HasPublishedContent == false) - // Is404 = true; // safety - - // _readonly = true; - //} - - //#endregion - - #region PublishedContent - - ///// - ///// Gets or sets the requested content. - ///// - ///// Setting the requested content clears Template. - //public IPublishedContent PublishedContent - //{ - // get { return _publishedContent; } - // set - // { - // EnsureWriteable(); - // _publishedContent = value; - // IsInternalRedirectPublishedContent = false; - // TemplateModel = null; - // } - //} - - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - public void SetInternalRedirectPublishedContent(IPublishedContent content) - { - //if (content == null) - // throw new ArgumentNullException(nameof(content)); - //EnsureWriteable(); - - //// unless a template has been set already by the finder, - //// template should be null at that point. - - //// IsInternalRedirect if IsInitial, or already IsInternalRedirect - //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - - //// redirecting to self - //if (content.Id == PublishedContent.Id) // neither can be null - //{ - // // no need to set PublishedContent, we're done - // IsInternalRedirectPublishedContent = isInternalRedirect; - // return; - //} - - //// else - - //// save - //var template = Template; - - //// set published content - this resets the template, and sets IsInternalRedirect to false - //PublishedContent = content; - //IsInternalRedirectPublishedContent = isInternalRedirect; - - //// must restore the template if it's an internal redirect & the config option is set - //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - //{ - // // restore - // TemplateModel = template; - //} - } - - /// - /// Gets the initial requested content. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - public IPublishedContent InitialPublishedContent => _initialPublishedContent; - - /// - /// Gets value indicating whether the current published content is the initial one. - /// - public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent; - - /// - /// Indicates that the current PublishedContent is the initial one. - /// - public void SetIsInitialPublishedContent() - { - EnsureWriteable(); - - // note: it can very well be null if the initial content was not found - _initialPublishedContent = _publishedContent; - IsInternalRedirectPublishedContent = false; - } - - /// - /// Gets or sets a value indicating whether the current published content has been obtained - /// from the initial published content following internal redirections exclusively. - /// - /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to - /// apply the internal redirect or not, when content is not the initial content. - public bool IsInternalRedirectPublishedContent { get; private set; } - - - #endregion - - /// - /// Gets or sets the template model to use to display the requested content. - /// - public ITemplate Template { get; } - - /// - /// Gets the alias of the template to use to display the requested content. - /// - public string TemplateAlias => Template?.Alias; - - - /// - /// Gets or sets the content request's domain. - /// - /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, - /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - public DomainAndUri Domain - { - get { return _domain; } - set - { - EnsureWriteable(); - _domain = value; - } - } - - /// - /// Gets a value indicating whether the content request has a domain. - /// - public bool HasDomain => Domain != null; - - /// - /// Gets or sets the content request's culture. - /// - public CultureInfo Culture - { - get { return _culture ?? Thread.CurrentThread.CurrentCulture; } - set - { - EnsureWriteable(); - _culture = value; - } - } - - // note: do we want to have an ordered list of alternate cultures, - // to allow for fallbacks when doing dictionary lookup and such? - - - #region Status - - /// - /// Gets or sets a value indicating whether the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder and can also be used in - /// custom content finders or Prepared event handlers, where we want to allow developers - /// to indicate a request is 404 but not to cancel it. - public bool Is404 - { - get { return _is404; } - set - { - EnsureWriteable(); - _is404 = value; - } - } - - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false; - - /// - /// Gets or sets a value indicating whether the redirect is permanent. - /// - public bool IsRedirectPermanent { get; private set; } - - /// - /// Gets or sets the URL to redirect to, when the content request triggers a redirect. - /// - public string RedirectUrl { get; private set; } - - /// - /// Indicates that the content request should trigger a redirect (302). - /// - /// The URL to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url) - { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = false; - } - - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The URL to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirectPermanent(string url) - { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = true; - } - - /// - /// Indicates that the content request should trigger a redirect, with a specified status code. - /// - /// The URL to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url, int status) - { - EnsureWriteable(); - - if (status < 300 || status > 308) - throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308."); - - RedirectUrl = url; - IsRedirectPermanent = (status == 301 || status == 308); - if (status != 301 && status != 302) // default redirect statuses - ResponseStatusCode = status; - } - - /// - /// Gets or sets the content request http response status code. - /// - /// Does not actually set the http response status code, only registers that the response - /// should use the specified code. The code will or will not be used, in due time. - public int ResponseStatusCode { get; private set; } - - /// - /// Gets or sets the content request http response status description. - /// - /// Does not actually set the http response status description, only registers that the response - /// should use the specified description. The description will or will not be used, in due time. - public string ResponseStatusDescription { get; private set; } - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// The description. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - public void SetResponseStatus(int code, string description = null) - { - EnsureWriteable(); - - // .Status is deprecated - // .SubStatusCode is IIS 7+ internal, ignore - ResponseStatusCode = code; - ResponseStatusDescription = description; - } - - #endregion - - #region Response Cache - - /// - /// Gets or sets the System.Web.HttpCacheability - /// - // Note: we used to set a default value here but that would then be the default - // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example - // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 - //public HttpCacheability Cacheability { get; set; } - - /// - /// Gets or sets a list of Extensions to append to the Response.Cache object. - /// - public List CacheExtensions { get; set; } = new List(); - - /// - /// Gets or sets a dictionary of Headers to append to the Response object. - /// - public Dictionary Headers { get; set; } = new Dictionary(); - - #endregion - } } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 9ff0ce5dce..77c3420399 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -15,9 +15,9 @@ namespace Umbraco.Web.Routing private IReadOnlyDictionary _headers; private bool _cacheability; private IReadOnlyList _cacheExtensions; - private IPublishedContent _internalRedirectContent; private string _redirectUrl; private HttpStatusCode? _responseStatus; + private IPublishedContent _publishedContent; /// /// Initializes a new instance of the class. @@ -41,19 +41,28 @@ namespace Umbraco.Web.Routing public ITemplate Template { get; private set; } /// - public bool IsInternalRedirectPublishedContent { get; private set; } // TODO: Not sure what this is yet + public bool IsInternalRedirect { get; private set; } /// public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null; /// - public IPublishedContent PublishedContent { get; private set; } + public IPublishedContent PublishedContent + { + get => _publishedContent; + private set + { + _publishedContent = value; + IsInternalRedirect = false; + Template = null; + } + } /// public IPublishedRequest Build() => new PublishedRequest( Uri, PublishedContent, - IsInternalRedirectPublishedContent, + IsInternalRedirect, Template, Domain, Culture, @@ -64,7 +73,7 @@ namespace Umbraco.Web.Routing _cacheability); /// - public IPublishedRequestBuilder SetCacheabilityNoCache(bool cacheability) + public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability) { _cacheability = cacheability; return this; @@ -88,6 +97,7 @@ namespace Umbraco.Web.Routing public IPublishedRequestBuilder SetDomain(DomainAndUri domain) { Domain = domain; + SetCulture(domain.Culture); return this; } @@ -99,9 +109,25 @@ namespace Umbraco.Web.Routing } /// - public IPublishedRequestBuilder SetInternalRedirectPublishedContent(IPublishedContent content) + public IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content) { - _internalRedirectContent = content; + // unless a template has been set already by the finder, + // template should be null at that point. + + // redirecting to self + if (PublishedContent != null && content.Id == PublishedContent.Id) + { + // no need to set PublishedContent, we're done + IsInternalRedirect = true; + return this; + } + + // else + + // set published content - this resets the template, and sets IsInternalRedirect to false + PublishedContent = content; + IsInternalRedirect = true; + return this; } @@ -109,6 +135,7 @@ namespace Umbraco.Web.Routing public IPublishedRequestBuilder SetPublishedContent(IPublishedContent content) { PublishedContent = content; + IsInternalRedirect = false; return this; } @@ -152,7 +179,7 @@ namespace Umbraco.Web.Routing } // NOTE - can we still get it with whitespaces in it due to old legacy bugs? - alias = alias.Replace(" ", ""); + alias = alias.Replace(" ", string.Empty); ITemplate model = _fileService.GetTemplate(alias); if (model == null) diff --git a/src/Umbraco.Core/Routing/PublishedRequestOld.cs b/src/Umbraco.Core/Routing/PublishedRequestOld.cs new file mode 100644 index 0000000000..c851d4ebb6 --- /dev/null +++ b/src/Umbraco.Core/Routing/PublishedRequestOld.cs @@ -0,0 +1,391 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Routing +{ + // TODO: Kill this, but we need to port all of it's functionality + public class PublishedRequestOld // : IPublishedRequest + { + private readonly IPublishedRouter _publishedRouter; + private readonly WebRoutingSettings _webRoutingSettings; + + private bool _readonly; // after prepared + private bool _is404; + private DomainAndUri _domain; + private CultureInfo _culture; + private IPublishedContent _publishedContent; + private IPublishedContent _initialPublishedContent; // found by finders before 404, redirects, etc + + /// + /// Initializes a new instance of the class. + /// + public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri uri = null) + { + UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); + _webRoutingSettings = webRoutingSettings.Value; + Uri = uri ?? umbracoContext.CleanedUmbracoUrl; + } + + /// + /// Gets the UmbracoContext. + /// + public IUmbracoContext UmbracoContext { get; } + + /// + /// Gets or sets the cleaned up Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + public Uri Uri { get; } + + // utility for ensuring it is ok to set some properties + public void EnsureWriteable() + { + if (_readonly) + { + throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); + } + } + + public bool CacheabilityNoCache { get; set; } + + ///// + ///// Prepares the request. + ///// + //public void Prepare() + //{ + // _publishedRouter.PrepareRequest(this); + //} + + /// + /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// + public bool IgnorePublishedContentCollisions { get; set; } + + //#region Events + + ///// + ///// Triggers before the published content request is prepared. + ///// + ///// When the event triggers, no preparation has been done. It is still possible to + ///// modify the request's Uri property, for example to restore its original, public-facing value + ///// that might have been modified by an in-between equipment such as a load-balancer. + //public static event EventHandler Preparing; + + ///// + ///// Triggers once the published content request has been prepared, but before it is processed. + ///// + ///// When the event triggers, preparation is done ie domain, culture, document, template, + ///// rendering engine, etc. have been setup. It is then possible to change anything, before + ///// the request is actually processed and rendered by Umbraco. + //public static event EventHandler Prepared; + + ///// + ///// Triggers the Preparing event. + ///// + //public void OnPreparing() + //{ + // Preparing?.Invoke(this, EventArgs.Empty); + //} + + ///// + ///// Triggers the Prepared event. + ///// + //public void OnPrepared() + //{ + // Prepared?.Invoke(this, EventArgs.Empty); + + // if (HasPublishedContent == false) + // Is404 = true; // safety + + // _readonly = true; + //} + + //#endregion + + #region PublishedContent + + ///// + ///// Gets or sets the requested content. + ///// + ///// Setting the requested content clears Template. + //public IPublishedContent PublishedContent + //{ + // get { return _publishedContent; } + // set + // { + // EnsureWriteable(); + // _publishedContent = value; + // IsInternalRedirectPublishedContent = false; + // TemplateModel = null; + // } + //} + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + public void SetInternalRedirectPublishedContent(IPublishedContent content) + { + //if (content == null) + // throw new ArgumentNullException(nameof(content)); + //EnsureWriteable(); + + //// unless a template has been set already by the finder, + //// template should be null at that point. + + //// IsInternalRedirect if IsInitial, or already IsInternalRedirect + //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + + //// redirecting to self + //if (content.Id == PublishedContent.Id) // neither can be null + //{ + // // no need to set PublishedContent, we're done + // IsInternalRedirectPublishedContent = isInternalRedirect; + // return; + //} + + //// else + + //// save + //var template = Template; + + //// set published content - this resets the template, and sets IsInternalRedirect to false + //PublishedContent = content; + //IsInternalRedirectPublishedContent = isInternalRedirect; + + //// must restore the template if it's an internal redirect & the config option is set + //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + //{ + // // restore + // TemplateModel = template; + //} + } + + /// + /// Gets the initial requested content. + /// + /// The initial requested content is the content that was found by the finders, + /// before anything such as 404, redirect... took place. + public IPublishedContent InitialPublishedContent => _initialPublishedContent; + + /// + /// Gets value indicating whether the current published content is the initial one. + /// + public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent; + + /// + /// Indicates that the current PublishedContent is the initial one. + /// + public void SetIsInitialPublishedContent() + { + EnsureWriteable(); + + // note: it can very well be null if the initial content was not found + _initialPublishedContent = _publishedContent; + IsInternalRedirectPublishedContent = false; + } + + /// + /// Gets or sets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + public bool IsInternalRedirectPublishedContent { get; private set; } + + + #endregion + + /// + /// Gets or sets the template model to use to display the requested content. + /// + public ITemplate Template { get; } + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public string TemplateAlias => Template?.Alias; + + + /// + /// Gets or sets the content request's domain. + /// + /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, + /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". + public DomainAndUri Domain + { + get { return _domain; } + set + { + EnsureWriteable(); + _domain = value; + } + } + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public bool HasDomain => Domain != null; + + /// + /// Gets or sets the content request's culture. + /// + public CultureInfo Culture + { + get { return _culture ?? Thread.CurrentThread.CurrentCulture; } + set + { + EnsureWriteable(); + _culture = value; + } + } + + // note: do we want to have an ordered list of alternate cultures, + // to allow for fallbacks when doing dictionary lookup and such? + + + #region Status + + /// + /// Gets or sets a value indicating whether the requested content could not be found. + /// + /// This is set in the PublishedContentRequestBuilder and can also be used in + /// custom content finders or Prepared event handlers, where we want to allow developers + /// to indicate a request is 404 but not to cancel it. + public bool Is404 + { + get { return _is404; } + set + { + EnsureWriteable(); + _is404 = value; + } + } + + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false; + + /// + /// Gets or sets a value indicating whether the redirect is permanent. + /// + public bool IsRedirectPermanent { get; private set; } + + /// + /// Gets or sets the URL to redirect to, when the content request triggers a redirect. + /// + public string RedirectUrl { get; private set; } + + /// + /// Indicates that the content request should trigger a redirect (302). + /// + /// The URL to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirect(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = false; + } + + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The URL to redirect to. + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirectPermanent(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = true; + } + + /// + /// Indicates that the content request should trigger a redirect, with a specified status code. + /// + /// The URL to redirect to. + /// The status code (300-308). + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + public void SetRedirect(string url, int status) + { + EnsureWriteable(); + + if (status < 300 || status > 308) + throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308."); + + RedirectUrl = url; + IsRedirectPermanent = (status == 301 || status == 308); + if (status != 301 && status != 302) // default redirect statuses + ResponseStatusCode = status; + } + + /// + /// Gets or sets the content request http response status code. + /// + /// Does not actually set the http response status code, only registers that the response + /// should use the specified code. The code will or will not be used, in due time. + public int ResponseStatusCode { get; private set; } + + /// + /// Gets or sets the content request http response status description. + /// + /// Does not actually set the http response status description, only registers that the response + /// should use the specified description. The description will or will not be used, in due time. + public string ResponseStatusDescription { get; private set; } + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// The description. + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + public void SetResponseStatus(int code, string description = null) + { + EnsureWriteable(); + + // .Status is deprecated + // .SubStatusCode is IIS 7+ internal, ignore + ResponseStatusCode = code; + ResponseStatusDescription = description; + } + + #endregion + + #region Response Cache + + /// + /// Gets or sets the System.Web.HttpCacheability + /// + // Note: we used to set a default value here but that would then be the default + // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example + // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 + //public HttpCacheability Cacheability { get; set; } + + /// + /// Gets or sets a list of Extensions to append to the Response.Cache object. + /// + public List CacheExtensions { get; set; } = new List(); + + /// + /// Gets or sets a dictionary of Headers to append to the Response object. + /// + public Dictionary Headers { get; set; } = new Dictionary(); + + #endregion + } +} diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index b4818cc947..8d5c2774ed 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -271,9 +271,7 @@ namespace Umbraco.Web.Routing // matching an existing domain _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); - request - .SetDomain(domainAndUri) - .SetCulture(domainAndUri.Culture); + request.SetDomain(domainAndUri); // canonical? not implemented at the moment // if (...) @@ -368,7 +366,7 @@ namespace Umbraco.Web.Routing // so internal redirect, 404, etc has precedence over redirect // handle not-found, redirects, access... - HandlePublishedContent(request); + HandlePublishedContent(request, foundContentByFinders); // find a template FindTemplate(request, foundContentByFinders); @@ -404,11 +402,13 @@ namespace Umbraco.Web.Routing /// /// Handles the published content (if any). /// + /// The request builder. + /// If the content was found by the finders, before anything such as 404, redirect... took place. /// /// Handles "not found", internal redirects, access validation... /// things that must be handled in one place because they can create loops /// - private void HandlePublishedContent(IPublishedRequestBuilder request) + private void HandlePublishedContent(IPublishedRequestBuilder request, bool contentFoundByFinders) { // because these might loop, we have to have some sort of infinite loop detection int i = 0, j = 0; @@ -435,7 +435,7 @@ namespace Umbraco.Web.Routing // follow internal redirects as long as it's not running out of control ie infinite loop of some sort j = 0; - while (FollowInternalRedirects(request) && j++ < maxLoop) + while (FollowInternalRedirects(request, contentFoundByFinders) && j++ < maxLoop) { } // we're running out of control @@ -467,12 +467,14 @@ namespace Umbraco.Web.Routing /// /// Follows internal redirections through the umbracoInternalRedirectId document property. /// + /// The request builder. + /// If the content was found by the finders, before anything such as 404, redirect... took place. /// A value indicating whether redirection took place and led to a new published document. /// /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. /// As per legacy, if the redirect does not work, we just ignore it. /// - private bool FollowInternalRedirects(IPublishedRequestBuilder request) + private bool FollowInternalRedirects(IPublishedRequestBuilder request, bool contentFoundByFinders) { if (request.PublishedContent == null) { @@ -528,7 +530,18 @@ namespace Umbraco.Web.Routing } else { - request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here + // save since it will be cleared + ITemplate template = request.Template; + + request.SetInternalRedirect(internalRedirectNode); // don't use .PublishedContent here + + // must restore the template if it's an internal redirect & the config option is set + if (request.IsInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + { + // restore + request.SetTemplate(template); + } + redirect = true; _logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId); } @@ -615,7 +628,7 @@ namespace Umbraco.Web.Routing // does not apply // + optionally, apply the alternate template on internal redirects var useAltTemplate = contentFoundByFinders - || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent); + || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirect); var altTemplate = useAltTemplate ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index ad964305d7..c80dd9c1f3 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -49,10 +49,11 @@ namespace Umbraco.Web /// IDomainCache Domains { get; } - ///// - ///// Gets or sets the PublishedRequest object - ///// - //// TODO: Can we refactor this? The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx + /// + /// Gets or sets the PublishedRequest object + /// + //// TODO: Can we refactor this so it's not a settable thing required for routing? + //// The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx/factory. IPublishedRequest PublishedRequest { get; set; } /// diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs new file mode 100644 index 0000000000..80e39f82ba --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Web.Routing +{ + [TestFixture] + public class PublishedRequestBuilderTests + { + private readonly Uri _baseUri = new Uri("https://example.com"); + + private IPublishedRequestBuilder GetBuilder() => new PublishedRequestBuilder( + _baseUri, + Mock.Of()); + + [Test] + public void Setting_Published_Content_Clears_Template_And_Redirect() + { + IPublishedRequestBuilder sut = GetBuilder(); + sut.SetTemplate(Mock.Of()); + + Assert.IsNotNull(sut.Template); + + sut.SetInternalRedirect(Mock.Of()); + + Assert.IsNull(sut.Template); + Assert.IsTrue(sut.IsInternalRedirect); + + sut.SetTemplate(Mock.Of()); + sut.SetPublishedContent(Mock.Of()); + + Assert.IsNull(sut.Template); + Assert.IsFalse(sut.IsInternalRedirect); + } + + [Test] + public void Setting_Domain_Also_Sets_Culture() + { + IPublishedRequestBuilder sut = GetBuilder(); + + Assert.IsNull(sut.Culture); + + sut.SetDomain( + new DomainAndUri( + new Domain(1, "test", 2, CultureInfo.GetCultureInfo("en-AU"), false), new Uri("https://example.com/en-au"))); + + Assert.IsNotNull(sut.Domain); + Assert.IsNotNull(sut.Culture); + } + + [Test] + public void Builds_All_Values() + { + IPublishedRequestBuilder sut = GetBuilder(); + + IPublishedContent content = Mock.Of(x => x.Id == 1); + ITemplate template = Mock.Of(x => x.Id == 1); + string[] cacheExt = new[] { "must-revalidate" }; + var auCulture = CultureInfo.GetCultureInfo("en-AU"); + var usCulture = CultureInfo.GetCultureInfo("en-US"); + var domain = new DomainAndUri( + new Domain(1, "test", 2, auCulture, false), new Uri("https://example.com/en-au")); + IReadOnlyDictionary headers = new Dictionary { ["Hello"] = "world" }; + var redirect = "https://test.com"; + + sut + .SetNoCacheHeader(true) + .SetCacheExtensions(cacheExt) + .SetDomain(domain) + .SetCulture(usCulture) + .SetHeaders(headers) + .SetInternalRedirect(content) + .SetRedirect(redirect) + .SetTemplate(template); + + IPublishedRequest request = sut.Build(); + + Assert.AreEqual(true, request.CacheabilityNoCache); + Assert.AreEqual(cacheExt, request.CacheExtensions); + Assert.AreEqual(usCulture, request.Culture); + Assert.AreEqual(domain, request.Domain); + Assert.AreEqual(headers, request.Headers); + Assert.AreEqual(true, request.IsInternalRedirect); + Assert.AreEqual(content, request.PublishedContent); + Assert.AreEqual(redirect, request.RedirectUrl); + Assert.AreEqual(302, request.ResponseStatusCode); + Assert.AreEqual(template, request.Template); + Assert.AreEqual(_baseUri, request.Uri); + } + } +} From 504837054e87aa473dd79a6bffb9f1795bb96bfe Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 00:25:09 +1100 Subject: [PATCH 36/88] puts back in events but uses event aggregator, changes IPublishedRouter to async --- .../Routing/CreatingRequestNotification.cs | 21 ++++ src/Umbraco.Core/Routing/IPublishedRouter.cs | 7 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 52 +++++--- .../Routing/RoutingRequestNotification.cs | 20 +++ .../Routing/UrlProviderExtensions.cs | 117 +++++++++++------- .../Templates/ITemplateRenderer.cs | 5 +- .../Templates/UmbracoComponentRenderer.cs | 2 +- .../PublishedContent/PublishedRouterTests.cs | 9 +- .../Routing/ContentFinderByAliasTests.cs | 5 +- .../ContentFinderByAliasWithDomainsTests.cs | 5 +- .../Routing/ContentFinderByIdTests.cs | 6 +- .../ContentFinderByPageIdQueryTests.cs | 5 +- .../ContentFinderByUrlAndTemplateTests.cs | 5 +- .../Routing/ContentFinderByUrlTests.cs | 21 ++-- .../ContentFinderByUrlWithDomainsTests.cs | 9 +- .../Routing/DomainsAndCulturesTests.cs | 13 +- .../Routing/GetContentUrlsTests.cs | 19 +-- .../Routing/RenderRouteHandlerTests.cs | 9 +- .../Routing/UrlsWithNestedDomains.cs | 5 +- src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 4 +- .../Web/Mvc/SurfaceControllerTests.cs | 5 +- .../Mapping/ContentMapDefinition.cs | 16 ++- .../Templates/TemplateRenderer.cs | 10 +- .../Routing/UmbracoRouteValueTransformer.cs | 12 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 4 +- 25 files changed, 245 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Core/Routing/CreatingRequestNotification.cs create mode 100644 src/Umbraco.Core/Routing/RoutingRequestNotification.cs diff --git a/src/Umbraco.Core/Routing/CreatingRequestNotification.cs b/src/Umbraco.Core/Routing/CreatingRequestNotification.cs new file mode 100644 index 0000000000..859ccd24b0 --- /dev/null +++ b/src/Umbraco.Core/Routing/CreatingRequestNotification.cs @@ -0,0 +1,21 @@ +using System; +using Umbraco.Core.Events; + +namespace Umbraco.Web.Routing +{ + /// + /// Used for notifying when an Umbraco request is being created + /// + public class CreatingRequestNotification : INotification + { + /// + /// Initializes a new instance of the class. + /// + public CreatingRequestNotification(Uri url) => Url = url; + + /// + /// Gets or sets the URL for the request + /// + public Uri Url { get; set; } + } +} diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index aaccb4b4d2..2a6f6b66d9 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Umbraco.Core.Models; namespace Umbraco.Web.Routing @@ -16,21 +17,21 @@ namespace Umbraco.Web.Routing /// /// The current request Uri. /// A published request builder. - IPublishedRequestBuilder CreateRequest(Uri uri); + Task CreateRequestAsync(Uri uri); /// /// Prepares a request for rendering. /// /// The request. /// A value indicating whether the request was successfully prepared and can be rendered. - IPublishedRequest RouteRequest(IPublishedRequestBuilder request); + Task RouteRequestAsync(IPublishedRequestBuilder request); /// /// Tries to route a request. /// /// The request. /// A value indicating whether the request can be routed to a document. - bool TryRouteRequest(IPublishedRequestBuilder request); + Task TryRouteRequestAsync(IPublishedRequestBuilder request); // TODO: This shouldn't be required and should be handled differently during route building ///// diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 8d5c2774ed..ae77abe4a7 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -3,10 +3,12 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -16,6 +18,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Routing { + /// /// Provides the default implementation. /// @@ -35,6 +38,7 @@ namespace Umbraco.Web.Routing private readonly IContentTypeService _contentTypeService; private readonly IPublicAccessService _publicAccessService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IEventAggregator _eventAggregator; /// /// Initializes a new instance of the class. @@ -53,7 +57,8 @@ namespace Umbraco.Web.Routing IFileService fileService, IContentTypeService contentTypeService, IPublicAccessService publicAccessService, - IUmbracoContextAccessor umbracoContextAccessor) + IUmbracoContextAccessor umbracoContextAccessor, + IEventAggregator eventAggregator) { _webRoutingSettings = webRoutingSettings.Value ?? throw new ArgumentNullException(nameof(webRoutingSettings)); _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); @@ -69,31 +74,52 @@ namespace Umbraco.Web.Routing _contentTypeService = contentTypeService; _publicAccessService = publicAccessService; _umbracoContextAccessor = umbracoContextAccessor; + _eventAggregator = eventAggregator; } /// - public IPublishedRequestBuilder CreateRequest(Uri uri) => new PublishedRequestBuilder(uri, _fileService); + public async Task CreateRequestAsync(Uri uri) + { + // trigger the Creating event - at that point the URL can be changed + // this is based on this old task here: https://issues.umbraco.org/issue/U4-7914 which was fulfiled by + // this PR https://github.com/umbraco/Umbraco-CMS/pull/1137 + // It's to do with proxies, quote: + + /* + "Thinking about another solution. + We already have an event, PublishedContentRequest.Prepared, which triggers once the request has been prepared and domain, content, template have been figured out -- but before it renders -- so ppl can change things before rendering. + Wondering whether we could have a event, PublishedContentRequest.Preparing, which would trigger before the request is prepared, and would let ppl change the value of the request's URI (which by default derives from the HttpContext request). + That way, if an in-between equipement changes the URI, you could replace it with the original, public-facing URI before we process the request, meaning you could register your HTTPS domain and it would work. And you would have to supply code for each equipment. Less magic in Core." + */ + + // but now we'll just have one event for creating so if people wish to change the URL here they can but nothing else + var creatingRequest = new CreatingRequestNotification(uri); + await _eventAggregator.PublishAsync(creatingRequest); + + var publishedRequestBuilder = new PublishedRequestBuilder(creatingRequest.Url, _fileService); + return publishedRequestBuilder; + } /// - public bool TryRouteRequest(IPublishedRequestBuilder request) + public Task TryRouteRequestAsync(IPublishedRequestBuilder request) { FindDomain(request); // TODO: This was ported from v8 but how could it possibly have a redirect here? if (request.IsRedirect()) { - return false; + return Task.FromResult(false); } // TODO: This was ported from v8 but how could it possibly have content here? if (request.HasPublishedContent()) { - return true; + return Task.FromResult(true); } FindPublishedContent(request); - return request.Build().Success(); + return Task.FromResult(request.Build().Success()); } private void SetVariationContext(CultureInfo culture) @@ -112,12 +138,8 @@ namespace Umbraco.Web.Routing } /// - public IPublishedRequest RouteRequest(IPublishedRequestBuilder request) + public async Task RouteRequestAsync(IPublishedRequestBuilder request) { - //// trigger the Preparing event - at that point anything can still be changed - //// the idea is that it is possible to change the uri - //request.OnPreparing(); - // find domain FindDomain(request); @@ -146,10 +168,10 @@ namespace Umbraco.Web.Routing // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain SetVariationContext(request.Culture); - //// trigger the Prepared event - at that point it is still possible to change about anything - //// even though the request might be flagged for redirection - we'll redirect _after_ the event - //// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change - //request.OnPrepared(); + // trigger the routing request (used to be called Prepared) event - at that point it is still possible to change about anything + // even though the request might be flagged for redirection - we'll redirect _after_ the event + var routingRequest = new RoutingRequestNotification(request); + await _eventAggregator.PublishAsync(routingRequest); // we don't take care of anything so if the content has changed, it's up to the user // to find out the appropriate template diff --git a/src/Umbraco.Core/Routing/RoutingRequestNotification.cs b/src/Umbraco.Core/Routing/RoutingRequestNotification.cs new file mode 100644 index 0000000000..dbf1cbc15b --- /dev/null +++ b/src/Umbraco.Core/Routing/RoutingRequestNotification.cs @@ -0,0 +1,20 @@ +using Umbraco.Core.Events; + +namespace Umbraco.Web.Routing +{ + /// + /// Used for notifying when an Umbraco request is being built + /// + public class RoutingRequestNotification : INotification + { + /// + /// Initializes a new instance of the class. + /// + public RoutingRequestNotification(IPublishedRequestBuilder requestBuilder) => RequestBuilder = requestBuilder; + + /// + /// Gets the + /// + public IPublishedRequestBuilder RequestBuilder { get; } + } +} diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 19d65b8f3a..e7095feb2b 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Services; using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; namespace Umbraco.Web.Routing { @@ -18,7 +19,8 @@ namespace Umbraco.Web.Routing /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list. /// Contains all the URLs that we can figure out (based upon domains, etc). /// - public static IEnumerable GetContentUrls(this IContent content, + public static async Task> GetContentUrlsAsync( + this IContent content, IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, ILocalizationService localizationService, @@ -40,10 +42,12 @@ namespace Umbraco.Web.Routing if (uriUtility == null) throw new ArgumentNullException(nameof(uriUtility)); if (variationContextAccessor == null) throw new ArgumentNullException(nameof(variationContextAccessor)); + var result = new List(); + if (content.Published == false) { - yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); - yield break; + result.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished"))); + return result; } // build a list of URLs, for the back-office @@ -57,47 +61,47 @@ namespace Umbraco.Web.Routing // for URLs for all cultures. // and, not only for those assigned to domains in the branch, because we want // to show what GetUrl() would return, for every culture. - var urls = new HashSet(); var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); - //get all URLs for all cultures - //in a HashSet, so de-duplicates too - foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider)) + // get all URLs for all cultures + // in a HashSet, so de-duplicates too + foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider)) { urls.Add(cultureUrl); } - //return the real URLs first, then the messages - foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + // return the real URLs first, then the messages + foreach (IGrouping urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) { - //in some cases there will be the same URL for multiple cultures: + // in some cases there will be the same URL for multiple cultures: // * The entire branch is invariant // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed - - foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) - yield return dUrl; + foreach (UrlInfo dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + { + result.Add(dUrl); + } } // get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless. // for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them. foreach (var otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) - if (urls.Add(otherUrl)) //avoid duplicates - yield return otherUrl; + { + // avoid duplicates + if (urls.Add(otherUrl)) + { + result.Add(otherUrl); + } + } + + return result; } /// /// Tries to return a for each culture for the content while detecting collisions/errors /// - /// - /// - /// - /// - /// - /// - /// - /// - private static IEnumerable GetContentUrlsByCulture(IContent content, + private static async Task> GetContentUrlsByCultureAsync( + IContent content, IEnumerable cultures, IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, @@ -108,14 +112,17 @@ namespace Umbraco.Web.Routing UriUtility uriUtility, IPublishedUrlProvider publishedUrlProvider) { + var result = new List(); + foreach (var culture in cultures) { // if content is variant, and culture is not published, skip if (content.ContentType.VariesByCulture() && !content.IsCulturePublished(culture)) + { continue; + } // if it's variant and culture is published, or if it's invariant, proceed - string url; try { @@ -131,47 +138,63 @@ namespace Umbraco.Web.Routing { // deal with 'could not get the URL' case "#": - yield return HandleCouldNotGetUrl(content, culture, contentService, textService); + result.Add(HandleCouldNotGetUrl(content, culture, contentService, textService)); break; // deal with exceptions case "#ex": - yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture); + result.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture)); break; // got a URL, deal with collisions, add URL default: - if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility, out var urlInfo)) // detect collisions, etc - yield return urlInfo; + // detect collisions, etc + Attempt hasCollision = await DetectCollisionAsync(content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); + if (hasCollision) + { + result.Add(hasCollision.Result); + } else - yield return UrlInfo.Url(url, culture); + { + result.Add(UrlInfo.Url(url, culture)); + } + break; } } + + return result; } private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its URL is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. - var parent = content; + IContent parent = content; do { parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; } while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); - if (parent == null) // oops, internal error + if (parent == null) + { + // oops, internal error return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture); - - else if (!parent.Published) // totally not published + } + else if (!parent.Published) + { + // totally not published return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture); - - else // culture not published + } + else + { + // culture not published return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture); + } } - private static bool DetectCollision(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility, out UrlInfo urlInfo) + private static async Task> DetectCollisionAsync(IContent content, string url, string culture, IUmbracoContext umbracoContext, IPublishedRouter publishedRouter, ILocalizedTextService textService, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility) { // test for collisions on the 'main' URL var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); @@ -181,15 +204,13 @@ namespace Umbraco.Web.Routing } uri = uriUtility.UriToUmbraco(uri); - IPublishedRequestBuilder pcr = publishedRouter.CreateRequest(uri); - publishedRouter.TryRouteRequest(pcr); - - urlInfo = null; + IPublishedRequestBuilder pcr = await publishedRouter.CreateRequestAsync(uri); + var routeResult = await publishedRouter.TryRouteRequestAsync(pcr); if (pcr.PublishedContent == null) { - urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); - return true; + var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); + return Attempt.Succeed(urlInfo); } // TODO: What is this? @@ -211,12 +232,12 @@ namespace Umbraco.Web.Routing l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); - return true; + var urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); + return Attempt.Succeed(urlInfo); } // no collision - return false; + return Attempt.Fail(); } } } diff --git a/src/Umbraco.Core/Templates/ITemplateRenderer.cs b/src/Umbraco.Core/Templates/ITemplateRenderer.cs index f01edc58ed..7a6248e034 100644 --- a/src/Umbraco.Core/Templates/ITemplateRenderer.cs +++ b/src/Umbraco.Core/Templates/ITemplateRenderer.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.IO; +using System.Threading.Tasks; namespace Umbraco.Web.Templates { @@ -7,6 +8,6 @@ namespace Umbraco.Web.Templates /// public interface ITemplateRenderer { - void Render(int pageId, int? altTemplateId, StringWriter writer); + Task RenderAsync(int pageId, int? altTemplateId, StringWriter writer); } } diff --git a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs index 4618eb2822..53e856ced4 100644 --- a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs @@ -43,7 +43,7 @@ namespace Umbraco.Core.Templates { try { - _templateRenderer.Render(contentId, altTemplateId, sw); + _templateRenderer.RenderAsync(contentId, altTemplateId, sw); } catch (Exception ex) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 3621580dcb..02ccc69f80 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Moq; using NUnit.Framework; using Umbraco.Core.Models; @@ -16,22 +17,22 @@ namespace Umbraco.Tests.PublishedContent public class PublishedRouterTests : BaseWebTest { [Test] - public void ConfigureRequest_Returns_False_Without_HasPublishedContent() + public async Task ConfigureRequest_Returns_False_Without_HasPublishedContent() { var umbracoContext = GetUmbracoContext("/test"); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var result = publishedRouter.ConfigureRequest(request); Assert.IsFalse(result.Success()); } [Test] - public void ConfigureRequest_Returns_False_When_IsRedirect() + public async Task ConfigureRequest_Returns_False_When_IsRedirect() { var umbracoContext = GetUmbracoContext("/test"); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var content = GetPublishedContentMock(); request.SetPublishedContent(content.Object); request.SetCulture(new CultureInfo("en-AU")); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs index 34051c96bd..e510dd8381 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Routing; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -45,11 +46,11 @@ namespace Umbraco.Tests.Routing [TestCase("/only/one/alias", 100111)] [TestCase("/ONLY/one/Alias", 100111)] [TestCase("/alias43", 100121)] - public void Lookup_By_Url_Alias(string urlAsString, int nodeMatch) + public async Task Lookup_By_Url_Alias(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var lookup = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 5a390f667c..4746720329 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; @@ -52,13 +53,13 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/bar/foo", "de-DE", 100111)] // ok [TestCase("http://domain1.com/en/bar/foo", "en-US", -100111)] // no, alias must include "en/" [TestCase("http://domain1.com/en/bar/nil", "en-US", 100111)] // ok, alias includes "en/" - public void Lookup_By_Url_Alias_And_Domain(string inputUrl, string expectedCulture, int expectedNode) + public async Task Lookup_By_Url_Alias_And_Domain(string inputUrl, string expectedCulture, int expectedNode) { //SetDomains1(); var umbracoContext = GetUmbracoContext(inputUrl); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var request = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var request = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain publishedRouter.FindDomain(request); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs index 0ed3161caf..a484597c9c 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByIdTests.cs @@ -6,20 +6,20 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { [TestFixture] public class ContentFinderByIdTests : BaseWebTest { - [TestCase("/1046", 1046)] [TestCase("/1046.aspx", 1046)] - public void Lookup_By_Id(string urlAsString, int nodeMatch) + public async Task Lookup_By_Id(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); var lookup = new ContentFinderByIdPath(Microsoft.Extensions.Options.Options.Create(webRoutingSettings), LoggerFactory.CreateLogger(), Factory.GetRequiredService(), GetUmbracoContextAccessor(umbracoContext)); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs index 24872a128e..05f4ee67b7 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByPageIdQueryTests.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Moq; using NUnit.Framework; using Umbraco.Tests.TestHelpers; @@ -14,12 +15,12 @@ namespace Umbraco.Tests.Routing [TestCase("/default.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? [TestCase("/some/other/page?umbPageId=1046", 1046)] // TODO: Should this match?? [TestCase("/some/other/page.aspx?umbPageId=1046", 1046)] // TODO: Should this match?? - public void Lookup_By_Page_Id(string urlAsString, int nodeMatch) + public async Task Lookup_By_Page_Id(string urlAsString, int nodeMatch) { var umbracoContext = GetUmbracoContext(urlAsString); var httpContext = GetHttpContextFactory(urlAsString).HttpContext; var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var mockRequestAccessor = new Mock(); mockRequestAccessor.Setup(x => x.GetRequestValue("umbPageID")).Returns(httpContext.Request.QueryString["umbPageID"]); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index 82b433a1a0..959836d3ff 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -5,6 +5,7 @@ using Umbraco.Web.Routing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models; using Umbraco.Tests.Testing; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Routing [TestCase("/home/Sub1/blah")] [TestCase("/Home/Sub1/Blah")] //different cases [TestCase("/home/Sub1.aspx/blah")] - public void Match_Document_By_Url_With_Template(string urlAsString) + public async Task Match_Document_By_Url_With_Template(string urlAsString) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; @@ -33,7 +34,7 @@ namespace Umbraco.Tests.Routing var template2 = CreateTemplate("blah"); var umbracoContext = GetUmbracoContext(urlAsString, template1.Id, globalSettings: globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var reqBuilder = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var reqBuilder = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var webRoutingSettings = new WebRoutingSettings(); var lookup = new ContentFinderByUrlAndTemplate( LoggerFactory.CreateLogger(), diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 807cf729ef..2b5364c22a 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -8,6 +8,7 @@ using Umbraco.Web.Routing; using Microsoft.Extensions.Logging; using Umbraco.Web; using Moq; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -26,14 +27,14 @@ namespace Umbraco.Tests.Routing // we've made it return "/test-page" => we have to support that URL back in the lookup... [TestCase("/home", 1046)] [TestCase("/test-page", 1172)] - public void Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) + public async Task Match_Document_By_Url_Hide_Top_Level(string urlString, int expectedId) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = true }; var snapshotService = CreatePublishedSnapshotService(globalSettings); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings, snapshotService: snapshotService); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsTrue(globalSettings.HideTopLevelNodeFromPath); @@ -61,13 +62,13 @@ namespace Umbraco.Tests.Routing [TestCase("/home/Sub1", 1173)] [TestCase("/Home/Sub1", 1173)] //different cases [TestCase("/home/Sub1.aspx", 1173)] - public void Match_Document_By_Url(string urlString, int expectedId) + public async Task Match_Document_By_Url(string urlString, int expectedId) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsFalse(globalSettings.HideTopLevelNodeFromPath); @@ -85,13 +86,13 @@ namespace Umbraco.Tests.Routing [TestCase("/", 1046)] [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] - public void Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId) + public async Task Match_Document_By_Url_With_Special_Characters(string urlString, int expectedId) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -111,13 +112,13 @@ namespace Umbraco.Tests.Routing [TestCase("/", 1046)] [TestCase("/home/sub1/custom-sub-3-with-accént-character", 1179)] [TestCase("/home/sub1/custom-sub-4-with-æøå", 1180)] - public void Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId) + public async Task Match_Document_By_Url_With_Special_Characters_Using_Hostname(string urlString, int expectedId) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/"))); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); @@ -139,13 +140,13 @@ namespace Umbraco.Tests.Routing [TestCase("/æøå/home/sub1", 1173)] [TestCase("/æøå/home/sub1/custom-sub-3-with-accént-character", 1179)] [TestCase("/æøå/home/sub1/custom-sub-4-with-æøå", 1180)] - public void Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId) + public async Task Match_Document_By_Url_With_Special_Characters_In_Hostname(string urlString, int expectedId) { var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå"))); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 84f86f1e09..12115ba3ad 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Routing; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -126,7 +127,7 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/1001-1", 10011)] [TestCase("http://domain1.com/1001-2/1001-2-1", 100121)] - public void Lookup_SingleDomain(string url, int expectedId) + public async Task Lookup_SingleDomain(string url, int expectedId) { SetDomains3(); @@ -134,7 +135,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); @@ -164,7 +165,7 @@ namespace Umbraco.Tests.Routing [TestCase("https://domain1.com/", 1001, "en-US")] [TestCase("https://domain3.com/", 1001, "")] // because domain3 is explicitely set on http - public void Lookup_NestedDomains(string url, int expectedId, string expectedCulture) + public async Task Lookup_NestedDomains(string url, int expectedId, string expectedCulture) { SetDomains4(); @@ -175,7 +176,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(url, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index 4d5111df08..dd5fd6351d 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Models; using Umbraco.Web.Routing; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -261,7 +262,7 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/fr", "fr-FR", 10012)] [TestCase("http://domain1.com/fr/1001-2-1", "fr-FR", 100121)] #endregion - public void DomainAndCulture(string inputUrl, string expectedCulture, int expectedNode) + public async Task DomainAndCulture(string inputUrl, string expectedCulture, int expectedNode) { SetDomains1(); @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); @@ -306,7 +307,7 @@ namespace Umbraco.Tests.Routing [TestCase("/1003/1003-1", "nl-NL", 10031)] // wildcard on 10031 applies [TestCase("/1003/1003-1/1003-1-1", "nl-NL", 100311)] // wildcard on 10031 applies #endregion - public void DomainAndCultureWithWildcards(string inputUrl, string expectedCulture, int expectedNode) + public async Task DomainAndCultureWithWildcards(string inputUrl, string expectedCulture, int expectedNode) { SetDomains2(); @@ -317,7 +318,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); @@ -363,14 +364,14 @@ namespace Umbraco.Tests.Routing [TestCase("http://domain1.com/fr", "fr-FR", 10012)] [TestCase("http://domain1.com/fr/1001-2-1", "fr-FR", 100121)] #endregion - public void DomainGeneric(string inputUrl, string expectedCulture, int expectedNode) + public async Task DomainGeneric(string inputUrl, string expectedCulture, int expectedNode) { SetDomains3(); var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; var umbracoContext = GetUmbracoContext(inputUrl, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext), Factory); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // lookup domain publishedRouter.FindDomain(frequest); diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs index 31bbd06ade..7cb9983155 100644 --- a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -11,6 +11,7 @@ using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Routing; using Microsoft.Extensions.Logging; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -55,7 +56,7 @@ namespace Umbraco.Tests.Routing } [Test] - public void Content_Not_Published() + public async Task Content_Not_Published() { var contentType = MockedContentTypes.CreateBasicContentType(); var content = MockedContent.CreateBasicContent(contentType); @@ -67,13 +68,13 @@ namespace Umbraco.Tests.Routing GetUmbracoContextAccessor(umbContext), Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbContext)) })); - var urls = content.GetContentUrls(publishedRouter, + var urls = (await content.GetContentUrlsAsync(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, VariationContextAccessor, LoggerFactory.CreateLogger(), UriUtility, - PublishedUrlProvider).ToList(); + PublishedUrlProvider)).ToList(); Assert.AreEqual(1, urls.Count); Assert.AreEqual("content/itemNotPublished", urls[0].Text); @@ -81,7 +82,7 @@ namespace Umbraco.Tests.Routing } [Test] - public void Invariant_Root_Content_Published_No_Domains() + public async Task Invariant_Root_Content_Published_No_Domains() { var contentType = MockedContentTypes.CreateBasicContentType(); var content = MockedContent.CreateBasicContent(contentType); @@ -108,13 +109,13 @@ namespace Umbraco.Tests.Routing umbracoContextAccessor, Factory, contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); - var urls = content.GetContentUrls(publishedRouter, + var urls = (await content.GetContentUrlsAsync(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, VariationContextAccessor, LoggerFactory.CreateLogger(), UriUtility, - publishedUrlProvider).ToList(); + publishedUrlProvider)).ToList(); Assert.AreEqual(1, urls.Count); Assert.AreEqual("/home/", urls[0].Text); @@ -123,7 +124,7 @@ namespace Umbraco.Tests.Routing } [Test] - public void Invariant_Child_Content_Published_No_Domains() + public async Task Invariant_Child_Content_Published_No_Domains() { var contentType = MockedContentTypes.CreateBasicContentType(); var parent = MockedContent.CreateBasicContent(contentType); @@ -155,14 +156,14 @@ namespace Umbraco.Tests.Routing umbracoContextAccessor, Factory, contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(LoggerFactory.CreateLogger(), umbracoContextAccessor) })); - var urls = child.GetContentUrls(publishedRouter, + var urls = (await child.GetContentUrlsAsync(publishedRouter, umbContext, GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, VariationContextAccessor, LoggerFactory.CreateLogger(), UriUtility, publishedUrlProvider - ).ToList(); + )).ToList(); Assert.AreEqual(1, urls.Count); Assert.AreEqual("/home/sub1/", urls[0].Text); diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs index 63e8180aff..7edb316c2e 100644 --- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs +++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs @@ -27,6 +27,7 @@ using Umbraco.Web.Runtime; using Umbraco.Web.WebApi; using Current = Umbraco.Web.Composing.Current; using Umbraco.Core.DependencyInjection; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -83,7 +84,7 @@ namespace Umbraco.Tests.Routing /// Will route to the default controller and action since no custom controller is defined for this node route /// [Test] - public void Umbraco_Route_Umbraco_Defined_Controller_Action() + public async Task Umbraco_Route_Umbraco_Defined_Controller_Action() { var url = "~/dummy-page"; var template = CreateTemplate("homePage"); @@ -92,7 +93,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(url, template.Id, routeData); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); frequest.SetPublishedContent(umbracoContext.Content.GetById(1174)); frequest.SetTemplate(template); @@ -117,7 +118,7 @@ namespace Umbraco.Tests.Routing [TestCase("homePage")] [TestCase("site1/template2")] [TestCase("site1\\template2")] - public void Umbraco_Route_User_Defined_Controller_Action(string templateName) + public async Task Umbraco_Route_User_Defined_Controller_Action(string templateName) { // NOTE - here we create templates with crazy aliases... assuming that these // could exist in the database... yet creating templates should sanitize @@ -130,7 +131,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext("~/dummy-page", template.Id, routeData, true); var httpContext = GetHttpContextFactory(url, routeData).HttpContext; var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); frequest.SetPublishedContent(umbracoContext.Content.GetById(1172)); frequest.SetTemplate(template); diff --git a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs index cdc62b1a35..73b6f17d19 100644 --- a/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs +++ b/src/Umbraco.Tests/Routing/UrlsWithNestedDomains.cs @@ -13,6 +13,7 @@ using Umbraco.Tests.LegacyXmlPublishedCache; using Umbraco.Web; using Umbraco.Web.Routing; using Umbraco.Core.DependencyInjection; +using System.Threading.Tasks; namespace Umbraco.Tests.Routing { @@ -32,7 +33,7 @@ namespace Umbraco.Tests.Routing } [Test] - public void DoNotPolluteCache() + public async Task DoNotPolluteCache() { var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = true }; var globalSettings = new GlobalSettings { HideTopLevelNodeFromPath = false }; @@ -60,7 +61,7 @@ namespace Umbraco.Tests.Routing // route a rogue URL var publishedRouter = CreatePublishedRouter(umbracoContextAccessor); - var frequest = publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); publishedRouter.FindDomain(frequest); Assert.IsTrue(frequest.HasDomain()); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index e7ccc01acb..719c785fd7 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -8,6 +8,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -114,7 +115,8 @@ namespace Umbraco.Tests.TestHelpers container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), container?.GetRequiredService() ?? Current.Factory.GetRequiredService(), - umbracoContextAccessor + umbracoContextAccessor, + Mock.Of() ); } } diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 3e5e799dba..09748e9621 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -126,7 +127,7 @@ namespace Umbraco.Tests.Web.Mvc } [Test] - public void Mock_Current_Page() + public async Task Mock_Current_Page() { var globalSettings = TestObjects.GetGlobalSettings(); var httpContextAccessor = TestHelper.GetHttpContextAccessor(); @@ -152,7 +153,7 @@ namespace Umbraco.Tests.Web.Mvc var webRoutingSettings = new WebRoutingSettings(); var publishedRouter = BaseWebTest.CreatePublishedRouter(umbracoContextAccessor, webRoutingSettings); - var frequest = publishedRouter.CreateRequest(new Uri("http://localhost/test")); + var frequest = await publishedRouter.CreateRequestAsync(new Uri("http://localhost/test")); frequest.SetPublishedContent(content); var routeDefinition = new RouteDefinition diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 3e81bde207..9d610b81f0 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -199,13 +199,21 @@ namespace Umbraco.Web.BackOffice.Mapping private UrlInfo[] GetUrls(IContent source) { if (source.ContentType.IsElement) + { return Array.Empty(); + } var umbracoContext = _umbracoContextAccessor.UmbracoContext; - var urls = umbracoContext == null - ? new[] { UrlInfo.Message("Cannot generate URLs without a current Umbraco Context") } - : source.GetContentUrls(_publishedRouter, umbracoContext, _localizationService, _localizedTextService, _contentService, _variationContextAccessor, _loggerFactory.CreateLogger(), _uriUtility, _publishedUrlProvider).ToArray(); + if (umbracoContext == null) + { + return new[] { UrlInfo.Message("Cannot generate URLs without a current Umbraco Context") }; + } + + // NOTE: unfortunately we're not async, we'll use .Result and hope this won't cause a deadlock anywhere for now + var urls = source.GetContentUrlsAsync(_publishedRouter, umbracoContext, _localizationService, _localizedTextService, _contentService, _variationContextAccessor, _loggerFactory.CreateLogger(), _uriUtility, _publishedUrlProvider) + .Result + .ToArray(); return urls; } diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index 654eb8f8d7..d9b7bf95a4 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; @@ -14,11 +15,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Extensions; -using Umbraco.Web.Models; using Umbraco.Web.Routing; using Umbraco.Web.Templates; @@ -41,7 +40,8 @@ namespace Umbraco.Web.Common.Templates private readonly IHttpContextAccessor _httpContextAccessor; private readonly ICompositeViewEngine _viewEngine; - public TemplateRenderer(IUmbracoContextAccessor umbracoContextAccessor, + public TemplateRenderer( + IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, IFileService fileService, ILocalizationService textService, @@ -60,7 +60,7 @@ namespace Umbraco.Web.Common.Templates _viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine)); } - public void Render(int pageId, int? altTemplateId, StringWriter writer) + public async Task RenderAsync(int pageId, int? altTemplateId, StringWriter writer) { if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -69,7 +69,7 @@ namespace Umbraco.Web.Common.Templates // instantiate a request and process // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL, though this isn't going to matter // terribly much for this implementation since we are just creating a doc content request to modify it's properties manually. - var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + var requestBuilder = await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var doc = umbracoContext.Content.GetById(pageId); diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index dc72777fa8..4916faeece 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -102,7 +102,7 @@ namespace Umbraco.Web.Website.Routing return await Task.FromResult(values); } - RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); + IPublishedRequest publishedRequest = await RouteRequestAsync(_umbracoContextAccessor.UmbracoContext); UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest); @@ -228,23 +228,19 @@ namespace Umbraco.Web.Website.Routing return descriptors; } - private bool RouteRequest(IUmbracoContext umbracoContext, out IPublishedRequest publishedRequest) + private async Task RouteRequestAsync(IUmbracoContext umbracoContext) { - // TODO: I suspect one day this will be async - // ok, process // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current url - IPublishedRequestBuilder requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); + IPublishedRequestBuilder requestBuilder = await _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); // TODO: This is ugly with the re-assignment to umbraco context but at least its now // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? - publishedRequest = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); - - return publishedRequest.Success(); + return umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder); // // HandleHttpResponseStatus returns a value indicating that the request should // // not be processed any further, eg because it has been redirect. then, exit. diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 308e7dd48e..aef8b4bc3e 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -135,8 +135,8 @@ namespace Umbraco.Web // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL - var requestBuilder = _publishedRouter.CreateRequest(umbracoContext.CleanedUmbracoUrl); - var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequest(requestBuilder); + var requestBuilder = _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl).Result; + var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder).Result; // NOTE: This has been ported to netcore // HandleHttpResponseStatus returns a value indicating that the request should From 53bc92608ac647b468b0108bcb85443a067effd0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 00:33:47 +1100 Subject: [PATCH 37/88] rename property --- src/Umbraco.Core/Routing/IPublishedRequest.cs | 5 ++++- src/Umbraco.Core/Routing/PublishedRequest.cs | 4 ++-- .../Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs | 2 +- .../Controllers/PublishedRequestFilterAttribute.cs | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 8bfb49d9ac..3d6ab51276 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -72,6 +72,9 @@ namespace Umbraco.Web.Routing /// IReadOnlyDictionary Headers { get; } - bool CacheabilityNoCache { get; } + /// + /// Gets a value indicating if the no-cache value should be added to the Cache-Control header + /// + bool SetNoCacheHeader { get; } } } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index bc8450177e..7f1f8b63b5 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Routing ResponseStatusCode = responseStatusCode; CacheExtensions = cacheExtensions; Headers = headers; - CacheabilityNoCache = cacheabilityNoCache; + SetNoCacheHeader = cacheabilityNoCache; } /// @@ -64,6 +64,6 @@ namespace Umbraco.Web.Routing public IReadOnlyDictionary Headers { get; } /// - public bool CacheabilityNoCache { get; } + public bool SetNoCacheHeader { get; } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs index 80e39f82ba..cf4ca44f10 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Web.Routing IPublishedRequest request = sut.Build(); - Assert.AreEqual(true, request.CacheabilityNoCache); + Assert.AreEqual(true, request.SetNoCacheHeader); Assert.AreEqual(cacheExt, request.CacheExtensions); Assert.AreEqual(usCulture, request.Culture); Assert.AreEqual(domain, request.Domain); diff --git a/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs index 61e21df4cb..a33dba9ca6 100644 --- a/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Controllers/PublishedRequestFilterAttribute.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.Common.Controllers { var cacheControlHeaders = new List(); - if (pcr.CacheabilityNoCache) + if (pcr.SetNoCacheHeader) { cacheControlHeaders.Add("no-cache"); } From bc6768101ea6cfecdc45d2d8ea604b6f3b2a8590 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 02:10:13 +1100 Subject: [PATCH 38/88] Splits up UmbracoRouteValueTransformer and trying to figure out this hijacked route conundrum with no template :( --- src/Umbraco.Core/Routing/PublishedRouter.cs | 23 ++- .../PublishedContent/PublishedRouterTests.cs | 4 +- .../Controllers/RenderController.cs | 43 ++++- .../UmbracoBuilderExtensions.cs | 2 + .../Routing/HijackedRouteEvaluator.cs | 90 +++++++++++ .../Routing/HijackedRouteResult.cs | 50 ++++++ .../Routing/IUmbracoRouteValuesFactory.cs | 18 +++ .../Routing/UmbracoRouteValueTransformer.cs | 153 +----------------- .../Routing/UmbracoRouteValuesFactory.cs | 135 ++++++++++++++++ 9 files changed, 351 insertions(+), 167 deletions(-) create mode 100644 src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs create mode 100644 src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs create mode 100644 src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs create mode 100644 src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index ae77abe4a7..a0f7fe6344 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -177,12 +177,11 @@ namespace Umbraco.Web.Routing // to find out the appropriate template // complete the PCR and assign the remaining values - return ConfigureRequest(request); + return BuildRequest(request); } /// - /// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method - /// finalizes the PCR with the values assigned. + /// This method finalizes/builds the PCR with the values assigned. /// /// /// Returns false if the request was not successfully configured @@ -191,15 +190,15 @@ namespace Umbraco.Web.Routing /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values /// but need to finalize it themselves. /// - internal IPublishedRequest ConfigureRequest(IPublishedRequestBuilder frequest) + internal IPublishedRequest BuildRequest(IPublishedRequestBuilder frequest) { + IPublishedRequest result = frequest.Build(); + if (!frequest.HasPublishedContent()) { - return frequest.Build(); + return result; } - IPublishedRequest result = frequest.Build(); - // set the culture -- again, 'cos it might have changed in the event handler SetVariationContext(result.Culture); @@ -388,7 +387,7 @@ namespace Umbraco.Web.Routing // so internal redirect, 404, etc has precedence over redirect // handle not-found, redirects, access... - HandlePublishedContent(request, foundContentByFinders); + HandlePublishedContent(request); // find a template FindTemplate(request, foundContentByFinders); @@ -425,12 +424,11 @@ namespace Umbraco.Web.Routing /// Handles the published content (if any). /// /// The request builder. - /// If the content was found by the finders, before anything such as 404, redirect... took place. /// /// Handles "not found", internal redirects, access validation... /// things that must be handled in one place because they can create loops /// - private void HandlePublishedContent(IPublishedRequestBuilder request, bool contentFoundByFinders) + private void HandlePublishedContent(IPublishedRequestBuilder request) { // because these might loop, we have to have some sort of infinite loop detection int i = 0, j = 0; @@ -457,7 +455,7 @@ namespace Umbraco.Web.Routing // follow internal redirects as long as it's not running out of control ie infinite loop of some sort j = 0; - while (FollowInternalRedirects(request, contentFoundByFinders) && j++ < maxLoop) + while (FollowInternalRedirects(request) && j++ < maxLoop) { } // we're running out of control @@ -490,13 +488,12 @@ namespace Umbraco.Web.Routing /// Follows internal redirections through the umbracoInternalRedirectId document property. /// /// The request builder. - /// If the content was found by the finders, before anything such as 404, redirect... took place. /// A value indicating whether redirection took place and led to a new published document. /// /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. /// As per legacy, if the redirect does not work, we just ignore it. /// - private bool FollowInternalRedirects(IPublishedRequestBuilder request, bool contentFoundByFinders) + private bool FollowInternalRedirects(IPublishedRequestBuilder request) { if (request.PublishedContent == null) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 02ccc69f80..52b76a0021 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.PublishedContent var umbracoContext = GetUmbracoContext("/test"); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - var result = publishedRouter.ConfigureRequest(request); + var result = publishedRouter.BuildRequest(request); Assert.IsFalse(result.Success()); } @@ -37,7 +37,7 @@ namespace Umbraco.Tests.PublishedContent request.SetPublishedContent(content.Object); request.SetCulture(new CultureInfo("en-AU")); request.SetRedirect("/hello"); - var result = publishedRouter.ConfigureRequest(request); + var result = publishedRouter.BuildRequest(request); Assert.IsFalse(result.Success()); } diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 7f6d61de98..e0df571988 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -118,6 +118,15 @@ namespace Umbraco.Web.Common.Controllers /// public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); + /// + /// The action that renders when there is no template assigned, templates are not disabled and there is no hijacked route + /// + /// + /// This action renders even if there might be content found, but if there is no template assigned, templates are not disabled and there is no hijacked route + /// then we render the blank screen. + /// + public IActionResult NoTemplate() => GetNoTemplateResult(UmbracoRouteValues.PublishedRequest); + /// /// Before the controller executes we will handle redirects and not founds /// @@ -126,10 +135,10 @@ namespace Umbraco.Web.Common.Controllers IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest; _logger.LogDebug( - "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", - pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", - pcr.Is404() ? "true" : "false", - pcr.ResponseStatusCode); + "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", + pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", + pcr.Is404() ? "true" : "false", + pcr.ResponseStatusCode); UmbracoRouteResult routeStatus = pcr.GetRouteResult(); switch (routeStatus) @@ -142,9 +151,8 @@ namespace Umbraco.Web.Common.Controllers : Redirect(pcr.RedirectUrl); break; case UmbracoRouteResult.NotFound: - // set the redirect result and do not call next to short circuit - context.Result = new PublishedContentNotFoundResult(UmbracoContext); + context.Result = GetNoTemplateResult(pcr); break; case UmbracoRouteResult.Success: default: @@ -153,5 +161,28 @@ namespace Umbraco.Web.Common.Controllers break; } } + + private PublishedContentNotFoundResult GetNoTemplateResult(IPublishedRequest pcr) + { + // missing template, so we're in a 404 here + // so the content, if any, is a custom 404 page of some sort + if (!pcr.HasPublishedContent()) + { + // means the builder could not find a proper document to handle 404 + return new PublishedContentNotFoundResult(UmbracoContext); + } + else if (!pcr.HasTemplate()) + { + // means the engine could find a proper document, but the document has no template + // at that point there isn't much we can do + return new PublishedContentNotFoundResult( + UmbracoContext, + "In addition, no template exists to render the custom 404."); + } + else + { + return new PublishedContentNotFoundResult(UmbracoContext); + } + } } } diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index a94ee5e678..c4aae8839f 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -37,6 +37,8 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddDataProtection(); builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.AddDistributedCache(); diff --git a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs new file mode 100644 index 0000000000..d617dbbab4 --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Web.Common.Controllers; + +namespace Umbraco.Web.Website.Routing +{ + /// + /// Determines if a custom controller can hijack the current route + /// + public class HijackedRouteEvaluator + { + private readonly ILogger _logger; + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private const string DefaultActionName = nameof(RenderController.Index); + + /// + /// Initializes a new instance of the class. + /// + public HijackedRouteEvaluator( + ILogger logger, + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) + { + _logger = logger; + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + } + + public HijackedRouteResult Evaluate(string controller, string action) + { + IReadOnlyList candidates = FindControllerCandidates(controller, action, DefaultActionName); + + // check if there's a custom controller assigned, base on the document type alias. + var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(controller)).ToList(); + + // check if that custom controller exists + if (customControllerCandidates.Count > 0) + { + ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0]; + + // ensure the controller is of type IRenderController and ControllerBase + if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo) + && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) + { + // now check if the custom action matches + var customActionExists = action != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(action)); + + // it's a hijacked route with a custom controller, so return the the values + return new HijackedRouteResult( + true, + controllerDescriptor.ControllerName, + controllerDescriptor.ControllerTypeInfo, + customActionExists ? action : DefaultActionName); + } + else + { + _logger.LogWarning( + "The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.", + controller, + controllerDescriptor.ControllerTypeInfo.FullName, + typeof(IRenderController).FullName, + typeof(ControllerBase).FullName); + + // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults + // that have already been set above. + } + } + + return HijackedRouteResult.Failed(); + } + + /// + /// Return a list of controller candidates that match the custom controller and action names + /// + private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName) + { + var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items + .Cast() + .Where(x => x.ControllerName.InvariantEquals(customControllerName) + && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName)))) + .ToList(); + + return descriptors; + } + } +} diff --git a/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs new file mode 100644 index 0000000000..f88bdfa2fd --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/HijackedRouteResult.cs @@ -0,0 +1,50 @@ +using System; + +namespace Umbraco.Web.Website.Routing +{ + /// + /// The result from evaluating if a route can be hijacked + /// + public class HijackedRouteResult + { + /// + /// Returns a failed result + /// + public static HijackedRouteResult Failed() => new HijackedRouteResult(false, null, null, null); + + /// + /// Initializes a new instance of the class. + /// + public HijackedRouteResult( + bool success, + string controllerName, + Type controllerType, + string actionName) + { + Success = success; + ControllerName = controllerName; + ControllerType = controllerType; + ActionName = actionName; + } + + /// + /// Gets a value indicating if the route could be hijacked + /// + public bool Success { get; } + + /// + /// Gets the Controller name + /// + public string ControllerName { get; } + + /// + /// Gets the Controller type + /// + public Type ControllerType { get; } + + /// + /// Gets the Acton name + /// + public string ActionName { get; } + } +} diff --git a/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs new file mode 100644 index 0000000000..7af41d865b --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/IUmbracoRouteValuesFactory.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Website.Routing +{ + /// + /// Used to create + /// + public interface IUmbracoRouteValuesFactory + { + /// + /// Creates + /// + UmbracoRouteValues Create(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request); + } +} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 4916faeece..9cb920f434 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -1,22 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.Strings; using Umbraco.Extensions; -using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Routing; using Umbraco.Web.Routing; using Umbraco.Web.Website.Controllers; @@ -37,13 +28,11 @@ namespace Umbraco.Web.Website.Routing { private readonly ILogger _logger; private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IUmbracoRenderingDefaults _renderingDefaults; - private readonly IShortStringHelper _shortStringHelper; - private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IPublishedRouter _publishedRouter; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtime; + private readonly IUmbracoRouteValuesFactory _routeValuesFactory; /// /// Initializes a new instance of the class. @@ -51,23 +40,19 @@ namespace Umbraco.Web.Website.Routing public UmbracoRouteValueTransformer( ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, - IUmbracoRenderingDefaults renderingDefaults, - IShortStringHelper shortStringHelper, - IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IPublishedRouter publishedRouter, IOptions globalSettings, IHostingEnvironment hostingEnvironment, - IRuntimeState runtime) + IRuntimeState runtime, + IUmbracoRouteValuesFactory routeValuesFactory) { _logger = logger; _umbracoContextAccessor = umbracoContextAccessor; - _renderingDefaults = renderingDefaults; - _shortStringHelper = shortStringHelper; - _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _publishedRouter = publishedRouter; _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; _runtime = runtime; + _routeValuesFactory = routeValuesFactory; } /// @@ -104,7 +89,7 @@ namespace Umbraco.Web.Website.Routing IPublishedRequest publishedRequest = await RouteRequestAsync(_umbracoContextAccessor.UmbracoContext); - UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest); + UmbracoRouteValues routeDef = _routeValuesFactory.Create(httpContext, values, publishedRequest); values["controller"] = routeDef.ControllerName; if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) @@ -115,119 +100,6 @@ namespace Umbraco.Web.Website.Routing return await Task.FromResult(values); } - /// - /// Returns a object based on the current content request - /// - private UmbracoRouteValues GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) - { - if (httpContext is null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (values is null) - { - throw new ArgumentNullException(nameof(values)); - } - - if (request is null) - { - throw new ArgumentNullException(nameof(request)); - } - - Type defaultControllerType = _renderingDefaults.DefaultControllerType; - var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - - string customActionName = null; - - // check that a template is defined), if it doesn't and there is a hijacked route it will just route - // to the index Action - if (request.HasTemplate()) - { - // the template Alias should always be already saved with a safe name. - // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed - // with the action name attribute. - customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper); - } - - // creates the default route definition which maps to the 'UmbracoController' controller - var def = new UmbracoRouteValues( - request, - defaultControllerName, - defaultControllerType, - templateName: customActionName); - - var customControllerName = request.PublishedContent?.ContentType.Alias; - if (customControllerName != null) - { - def = DetermineHijackedRoute(def, customControllerName, customActionName, request); - } - - // store the route definition - values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); - - return def; - } - - private UmbracoRouteValues DetermineHijackedRoute(UmbracoRouteValues routeValues, string customControllerName, string customActionName, IPublishedRequest request) - { - IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, routeValues.ActionName); - - // check if there's a custom controller assigned, base on the document type alias. - var customControllerCandidates = candidates.Where(x => x.ControllerName.InvariantEquals(customControllerName)).ToList(); - - // check if that custom controller exists - if (customControllerCandidates.Count > 0) - { - ControllerActionDescriptor controllerDescriptor = customControllerCandidates[0]; - - // ensure the controller is of type IRenderController and ControllerBase - if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo) - && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) - { - // now check if the custom action matches - var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName)); - - // it's a hijacked route with a custom controller, so return the the values - return new UmbracoRouteValues( - request, - controllerDescriptor.ControllerName, - controllerDescriptor.ControllerTypeInfo, - customActionExists ? customActionName : routeValues.ActionName, - customActionName, - true); // Hijacked = true - } - else - { - _logger.LogWarning( - "The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.", - request.PublishedContent.ContentType.Alias, - controllerDescriptor.ControllerTypeInfo.FullName, - typeof(IRenderController).FullName, - typeof(ControllerBase).FullName); - - // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults - // that have already been set above. - } - } - - return routeValues; - } - - /// - /// Return a list of controller candidates that match the custom controller and action names - /// - private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName) - { - var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items - .Cast() - .Where(x => x.ControllerName.InvariantEquals(customControllerName) - && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName)))) - .ToList(); - - return descriptors; - } - private async Task RouteRequestAsync(IUmbracoContext umbracoContext) { // ok, process @@ -240,20 +112,9 @@ namespace Umbraco.Web.Website.Routing // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? - return umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder); + IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder); - // // HandleHttpResponseStatus returns a value indicating that the request should - // // not be processed any further, eg because it has been redirect. then, exit. - // if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) - // return; - // if (!request.HasPublishedContent == false) - // { - // // httpContext.RemapHandler(new PublishedContentNotFoundHandler()); - // } - // else - // { - // // RewriteToUmbracoHandler(httpContext, request); - // } + return publishedRequest; } } } diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs new file mode 100644 index 0000000000..b101cf155a --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -0,0 +1,135 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Strings; +using Umbraco.Extensions; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Features; +using Umbraco.Web.Routing; +using Umbraco.Web.Website.Controllers; + +namespace Umbraco.Web.Website.Routing +{ + + /// + /// Used to create + /// + public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory + { + private readonly ILogger _logger; + private readonly IUmbracoRenderingDefaults _renderingDefaults; + private readonly IShortStringHelper _shortStringHelper; + private readonly UmbracoFeatures _umbracoFeatures; + private readonly HijackedRouteEvaluator _hijackedRouteEvaluator; + private readonly Lazy _defaultControllerName; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRouteValuesFactory( + ILogger logger, + IUmbracoRenderingDefaults renderingDefaults, + IShortStringHelper shortStringHelper, + UmbracoFeatures umbracoFeatures, + HijackedRouteEvaluator hijackedRouteEvaluator) + { + _logger = logger; + _renderingDefaults = renderingDefaults; + _shortStringHelper = shortStringHelper; + _umbracoFeatures = umbracoFeatures; + _hijackedRouteEvaluator = hijackedRouteEvaluator; + _defaultControllerName = new Lazy(() => ControllerExtensions.GetControllerName(_renderingDefaults.DefaultControllerType)); + } + + /// + /// Gets the default controller name + /// + protected string DefaultControllerName => _defaultControllerName.Value; + + /// + public UmbracoRouteValues Create(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) + { + if (httpContext is null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (values is null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + + Type defaultControllerType = _renderingDefaults.DefaultControllerType; + + string customActionName = null; + + // check that a template is defined), if it doesn't and there is a hijacked route it will just route + // to the index Action + if (request.HasTemplate()) + { + // the template Alias should always be already saved with a safe name. + // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed + // with the action name attribute. + customActionName = request.GetTemplateAlias()?.Split('.')[0].ToSafeAlias(_shortStringHelper); + } + + // creates the default route definition which maps to the 'UmbracoController' controller + var def = new UmbracoRouteValues( + request, + DefaultControllerName, + defaultControllerType, + templateName: customActionName); + + var customControllerName = request.PublishedContent?.ContentType.Alias; + if (customControllerName != null) + { + HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, customActionName); + if (hijackedResult.Success) + { + def = new UmbracoRouteValues( + request, + hijackedResult.ControllerName, + hijackedResult.ControllerType, + hijackedResult.ActionName, + customActionName, + true); + } + } + + // Here we need to check if there is no hijacked route and no template assigned, + // if this is the case we want to return a blank page. + // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, + // for example for json rendering in headless. + if (!request.HasTemplate() + && !_umbracoFeatures.Disabled.DisableTemplates + && !def.HasHijackedRoute) + { + // TODO: this is basically a 404, in v8 this will re-run the pipeline + // in order to see if a last chance finder finds some content + // At this point we're already done building the request and we have an immutable + // request. in v8 it was re-mutated :/ I really don't want to do that + + // In this case we'll render the NoTemplate action + def = new UmbracoRouteValues( + request, + DefaultControllerName, + defaultControllerType, + nameof(RenderController.NoTemplate)); + } + + // store the route definition + values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); + + return def; + } + } +} From b017ed0b1b1b5fc2ad258305b7884fa37390ce60 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 02:36:55 +1100 Subject: [PATCH 39/88] Gets the UpdateRequestToNotFound thing working --- src/Umbraco.Core/Routing/IPublishedRouter.cs | 25 ++++--- src/Umbraco.Core/Routing/PublishedRouter.cs | 53 ++++++------- .../Controllers/RenderController.cs | 9 --- .../Routing/HijackedRouteEvaluator.cs | 3 + .../Routing/UmbracoRouteValuesFactory.cs | 74 +++++++++++++------ 5 files changed, 93 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index 2a6f6b66d9..6d974fcc44 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -33,16 +33,19 @@ namespace Umbraco.Web.Routing /// A value indicating whether the request can be routed to a document. Task TryRouteRequestAsync(IPublishedRequestBuilder request); - // TODO: This shouldn't be required and should be handled differently during route building - ///// - ///// Updates the request to "not found". - ///// - ///// The request. - ///// - ///// This method is invoked when the pipeline decides it cannot render - ///// the request, for whatever reason, and wants to force it to be re-routed - ///// and rendered as if no document were found (404). - ///// - //void UpdateRequestToNotFound(IPublishedRequest request); + /// + /// Updates the request to "not found". + /// + /// The request. + /// + /// A new based on values from the original + /// This method is invoked when the pipeline decides it cannot render + /// the request, for whatever reason, and wants to force it to be re-routed + /// and rendered as if no document were found (404). + /// This occurs if there is no template found and route hijacking was not matched. + /// In that case it's the same as if there was no content which means even if there was + /// content matched we want to run the request through the last chance finders. + /// + IPublishedRequestBuilder UpdateRequestToNotFound(IPublishedRequest request); } } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index a0f7fe6344..70049660ca 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -205,40 +205,33 @@ namespace Umbraco.Web.Routing return result; } - // TODO: This shouldn't be required and should be handled differently during route building - ///// - //public void UpdateRequestToNotFound(IPublishedRequest request) - //{ - // // clear content - // var content = request.PublishedContent; - // request.PublishedContent = null; + /// + public IPublishedRequestBuilder UpdateRequestToNotFound(IPublishedRequest request) + { + var builder = new PublishedRequestBuilder(request.Uri, _fileService); - // HandlePublishedContent(request); // will go 404 - // FindTemplate(request); + // clear content + IPublishedContent content = request.PublishedContent; + builder.SetPublishedContent(null); - // // if request has been flagged to redirect then return - // // whoever called us is in charge of redirecting - // if (request.IsRedirect) - // { - // return; - // } + HandlePublishedContent(builder); // will go 404 + FindTemplate(builder, false); - // if (request.HasPublishedContent == false) - // { - // // means the engine could not find a proper document to handle 404 - // // restore the saved content so we know it exists - // request.PublishedContent = content; - // return; - // } + // if request has been flagged to redirect then return + if (request.IsRedirect()) + { + return builder; + } - // if (request.HasTemplate == false) - // { - // // means we may have a document, but we have no template - // // at that point there isn't much we can do and there is no point returning - // // to Mvc since Mvc can't do much either - // return; - // } - //} + if (!builder.HasPublishedContent()) + { + // means the engine could not find a proper document to handle 404 + // restore the saved content so we know it exists + builder.SetPublishedContent(content); + } + + return builder; + } /// /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index e0df571988..1556333402 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -118,15 +118,6 @@ namespace Umbraco.Web.Common.Controllers /// public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); - /// - /// The action that renders when there is no template assigned, templates are not disabled and there is no hijacked route - /// - /// - /// This action renders even if there might be content found, but if there is no template assigned, templates are not disabled and there is no hijacked route - /// then we render the blank screen. - /// - public IActionResult NoTemplate() => GetNoTemplateResult(UmbracoRouteValues.PublishedRequest); - /// /// Before the controller executes we will handle redirects and not founds /// diff --git a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs index d617dbbab4..a72268d298 100644 --- a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs +++ b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs @@ -30,6 +30,9 @@ namespace Umbraco.Web.Website.Routing _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; } + /// + /// Determines if a custom controller can hijack the current route + /// public HijackedRouteResult Evaluate(string controller, string action) { IReadOnlyList candidates = FindControllerCandidates(controller, action, DefaultActionName); diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index b101cf155a..c4f89bf8aa 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -1,12 +1,9 @@ using System; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Strings; using Umbraco.Extensions; -using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Routing; using Umbraco.Web.Features; using Umbraco.Web.Routing; @@ -20,28 +17,28 @@ namespace Umbraco.Web.Website.Routing /// public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory { - private readonly ILogger _logger; private readonly IUmbracoRenderingDefaults _renderingDefaults; private readonly IShortStringHelper _shortStringHelper; private readonly UmbracoFeatures _umbracoFeatures; private readonly HijackedRouteEvaluator _hijackedRouteEvaluator; + private readonly IPublishedRouter _publishedRouter; private readonly Lazy _defaultControllerName; /// /// Initializes a new instance of the class. /// public UmbracoRouteValuesFactory( - ILogger logger, IUmbracoRenderingDefaults renderingDefaults, IShortStringHelper shortStringHelper, UmbracoFeatures umbracoFeatures, - HijackedRouteEvaluator hijackedRouteEvaluator) + HijackedRouteEvaluator hijackedRouteEvaluator, + IPublishedRouter publishedRouter) { - _logger = logger; _renderingDefaults = renderingDefaults; _shortStringHelper = shortStringHelper; _umbracoFeatures = umbracoFeatures; _hijackedRouteEvaluator = hijackedRouteEvaluator; + _publishedRouter = publishedRouter; _defaultControllerName = new Lazy(() => ControllerExtensions.GetControllerName(_renderingDefaults.DefaultControllerType)); } @@ -89,22 +86,51 @@ namespace Umbraco.Web.Website.Routing defaultControllerType, templateName: customActionName); + def = CheckHijackedRoute(def); + + def = CheckNoTemplate(def); + + // store the route definition + values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); + + return def; + } + + /// + /// Check if the route is hijacked and return new route values + /// + /// + /// + private UmbracoRouteValues CheckHijackedRoute(UmbracoRouteValues def) + { + IPublishedRequest request = def.PublishedRequest; + var customControllerName = request.PublishedContent?.ContentType.Alias; if (customControllerName != null) { - HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, customActionName); + HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, def.TemplateName); if (hijackedResult.Success) { - def = new UmbracoRouteValues( + return new UmbracoRouteValues( request, hijackedResult.ControllerName, hijackedResult.ControllerType, hijackedResult.ActionName, - customActionName, + def.TemplateName, true); } } + return def; + } + + /// + /// Special check for when no template or hijacked route is done which needs to re-run through the routing pipeline again for last chance finders + /// + private UmbracoRouteValues CheckNoTemplate(UmbracoRouteValues def) + { + IPublishedRequest request = def.PublishedRequest; + // Here we need to check if there is no hijacked route and no template assigned, // if this is the case we want to return a blank page. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, @@ -113,21 +139,27 @@ namespace Umbraco.Web.Website.Routing && !_umbracoFeatures.Disabled.DisableTemplates && !def.HasHijackedRoute) { - // TODO: this is basically a 404, in v8 this will re-run the pipeline - // in order to see if a last chance finder finds some content - // At this point we're already done building the request and we have an immutable - // request. in v8 it was re-mutated :/ I really don't want to do that + Core.Models.PublishedContent.IPublishedContent content = request.PublishedContent; + + // This is basically a 404 even if there is content found. + // We then need to re-run this through the pipeline for the last + // chance finders to work. + IPublishedRequestBuilder builder = _publishedRouter.UpdateRequestToNotFound(request); + request = builder.Build(); - // In this case we'll render the NoTemplate action def = new UmbracoRouteValues( request, - DefaultControllerName, - defaultControllerType, - nameof(RenderController.NoTemplate)); - } + def.ControllerName, + def.ControllerType, + def.ActionName, + def.TemplateName); - // store the route definition - values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); + // if the content has changed, we must then again check for hijacked routes + if (content != request.PublishedContent) + { + def = CheckHijackedRoute(def); + } + } return def; } From b4922d26854b6945fa8a701a977266af0e1cf3fa Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 10:42:57 +1100 Subject: [PATCH 40/88] re-enables the weird IgnorePublishedContentCollisions, simplifies IPublishedRouter interface (more flexible with request options), --- src/Umbraco.Core/Routing/IPublishedRequest.cs | 25 +++++++++++----- .../Routing/IPublishedRequestBuilder.cs | 14 +++++++++ src/Umbraco.Core/Routing/IPublishedRouter.cs | 18 +++--------- src/Umbraco.Core/Routing/PublishedRequest.cs | 5 ++-- .../Routing/PublishedRequestBuilder.cs | 7 ++++- src/Umbraco.Core/Routing/PublishedRouter.cs | 21 +++++++++----- src/Umbraco.Core/Routing/RouteDirection.cs | 18 ++++++++++++ .../Routing/RouteRequestOptions.cs | 29 +++++++++++++++++++ .../Routing/UrlProviderExtensions.cs | 15 +++++----- .../Routing/UmbracoRouteValueTransformer.cs | 3 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 3 +- 11 files changed, 116 insertions(+), 42 deletions(-) create mode 100644 src/Umbraco.Core/Routing/RouteDirection.cs create mode 100644 src/Umbraco.Core/Routing/RouteRequestOptions.cs diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 3d6ab51276..57b38dbff8 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -6,7 +6,9 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Routing { - + /// + /// The result of Umbraco routing built with the + /// public interface IPublishedRequest { /// @@ -15,11 +17,6 @@ namespace Umbraco.Web.Routing /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. Uri Uri { get; } - /// - /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. - /// - bool IgnorePublishedContentCollisions { get; } - /// /// Gets a value indicating the requested content. /// @@ -73,8 +70,22 @@ namespace Umbraco.Web.Routing IReadOnlyDictionary Headers { get; } /// - /// Gets a value indicating if the no-cache value should be added to the Cache-Control header + /// Gets a value indicating whether the no-cache value should be added to the Cache-Control header /// bool SetNoCacheHeader { get; } + + /// + /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// + /// + /// This is an uncommon API used for edge cases with complex routing and would be used + /// by developers to configure the request to disable collision checks in . + /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since + /// collission checking only occurs in the back office which is launched by + /// for which events do not execute. + /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345 + /// but it's still unclear how this was used. + /// + bool IgnorePublishedContentCollisions { get; } } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index 706315795e..f8e5837838 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -138,5 +138,19 @@ namespace Umbraco.Web.Routing /// Sets a dictionary of Headers to append to the Response object. /// IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); + + /// + /// Can be called to configure the result to ignore URL collisions + /// + /// + /// This is an uncommon API used for edge cases with complex routing and would be used + /// by developers to configure the request to disable collision checks in . + /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since + /// collission checking only occurs in the back office which is launched by + /// for which events do not execute. + /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345 + /// but it's still unclear how this was used. + /// + void IgnorePublishedContentCollisions(); } } diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index 6d974fcc44..b4c35c0e4d 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; -using Umbraco.Core.Models; namespace Umbraco.Web.Routing { @@ -10,8 +8,6 @@ namespace Umbraco.Web.Routing ///
public interface IPublishedRouter { - // TODO: consider this and UmbracoRouteValueTransformer - move some code around? - /// /// Creates a published request. /// @@ -20,18 +16,12 @@ namespace Umbraco.Web.Routing Task CreateRequestAsync(Uri uri); /// - /// Prepares a request for rendering. + /// Sends a through the routing pipeline and builds a result. /// /// The request. - /// A value indicating whether the request was successfully prepared and can be rendered. - Task RouteRequestAsync(IPublishedRequestBuilder request); - - /// - /// Tries to route a request. - /// - /// The request. - /// A value indicating whether the request can be routed to a document. - Task TryRouteRequestAsync(IPublishedRequestBuilder request); + /// The options. + /// The built instance. + Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options); /// /// Updates the request to "not found". diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 7f1f8b63b5..bb1c28cab1 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -13,11 +13,9 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache) + public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); - // TODO: What is this? - //IgnorePublishedContentCollisions = ignorePublishedContentCollisions; PublishedContent = publishedContent; IsInternalRedirect = isInternalRedirect; Template = template; @@ -28,6 +26,7 @@ namespace Umbraco.Web.Routing CacheExtensions = cacheExtensions; Headers = headers; SetNoCacheHeader = cacheabilityNoCache; + IgnorePublishedContentCollisions = ignorePublishedContentCollisions; } /// diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 77c3420399..4230e73a78 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -18,6 +18,7 @@ namespace Umbraco.Web.Routing private string _redirectUrl; private HttpStatusCode? _responseStatus; private IPublishedContent _publishedContent; + private bool _ignorePublishedContentCollisions; /// /// Initializes a new instance of the class. @@ -70,7 +71,8 @@ namespace Umbraco.Web.Routing _responseStatus.HasValue ? (int?)_responseStatus : null, _cacheExtensions, _headers, - _cacheability); + _cacheability, + _ignorePublishedContentCollisions); /// public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability) @@ -190,5 +192,8 @@ namespace Umbraco.Web.Routing Template = model; return true; } + + /// + public void IgnorePublishedContentCollisions() => _ignorePublishedContentCollisions = true; } } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 70049660ca..c17eb3e2b7 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -100,26 +100,25 @@ namespace Umbraco.Web.Routing return publishedRequestBuilder; } - /// - public Task TryRouteRequestAsync(IPublishedRequestBuilder request) + private IPublishedRequest TryRouteRequest(IPublishedRequestBuilder request) { FindDomain(request); // TODO: This was ported from v8 but how could it possibly have a redirect here? if (request.IsRedirect()) { - return Task.FromResult(false); + return request.Build(); } // TODO: This was ported from v8 but how could it possibly have content here? if (request.HasPublishedContent()) { - return Task.FromResult(true); + return request.Build(); } FindPublishedContent(request); - return Task.FromResult(request.Build().Success()); + return request.Build(); } private void SetVariationContext(CultureInfo culture) @@ -138,11 +137,18 @@ namespace Umbraco.Web.Routing } /// - public async Task RouteRequestAsync(IPublishedRequestBuilder request) + public async Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options) { + // outbound routing performs different/simpler logic + if (options.RouteDirection == RouteDirection.Outbound) + { + return TryRouteRequest(request); + } + // find domain FindDomain(request); + // TODO: This was ported from v8 but how could it possibly have a redirect here? // if request has been flagged to redirect then return // whoever called us is in charge of actually redirecting if (request.IsRedirect()) @@ -156,7 +162,8 @@ namespace Umbraco.Web.Routing // find the published content if it's not assigned. This could be manually assigned with a custom route handler, or // with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. - if (request.PublishedContent == null) + // TODO: This might very well change when we look into porting custom routes in netcore like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. + if (!request.HasPublishedContent()) { // find the document & template FindPublishedContentAndTemplate(request); diff --git a/src/Umbraco.Core/Routing/RouteDirection.cs b/src/Umbraco.Core/Routing/RouteDirection.cs new file mode 100644 index 0000000000..1d811b06e3 --- /dev/null +++ b/src/Umbraco.Core/Routing/RouteDirection.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Web.Routing +{ + /// + /// The direction of a route + /// + public enum RouteDirection + { + /// + /// An inbound route used to map a URL to a content item + /// + Inbound = 1, + + /// + /// An outbound route used to generate a URL for a content item + /// + Outbound = 2 + } +} diff --git a/src/Umbraco.Core/Routing/RouteRequestOptions.cs b/src/Umbraco.Core/Routing/RouteRequestOptions.cs new file mode 100644 index 0000000000..91a343e2f0 --- /dev/null +++ b/src/Umbraco.Core/Routing/RouteRequestOptions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Umbraco.Web.Routing +{ + /// + /// Options for routing an Umbraco request + /// + public struct RouteRequestOptions : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + public RouteRequestOptions(RouteDirection direction) => RouteDirection = direction; + + /// + /// Gets the + /// + public RouteDirection RouteDirection { get; } + + /// + public override bool Equals(object obj) => obj is RouteRequestOptions options && Equals(options); + + /// + public bool Equals(RouteRequestOptions other) => RouteDirection == other.RouteDirection; + + /// + public override int GetHashCode() => 15391035 + RouteDirection.GetHashCode(); + } +} diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index e7095feb2b..698b9ab526 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -204,20 +204,19 @@ namespace Umbraco.Web.Routing } uri = uriUtility.UriToUmbraco(uri); - IPublishedRequestBuilder pcr = await publishedRouter.CreateRequestAsync(uri); - var routeResult = await publishedRouter.TryRouteRequestAsync(pcr); + IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri); + IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); - if (pcr.PublishedContent == null) + if (!pcr.HasPublishedContent()) { var urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return Attempt.Succeed(urlInfo); } - // TODO: What is this? - //if (pcr.IgnorePublishedContentCollisions) - //{ - // return false; - //} + if (pcr.IgnorePublishedContentCollisions) + { + return Attempt.Fail(); + } if (pcr.PublishedContent.Id != content.Id) { diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 9cb920f434..de6cb72edb 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -11,6 +11,7 @@ using Umbraco.Extensions; using Umbraco.Web.Common.Routing; using Umbraco.Web.Routing; using Umbraco.Web.Website.Controllers; +using RouteDirection = Umbraco.Web.Routing.RouteDirection; namespace Umbraco.Web.Website.Routing { @@ -112,7 +113,7 @@ namespace Umbraco.Web.Website.Routing // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? - IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder); + IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); return publishedRequest; } diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index aef8b4bc3e..f45371707a 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Web.Composing; using Umbraco.Web.Routing; +using RouteDirection = Umbraco.Web.Routing.RouteDirection; namespace Umbraco.Web { @@ -136,7 +137,7 @@ namespace Umbraco.Web // instantiate, prepare and process the published content request // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL var requestBuilder = _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl).Result; - var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder).Result; + var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)).Result; // NOTE: This has been ported to netcore // HandleHttpResponseStatus returns a value indicating that the request should From c15b416e889efbd24b8bf72d09f1866d2db3da2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 11:29:07 +1100 Subject: [PATCH 41/88] Ensures the culture is set for requests for both front-end and back office --- src/Umbraco.Core/Routing/PublishedRouter.cs | 4 -- .../Routing/UmbracoModuleTests.cs | 4 +- .../Controllers/RenderController.cs | 14 ++++--- .../UmbracoBuilderExtensions.cs | 3 ++ .../ApplicationBuilderExtensions.cs | 4 +- ...mbracoBackOfficeIdentityCultureProvider.cs | 12 ++++-- .../UmbracoPublishedContentCultureProvider.cs | 31 ++++++++++++++++ .../UmbracoRequestLocalizationOptions.cs | 37 +++++++++++++++++++ .../UmbracoMvcConfigureOptions.cs | 3 +- src/Umbraco.Web/UmbracoInjectedModule.cs | 6 --- 10 files changed, 94 insertions(+), 24 deletions(-) rename src/Umbraco.Web.Common/{Extensions => Localization}/UmbracoBackOfficeIdentityCultureProvider.cs (61%) create mode 100644 src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs create mode 100644 src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs rename src/Umbraco.Web.Common/{AspNetCore => Mvc}/UmbracoMvcConfigureOptions.cs (95%) diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index c17eb3e2b7..b61baa1990 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -123,10 +123,6 @@ namespace Umbraco.Web.Routing private void SetVariationContext(CultureInfo culture) { - // set the culture on the thread - once, so it's set when running document lookups - // TODO: Set this on HttpContext! - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture; - VariationContext variationContext = _variationContextAccessor.VariationContext; if (variationContext != null && variationContext.Culture == culture?.Name) { diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 267d870514..dbcedb6225 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Microsoft.Extensions.Logging; using Moq; @@ -35,8 +35,6 @@ namespace Umbraco.Tests.Routing null, // FIXME: PublishedRouter complexities... Mock.Of(), new RoutableDocumentFilter(globalSettings, IOHelper), - UriUtility, - AppCaches.RequestCache, globalSettings, HostingEnvironment ); diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 1556333402..d9a2d05979 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ActionsResults; @@ -70,12 +71,13 @@ namespace Umbraco.Web.Common.Controllers return _umbracoRouteValues; } - if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) + _umbracoRouteValues = HttpContext.GetRouteValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken) as UmbracoRouteValues; + + if (_umbracoRouteValues == null) { throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); } - _umbracoRouteValues = (UmbracoRouteValues)def; return _umbracoRouteValues; } } @@ -126,10 +128,10 @@ namespace Umbraco.Web.Common.Controllers IPublishedRequest pcr = UmbracoRouteValues.PublishedRequest; _logger.LogDebug( - "Response status: Redirect={Redirect}, Is404={Is404}, StatusCode={ResponseStatusCode}", - pcr.IsRedirect() ? (pcr.IsRedirectPermanent() ? "permanent" : "redirect") : "none", - pcr.Is404() ? "true" : "false", - pcr.ResponseStatusCode); + "Response status: Content={Content}, StatusCode={ResponseStatusCode}, Culture={Culture}", + pcr.PublishedContent?.Id ?? -1, + pcr.ResponseStatusCode, + pcr.Culture); UmbracoRouteResult routeStatus = pcr.GetRouteResult(); switch (routeStatus) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index a2dde620b9..8a6f44f456 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -48,9 +48,11 @@ using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Common.Install; using Umbraco.Web.Common.Lifetime; +using Umbraco.Web.Common.Localization; using Umbraco.Web.Common.Macros; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Mvc; using Umbraco.Web.Common.Profiler; using Umbraco.Web.Common.Routing; using Umbraco.Web.Common.Security; @@ -226,6 +228,7 @@ namespace Umbraco.Web.Common.DependencyInjection }); builder.Services.ConfigureOptions(); + builder.Services.ConfigureOptions(); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.AddUmbracoImageSharp(builder.Config); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 5dee7d10e1..73682ca6c5 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Serilog.Context; @@ -57,9 +58,10 @@ namespace Umbraco.Extensions // where we need to have UseAuthentication and UseAuthorization proceeding this call but before // endpoints are defined. app.UseRouting(); - app.UseRequestLocalization(); app.UseAuthentication(); app.UseAuthorization(); + // This must come after auth because the culture is based on the auth'd user + app.UseRequestLocalization(); // Must be called after UseRouting and before UseEndpoints app.UseSession(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs similarity index 61% rename from src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs rename to src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs index a5af18fbda..a09230a3fc 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoBackOfficeIdentityCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs @@ -1,22 +1,28 @@ +using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; using Umbraco.Core.Security; -namespace Umbraco.Web.Common.Extensions +namespace Umbraco.Web.Common.Localization { + + /// + /// Sets the request culture to the culture of the back office user if one is determined to be in the request + /// public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider { + /// public override Task DetermineProviderCultureResult(HttpContext httpContext) { - var culture = httpContext.User.Identity.GetCulture(); + CultureInfo culture = httpContext.User.Identity.GetCulture(); if (culture is null) { return NullProviderCultureResult; } - return Task.FromResult(new ProviderCultureResult(culture.Name, culture.Name)); + return Task.FromResult(new ProviderCultureResult(culture.Name)); } } } diff --git a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs new file mode 100644 index 0000000000..cc683848c3 --- /dev/null +++ b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Routing; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Localization +{ + /// + /// Sets the request culture to the culture of the if one is found in the request + /// + public class UmbracoPublishedContentCultureProvider : RequestCultureProvider + { + /// + public override Task DetermineProviderCultureResult(HttpContext httpContext) + { + if (httpContext.GetRouteValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken) is UmbracoRouteValues routeValues) + { + CultureInfo culture = routeValues.PublishedRequest?.Culture; + if (culture != null) + { + return Task.FromResult(new ProviderCultureResult(culture.Name)); + } + } + + return NullProviderCultureResult; + } + } +} diff --git a/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs new file mode 100644 index 0000000000..1a27798c35 --- /dev/null +++ b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration.Models; + +namespace Umbraco.Web.Common.Localization +{ + /// + /// Custom Umbraco options configuration for + /// + public class UmbracoRequestLocalizationOptions : IConfigureOptions + { + private readonly IOptions _globalSettings; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRequestLocalizationOptions(IOptions globalSettings) => _globalSettings = globalSettings; + + /// + public void Configure(RequestLocalizationOptions options) + { + // set the default culture to what is in config + options.DefaultRequestCulture = new RequestCulture(_globalSettings.Value.DefaultUILanguage); + + // add a custom provider + if (options.RequestCultureProviders == null) + { + options.RequestCultureProviders = new List(); + } + + options.RequestCultureProviders.Insert(0, new UmbracoBackOfficeIdentityCultureProvider()); + options.RequestCultureProviders.Insert(1, new UmbracoPublishedContentCultureProvider()); + } + } +} diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs similarity index 95% rename from src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs rename to src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs index 6c8420cd03..c212334560 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs +++ b/src/Umbraco.Web.Common/Mvc/UmbracoMvcConfigureOptions.cs @@ -3,8 +3,9 @@ using Microsoft.Extensions.Options; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; -namespace Umbraco.Web.Common.AspNetCore +namespace Umbraco.Web.Common.Mvc { + /// /// Options for globally configuring MVC for Umbraco /// diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index f45371707a..9831652fbf 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -37,10 +37,8 @@ namespace Umbraco.Web private readonly IPublishedRouter _publishedRouter; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly RoutableDocumentFilter _routableDocumentLookup; - private readonly IRequestCache _requestCache; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; - private readonly UriUtility _uriUtility; public UmbracoInjectedModule( IRuntimeState runtime, @@ -48,8 +46,6 @@ namespace Umbraco.Web IPublishedRouter publishedRouter, IUmbracoContextFactory umbracoContextFactory, RoutableDocumentFilter routableDocumentLookup, - UriUtility uriUtility, - IRequestCache requestCache, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { @@ -58,8 +54,6 @@ namespace Umbraco.Web _publishedRouter = publishedRouter; _umbracoContextFactory = umbracoContextFactory; _routableDocumentLookup = routableDocumentLookup; - _uriUtility = uriUtility; - _requestCache = requestCache; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; } From 74f51fe7ed4d279d5bf7d79b351f8936705d8262 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 11:35:27 +1100 Subject: [PATCH 42/88] removes ported code --- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 157 +--------------------- 1 file changed, 4 insertions(+), 153 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index f4520d2af9..c88958d2fe 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -206,104 +206,6 @@ namespace Umbraco.Web.Mvc return handler; } - /// - /// Returns a RouteDefinition object based on the current content request - /// - /// - /// - /// - internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, IPublishedRequest request) - { - if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); - if (request == null) throw new ArgumentNullException(nameof(request)); - - var defaultControllerType = Current.DefaultRenderMvcControllerType; - var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - // creates the default route definition which maps to the 'UmbracoController' controller - var def = new RouteDefinition - { - ControllerName = defaultControllerName, - ControllerType = defaultControllerType, - PublishedRequest = request, - ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), - HasHijackedRoute = false - }; - - // check that a template is defined), if it doesn't and there is a hijacked route it will just route - // to the index Action - if (request.HasTemplate()) - { - // the template Alias should always be already saved with a safe name. - // if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed - // with the action name attribute. - var templateName = request.GetTemplateAlias().Split('.')[0].ToSafeAlias(_shortStringHelper); - def.ActionName = templateName; - } - - // check if there's a custom controller assigned, base on the document type alias. - var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, request.PublishedContent.ContentType.Alias); - - // check if that controller exists - if (controllerType != null) - { - // ensure the controller is of type IRenderMvcController and ControllerBase - if (TypeHelper.IsTypeAssignableFrom(controllerType) - && TypeHelper.IsTypeAssignableFrom(controllerType)) - { - // set the controller and name to the custom one - def.ControllerType = controllerType; - def.ControllerName = ControllerExtensions.GetControllerName(controllerType); - if (def.ControllerName != defaultControllerName) - { - def.HasHijackedRoute = true; - } - } - else - { - Current.Logger.LogWarning("The current Document Type {ContentTypeAlias} matches a locally declared controller of type {ControllerName}. Custom Controllers for Umbraco routing must implement '{UmbracoRenderController}' and inherit from '{UmbracoControllerBase}'.", - request.PublishedContent.ContentType.Alias, - controllerType.FullName, - typeof(IRenderController).FullName, - typeof(ControllerBase).FullName); - - // we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults - // that have already been set above. - } - } - - // store the route definition - requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; - - return def; - } - - internal IHttpHandler GetHandlerOnMissingTemplate(IPublishedRequest request) - { - if (request == null) throw new ArgumentNullException(nameof(request)); - - // missing template, so we're in a 404 here - // so the content, if any, is a custom 404 page of some sort - - - // TODO: Handle this differently in netcore.... - - //if (request.HasPublishedContent() == false) - //{ - // // means the builder could not find a proper document to handle 404 - // return new PublishedContentNotFoundHandler(); - //} - - //if (request.HasTemplate() == false) - //{ - // // means the engine could find a proper document, but the document has no template - // // at that point there isn't much we can do and there is no point returning - // // to Mvc since Mvc can't do much - // return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); - //} - - return null; - } - /// /// this will determine the controller and set the values in the route data /// @@ -312,8 +214,9 @@ namespace Umbraco.Web.Mvc if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); if (request == null) throw new ArgumentNullException(nameof(request)); - var routeDef = GetUmbracoRouteDefinition(requestContext, request); + //var routeDef = GetUmbracoRouteDefinition(requestContext, request); + // TODO: Need to port this to netcore // Need to check for a special case if there is form data being posted back to an Umbraco URL var postedInfo = GetFormInfo(requestContext); if (postedInfo != null) @@ -321,61 +224,9 @@ namespace Umbraco.Web.Mvc return HandlePostedValues(requestContext, postedInfo); } - // TODO: Surely this check is part of the PublishedRouter? - - // Here we need to check if there is no hijacked route and no template assigned, - // if this is the case we want to return a blank page, but we'll leave that up to the NoTemplateHandler. - // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, - // for example for json rendering in headless. - if (request.HasTemplate() == false && Features.Disabled.DisableTemplates == false && routeDef.HasHijackedRoute == false) - { - - // TODO: Handle this differently in netcore.... - - // request.UpdateToNotFound(); // request will go 404 - - // HandleHttpResponseStatus returns a value indicating that the request should - // not be processed any further, eg because it has been redirect. then, exit. - //if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, request, Current.Logger)) - // return null; - - var handler = GetHandlerOnMissingTemplate(request); - - // if it's not null it's the PublishedContentNotFoundHandler (no document was found to handle 404, or document with no template was found) - // if it's null it means that a document was found - - // if we have a handler, return now - if (handler != null) - return handler; - - // else we are running Mvc - // update the route data - because the PublishedContent has changed - UpdateRouteDataForRequest( - new ContentModel(request.PublishedContent), - requestContext); - // update the route definition - routeDef = GetUmbracoRouteDefinition(requestContext, request); - } - - // no post values, just route to the controller/action required (local) - - requestContext.RouteData.Values["controller"] = routeDef.ControllerName; - if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) - requestContext.RouteData.Values["action"] = routeDef.ActionName; - - // Set the session state requirements - requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext, routeDef.ControllerName)); - - // reset the friendly path so in the controllers and anything occurring after this point in time, - //the URL is reset back to the original request. - requestContext.HttpContext.RewritePath(UmbracoContext.OriginalRequestUrl.PathAndQuery); - - return new UmbracoMvcHandler(requestContext); + // NOTE: Code here has been removed and ported to netcore + throw new NotSupportedException("This code was already ported to netcore"); } - private SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext, string controllerName) - { - return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); - } } } From ee1663c9784648aeeb463327d44b2bec782f7624 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 15:21:08 +1100 Subject: [PATCH 43/88] missing commit --- .../Routing/NotFoundHandlerHelper.cs | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs index 80aa0f1bc6..74ce0979f6 100644 --- a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; @@ -6,6 +6,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Xml; @@ -16,27 +17,6 @@ namespace Umbraco.Web.Routing /// internal class NotFoundHandlerHelper { - /// - /// Returns the Umbraco page id to use as the Not Found page based on the configured 404 pages and the current request - /// - /// - /// - /// The server name attached to the request, normally would be the source of HttpContext.Current.Request.ServerVariables["SERVER_NAME"] - /// - /// - /// - /// - /// - internal static int? GetCurrentNotFoundPageId( - ContentErrorPage[] error404Collection, - string requestServerName, - IEntityService entityService, - IPublishedContentQuery publishedContentQuery, - IDomainService domainService) - { - throw new NotImplementedException(); - } - internal static int? GetCurrentNotFoundPageId( ContentErrorPage[] error404Collection, IEntityService entityService, @@ -46,13 +26,15 @@ namespace Umbraco.Web.Routing if (error404Collection.Length > 1) { // test if a 404 page exists with current culture thread - var cultureErr = error404Collection.FirstOrDefault(x => x.Culture == errorCulture.Name) + ContentErrorPage cultureErr = error404Collection.FirstOrDefault(x => x.Culture == errorCulture.Name) ?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one! if (cultureErr != null) + { return GetContentIdFromErrorPageConfig(cultureErr, entityService, publishedContentQuery); + } } - else + else if (error404Collection.Length == 1) { return GetContentIdFromErrorPageConfig(error404Collection.First(), entityService, publishedContentQuery); } @@ -63,25 +45,25 @@ namespace Umbraco.Web.Routing /// /// Returns the content id based on the configured ContentErrorPage section. /// - /// - /// - /// - /// internal static int? GetContentIdFromErrorPageConfig(ContentErrorPage errorPage, IEntityService entityService, IPublishedContentQuery publishedContentQuery) { - if (errorPage.HasContentId) return errorPage.ContentId; + if (errorPage.HasContentId) + { + return errorPage.ContentId; + } if (errorPage.HasContentKey) { - //need to get the Id for the GUID + // need to get the Id for the GUID // TODO: When we start storing GUIDs into the IPublishedContent, then we won't have to look this up // but until then we need to look it up in the db. For now we've implemented a cached service for // converting Int -> Guid and vice versa. - var found = entityService.GetId(errorPage.ContentKey, UmbracoObjectTypes.Document); + Attempt found = entityService.GetId(errorPage.ContentKey, UmbracoObjectTypes.Document); if (found) { return found.Result; } + return null; } @@ -89,21 +71,23 @@ namespace Umbraco.Web.Routing { try { - //we have an xpath statement to execute + // we have an xpath statement to execute var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery( xpathExpression: errorPage.ContentXPath, nodeContextId: null, getPath: nodeid => { - var ent = entityService.Get(nodeid); + Core.Models.Entities.IEntitySlim ent = entityService.Get(nodeid); return ent.Path.Split(',').Reverse(); }, publishedContentExists: i => publishedContentQuery.Content(i) != null); - //now we'll try to execute the expression - var nodeResult = publishedContentQuery.ContentSingleAtXPath(xpathResult); + // now we'll try to execute the expression + IPublishedContent nodeResult = publishedContentQuery.ContentSingleAtXPath(xpathResult); if (nodeResult != null) + { return nodeResult.Id; + } } catch (Exception ex) { @@ -111,6 +95,7 @@ namespace Umbraco.Web.Routing return null; } } + return null; } From f8033c52817317aed941ab9bc811aa6d16bd79f3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 15:27:07 +1100 Subject: [PATCH 44/88] Moves RoutableDocumentFilter along with tests --- .../Routing/RoutableDocumentFilterTests.cs | 112 ++++++++++ .../Routing/RoutableDocumentFilterTests.cs | 79 ------- .../Routing/UmbracoModuleTests.cs | 1 - src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../Routing/RoutableDocumentFilter.cs | 194 ++++++++++++++++ .../UmbracoBuilderExtensions.cs | 2 + src/Umbraco.Web/RoutableDocumentFilter.cs | 210 ------------------ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 - src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/UmbracoInjectedModule.cs | 16 +- 10 files changed, 315 insertions(+), 303 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs delete mode 100644 src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs create mode 100644 src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs delete mode 100644 src/Umbraco.Web/RoutableDocumentFilter.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs new file mode 100644 index 0000000000..87ee6d0ea0 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Web.Common.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing +{ + [TestFixture] + public class RoutableDocumentFilterTests + { + private GlobalSettings GetGlobalSettings() => new GlobalSettings(); + + private IHostingEnvironment GetHostingEnvironment() + { + var hostingEnv = new Mock(); + hostingEnv.Setup(x => x.ToAbsolute(It.IsAny())).Returns((string virtualPath) => virtualPath.TrimStart('~', '/')); + return hostingEnv.Object; + } + + [TestCase("/umbraco/editContent.aspx")] + [TestCase("/install/default.aspx")] + [TestCase("/install/")] + [TestCase("/install")] + [TestCase("/install/?installStep=asdf")] + [TestCase("/install/test.aspx")] + public void Is_Reserved_Path_Or_Url(string url) + { + var routableDocFilter = new RoutableDocumentFilter( + GetGlobalSettings(), + GetHostingEnvironment(), + new DefaultEndpointDataSource()); + + // Will be false if it is a reserved path + Assert.IsFalse(routableDocFilter.IsDocumentRequest(url)); + } + + [TestCase("/base/somebasehandler")] + [TestCase("/")] + [TestCase("/home.aspx")] + [TestCase("/umbraco-test")] + [TestCase("/install-test")] + [TestCase("/install.aspx")] + public void Is_Not_Reserved_Path_Or_Url(string url) + { + var routableDocFilter = new RoutableDocumentFilter( + GetGlobalSettings(), + GetHostingEnvironment(), + new DefaultEndpointDataSource()); + + // Will be true if it's not reserved + Assert.IsTrue(routableDocFilter.IsDocumentRequest(url)); + } + + [TestCase("/Do/Not/match", false)] + [TestCase("/Umbraco/RenderMvcs", false)] + [TestCase("/Umbraco/RenderMvc", true)] + [TestCase("/umbraco/RenderMvc/Index", true)] + [TestCase("/Umbraco/RenderMvc/Index/1234", true)] + [TestCase("/Umbraco/RenderMvc/Index/1234/", true)] + [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)] + [TestCase("/api", true)] + [TestCase("/api/WebApiTest", true)] + [TestCase("/Api/WebApiTest/1234", true)] + [TestCase("/api/WebApiTest/Index/1234", false)] + public void Is_Reserved_By_Route(string url, bool isReserved) + { + var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + + RouteEndpoint endpoint1 = CreateEndpoint( + "Umbraco/RenderMvc/{action?}/{id?}", + new { controller = "RenderMvc" }, + "Umbraco_default", + 0); + + RouteEndpoint endpoint2 = CreateEndpoint( + "api/{controller?}/{id?}", + new { action = "Index" }, + "WebAPI", + 1); + + var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2); + + var routableDocFilter = new RoutableDocumentFilter( + globalSettings, + GetHostingEnvironment(), + endpointDataSource); + + Assert.AreEqual( + !isReserved, // not reserved if it's a document request + routableDocFilter.IsDocumentRequest(url)); + } + + // borrowed from https://github.com/dotnet/aspnetcore/blob/19559e73da2b6d335b864ed2855dd8a0c7a207a0/src/Mvc/Mvc.Core/test/Routing/ControllerLinkGeneratorExtensionsTest.cs#L171 + private RouteEndpoint CreateEndpoint( + string template, + object defaults = null, + string name = null, + int order = 0) => new RouteEndpoint( + (httpContext) => Task.CompletedTask, + RoutePatternFactory.Parse(template, defaults, null), + order, + new EndpointMetadataCollection(Array.Empty()), + name); + } +} diff --git a/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs deleted file mode 100644 index e3d6477988..0000000000 --- a/src/Umbraco.Tests/Routing/RoutableDocumentFilterTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Web.Mvc; -using System.Web.Routing; -using NUnit.Framework; -using Umbraco.Core.Configuration.Models; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web; - -namespace Umbraco.Tests.Routing -{ - [TestFixture] - public class RoutableDocumentFilterTests : BaseWebTest - { - [TestCase("/umbraco/editContent.aspx")] - [TestCase("/install/default.aspx")] - [TestCase("/install/")] - [TestCase("/install")] - [TestCase("/install/?installStep=asdf")] - [TestCase("/install/test.aspx")] - public void Is_Reserved_Path_Or_Url(string url) - { - var globalSettings = TestObjects.GetGlobalSettings(); - var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper); - Assert.IsTrue(routableDocFilter.IsReservedPathOrUrl(url)); - } - - [TestCase("/base/somebasehandler")] - [TestCase("/")] - [TestCase("/home.aspx")] - [TestCase("/umbraco-test")] - [TestCase("/install-test")] - [TestCase("/install.aspx")] - public void Is_Not_Reserved_Path_Or_Url(string url) - { - var globalSettings = TestObjects.GetGlobalSettings(); - var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper); - Assert.IsFalse(routableDocFilter.IsReservedPathOrUrl(url)); - } - - [TestCase("/Do/Not/match", false)] - [TestCase("/Umbraco/RenderMvcs", false)] - [TestCase("/Umbraco/RenderMvc", true)] - [TestCase("/Umbraco/RenderMvc/Index", true)] - [TestCase("/Umbraco/RenderMvc/Index/1234", true)] - [TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)] - [TestCase("/api", true)] - [TestCase("/api/WebApiTest", true)] - [TestCase("/api/WebApiTest/1234", true)] - [TestCase("/api/WebApiTest/Index/1234", false)] - public void Is_Reserved_By_Route(string url, bool shouldMatch) - { - //reset the app config, we only want to test routes not the hard coded paths - - var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; - - var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper); - - var routes = new RouteCollection(); - - routes.MapRoute( - "Umbraco_default", - "Umbraco/RenderMvc/{action}/{id}", - new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional }); - routes.MapRoute( - "WebAPI", - "api/{controller}/{id}", - new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional }); - - - var context = new FakeHttpContextFactory(url); - - - Assert.AreEqual( - shouldMatch, - routableDocFilter.IsReservedPathOrUrl(url, context.HttpContext, routes)); - } - } -} diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index dbcedb6225..166dea39f8 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -34,7 +34,6 @@ namespace Umbraco.Tests.Routing logger, null, // FIXME: PublishedRouter complexities... Mock.Of(), - new RoutableDocumentFilter(globalSettings, IOHelper), globalSettings, HostingEnvironment ); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cb3df3b905..2e7818c9a4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -153,7 +153,6 @@ - diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs new file mode 100644 index 0000000000..b00dede27a --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; +using Umbraco.Core; +using Umbraco.Core.Collections; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; + +namespace Umbraco.Web.Common.Routing +{ + /// + /// Utility class used to check if the current request is for a front-end request + /// + /// + /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes. + /// + public sealed class RoutableDocumentFilter + { + private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(); + private readonly GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly EndpointDataSource _endpointDataSource; + private readonly object _routeLocker = new object(); + +#pragma warning disable IDE0044 // Add readonly modifier + private object _initLocker = new object(); + private bool _isInit = false; + private HashSet _reservedList; +#pragma warning restore IDE0044 // Add readonly modifier + + /// + /// Initializes a new instance of the class. + /// + public RoutableDocumentFilter(GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) + { + _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; + _endpointDataSource = endpointDataSource; + _endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null); + } + + private void EndpointsChanged(object value) + { + lock (_routeLocker) + { + // try clearing each entry + foreach (var r in _routeChecks.Keys.ToList()) + { + _routeChecks.TryRemove(r, out _); + } + + // re-register after it has changed so we keep listening + _endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null); + } + } + + /// + /// Checks if the request is a document request (i.e. one that the module should handle) + /// + public bool IsDocumentRequest(string absPath) + { + var maybeDoc = true; + + // a document request should be + // /foo/bar/nil + // /foo/bar/nil/ + // /foo/bar/nil.aspx + // where /foo is not a reserved path + + // TODO: Remove aspx checks + + // if the path contains an extension that is not .aspx + // then it cannot be a document request + var extension = Path.GetExtension(absPath); + if (maybeDoc && extension.IsNullOrWhiteSpace() == false && !extension.InvariantEquals(".aspx")) + { + maybeDoc = false; + } + + // at that point, either we have no extension, or it is .aspx + + // if the path is reserved then it cannot be a document request + if (maybeDoc && IsReservedPathOrUrl(absPath)) + { + maybeDoc = false; + } + + return maybeDoc; + } + + /// + /// Determines whether the specified URL is reserved or is inside a reserved path. + /// + /// The Path of the URL to check. + /// + /// true if the specified URL is reserved; otherwise, false. + /// + private bool IsReservedPathOrUrl(string absPath) + { + LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _initLocker, () => + { + // store references to strings to determine changes + var reservedPathsCache = _globalSettings.ReservedPaths; + var reservedUrlsCache = _globalSettings.ReservedUrls; + + // add URLs and paths to a new list + var newReservedList = new HashSet(); + foreach (var reservedUrlTrimmed in NormalizePaths(reservedUrlsCache, false)) + { + newReservedList.Add(reservedUrlTrimmed); + } + + foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache, true)) + { + newReservedList.Add(reservedPathTrimmed); + } + + // use the new list from now on + return newReservedList; + }); + + // The URL should be cleaned up before checking: + // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' + // * We shouldn't be comparing the query at all + if (absPath.Contains('?')) + { + absPath = absPath.Split('?', StringSplitOptions.RemoveEmptyEntries)[0]; + } + + if (absPath.Contains('.') == false) + { + absPath = absPath.EnsureEndsWith('/'); + } + + // return true if URL starts with an element of the reserved list + var isReserved = _reservedList.Any(x => absPath.InvariantStartsWith(x)); + + if (isReserved) + { + return true; + } + + // check if the current request matches a route, if so then it is reserved. + var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); + if (hasRoute) + { + return true; + } + + return false; + } + + private IEnumerable NormalizePaths(string paths, bool ensureTrailingSlash) => paths + .Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim().ToLowerInvariant()) + .Where(x => x.IsNullOrWhiteSpace() == false) + .Select(reservedPath => + { + var r = _hostingEnvironment.ToAbsolute(reservedPath).Trim().EnsureStartsWith('/'); + return ensureTrailingSlash + ? r.EnsureEndsWith('/') + : r; + }) + .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false); + + private bool MatchesEndpoint(string absPath) + { + // Borrowed from https://stackoverflow.com/a/59550580 + + // Return a collection of Microsoft.AspNetCore.Http.Endpoint instances. + IEnumerable routeEndpoints = _endpointDataSource?.Endpoints.Cast(); + var routeValues = new RouteValueDictionary(); + + // string localPath = new Uri(absPath).LocalPath; + + // To get the matchedEndpoint of the provide url + RouteEndpoint matchedEndpoint = routeEndpoints + .Where(e => new TemplateMatcher( + TemplateParser.Parse(e.RoutePattern.RawText), + new RouteValueDictionary()) + .TryMatch(absPath, routeValues)) + .OrderBy(c => c.Order) + .FirstOrDefault(); + + return matchedEndpoint != null; + } + } +} diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index c4aae8839f..a641f32235 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -6,6 +6,7 @@ using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Website.Collections; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; @@ -40,6 +41,7 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.AddDistributedCache(); diff --git a/src/Umbraco.Web/RoutableDocumentFilter.cs b/src/Umbraco.Web/RoutableDocumentFilter.cs deleted file mode 100644 index c675ad9872..0000000000 --- a/src/Umbraco.Web/RoutableDocumentFilter.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.IO; -using System.Web; -using System.Web.Routing; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using System.Threading; -using System.Collections.Generic; -using System.Linq; -using System.Collections.Concurrent; -using Umbraco.Core.Collections; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.IO; -using Umbraco.Web.Composing; - -namespace Umbraco.Web -{ - /// - /// Utility class used to check if the current request is for a front-end request - /// - /// - /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes. - /// - public sealed class RoutableDocumentFilter - { - public RoutableDocumentFilter(GlobalSettings globalSettings, IIOHelper ioHelper) - { - _globalSettings = globalSettings; - _ioHelper = ioHelper; - } - - private static readonly ConcurrentDictionary RouteChecks = new ConcurrentDictionary(); - private readonly GlobalSettings _globalSettings; - private readonly IIOHelper _ioHelper; - private object _locker = new object(); - private bool _isInit = false; - private int? _routeCount; - private HashSet _reservedList; - - /// - /// Checks if the request is a document request (i.e. one that the module should handle) - /// - /// - /// - /// - public bool IsDocumentRequest(HttpContextBase httpContext, Uri uri) - { - var maybeDoc = true; - var lpath = uri.AbsolutePath.ToLowerInvariant(); - - // handle directory-URLs used for asmx - // TODO: legacy - what's the point really? - var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase); - if (asmxPos >= 0) - { - // use uri.AbsolutePath, not path, 'cos path has been lowercased - httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath - uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo - uri.Query.TrimStart('?')); - maybeDoc = false; - } - - // a document request should be - // /foo/bar/nil - // /foo/bar/nil/ - // /foo/bar/nil.aspx - // where /foo is not a reserved path - - // if the path contains an extension that is not .aspx - // then it cannot be a document request - var extension = Path.GetExtension(lpath); - if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx") - maybeDoc = false; - - // at that point, either we have no extension, or it is .aspx - - // if the path is reserved then it cannot be a document request - if (maybeDoc && IsReservedPathOrUrl(lpath, httpContext, RouteTable.Routes)) - maybeDoc = false; - - //NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :) - //if (!maybeDoc) - //{ - // Logger.LogWarning("Not a document"); - //} - return maybeDoc; - } - - /// - /// Determines whether the specified URL is reserved or is inside a reserved path. - /// - /// The URL to check. - /// - /// true if the specified URL is reserved; otherwise, false. - /// - internal bool IsReservedPathOrUrl(string url) - { - LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _locker, () => - { - // store references to strings to determine changes - var reservedPathsCache = _globalSettings.ReservedPaths; - var reservedUrlsCache = _globalSettings.ReservedUrls; - - // add URLs and paths to a new list - var newReservedList = new HashSet(); - foreach (var reservedUrlTrimmed in reservedUrlsCache - .Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim().ToLowerInvariant()) - .Where(x => x.IsNullOrWhiteSpace() == false) - .Select(reservedUrl => _ioHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/")) - .Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false)) - { - newReservedList.Add(reservedUrlTrimmed); - } - - foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries))) - { - newReservedList.Add(reservedPathTrimmed); - } - - foreach (var reservedPathTrimmed in NormalizePaths(ReservedPaths)) - { - newReservedList.Add(reservedPathTrimmed); - } - - // use the new list from now on - return newReservedList; - }); - - //The URL should be cleaned up before checking: - // * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/' - // * We shouldn't be comparing the query at all - var pathPart = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant(); - if (pathPart.Contains(".") == false) - { - pathPart = pathPart.EnsureEndsWith('/'); - } - - // return true if URL starts with an element of the reserved list - return _reservedList.Any(x => pathPart.InvariantStartsWith(x)); - } - - private IEnumerable NormalizePaths(IEnumerable paths) - { - return paths - .Select(x => x.Trim().ToLowerInvariant()) - .Where(x => x.IsNullOrWhiteSpace() == false) - .Select(reservedPath => _ioHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/")) - .Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false); - } - - /// - /// Determines whether the current request is reserved based on the route table and - /// whether the specified URL is reserved or is inside a reserved path. - /// - /// - /// - /// The route collection to lookup the request in - /// - internal bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes) - { - if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); - if (routes == null) throw new ArgumentNullException(nameof(routes)); - - //This is some rudimentary code to check if the route table has changed at runtime, we're basically just keeping a count - //of the routes. This isn't fail safe but there's no way to monitor changes to the route table. Else we need to create a hash - //of all routes and then recompare but that will be annoying to do on each request and then we might as well just do the whole MVC - //route on each request like we were doing before instead of caching the result of GetRouteData. - var changed = false; - using (routes.GetReadLock()) - { - if (!_routeCount.HasValue || _routeCount.Value != routes.Count) - { - //the counts are not set or have changed, need to reset - changed = true; - } - } - if (changed) - { - using (routes.GetWriteLock()) - { - _routeCount = routes.Count; - - //try clearing each entry - foreach(var r in RouteChecks.Keys.ToList()) - RouteChecks.TryRemove(r, out _); - } - } - - var absPath = httpContext?.Request?.Url.AbsolutePath; - - if (absPath.IsNullOrWhiteSpace()) - return false; - - //check if the current request matches a route, if so then it is reserved. - var hasRoute = RouteChecks.GetOrAdd(absPath, x => routes.GetRouteData(httpContext) != null); - if (hasRoute) - return true; - - //continue with the standard ignore routine - return IsReservedPathOrUrl(url); - } - - /// - /// This is used internally to track any registered callback paths for Identity providers. If the request path matches - /// any of the registered paths, then the module will let the request keep executing - /// - internal static readonly ConcurrentHashSet ReservedPaths = new ConcurrentHashSet(); - } -} diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index cac49f9421..d4e989854f 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -46,8 +46,6 @@ namespace Umbraco.Web.Runtime return new UmbracoHelper(); }); - builder.Services.AddUnique(); - // configure the container for web //composition.ConfigureForWeb(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c9ea1b1198..80e8024786 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -150,7 +150,6 @@ - diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 9831652fbf..50d843932b 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -36,7 +36,6 @@ namespace Umbraco.Web private readonly ILogger _logger; private readonly IPublishedRouter _publishedRouter; private readonly IUmbracoContextFactory _umbracoContextFactory; - private readonly RoutableDocumentFilter _routableDocumentLookup; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -45,7 +44,6 @@ namespace Umbraco.Web ILogger logger, IPublishedRouter publishedRouter, IUmbracoContextFactory umbracoContextFactory, - RoutableDocumentFilter routableDocumentLookup, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { @@ -53,7 +51,6 @@ namespace Umbraco.Web _logger = logger; _publishedRouter = publishedRouter; _umbracoContextFactory = umbracoContextFactory; - _routableDocumentLookup = routableDocumentLookup; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; } @@ -157,14 +154,15 @@ namespace Umbraco.Web var reason = EnsureRoutableOutcome.IsRoutable; - // ensure this is a document request - if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl)) - { - reason = EnsureRoutableOutcome.NotDocumentRequest; - } + //// ensure this is a document request + //if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl)) + //{ + // reason = EnsureRoutableOutcome.NotDocumentRequest; + //} + // ensure the runtime is in the proper state // and deal with needed redirects, etc - else if (!EnsureRuntime(httpContext, uri)) + if (!EnsureRuntime(httpContext, uri)) { reason = EnsureRoutableOutcome.NotReady; } From b801199e7cf1add15d5f6b2dd2a176df9fb936e6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 15:30:35 +1100 Subject: [PATCH 45/88] missing commit --- .../Routing/UmbracoRouteValuesFactory.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index c4f89bf8aa..fd92f7f11e 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -99,8 +99,6 @@ namespace Umbraco.Web.Website.Routing /// /// Check if the route is hijacked and return new route values /// - /// - /// private UmbracoRouteValues CheckHijackedRoute(UmbracoRouteValues def) { IPublishedRequest request = def.PublishedRequest; @@ -131,11 +129,12 @@ namespace Umbraco.Web.Website.Routing { IPublishedRequest request = def.PublishedRequest; - // Here we need to check if there is no hijacked route and no template assigned, - // if this is the case we want to return a blank page. + // Here we need to check if there is no hijacked route and no template assigned but there is a content item. + // If this is the case we want to return a blank page. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, // for example for json rendering in headless. - if (!request.HasTemplate() + if (request.HasPublishedContent() + && !request.HasTemplate() && !_umbracoFeatures.Disabled.DisableTemplates && !def.HasHijackedRoute) { From 0ce90cf359a2093ab7a81407bb26569c3ca239c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 17:21:35 +1100 Subject: [PATCH 46/88] Moves UrlExtensions methods to new service and reduce the huge amount of allocated strings during routing --- .../DependencyInjection/UmbracoBuilder.cs | 2 + .../Routing/UmbracoRequestPaths.cs | 151 ++++++++++++++++++ src/Umbraco.Core/UriExtensions.cs | 145 ----------------- .../LiveModelsProvider.cs | 15 +- .../Objects/TestUmbracoContextFactory.cs | 3 +- .../Extensions/UriExtensionsTests.cs | 45 +----- .../Routing/UmbracoRequestPathsTests.cs | 105 ++++++++++++ .../Security/BackOfficeCookieManagerTests.cs | 34 ++-- .../EndpointRouteBuilderExtensionsTests.cs | 2 +- .../Routing/RoutableDocumentFilterTests.cs | 32 +++- .../Controllers/SurfaceControllerTests.cs | 9 +- .../Routing/UmbracoModuleTests.cs | 23 --- .../UnhandledExceptionLoggerMiddleware.cs | 6 +- .../PreviewAuthenticationMiddleware.cs | 26 +-- .../Routing/BackOfficeAreaRoutes.cs | 9 +- .../Routing/PreviewRoutes.cs | 1 + .../Security/BackOfficeCookieManager.cs | 43 +++-- .../Security/BackOfficeSessionIdValidator.cs | 26 ++- .../ConfigureBackOfficeCookieOptions.cs | 9 +- .../EndpointRouteBuilderExtensions.cs | 70 ++++---- .../Extensions/HttpRequestExtensions.cs | 34 ++-- .../Install/InstallAreaRoutes.cs | 1 + .../Middleware/UmbracoRequestMiddleware.cs | 19 ++- .../Routing/RoutableDocumentFilter.cs | 7 +- .../UmbracoContext/UmbracoContext.cs | 9 +- .../UmbracoContext/UmbracoContextFactory.cs | 9 +- .../Routing/UmbracoRouteValueTransformer.cs | 8 - src/Umbraco.Web/UmbracoContext.cs | 94 ++--------- src/Umbraco.Web/UmbracoInjectedModule.cs | 57 ------- 29 files changed, 473 insertions(+), 521 deletions(-) create mode 100644 src/Umbraco.Core/Routing/UmbracoRequestPaths.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs rename src/Umbraco.Web.Common/{Routing => Extensions}/EndpointRouteBuilderExtensions.cs (64%) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 96f01d111a..260ec5487f 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -24,6 +24,7 @@ using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Routing; using Umbraco.Core.Runtime; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -146,6 +147,7 @@ namespace Umbraco.Core.DependencyInjection this.AddNotificationHandler(); Services.AddSingleton(); + Services.AddSingleton(); this.AddNotificationHandler(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs new file mode 100644 index 0000000000..5ec8be071f --- /dev/null +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; + +namespace Umbraco.Core.Routing +{ + /// + /// Utility for checking paths + /// + public class UmbracoRequestPaths + { + private readonly string _backOfficePath; + private readonly string _mvcArea; + private readonly string _backOfficeMvcPath; + private readonly string _previewMvcPath; + private readonly string _installPath; + private readonly string _appPath; + private readonly List _aspLegacyJsExt = new List { ".asmx/", ".aspx/", ".ashx/", ".axd/", ".svc/" }; + private readonly List _aspLegacyExt = new List { ".asmx", ".aspx", ".ashx", ".axd", ".svc" }; + + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvironment hostingEnvironment) + { + var applicationPath = hostingEnvironment.ApplicationVirtualPath; + _appPath = applicationPath.TrimStart('/'); + + _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment) + .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); + + _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); + + _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; + _previewMvcPath = "/" + _mvcArea + "/Preview/"; + _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); + } + + /// + /// Checks if the current uri is a back office request + /// + /// + /// There are some special routes we need to check to properly determine this: + /// + /// If any route has an extension in the path like .aspx = back office + /// + /// These are def back office: + /// /Umbraco/BackOffice = back office + /// /Umbraco/Preview = back office + /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end + /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice + /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. + /// + /// These are def front-end: + /// /Umbraco/Surface = front-end + /// /Umbraco/Api = front-end + /// But if we've got this far we'll just have to assume it's front-end anyways. + /// + /// + public bool IsBackOfficeRequest(string absPath) + { + var fullUrlPath = absPath.TrimStart('/'); + var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/'); + + // check if this is in the umbraco back office + var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath); + + // if not, then def not back office + if (isUmbracoPath == false) + { + return false; + } + + // if its the normal /umbraco path + if (urlPath.InvariantEquals("/" + _mvcArea) + || urlPath.InvariantEquals("/" + _mvcArea + "/")) + { + return true; + } + + // check for a file extension + var extension = Path.GetExtension(absPath); + + // has an extension, def back office + if (extension.IsNullOrWhiteSpace() == false) + { + return true; + } + + // check for special case asp.net calls like: + // /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension + if (_aspLegacyJsExt.Any(x => urlPath.InvariantContains(x))) + { + return true; + } + + // check for special back office paths + if (urlPath.InvariantStartsWith(_backOfficeMvcPath) + || urlPath.InvariantStartsWith(_previewMvcPath)) + { + return true; + } + + // check for special front-end paths + // TODO: These should be constants - will need to update when we do front-end routing + if (urlPath.InvariantStartsWith("/" + _mvcArea + "/Surface/") + || urlPath.InvariantStartsWith("/" + _mvcArea + "/Api/")) + { + return false; + } + + // if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by + // checking how many parts the route has, for example, all PluginController routes will be routed like + // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} + // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a + // plugin controller for the front-end. + if (urlPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length >= 3) + { + return false; + } + + // if its anything else we can assume it's back office + return true; + } + + /// + /// Checks if the current uri is an install request + /// + public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath); + + /// + /// Rudimentary check to see if it's not a server side request + /// + public bool IsClientSideRequest(string absPath) + { + var ext = Path.GetExtension(absPath); + if (ext.IsNullOrWhiteSpace()) + { + return false; + } + + return _aspLegacyExt.Any(ext.InvariantEquals) == false; + } + } +} diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index ea846f7f7a..497159309a 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -13,151 +13,6 @@ namespace Umbraco.Core /// public static class UriExtensions { - /// - /// Checks if the current uri is a back office request - /// - /// - /// - /// - /// - /// - /// There are some special routes we need to check to properly determine this: - /// - /// If any route has an extension in the path like .aspx = back office - /// - /// These are def back office: - /// /Umbraco/BackOffice = back office - /// /Umbraco/Preview = back office - /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end - /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice - /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// - /// These are def front-end: - /// /Umbraco/Surface = front-end - /// /Umbraco/Api = front-end - /// But if we've got this far we'll just have to assume it's front-end anyways. - /// - /// - public static bool IsBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var applicationPath = hostingEnvironment.ApplicationVirtualPath; - - var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'}); - var appPath = applicationPath.TrimStart(new[] {'/'}); - var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/'); - - //check if this is in the umbraco back office - var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); - var isUmbracoPath = urlPath.InvariantStartsWith(backOfficePath.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/')); - //if not, then def not back office - if (isUmbracoPath == false) return false; - - var mvcArea = globalSettings.GetUmbracoMvcArea(hostingEnvironment); - //if its the normal /umbraco path - if (urlPath.InvariantEquals("/" + mvcArea) - || urlPath.InvariantEquals("/" + mvcArea + "/")) - { - return true; - } - - //check for a file extension - var extension = Path.GetExtension(url.LocalPath); - //has an extension, def back office - if (extension.IsNullOrWhiteSpace() == false) return true; - //check for special case asp.net calls like: - // /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension - if (urlPath.InvariantContains(".asmx/") - || urlPath.InvariantContains(".aspx/") - || urlPath.InvariantContains(".ashx/") - || urlPath.InvariantContains(".axd/") - || urlPath.InvariantContains(".svc/")) - { - return true; - } - - //check for special back office paths - if (urlPath.InvariantStartsWith("/" + mvcArea + "/BackOffice/") - || urlPath.InvariantStartsWith("/" + mvcArea + "/Preview/")) - { - return true; - } - - //check for special front-end paths - // TODO: These should be constants - will need to update when we do front-end routing - if (urlPath.InvariantStartsWith("/" + mvcArea + "/Surface/") - || urlPath.InvariantStartsWith("/" + mvcArea + "/Api/")) - { - return false; - } - - //if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by - // checking how many parts the route has, for example, all PluginController routes will be routed like - // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} - // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a - // plugin controller for the front-end. - if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3) - { - return false; - } - - // if its anything else we can assume it's back office - return true; - } - - /// - /// Checks if the current uri is an install request - /// - public static bool IsInstallerRequest(this Uri url, IHostingEnvironment hostingEnvironment) - { - var authority = url.GetLeftPart(UriPartial.Authority); - var afterAuthority = url.GetLeftPart(UriPartial.Query) - .TrimStart(authority) - .TrimStart("/"); - - // check if this is in the umbraco back office - return afterAuthority.InvariantStartsWith(hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install).TrimStart("/")); - } - - /// - /// Checks if the uri is a request for the default back office page - /// - public static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); - if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/")) - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/')) - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default") - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default/")) - { - return true; - } - - return false; - } - - /// - /// This is a performance tweak to check if this not an ASP.Net server file - /// .Net will pass these requests through to the module when in integrated mode. - /// We want to ignore all of these requests immediately. - /// - /// - /// - public static bool IsClientSideRequest(this Uri url) - { - try - { - var ext = Path.GetExtension(url.LocalPath); - if (ext.IsNullOrWhiteSpace()) return false; - var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"}; - return toInclude.Any(ext.InvariantEquals) == false; - } - catch (ArgumentException) - { - StaticApplicationLogging.Logger.LogDebug("Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath); - return false; - } - } - /// /// Rewrites the path of uri. /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index b8488e0852..d7fc051500 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -11,6 +11,7 @@ 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 { @@ -115,12 +116,16 @@ namespace Umbraco.ModelsBuilder.Embedded public void AppEndRequest(HttpContext context) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); - - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) + { return; + } + + if (!IsEnabled) + { + return; + } - if (!IsEnabled) return; GenerateModelsIfRequested(); } } diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs index 91d05ba0df..37b96b9947 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Moq; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Tests.Common; using Umbraco.Web; @@ -63,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects snapshotService.Object, new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), new AspNetCoreCookieManager(httpContextAccessor), diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs index a072a1a189..fc8ecd0474 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -25,49 +25,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions private IWebHostEnvironment _hostEnvironment; private GlobalSettings _globalSettings; - [TestCase("http://www.domain.com/umbraco/preview/frame?id=1234", "", true)] - [TestCase("http://www.domain.com/umbraco", "", true)] - [TestCase("http://www.domain.com/Umbraco/", "", true)] - [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] - [TestCase("http://www.domain.com/umbraco/test/test", "", false)] - [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] - [TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)] - [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] - [TestCase("http://www.domain.com/umbrac", "", false)] - [TestCase("http://www.domain.com/test", "", false)] - [TestCase("http://www.domain.com/test/umbraco", "", false)] - [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] - [TestCase("http://www.domain.com/Umbraco/anything", "", true)] - [TestCase("http://www.domain.com/Umbraco/anything/", "", true)] - [TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)] - [TestCase("http://www.domain.com/umbraco/api/blah", "", false)] - [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] - [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] - [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] - [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] - public void Is_Back_Office_Request(string input, string virtualPath, bool expected) - { - var source = new Uri(input); - var hostingEnvironment = CreateHostingEnvironment(virtualPath); - Assert.AreEqual(expected, source.IsBackOfficeRequest(_globalSettings, hostingEnvironment)); - } - - [TestCase("http://www.domain.com/install", true)] - [TestCase("http://www.domain.com/Install/", true)] - [TestCase("http://www.domain.com/install/default.aspx", true)] - [TestCase("http://www.domain.com/install/test/test", true)] - [TestCase("http://www.domain.com/Install/test/test.aspx", true)] - [TestCase("http://www.domain.com/install/test/test.js", true)] - [TestCase("http://www.domain.com/instal", false)] - [TestCase("http://www.domain.com/umbraco", false)] - [TestCase("http://www.domain.com/umbraco/umbraco", false)] - public void Is_Installer_Request(string input, bool expected) - { - var source = new Uri(input); - var hostingEnvironment = CreateHostingEnvironment(); - Assert.AreEqual(expected, source.IsInstallerRequest(hostingEnvironment)); - } - private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") { var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs new file mode 100644 index 0000000000..da2ea985d0 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Routing; +using Umbraco.Web.Common.AspNetCore; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class UmbracoRequestPathsTests + { + private IWebHostEnvironment _hostEnvironment; + private GlobalSettings _globalSettings; + + [OneTimeSetUp] + public void Setup() + { + _hostEnvironment = Mock.Of(); + _globalSettings = new GlobalSettings(); + } + + private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") + { + var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; + var mockedOptionsMonitorOfHostingSettings = Mock.Of>(x => x.CurrentValue == hostingSettings); + return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, _hostEnvironment); + } + + [TestCase("/favicon.ico", true)] + [TestCase("/umbraco_client/Tree/treeIcons.css", true)] + [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)] + [TestCase("/base/somebasehandler", false)] + [TestCase("/", false)] + [TestCase("/home.aspx", false)] + public void Is_Client_Side_Request(string url, bool assert) + { + var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + + var uri = new Uri("http://test.com" + url); + var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); + Assert.AreEqual(assert, result); + } + + [Test] + public void Is_Client_Side_Request_InvalidPath_ReturnFalse() + { + var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + + // This URL is invalid. Default to false when the extension cannot be determined + var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); + var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); + Assert.AreEqual(false, result); + } + + [TestCase("http://www.domain.com/umbraco/preview/frame?id=1234", "", true)] + [TestCase("http://www.domain.com/umbraco", "", true)] + [TestCase("http://www.domain.com/Umbraco/", "", true)] + [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] + [TestCase("http://www.domain.com/umbraco/test/test", "", false)] + [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] + [TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)] + [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] + [TestCase("http://www.domain.com/umbrac", "", false)] + [TestCase("http://www.domain.com/test", "", false)] + [TestCase("http://www.domain.com/test/umbraco", "", false)] + [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] + [TestCase("http://www.domain.com/Umbraco/anything", "", true)] + [TestCase("http://www.domain.com/Umbraco/anything/", "", true)] + [TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)] + [TestCase("http://www.domain.com/umbraco/api/blah", "", false)] + [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] + [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] + [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] + [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] + public void Is_Back_Office_Request(string input, string virtualPath, bool expected) + { + var source = new Uri(input); + var hostingEnvironment = CreateHostingEnvironment(virtualPath); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); + Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); + } + + [TestCase("http://www.domain.com/install", true)] + [TestCase("http://www.domain.com/Install/", true)] + [TestCase("http://www.domain.com/install/default.aspx", true)] + [TestCase("http://www.domain.com/install/test/test", true)] + [TestCase("http://www.domain.com/Install/test/test.aspx", true)] + [TestCase("http://www.domain.com/install/test/test.js", true)] + [TestCase("http://www.domain.com/instal", false)] + [TestCase("http://www.domain.com/umbraco", false)] + [TestCase("http://www.domain.com/umbraco/umbraco", false)] + public void Is_Installer_Request(string input, bool expected) + { + var source = new Uri(input); + var hostingEnvironment = CreateHostingEnvironment(); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); + Assert.AreEqual(expected, umbracoRequestPaths.IsInstallerRequest(source.AbsolutePath)); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index b677f11f2c..569c79faef 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -2,11 +2,13 @@ // See LICENSE for more details. using System; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Extensions; using Umbraco.Web; using Umbraco.Web.BackOffice.Controllers; @@ -26,10 +28,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(), - globalSettings); + new UmbracoRequestPaths(Options.Create(globalSettings), Mock.Of())); - var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); + var result = mgr.ShouldAuthenticateRequest("/umbraco"); Assert.IsFalse(result); } @@ -43,10 +44,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"))); - var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); + var result = mgr.ShouldAuthenticateRequest("/umbraco"); Assert.IsTrue(result); } @@ -63,13 +65,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"))); - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); + var result = mgr.ShouldAuthenticateRequest(remainingTimeoutSecondsPath); Assert.IsTrue(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{isAuthPath}")); + result = mgr.ShouldAuthenticateRequest(isAuthPath); Assert.IsTrue(result); } @@ -83,14 +86,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"))); - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); + var result = mgr.ShouldAuthenticateRequest("/notbackoffice"); Assert.IsFalse(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/api/notbackoffice")); + result = mgr.ShouldAuthenticateRequest("/umbraco/api/notbackoffice"); Assert.IsFalse(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/surface/notbackoffice")); + result = mgr.ShouldAuthenticateRequest("/umbraco/surface/notbackoffice"); Assert.IsFalse(result); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs index f5b491a8af..0990cb9d9a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Routing; using NUnit.Framework; using Umbraco.Core; using Umbraco.Extensions; -using Umbraco.Web.Common.Routing; +using Umbraco.Web.Common.Extensions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs index 87ee6d0ea0..8c42779372 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -76,13 +76,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing RouteEndpoint endpoint1 = CreateEndpoint( "Umbraco/RenderMvc/{action?}/{id?}", new { controller = "RenderMvc" }, - "Umbraco_default", 0); RouteEndpoint endpoint2 = CreateEndpoint( "api/{controller?}/{id?}", new { action = "Index" }, - "WebAPI", 1); var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2); @@ -97,16 +95,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing routableDocFilter.IsDocumentRequest(url)); } + [TestCase("/umbraco", true)] + [TestCase("/umbraco/", true)] + [TestCase("/umbraco/Default", true)] + [TestCase("/umbraco/default/", true)] + [TestCase("/umbraco/default/123", true)] + [TestCase("/umbraco/default/blah/123", false)] + public void Is_Reserved_By_Default_Back_Office_Route(string url, bool isReserved) + { + var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + + RouteEndpoint endpoint1 = CreateEndpoint( + "umbraco/{action}/{id?}", + new { controller = "BackOffice", action = "Default" }, + 0); + + var endpointDataSource = new DefaultEndpointDataSource(endpoint1); + + var routableDocFilter = new RoutableDocumentFilter( + globalSettings, + GetHostingEnvironment(), + endpointDataSource); + + Assert.AreEqual( + !isReserved, // not reserved if it's a document request + routableDocFilter.IsDocumentRequest(url)); + } + // borrowed from https://github.com/dotnet/aspnetcore/blob/19559e73da2b6d335b864ed2855dd8a0c7a207a0/src/Mvc/Mvc.Core/test/Routing/ControllerLinkGeneratorExtensionsTest.cs#L171 private RouteEndpoint CreateEndpoint( string template, object defaults = null, - string name = null, int order = 0) => new RouteEndpoint( (httpContext) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, null), order, new EndpointMetadataCollection(Array.Empty()), - name); + null); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index 07f8118ad0..1ea3e99b54 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Tests.Common; @@ -49,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -80,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -115,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers publishedSnapshotService.Object, new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -149,7 +150,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 166dea39f8..2ec0113c2f 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -32,7 +32,6 @@ namespace Umbraco.Tests.Routing ( runtime, logger, - null, // FIXME: PublishedRouter complexities... Mock.Of(), globalSettings, HostingEnvironment @@ -77,28 +76,6 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(assert, result.Success); } - [TestCase("/favicon.ico", true)] - [TestCase("/umbraco_client/Tree/treeIcons.css", true)] - [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)] - [TestCase("/base/somebasehandler", false)] - [TestCase("/", false)] - [TestCase("/home.aspx", false)] - public void Is_Client_Side_Request(string url, bool assert) - { - var uri = new Uri("http://test.com" + url); - var result = uri.IsClientSideRequest(); - Assert.AreEqual(assert, result); - } - - [Test] - public void Is_Client_Side_Request_InvalidPath_ReturnFalse() - { - //This URL is invalid. Default to false when the extension cannot be determined - var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); - var result = uri.IsClientSideRequest(); - Assert.AreEqual(false, result); - } - //NOTE: This test shows how we can test most of the HttpModule, it however is testing a method that no longer exists and is testing too much, // we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc... // to ensure that each one is individually tested. diff --git a/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs b/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs index db6162afa8..e2192f694b 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Filters { @@ -21,9 +22,8 @@ namespace Umbraco.Web.BackOffice.Filters public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); // If it's a client side request just call next and don't try to log anything - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) { await next(context); } @@ -36,7 +36,7 @@ namespace Umbraco.Web.BackOffice.Filters } catch (Exception e) { - _logger.LogError(e, "Unhandled controller exception occurred for request '{RequestUrl}'", requestUri.AbsoluteUri); + _logger.LogError(e, "Unhandled controller exception occurred for request '{RequestUrl}'", context.Request.GetEncodedPathAndQuery()); // Throw the error again, just in case it gets handled throw; } diff --git a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs index 06715b4ad1..85bc7c9ef7 100644 --- a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs @@ -1,13 +1,10 @@ +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Middleware @@ -17,17 +14,7 @@ namespace Umbraco.Web.BackOffice.Middleware /// public class PreviewAuthenticationMiddleware : IMiddleware { - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - - public PreviewAuthenticationMiddleware( - IOptions globalSettings, - IHostingEnvironment hostingEnvironment) - { - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - } - + /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var request = context.Request; @@ -35,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Middleware { var isPreview = request.HasPreviewCookie() && context.User != null - && !request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment); + && !request.IsBackOfficeRequest(); if (isPreview) { @@ -43,7 +30,9 @@ namespace Umbraco.Web.BackOffice.Middleware .Get(Constants.Security.BackOfficeAuthenticationType); if (cookieOptions == null) + { throw new InvalidOperationException("No cookie options found with name " + Constants.Security.BackOfficeAuthenticationType); + } // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing. // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication @@ -55,11 +44,12 @@ namespace Umbraco.Web.BackOffice.Middleware { var backOfficeIdentity = unprotected.Principal.GetUmbracoIdentity(); if (backOfficeIdentity != null) + { // Ok, we've got a real ticket, now we can add this ticket's identity to the current // Principal, this means we'll have 2 identities assigned to the principal which we can // use to authorize the preview and allow for a back office User. - context.User.AddIdentity(backOfficeIdentity); + } } } diff --git a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs index c3fbc9c556..39cfe0002b 100644 --- a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; using Umbraco.Web.WebApi; @@ -61,14 +62,16 @@ namespace Umbraco.Web.BackOffice.Routing /// /// Map the minimal routes required to load the back office login and auth /// - /// private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints) { - endpoints.MapUmbracoRoute(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, + endpoints.MapUmbracoRoute( + _umbracoPathSegment, + Constants.Web.Mvc.BackOfficeArea, string.Empty, "Default", includeControllerNameInRoute: false, constraints: + // Limit the action/id to only allow characters - this is so this route doesn't hog all other // routes like: /umbraco/channels/word.aspx, etc... // (Not that we have to worry about too many of those these days, there still might be a need for these constraints). diff --git a/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs b/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs index d6c961ed54..947e7ac468 100644 --- a/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.SignalR; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; namespace Umbraco.Web.BackOffice.Routing diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index 513bbd255c..7d3d392712 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -1,14 +1,9 @@ -using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Extensions; +using Umbraco.Core.Routing; namespace Umbraco.Web.BackOffice.Security { @@ -23,9 +18,8 @@ namespace Umbraco.Web.BackOffice.Security { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IRuntimeState _runtime; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly GlobalSettings _globalSettings; private readonly string[] _explicitPaths; + private readonly UmbracoRequestPaths _umbracoRequestPaths; /// /// Initializes a new instance of the class. @@ -33,10 +27,10 @@ namespace Umbraco.Web.BackOffice.Security public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, - IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings) - : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, null) - { } + UmbracoRequestPaths umbracoRequestPaths) + : this(umbracoContextAccessor, runtime, null, umbracoRequestPaths) + { + } /// /// Initializes a new instance of the class. @@ -44,21 +38,18 @@ namespace Umbraco.Web.BackOffice.Security public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, - IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings, - IEnumerable explicitPaths) + IEnumerable explicitPaths, + UmbracoRequestPaths umbracoRequestPaths) { _umbracoContextAccessor = umbracoContextAccessor; _runtime = runtime; - _hostingEnvironment = hostingEnvironment; - _globalSettings = globalSettings; _explicitPaths = explicitPaths?.ToArray(); + _umbracoRequestPaths = umbracoRequestPaths; } /// /// Determines if we should authenticate the request /// - /// The to check /// true if the request should be authenticated /// /// We auth the request when: @@ -66,7 +57,7 @@ namespace Umbraco.Web.BackOffice.Security /// * it is an installer request /// * it is a preview request /// - public bool ShouldAuthenticateRequest(Uri requestUri) + public bool ShouldAuthenticateRequest(string absPath) { // Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues @@ -82,14 +73,14 @@ namespace Umbraco.Web.BackOffice.Security // check the explicit paths if (_explicitPaths != null) { - return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath)); + return _explicitPaths.Any(x => x.InvariantEquals(absPath)); } if (// check back office - requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) + _umbracoRequestPaths.IsBackOfficeRequest(absPath) // check installer - || requestUri.IsInstallerRequest(_hostingEnvironment)) + || _umbracoRequestPaths.IsInstallerRequest(absPath)) { return true; } @@ -103,16 +94,18 @@ namespace Umbraco.Web.BackOffice.Security /// string Microsoft.AspNetCore.Authentication.Cookies.ICookieManager.GetRequestCookie(HttpContext context, string key) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); + var absPath = context.Request.Path; - if (_umbracoContextAccessor.UmbracoContext == null || requestUri.IsClientSideRequest()) + if (_umbracoContextAccessor.UmbracoContext == null || _umbracoRequestPaths.IsClientSideRequest(absPath)) { return null; } - return ShouldAuthenticateRequest(requestUri) == false + return ShouldAuthenticateRequest(absPath) == false + // Don't auth request, don't return a cookie ? null + // Return the default implementation : GetRequestCookie(context, key); } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs index 1ccb94e988..5efbf65b78 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs @@ -8,13 +8,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security { +#pragma warning disable IDE0065 // Misplaced using directive using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager; +#pragma warning restore IDE0065 // Misplaced using directive /// /// Used to validate a cookie against a user's session id @@ -36,21 +37,24 @@ namespace Umbraco.Web.BackOffice.Security public const string CookieName = "UMB_UCONTEXT_C"; private readonly ISystemClock _systemClock; private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IBackOfficeUserManager _userManager; - public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions globalSettings, IHostingEnvironment hostingEnvironment, IBackOfficeUserManager userManager) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions globalSettings, IBackOfficeUserManager userManager) { _systemClock = systemClock; _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; _userManager = userManager; } public async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidatePrincipalContext context) { - if (!context.Request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment)) + if (!context.Request.IsBackOfficeRequest()) + { return; + } var valid = await ValidateSessionAsync(validateInterval, context.HttpContext, context.Options.CookieManager, _systemClock, context.Properties.IssuedUtc, context.Principal.Identity as ClaimsIdentity); @@ -81,7 +85,7 @@ namespace Umbraco.Web.BackOffice.Security DateTimeOffset? issuedUtc = null; var currentUtc = systemClock.UtcNow; - //read the last checked time from a custom cookie + // read the last checked time from a custom cookie var lastCheckedCookie = cookieManager.GetRequestCookie(httpContext, CookieName); if (lastCheckedCookie.IsNullOrWhiteSpace() == false) @@ -92,7 +96,7 @@ namespace Umbraco.Web.BackOffice.Security } } - //no cookie, use the issue time of the auth ticket + // no cookie, use the issue time of the auth ticket if (issuedUtc.HasValue == false) { issuedUtc = authTicketIssueDate; @@ -107,18 +111,24 @@ namespace Umbraco.Web.BackOffice.Security } if (validate == false) + { return true; + } var userId = currentIdentity.GetUserId(); var user = await _userManager.FindByIdAsync(userId); if (user == null) + { return false; + } var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); if (await _userManager.ValidateSessionIdAsync(userId, sessionId) == false) + { return false; + } - //we will re-issue the cookie last checked cookie + // we will re-issue the cookie last checked cookie cookieManager.AppendResponseCookie( httpContext, CookieName, diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index b8568a2f03..c267cb7489 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; @@ -36,6 +37,7 @@ namespace Umbraco.Web.BackOffice.Security private readonly IUserService _userService; private readonly IIpResolver _ipResolver; private readonly ISystemClock _systemClock; + private readonly UmbracoRequestPaths _umbracoRequestPaths; /// /// Initializes a new instance of the class. @@ -60,7 +62,8 @@ namespace Umbraco.Web.BackOffice.Security IDataProtectionProvider dataProtection, IUserService userService, IIpResolver ipResolver, - ISystemClock systemClock) + ISystemClock systemClock, + UmbracoRequestPaths umbracoRequestPaths) { _serviceProvider = serviceProvider; _umbracoContextAccessor = umbracoContextAccessor; @@ -72,6 +75,7 @@ namespace Umbraco.Web.BackOffice.Security _userService = userService; _ipResolver = ipResolver; _systemClock = systemClock; + _umbracoRequestPaths = umbracoRequestPaths; } /// @@ -115,8 +119,7 @@ namespace Umbraco.Web.BackOffice.Security options.CookieManager = new BackOfficeCookieManager( _umbracoContextAccessor, _runtimeState, - _hostingEnvironment, - _globalSettings); // _explicitPaths); TODO: Implement this once we do OAuth somehow + _umbracoRequestPaths); options.Events = new CookieAuthenticationEvents { diff --git a/src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs similarity index 64% rename from src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs rename to src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs index 1349145357..ccaa29544b 100644 --- a/src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs @@ -1,26 +1,18 @@ -using Microsoft.AspNetCore.Builder; +using System; +using System.Text; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using NUglify.Helpers; -using System; -using System.Text; using Umbraco.Extensions; -namespace Umbraco.Web.Common.Routing +namespace Umbraco.Web.Common.Extensions { public static class EndpointRouteBuilderExtensions { /// /// Used to map Umbraco controllers consistently /// - /// - /// - /// - /// - /// - /// - /// - /// public static void MapUmbracoRoute( this IEndpointRouteBuilder endpoints, Type controllerType, @@ -36,19 +28,26 @@ namespace Umbraco.Web.Common.Routing // build the route pattern var pattern = new StringBuilder(rootSegment); if (!prefixPathSegment.IsNullOrWhiteSpace()) + { pattern.Append("/").Append(prefixPathSegment); + } + if (includeControllerNameInRoute) + { pattern.Append("/").Append(controllerName); + } + pattern.Append("/").Append("{action}/{id?}"); var defaults = defaultAction.IsNullOrWhiteSpace() - ? (object) new { controller = controllerName } + ? (object)new { controller = controllerName } : new { controller = controllerName, action = defaultAction }; if (areaName.IsNullOrWhiteSpace()) { endpoints.MapControllerRoute( + // named consistently $"umbraco-{areaName}-{controllerName}".ToLowerInvariant(), pattern.ToString().ToLowerInvariant(), @@ -58,6 +57,7 @@ namespace Umbraco.Web.Common.Routing else { endpoints.MapAreaControllerRoute( + // named consistently $"umbraco-{areaName}-{controllerName}".ToLowerInvariant(), areaName, @@ -65,19 +65,11 @@ namespace Umbraco.Web.Common.Routing defaults, constraints); } - } /// /// Used to map Umbraco controllers consistently /// - /// - /// - /// - /// - /// - /// - /// public static void MapUmbracoRoute( this IEndpointRouteBuilder endpoints, string rootSegment, @@ -92,12 +84,6 @@ namespace Umbraco.Web.Common.Routing /// /// Used to map Umbraco api controllers consistently /// - /// - /// - /// - /// - /// If the route is a back office route - /// public static void MapUmbracoApiRoute( this IEndpointRouteBuilder endpoints, string rootSegment, @@ -111,13 +97,6 @@ namespace Umbraco.Web.Common.Routing /// /// Used to map Umbraco api controllers consistently /// - /// - /// - /// - /// - /// If the route is a back office route - /// - /// public static void MapUmbracoApiRoute( this IEndpointRouteBuilder endpoints, Type controllerType, @@ -126,10 +105,23 @@ namespace Umbraco.Web.Common.Routing bool isBackOffice, string defaultAction = "Index", object constraints = null) - => endpoints.MapUmbracoRoute(controllerType, rootSegment, areaName, - isBackOffice - ? (areaName.IsNullOrWhiteSpace() ? $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/Api" : $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/{areaName}") - : (areaName.IsNullOrWhiteSpace() ? "Api" : areaName), - defaultAction, true, constraints); + { + string prefixPathSegment = isBackOffice + ? areaName.IsNullOrWhiteSpace() + ? $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/Api" + : $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/{areaName}" + : areaName.IsNullOrWhiteSpace() + ? "Api" + : areaName; + + endpoints.MapUmbracoRoute( + controllerType, + rootSegment, + areaName, + prefixPathSegment, + defaultAction, + true, + constraints); + } } } diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index fe61941e5c..31e65edf65 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,34 +1,44 @@ -using System; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; namespace Umbraco.Extensions { + /// + /// Extension methods for + /// public static class HttpRequestExtensions { - /// /// Check if a preview cookie exist /// public static bool HasPreviewCookie(this HttpRequest request) => request.Cookies.TryGetValue(Constants.Web.PreviewCookieName, out var cookieVal) && !cookieVal.IsNullOrWhiteSpace(); - public static bool IsBackOfficeRequest(this HttpRequest request, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment); + /// + /// Returns true if the request is a back office request + /// + public static bool IsBackOfficeRequest(this HttpRequest request) + { + PathString absPath = request.Path; + UmbracoRequestPaths umbReqPaths = request.HttpContext.RequestServices.GetService(); + return umbReqPaths.IsBackOfficeRequest(absPath); + } + /// + /// Returns true if the request is for a client side extension + /// public static bool IsClientSideRequest(this HttpRequest request) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest(); - - public static bool IsDefaultBackOfficeRequest(this HttpRequest request, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsDefaultBackOfficeRequest(globalSettings, hostingEnvironment); + { + PathString absPath = request.Path; + UmbracoRequestPaths umbReqPaths = request.HttpContext.RequestServices.GetService(); + return umbReqPaths.IsClientSideRequest(absPath); + } public static string ClientCulture(this HttpRequest request) => request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; diff --git a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs index b21495e27d..f1fb7220bd 100644 --- a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs +++ b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Extensions; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; namespace Umbraco.Web.Common.Install diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 0474f2445c..30381dcb6a 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Extensions; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.PublishedCache.NuCache; @@ -59,10 +60,8 @@ namespace Umbraco.Web.Common.Middleware /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); - // do not process if client-side request - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) { await next(context); return; @@ -75,12 +74,14 @@ namespace Umbraco.Web.Common.Middleware bool isFrontEndRequest = umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest(); + var pathAndQuery = context.Request.GetEncodedPathAndQuery(); + try { if (isFrontEndRequest) { LogHttpRequest.TryGetCurrentHttpRequestId(out Guid httpRequestId, _requestCache); - _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); + _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, pathAndQuery); } try @@ -109,12 +110,12 @@ namespace Umbraco.Web.Common.Middleware if (isFrontEndRequest) { LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); - _logger.LogTrace("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, requestUri, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds); + _logger.LogTrace("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, pathAndQuery, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds); } try { - DisposeRequestCacheItems(_logger, _requestCache, requestUri); + DisposeRequestCacheItems(_logger, _requestCache, context.Request); } finally { @@ -126,10 +127,10 @@ namespace Umbraco.Web.Common.Middleware /// /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request /// - private static void DisposeRequestCacheItems(ILogger logger, IRequestCache requestCache, Uri requestUri) + private static void DisposeRequestCacheItems(ILogger logger, IRequestCache requestCache, HttpRequest request) { // do not process if client-side request - if (requestUri.IsClientSideRequest()) + if (request.IsClientSideRequest()) { return; } @@ -143,6 +144,7 @@ namespace Umbraco.Web.Common.Middleware keys.Add(i.Key); } } + // dispose each item and key that was found as disposable. foreach (var k in keys) { @@ -154,6 +156,7 @@ namespace Umbraco.Web.Common.Middleware { logger.LogError("Could not dispose item with key " + k, ex); } + try { k.DisposeIfDisposable(); diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index b00dede27a..1dc50a3fc2 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -7,10 +7,9 @@ using System.Threading; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Template; using Umbraco.Core; -using Umbraco.Core.Collections; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; namespace Umbraco.Web.Common.Routing { @@ -27,12 +26,10 @@ namespace Umbraco.Web.Common.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; private readonly object _routeLocker = new object(); - -#pragma warning disable IDE0044 // Add readonly modifier + private readonly List _backOfficePaths; private object _initLocker = new object(); private bool _isInit = false; private HashSet _reservedList; -#pragma warning restore IDE0044 // Add readonly modifier /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index 76a5823801..f7d3e61664 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -14,7 +15,6 @@ namespace Umbraco.Web /// public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { - private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; @@ -22,6 +22,7 @@ namespace Umbraco.Web private string _previewToken; private bool? _previewing; private readonly IBackOfficeSecurity _backofficeSecurity; + private readonly UmbracoRequestPaths _umbracoRequestPaths; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -30,7 +31,7 @@ namespace Umbraco.Web internal UmbracoContext( IPublishedSnapshotService publishedSnapshotService, IBackOfficeSecurity backofficeSecurity, - GlobalSettings globalSettings, + UmbracoRequestPaths umbracoRequestPaths, IHostingEnvironment hostingEnvironment, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility, @@ -43,7 +44,6 @@ namespace Umbraco.Web } VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); _hostingEnvironment = hostingEnvironment; _cookieManager = cookieManager; @@ -52,6 +52,7 @@ namespace Umbraco.Web ObjectCreated = DateTime.Now; UmbracoRequestId = Guid.NewGuid(); _backofficeSecurity = backofficeSecurity ?? throw new ArgumentNullException(nameof(backofficeSecurity)); + _umbracoRequestPaths = umbracoRequestPaths; // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); @@ -140,7 +141,7 @@ namespace Umbraco.Web { Uri requestUrl = _requestAccessor.GetRequestUrl(); if (requestUrl != null - && requestUrl.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false + && _umbracoRequestPaths.IsBackOfficeRequest(requestUrl.AbsolutePath) == false && _backofficeSecurity.CurrentUser != null) { var previewToken = _cookieManager.GetCookieValue(Constants.Web.PreviewCookieName); // may be null or empty diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs index ac0d776e71..67dfd72bad 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Web.PublishedCache; @@ -18,7 +19,7 @@ namespace Umbraco.Web private readonly IVariationContextAccessor _variationContextAccessor; private readonly IDefaultCultureAccessor _defaultCultureAccessor; - private readonly GlobalSettings _globalSettings; + private readonly UmbracoRequestPaths _umbracoRequestPaths; private readonly IHostingEnvironment _hostingEnvironment; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; @@ -33,7 +34,7 @@ namespace Umbraco.Web IPublishedSnapshotService publishedSnapshotService, IVariationContextAccessor variationContextAccessor, IDefaultCultureAccessor defaultCultureAccessor, - IOptions globalSettings, + UmbracoRequestPaths umbracoRequestPaths, IHostingEnvironment hostingEnvironment, UriUtility uriUtility, ICookieManager cookieManager, @@ -44,7 +45,7 @@ namespace Umbraco.Web _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _defaultCultureAccessor = defaultCultureAccessor ?? throw new ArgumentNullException(nameof(defaultCultureAccessor)); - _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings)); + _umbracoRequestPaths = umbracoRequestPaths ?? throw new ArgumentNullException(nameof(umbracoRequestPaths)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _uriUtility = uriUtility ?? throw new ArgumentNullException(nameof(uriUtility)); _cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager)); @@ -75,7 +76,7 @@ namespace Umbraco.Web return new UmbracoContext( _publishedSnapshotService, _backofficeSecurityAccessor.BackOfficeSecurity, - _globalSettings, + _umbracoRequestPaths, _hostingEnvironment, _variationContextAccessor, _uriUtility, diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index de6cb72edb..32b11bf876 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -71,14 +71,6 @@ namespace Umbraco.Web.Website.Routing return values; } - // Check for back office request - // TODO: This is how the module was doing it before but could just as easily be part of the RoutableDocumentFilter - // which still needs to be migrated. - if (httpContext.Request.IsDefaultBackOfficeRequest(_globalSettings, _hostingEnvironment)) - { - return values; - } - // Check if there is no existing content and return the no content controller if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent()) { diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 8707bea26b..7461364d3f 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -12,18 +12,12 @@ using Umbraco.Web.Security; namespace Umbraco.Web { - /// - /// Class that encapsulates Umbraco information of a specific HTTP request - /// + // NOTE: has all been ported to netcore but exists here just to keep the build working for tests + public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { private readonly IHttpContextAccessor _httpContextAccessor; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ICookieManager _cookieManager; private readonly Lazy _publishedSnapshot; - private string _previewToken; - private bool? _previewing; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -44,9 +38,6 @@ namespace Umbraco.Web if (backofficeSecurity == null) throw new ArgumentNullException(nameof(backofficeSecurity)); VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _httpContextAccessor = httpContextAccessor; - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); - _hostingEnvironment = hostingEnvironment; - _cookieManager = cookieManager; // ensure that this instance is disposed when the request terminates, though we *also* ensure // this happens in the Umbraco module since the UmbracoCOntext is added to the HttpContext items. @@ -134,68 +125,17 @@ namespace Umbraco.Web /// public IVariationContextAccessor VariationContextAccessor { get; } - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug - { - get - { - var request = GetRequestFromContext(); - //NOTE: the request can be null during app startup! - return Current.HostingEnvironment.IsDebugMode - && request != null - && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false - || string.IsNullOrEmpty(request["umbdebug"]) == false - || string.IsNullOrEmpty(request.Cookies["UMB-DEBUG"]?.Value) == false); - } - } + // NOTE: has been ported to netcore + public bool IsDebug => false; - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// - public bool InPreviewMode - { - get - { - if (_previewing.HasValue == false) DetectPreviewMode(); - return _previewing ?? false; - } - private set => _previewing = value; - } + // NOTE: has been ported to netcore + public bool InPreviewMode => false; - public string PreviewToken - { - get - { - if (_previewing.HasValue == false) DetectPreviewMode(); - return _previewToken; - } - } + // NOTE: has been ported to netcore + public string PreviewToken => null; - private void DetectPreviewMode() - { - var request = GetRequestFromContext(); - if (request?.Url != null - && request.Url.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false - && Security.CurrentUser != null) - { - var previewToken = _cookieManager.GetPreviewCookieValue(); // may be null or empty - _previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken; - } - - _previewing = _previewToken.IsNullOrWhiteSpace() == false; - } - - // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, - // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper - // default 'preview' mode - somehow we have to force it. and that could be recursive. - public IDisposable ForcedPreview(bool preview) - { - InPreviewMode = preview; - return PublishedSnapshot.ForcedPreview(preview, orig => InPreviewMode = orig); - } + // NOTE: has been ported to netcore + public IDisposable ForcedPreview(bool preview) => null; private HttpRequestBase GetRequestFromContext() { @@ -209,17 +149,7 @@ namespace Umbraco.Web } } - protected override void DisposeResources() - { - // DisposableObject ensures that this runs only once - - Security.DisposeIfDisposable(); - - // help caches release resources - // (but don't create caches just to dispose them) - // context is not multi-threaded - if (_publishedSnapshot.IsValueCreated) - _publishedSnapshot.Value.Dispose(); - } + // NOTE: has been ported to netcore + protected override void DisposeResources() { } } } diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 50d843932b..b50f4ce23e 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -3,7 +3,6 @@ using System.Web; using System.Web.Routing; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Exceptions; @@ -11,7 +10,6 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Web.Composing; using Umbraco.Web.Routing; -using RouteDirection = Umbraco.Web.Routing.RouteDirection; namespace Umbraco.Web { @@ -34,7 +32,6 @@ namespace Umbraco.Web { private readonly IRuntimeState _runtime; private readonly ILogger _logger; - private readonly IPublishedRouter _publishedRouter; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -42,31 +39,22 @@ namespace Umbraco.Web public UmbracoInjectedModule( IRuntimeState runtime, ILogger logger, - IPublishedRouter publishedRouter, IUmbracoContextFactory umbracoContextFactory, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { _runtime = runtime; _logger = logger; - _publishedRouter = publishedRouter; _umbracoContextFactory = umbracoContextFactory; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; } - #region HttpModule event handlers - /// /// Begins to process a request. /// - /// private void BeginRequest(HttpContextBase httpContext) { - // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; - // write the trace output for diagnostics at the end of the request httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); @@ -82,69 +70,25 @@ namespace Umbraco.Web /// /// Processes the Umbraco Request /// - /// /// - /// /// This will check if we are trying to route to the default back office page (i.e. ~/Umbraco/ or ~/Umbraco or ~/Umbraco/Default ) /// and ensure that the MVC handler executes for that. This is required because the route for /Umbraco will never execute because /// files/folders exist there and we cannot set the RouteCollection.RouteExistingFiles = true since that will muck a lot of other things up. /// So we handle it here and explicitly execute the MVC controller. - /// /// void ProcessRequest(HttpContextBase httpContext) { - // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; - - if (Current.UmbracoContext == null) - throw new InvalidOperationException("The Current.UmbracoContext is null, ProcessRequest cannot proceed unless there is a current UmbracoContext"); var umbracoContext = Current.UmbracoContext; - // re-write for the default back office path - if (httpContext.Request.Url.IsDefaultBackOfficeRequest(_globalSettings, _hostingEnvironment)) - { - if (EnsureRuntime(httpContext, umbracoContext.OriginalRequestUrl)) - RewriteToBackOfficeHandler(httpContext); - return; - } - // do not process if this request is not a front-end routable page var isRoutableAttempt = EnsureUmbracoRoutablePage(umbracoContext, httpContext); // raise event here UmbracoModule.OnRouteAttempt(this, new RoutableAttemptEventArgs(isRoutableAttempt.Result, umbracoContext)); if (isRoutableAttempt.Success == false) return; - - httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed"); - - // ok, process - - // note: requestModule.UmbracoRewrite also did some stripping of &umbPage - // from the querystring... that was in v3.x to fix some issues with pre-forms - // auth. Paul Sterling confirmed in Jan. 2013 that we can get rid of it. - - // instantiate, prepare and process the published content request - // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL - var requestBuilder = _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl).Result; - var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)).Result; - - // NOTE: This has been ported to netcore - // HandleHttpResponseStatus returns a value indicating that the request should - // not be processed any further, eg because it has been redirect. then, exit. - //if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) - // return; - //if (request.HasPublishedContent() == false) - // httpContext.RemapHandler(new PublishedContentNotFoundHandler()); - //else - // RewriteToUmbracoHandler(httpContext, request); } - #endregion - - #region Methods - /// /// Checks the current request and ensures that it is routable based on the structure of the request and URI /// @@ -251,7 +195,6 @@ namespace Umbraco.Web } - #endregion #region IHttpModule From 00968a332b500aeb7700e2baff94b0513feeb9a5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 17:28:08 +1100 Subject: [PATCH 47/88] fix options dependency --- .../Routing/RoutableDocumentFilterTests.cs | 7 ++++--- src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs index 8c42779372..b25da2351b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.Models; @@ -15,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestFixture] public class RoutableDocumentFilterTests { - private GlobalSettings GetGlobalSettings() => new GlobalSettings(); + private IOptions GetGlobalSettings() => Options.Create(new GlobalSettings()); private IHostingEnvironment GetHostingEnvironment() { @@ -86,7 +87,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2); var routableDocFilter = new RoutableDocumentFilter( - globalSettings, + Options.Create(globalSettings), GetHostingEnvironment(), endpointDataSource); @@ -113,7 +114,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var endpointDataSource = new DefaultEndpointDataSource(endpoint1); var routableDocFilter = new RoutableDocumentFilter( - globalSettings, + Options.Create(globalSettings), GetHostingEnvironment(), endpointDataSource); diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 1dc50a3fc2..8a928af72d 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; @@ -26,7 +27,6 @@ namespace Umbraco.Web.Common.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; private readonly object _routeLocker = new object(); - private readonly List _backOfficePaths; private object _initLocker = new object(); private bool _isInit = false; private HashSet _reservedList; @@ -34,9 +34,9 @@ namespace Umbraco.Web.Common.Routing /// /// Initializes a new instance of the class. /// - public RoutableDocumentFilter(GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) + public RoutableDocumentFilter(IOptions globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) { - _globalSettings = globalSettings; + _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; _endpointDataSource = endpointDataSource; _endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null); From 2feebe7a2cc14a30051b4835c27e61edc5995c9d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 17:54:21 +1100 Subject: [PATCH 48/88] Ensures the routable doc filter is used, adds notes about a small mem leak. --- .../Routing/RoutableDocumentFilter.cs | 22 ++++++++++++++----- .../Routing/UmbracoRouteValueTransformer.cs | 10 ++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 8a928af72d..f3f9c9b814 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Common.Routing /// public sealed class RoutableDocumentFilter { - private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; @@ -143,6 +143,11 @@ namespace Umbraco.Web.Common.Routing return true; } + // TODO: We have a problem here: + // For every page that is rendered we are storing the URL and if it's routable in _routeChecks which + // is a small memory leak. Not sure how we work around this since routes are all dynamic and we don't want + // to double route everything on each request. Maybe instead of a growing list it's a list with a max capacity? + // check if the current request matches a route, if so then it is reserved. var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); if (hasRoute) @@ -168,13 +173,20 @@ namespace Umbraco.Web.Common.Routing private bool MatchesEndpoint(string absPath) { - // Borrowed from https://stackoverflow.com/a/59550580 + // Borrowed and modified from https://stackoverflow.com/a/59550580 // Return a collection of Microsoft.AspNetCore.Http.Endpoint instances. - IEnumerable routeEndpoints = _endpointDataSource?.Endpoints.Cast(); - var routeValues = new RouteValueDictionary(); + IEnumerable routeEndpoints = _endpointDataSource?.Endpoints + .OfType() + .Where(x => + { + // We don't want to include dynamic endpoints in this check since we would have no idea if that + // matches since they will probably match everything. + bool isDynamic = x.Metadata.OfType().Any(x => x.IsDynamic); + return !isDynamic; + }); - // string localPath = new Uri(absPath).LocalPath; + var routeValues = new RouteValueDictionary(); // To get the matchedEndpoint of the provide url RouteEndpoint matchedEndpoint = routeEndpoints diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 32b11bf876..5d0c564df3 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -34,6 +34,7 @@ namespace Umbraco.Web.Website.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtime; private readonly IUmbracoRouteValuesFactory _routeValuesFactory; + private readonly RoutableDocumentFilter _routableDocumentFilter; /// /// Initializes a new instance of the class. @@ -45,7 +46,8 @@ namespace Umbraco.Web.Website.Routing IOptions globalSettings, IHostingEnvironment hostingEnvironment, IRuntimeState runtime, - IUmbracoRouteValuesFactory routeValuesFactory) + IUmbracoRouteValuesFactory routeValuesFactory, + RoutableDocumentFilter routableDocumentFilter) { _logger = logger; _umbracoContextAccessor = umbracoContextAccessor; @@ -54,6 +56,7 @@ namespace Umbraco.Web.Website.Routing _hostingEnvironment = hostingEnvironment; _runtime = runtime; _routeValuesFactory = routeValuesFactory; + _routableDocumentFilter = routableDocumentFilter; } /// @@ -71,6 +74,11 @@ namespace Umbraco.Web.Website.Routing return values; } + if (!_routableDocumentFilter.IsDocumentRequest(httpContext.Request.Path)) + { + return values; + } + // Check if there is no existing content and return the no content controller if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent()) { From 5176986ecb3267ebb00e0533b25948756c9399fd Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 17:59:18 +1100 Subject: [PATCH 49/88] reduces more strings --- src/Umbraco.Core/Routing/UmbracoRequestPaths.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index 5ec8be071f..6fa3328014 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -18,6 +18,8 @@ namespace Umbraco.Core.Routing private readonly string _mvcArea; private readonly string _backOfficeMvcPath; private readonly string _previewMvcPath; + private readonly string _surfaceMvcPath; + private readonly string _apiMvcPath; private readonly string _installPath; private readonly string _appPath; private readonly List _aspLegacyJsExt = new List { ".asmx/", ".aspx/", ".ashx/", ".axd/", ".svc/" }; @@ -39,6 +41,8 @@ namespace Umbraco.Core.Routing _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; _previewMvcPath = "/" + _mvcArea + "/Preview/"; + _surfaceMvcPath = "/" + _mvcArea + "/Surface/"; + _apiMvcPath = "/" + _mvcArea + "/Api/"; _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); } @@ -109,8 +113,8 @@ namespace Umbraco.Core.Routing // check for special front-end paths // TODO: These should be constants - will need to update when we do front-end routing - if (urlPath.InvariantStartsWith("/" + _mvcArea + "/Surface/") - || urlPath.InvariantStartsWith("/" + _mvcArea + "/Api/")) + if (urlPath.InvariantStartsWith(_surfaceMvcPath) + || urlPath.InvariantStartsWith(_apiMvcPath)) { return false; } From 8f28569444df3febc713ed4d9f54c6cdb96efda1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 18:03:10 +1100 Subject: [PATCH 50/88] more notes --- src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index f3f9c9b814..8f61918121 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -147,6 +147,9 @@ namespace Umbraco.Web.Common.Routing // For every page that is rendered we are storing the URL and if it's routable in _routeChecks which // is a small memory leak. Not sure how we work around this since routes are all dynamic and we don't want // to double route everything on each request. Maybe instead of a growing list it's a list with a max capacity? + // BUT... then do we need to do this at all? So long as the catch all route is registered LAST shouldn't all other routes + // just match before it anyways and then we don't need to check? The strange part is that the "/umbraco" route doesn't automatically + // match so we need to investigate that first. // check if the current request matches a route, if so then it is reserved. var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); From bd4006c577035bf907f69092d5b9d8d692582b84 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 13:39:09 +1100 Subject: [PATCH 51/88] Fixes the custom RequestCultureProvider to dynamically add cultures to the supported cultures list, changes the request/builder to not reference a ICultureInfo and instead just a string to avoid allocations and confusion since the handlers will end up as a string anyways. Removes the unnecessary cultureinfo concurrentdictionary because CultureInfo.GetCultureInfo does the same thing. --- src/Umbraco.Core/Routing/AliasUrlProvider.cs | 8 ++-- .../Routing/ContentFinderByIdPath.cs | 2 +- .../Routing/ContentFinderByRedirectUrl.cs | 2 +- .../Routing/ContentFinderByUrl.cs | 2 +- .../Routing/ContentFinderByUrlAlias.cs | 2 +- .../Routing/DefaultUrlProvider.cs | 12 ++++-- src/Umbraco.Core/Routing/Domain.cs | 6 +-- src/Umbraco.Core/Routing/DomainUtilities.cs | 14 +++---- src/Umbraco.Core/Routing/IPublishedRequest.cs | 6 ++- .../Routing/IPublishedRequestBuilder.cs | 4 +- src/Umbraco.Core/Routing/PublishedRequest.cs | 4 +- .../Routing/PublishedRequestBuilder.cs | 4 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 14 +++---- src/Umbraco.Core/Routing/SiteDomainHelper.cs | 12 +++--- .../Security/AuthenticationExtensions.cs | 13 +----- .../Routing/ContentFinderByConfigured404.cs | 4 +- .../Routing/NotFoundHandlerHelper.cs | 4 +- .../Implement/LocalizedTextService.cs | 15 +++---- .../PublishedSnapshotService.cs | 4 +- .../CoreThings/ObjectExtensionsTests.cs | 2 +- .../Routing/SiteDomainHelperTests.cs | 40 +++++++++---------- .../Routing/PublishedRequestBuilderTests.cs | 6 +-- .../LegacyXmlPublishedCache/DomainCache.cs | 16 +++----- .../PublishedContent/PublishedRouterTests.cs | 2 +- .../ContentFinderByAliasWithDomainsTests.cs | 4 +- .../Routing/ContentFinderByUrlTests.cs | 6 +-- .../ContentFinderByUrlWithDomainsTests.cs | 2 +- .../Routing/DomainsAndCulturesTests.cs | 6 +-- ...derWithoutHideTopLevelNodeFromPathTests.cs | 10 ++--- .../Controllers/LanguageController.cs | 3 +- ...mbracoBackOfficeIdentityCultureProvider.cs | 20 ++++++++++ .../UmbracoPublishedContentCultureProvider.cs | 34 ++++++++++++++-- .../UmbracoRequestLocalizationOptions.cs | 4 +- .../Templates/TemplateRenderer.cs | 4 +- 34 files changed, 168 insertions(+), 123 deletions(-) diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs index 1e6056942f..65e094690e 100644 --- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Options; @@ -112,10 +112,10 @@ namespace Umbraco.Web.Routing // if the property varies, get the variant value, URL is "/" // but! only if the culture is published, else ignore - if (varies && !node.HasCulture(domainUri.Culture.Name)) continue; + if (varies && !node.HasCulture(domainUri.Culture)) continue; var umbracoUrlName = varies - ? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name) + ? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture) : node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); var aliases = umbracoUrlName?.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries); @@ -127,7 +127,7 @@ namespace Umbraco.Web.Routing { var path = "/" + alias; var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture.Name); + yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture); } } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 414d1da871..46571f5d65 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -77,7 +77,7 @@ namespace Umbraco.Web.Routing if (!string.IsNullOrEmpty(cultureFromQuerystring)) { // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.SetCulture(CultureInfo.GetCultureInfo(cultureFromQuerystring)); + frequest.SetCulture(cultureFromQuerystring); } frequest.SetPublishedContent(node); diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index b887ff11be..38f04d1ddb 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.Routing ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) : frequest.Uri.GetAbsolutePathDecoded(); - IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture.Name); + IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture); if (redirectUrl == null) { diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index 44ae4335e2..27893cd3de 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Test route {Route}", route); - IPublishedContent node = umbCtx.Content.GetByRoute(umbCtx.InPreviewMode, route, culture: docreq.Culture?.Name); + IPublishedContent node = umbCtx.Content.GetByRoute(umbCtx.InPreviewMode, route, culture: docreq.Culture); if (node != null) { docreq.SetPublishedContent(node); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index f7ebd6bbc5..770fdf4003 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Routing node = FindContentByAlias( umbCtx.Content, frequest.Domain != null ? frequest.Domain.ContentId : 0, - frequest.Culture.Name, + frequest.Culture, frequest.Uri.GetAbsolutePathDecoded()); if (node != null) diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 51c212aa3c..d739f851ad 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; @@ -83,7 +83,9 @@ namespace Umbraco.Web.Routing var umbracoContext = _umbracoContextAccessor.UmbracoContext; var node = umbracoContext.Content.GetById(id); if (node == null) + { yield break; + } // look for domains, walking up the tree var n = node; @@ -96,17 +98,19 @@ namespace Umbraco.Web.Routing // no domains = exit if (domainUris ==null) + { yield break; + } foreach (var d in domainUris) { - var culture = d?.Culture?.Name; + var culture = d?.Culture; - //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok + // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok var route = umbracoContext.Content.GetRouteById(id, culture); if (route == null) continue; - //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) + // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); var path = pos == 0 ? route : route.Substring(pos); diff --git a/src/Umbraco.Core/Routing/Domain.cs b/src/Umbraco.Core/Routing/Domain.cs index b9116c6b51..7d1808ef97 100644 --- a/src/Umbraco.Core/Routing/Domain.cs +++ b/src/Umbraco.Core/Routing/Domain.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Web.Routing { @@ -15,7 +15,7 @@ namespace Umbraco.Web.Routing /// The identifier of the content which supports the domain. /// The culture of the domain. /// A value indicating whether the domain is a wildcard domain. - public Domain(int id, string name, int contentId, CultureInfo culture, bool isWildcard) + public Domain(int id, string name, int contentId, string culture, bool isWildcard) { Id = id; Name = name; @@ -55,7 +55,7 @@ namespace Umbraco.Web.Routing /// /// Gets the culture of the domain. /// - public CultureInfo Culture { get; } + public string Culture { get; } /// /// Gets a value indicating whether the domain is a wildcard domain. diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 85347abb42..fa5d84836d 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -51,8 +51,8 @@ namespace Umbraco.Web.Routing var rootContentId = domain?.ContentId ?? -1; var wcDomain = FindWildcardDomainInPath(umbracoContext.Domains.GetAll(true), contentPath, rootContentId); - if (wcDomain != null) return wcDomain.Culture.Name; - if (domain != null) return domain.Culture.Name; + if (wcDomain != null) return wcDomain.Culture; + if (domain != null) return domain.Culture; return umbracoContext.Domains.DefaultCulture; } @@ -233,13 +233,13 @@ namespace Umbraco.Web.Routing if (culture != null) // try the supplied culture { - var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(culture)).ToList(); + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); if (cultureDomains.Count > 0) return cultureDomains; } if (defaultCulture != null) // try the defaultCulture culture { - var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(defaultCulture)).ToList(); + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); if (cultureDomains.Count > 0) return cultureDomains; } @@ -254,13 +254,13 @@ namespace Umbraco.Web.Routing if (culture != null) // try the supplied culture { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)); + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); if (domainAndUri != null) return domainAndUri; } if (defaultCulture != null) // try the defaultCulture culture { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(defaultCulture)); + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); if (domainAndUri != null) return domainAndUri; } diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 57b38dbff8..fedfd69dc3 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -45,7 +45,11 @@ namespace Umbraco.Web.Routing /// /// Gets the content request's culture. /// - CultureInfo Culture { get; } + /// + /// This will get mapped to a CultureInfo eventually but CultureInfo are expensive to create so we want to leave that up to the + /// localization middleware to do. See https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165. + /// + string Culture { get; } /// /// Gets the url to redirect to, when the content request triggers a redirect. diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index f8e5837838..ced443a89c 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Routing /// /// Gets the assigned (if any) /// - CultureInfo Culture { get; } + string Culture { get; } /// /// Gets a value indicating whether the current published content has been obtained @@ -64,7 +64,7 @@ namespace Umbraco.Web.Routing /// /// Sets the culture for the request /// - IPublishedRequestBuilder SetCulture(CultureInfo culture); + IPublishedRequestBuilder SetCulture(string culture); /// /// Sets the found for the request diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index bb1c28cab1..badfb27dd2 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, CultureInfo culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache, bool ignorePublishedContentCollisions) + public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); PublishedContent = publishedContent; @@ -48,7 +48,7 @@ namespace Umbraco.Web.Routing public DomainAndUri Domain { get; } /// - public CultureInfo Culture { get; } + public string Culture { get; } /// public string RedirectUrl { get; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 4230e73a78..faa793c7ff 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Routing public DomainAndUri Domain { get; private set; } /// - public CultureInfo Culture { get; private set; } + public string Culture { get; private set; } /// public ITemplate Template { get; private set; } @@ -89,7 +89,7 @@ namespace Umbraco.Web.Routing } /// - public IPublishedRequestBuilder SetCulture(CultureInfo culture) + public IPublishedRequestBuilder SetCulture(string culture) { Culture = culture; return this; diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index b61baa1990..4d7f0eef82 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -121,15 +121,15 @@ namespace Umbraco.Web.Routing return request.Build(); } - private void SetVariationContext(CultureInfo culture) + private void SetVariationContext(string culture) { VariationContext variationContext = _variationContextAccessor.VariationContext; - if (variationContext != null && variationContext.Culture == culture?.Name) + if (variationContext != null && variationContext.Culture == culture) { return; } - _variationContextAccessor.VariationContext = new VariationContext(culture?.Name); + _variationContextAccessor.VariationContext = new VariationContext(culture); } /// @@ -272,7 +272,7 @@ namespace Umbraco.Web.Routing } // variant, ensure that the culture corresponding to the domain's language is published - return domainDocument.Cultures.ContainsKey(domain.Culture.Name); + return domainDocument.Cultures.ContainsKey(domain.Culture); } domains = domains.Where(IsPublishedContentDomain).ToList(); @@ -302,10 +302,10 @@ namespace Umbraco.Web.Routing // not matching any existing domain _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); - request.SetCulture(defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture)); + request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name); } - _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name); + _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture); return request.Domain != null; } @@ -331,7 +331,7 @@ namespace Umbraco.Web.Routing if (domain != null) { request.SetCulture(domain.Culture); - _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name); + _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture); } else { diff --git a/src/Umbraco.Core/Routing/SiteDomainHelper.cs b/src/Umbraco.Core/Routing/SiteDomainHelper.cs index 35475ae292..bf43514f4a 100644 --- a/src/Umbraco.Core/Routing/SiteDomainHelper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -284,9 +284,11 @@ namespace Umbraco.Web.Routing // we do our best, but can't do the impossible // get the "default" domain ie the first one for the culture, else the first one (exists, length > 0) if (qualifiedSites == null) - return domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)) ?? - domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(defaultCulture)) ?? - domainAndUris.First(); + { + return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) + ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) + ?? domainAndUris.First(); + } // find a site that contains the current authority var currentSite = qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); @@ -308,7 +310,7 @@ namespace Umbraco.Web.Routing .FirstOrDefault(domainAndUri => domainAndUri != null); // random, really - ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.Name.InvariantEquals(culture)) ?? domainAndUris.First(); + ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); return ret; } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 607c4748cc..13eab4ff80 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Globalization; using System.Security.Principal; -using System.Text; using System.Threading; namespace Umbraco.Core.Security @@ -13,7 +9,6 @@ namespace Umbraco.Core.Security /// /// Ensures that the thread culture is set based on the back office user's culture /// - /// public static void EnsureCulture(this IIdentity identity) { var culture = GetCulture(identity); @@ -27,16 +22,10 @@ namespace Umbraco.Core.Security { if (identity is UmbracoBackOfficeIdentity umbIdentity && umbIdentity.IsAuthenticated) { - return UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s)); + return CultureInfo.GetCultureInfo(umbIdentity.Culture); } return null; } - - - /// - /// Used so that we aren't creating a new CultureInfo object for every single request - /// - private static readonly ConcurrentDictionary UserCultures = new ConcurrentDictionary(); } } diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 231a68df58..5634fa4a93 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -54,7 +54,7 @@ namespace Umbraco.Web.Routing _logger.LogDebug("Looking for a page to handle 404."); // try to find a culture as best as we can - CultureInfo errorCulture = CultureInfo.CurrentUICulture; + string errorCulture = CultureInfo.CurrentUICulture.Name; if (frequest.Domain != null) { errorCulture = frequest.Domain.Culture; @@ -67,7 +67,7 @@ namespace Umbraco.Web.Routing while (pos > 1) { route = route.Substring(0, pos); - node = umbCtx.Content.GetByRoute(route, culture: frequest?.Culture?.Name); + node = umbCtx.Content.GetByRoute(route, culture: frequest?.Culture); if (node != null) { break; diff --git a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs index 74ce0979f6..d73b780974 100644 --- a/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs +++ b/src/Umbraco.Infrastructure/Routing/NotFoundHandlerHelper.cs @@ -21,12 +21,12 @@ namespace Umbraco.Web.Routing ContentErrorPage[] error404Collection, IEntityService entityService, IPublishedContentQuery publishedContentQuery, - CultureInfo errorCulture) + string errorCulture) { if (error404Collection.Length > 1) { // test if a 404 page exists with current culture thread - ContentErrorPage cultureErr = error404Collection.FirstOrDefault(x => x.Culture == errorCulture.Name) + ContentErrorPage cultureErr = error404Collection.FirstOrDefault(x => x.Culture.InvariantEquals(errorCulture)) ?? error404Collection.FirstOrDefault(x => x.Culture == "default"); // there should be a default one! if (cultureErr != null) diff --git a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs index 4d12f111e3..8547830ac7 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -86,7 +86,6 @@ namespace Umbraco.Core.Services.Implement /// /// Returns all key/values in storage for the given culture /// - /// public IDictionary GetAllStoredValues(CultureInfo culture) { if (culture == null) throw new ArgumentNullException("culture"); @@ -108,16 +107,18 @@ namespace Umbraco.Core.Services.Implement return result; } - //convert all areas + keys to a single key with a '/' + // convert all areas + keys to a single key with a '/' result = GetStoredTranslations(xmlSource, culture); - //merge with the English file in case there's keys in there that don't exist in the local file - var englishCulture = new CultureInfo("en-US"); + // merge with the English file in case there's keys in there that don't exist in the local file + var englishCulture = CultureInfo.GetCultureInfo("en-US"); if (culture.Equals(englishCulture) == false) { var englishResults = GetStoredTranslations(xmlSource, englishCulture); foreach (var englishResult in englishResults.Where(englishResult => result.ContainsKey(englishResult.Key) == false)) + { result.Add(englishResult.Key, englishResult.Value); + } } } else @@ -128,13 +129,13 @@ namespace Umbraco.Core.Services.Implement return result; } - //convert all areas + keys to a single key with a '/' + // convert all areas + keys to a single key with a '/' foreach (var area in _dictionarySource[culture]) { foreach (var key in area.Value) { var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key); - //i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case. + // i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case. if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 49283de276..5068e52b49 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -461,7 +461,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var domains = _serviceContext.DomainService.GetAll(true); foreach (var domain in domains .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) - .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard))) + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard))) { _domainStore.SetLocked(domain.Id, domain); } @@ -865,7 +865,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (domain == null) continue; if (domain.RootContentId.HasValue == false) continue; // anomaly if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly - var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode); + var culture = domain.LanguageIsoCode; _domainStore.SetLocked(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard)); break; } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs index 848edddf1c..b1ff2c8fbe 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings public void TestSetup() { _savedCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB"); // make sure the dates parse correctly + Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-GB"); // make sure the dates parse correctly } [TearDown] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs index 2aed3e0216..13f6794a5d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs @@ -19,8 +19,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TearDown] public void TearDown() => SiteDomainHelper.Clear(); // assuming this works! - private static readonly CultureInfo s_cultureFr = CultureInfo.GetCultureInfo("fr-fr"); - private static readonly CultureInfo s_cultureGb = CultureInfo.GetCultureInfo("en-gb"); + private static readonly string s_cultureFr = "fr-fr"; + private static readonly string s_cultureGb = "en-gb"; [Test] public void AddSites() @@ -185,7 +185,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new Domain(1, "domain1.com", -1, s_cultureGb, false), }; DomainAndUri[] domainAndUris = DomainAndUris(current, domains); - string output = helper.MapDomain(domainAndUris, current, s_cultureFr.Name, s_cultureFr.Name).Uri.ToString(); + string output = helper.MapDomain(domainAndUris, current, s_cultureFr, s_cultureFr).Uri.ToString(); Assert.AreEqual("https://domain1.com/", output); // will pick it all right @@ -196,7 +196,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new Domain(1, "https://domain2.com", -1, s_cultureGb, false) }; domainAndUris = DomainAndUris(current, domains); - output = helper.MapDomain(domainAndUris, current, s_cultureFr.Name, s_cultureFr.Name).Uri.ToString(); + output = helper.MapDomain(domainAndUris, current, s_cultureFr, s_cultureFr).Uri.ToString(); Assert.AreEqual("https://domain1.com/", output); current = new Uri("https://domain1.com/foo/bar"); @@ -206,7 +206,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new Domain(1, "https://domain4.com", -1, s_cultureGb, false) }; domainAndUris = DomainAndUris(current, domains); - output = helper.MapDomain(domainAndUris, current, s_cultureFr.Name, s_cultureFr.Name).Uri.ToString(); + output = helper.MapDomain(domainAndUris, current, s_cultureFr, s_cultureFr).Uri.ToString(); Assert.AreEqual("https://domain1.com/", output); current = new Uri("https://domain4.com/foo/bar"); @@ -216,7 +216,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new Domain(1, "https://domain4.com", -1, s_cultureGb, false) }; domainAndUris = DomainAndUris(current, domains); - output = helper.MapDomain(domainAndUris, current, s_cultureFr.Name, s_cultureFr.Name).Uri.ToString(); + output = helper.MapDomain(domainAndUris, current, s_cultureFr, s_cultureFr).Uri.ToString(); Assert.AreEqual("https://domain4.com/", output); } @@ -240,8 +240,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new DomainAndUri(new Domain(1, "domain1.com", -1, s_cultureFr, false), current), new DomainAndUri(new Domain(1, "domain2.com", -1, s_cultureGb, false), current), }, current, - s_cultureFr.Name, - s_cultureFr.Name).Uri.ToString(); + s_cultureFr, + s_cultureFr).Uri.ToString(); Assert.AreEqual("http://domain1.com/", output); // current is a site1 uri, domains do not contain current @@ -253,8 +253,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new DomainAndUri(new Domain(1, "domain1.net", -1, s_cultureFr, false), current), new DomainAndUri(new Domain(1, "domain2.net", -1, s_cultureGb, false), current) }, current, - s_cultureFr.Name, - s_cultureFr.Name).Uri.ToString(); + s_cultureFr, + s_cultureFr).Uri.ToString(); Assert.AreEqual("http://domain1.net/", output); // current is a site1 uri, domains do not contain current @@ -267,8 +267,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new DomainAndUri(new Domain(1, "domain2.net", -1, s_cultureFr, false), current), new DomainAndUri(new Domain(1, "domain1.net", -1, s_cultureGb, false), current) }, current, - s_cultureFr.Name, - s_cultureFr.Name).Uri.ToString(); + s_cultureFr, + s_cultureFr).Uri.ToString(); Assert.AreEqual("http://domain1.net/", output); } @@ -301,8 +301,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing }, current, true, - s_cultureFr.Name, - s_cultureFr.Name).ToArray(); + s_cultureFr, + s_cultureFr).ToArray(); Assert.AreEqual(1, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); @@ -320,8 +320,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing }, current, true, - s_cultureFr.Name, - s_cultureFr.Name).ToArray(); + s_cultureFr, + s_cultureFr).ToArray(); Assert.AreEqual(1, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); @@ -343,8 +343,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing }, current, true, - s_cultureFr.Name, - s_cultureFr.Name).ToArray(); + s_cultureFr, + s_cultureFr).ToArray(); Assert.AreEqual(3, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); @@ -364,8 +364,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing new DomainAndUri(new Domain(1, "domain1.org", -1, s_cultureGb, false), current), // yes: same site (though bogus setup) }, current, true, - s_cultureFr.Name, - s_cultureFr.Name).ToArray(); + s_cultureFr, + s_cultureFr).ToArray(); Assert.AreEqual(3, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs index cf4ca44f10..234226c3c7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Web/Routing/PublishedRequestBuilderTests.cs @@ -48,7 +48,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Web.Routing sut.SetDomain( new DomainAndUri( - new Domain(1, "test", 2, CultureInfo.GetCultureInfo("en-AU"), false), new Uri("https://example.com/en-au"))); + new Domain(1, "test", 2, "en-AU", false), new Uri("https://example.com/en-au"))); Assert.IsNotNull(sut.Domain); Assert.IsNotNull(sut.Culture); @@ -62,8 +62,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Web.Routing IPublishedContent content = Mock.Of(x => x.Id == 1); ITemplate template = Mock.Of(x => x.Id == 1); string[] cacheExt = new[] { "must-revalidate" }; - var auCulture = CultureInfo.GetCultureInfo("en-AU"); - var usCulture = CultureInfo.GetCultureInfo("en-US"); + var auCulture = "en-AU"; + var usCulture = "en-US"; var domain = new DomainAndUri( new Domain(1, "test", 2, auCulture, false), new Uri("https://example.com/en-au")); IReadOnlyDictionary headers = new Dictionary { ["Hello"] = "world" }; diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs index abaa239598..92c2691f90 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/DomainCache.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Linq; using Umbraco.Core; @@ -19,20 +19,14 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } /// - public IEnumerable GetAll(bool includeWildcards) - { - return _domainService.GetAll(includeWildcards) + public IEnumerable GetAll(bool includeWildcards) => _domainService.GetAll(includeWildcards) .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) - .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); - } + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard)); /// - public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) - { - return _domainService.GetAssignedDomains(documentId, includeWildcards) + public IEnumerable GetAssigned(int documentId, bool includeWildcards = false) => _domainService.GetAssignedDomains(documentId, includeWildcards) .Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false) - .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)); - } + .Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, x.LanguageIsoCode, x.IsWildcard)); /// public bool HasAssigned(int documentId, bool includeWildcards = false) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs index 52b76a0021..39fc49a9db 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedRouterTests.cs @@ -35,7 +35,7 @@ namespace Umbraco.Tests.PublishedContent var request = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var content = GetPublishedContentMock(); request.SetPublishedContent(content.Object); - request.SetCulture(new CultureInfo("en-AU")); + request.SetCulture("en-AU"); request.SetRedirect("/hello"); var result = publishedRouter.BuildRequest(request); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs index 4746720329..13f5e8b214 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByAliasWithDomainsTests.cs @@ -64,7 +64,9 @@ namespace Umbraco.Tests.Routing publishedRouter.FindDomain(request); if (expectedNode > 0) - Assert.AreEqual(expectedCulture, request.Culture.Name); + { + Assert.AreEqual(expectedCulture, request.Culture); + } var finder = new ContentFinderByUrlAlias(LoggerFactory.CreateLogger(), Mock.Of(), VariationContextAccessor, GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(request); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs index 2b5364c22a..c86fd0fe1c 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlTests.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Routing var snapshotService = CreatePublishedSnapshotService(globalSettings); var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings, snapshotService: snapshotService); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); - var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); + var frequest = await publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); Assert.IsTrue(globalSettings.HideTopLevelNodeFromPath); @@ -119,7 +119,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/"))); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite", -1, "en-US", false), new Uri("http://mysite/"))); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); @@ -147,7 +147,7 @@ namespace Umbraco.Tests.Routing var umbracoContext = GetUmbracoContext(urlString, globalSettings:globalSettings); var publishedRouter = CreatePublishedRouter(GetUmbracoContextAccessor(umbracoContext)); var frequest = await publishedRouter .CreateRequestAsync(umbracoContext.CleanedUmbracoUrl); - frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, CultureInfo.CurrentCulture, false), new Uri("http://mysite/æøå"))); + frequest.SetDomain(new DomainAndUri(new Domain(1, "mysite/æøå", -1, "en-US", false), new Uri("http://mysite/æøå"))); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs index 12115ba3ad..df8d372b98 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlWithDomainsTests.cs @@ -180,7 +180,7 @@ namespace Umbraco.Tests.Routing // must lookup domain else lookup by URL fails publishedRouter.FindDomain(frequest); - Assert.AreEqual(expectedCulture, frequest.Culture.Name); + Assert.AreEqual(expectedCulture, frequest.Culture); var lookup = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = lookup.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs index dd5fd6351d..cd4fdc6fdd 100644 --- a/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs +++ b/src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Routing // lookup domain publishedRouter.FindDomain(frequest); - Assert.AreEqual(expectedCulture, frequest.Culture.Name); + Assert.AreEqual(expectedCulture, frequest.Culture); var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); @@ -331,7 +331,7 @@ namespace Umbraco.Tests.Routing publishedRouter.HandleWildcardDomains(frequest); Assert.IsTrue(result); - Assert.AreEqual(expectedCulture, frequest.Culture.Name); + Assert.AreEqual(expectedCulture, frequest.Culture); Assert.AreEqual(frequest.PublishedContent.Id, expectedNode); } // domains such as "/en" are natively supported, and when instanciating @@ -377,7 +377,7 @@ namespace Umbraco.Tests.Routing publishedRouter.FindDomain(frequest); Assert.IsNotNull(frequest.Domain); - Assert.AreEqual(expectedCulture, frequest.Culture.Name); + Assert.AreEqual(expectedCulture, frequest.Culture); var finder = new ContentFinderByUrl(LoggerFactory.CreateLogger(), GetUmbracoContextAccessor(umbracoContext)); var result = finder.TryFindContent(frequest); diff --git a/src/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs b/src/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs index dca0caadc4..597a7e223e 100644 --- a/src/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs +++ b/src/Umbraco.Tests/Routing/UrlProviderWithoutHideTopLevelNodeFromPathTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -186,8 +186,8 @@ namespace Umbraco.Tests.Routing if (contentId != 9876) return Enumerable.Empty(); return new[] { - new Domain(2, "example.us", 9876, CultureInfo.GetCultureInfo("en-US"), false), //default - new Domain(3, "example.fr", 9876, CultureInfo.GetCultureInfo("fr-FR"), false) + new Domain(2, "example.us", 9876, "en-US", false), //default + new Domain(3, "example.fr", 9876, "fr-FR", false) }; }); domainCache.Setup(x => x.DefaultCulture).Returns(CultureInfo.GetCultureInfo("en-US").Name); @@ -240,8 +240,8 @@ namespace Umbraco.Tests.Routing if (contentId != 9876) return Enumerable.Empty(); return new[] { - new Domain(2, "example.us", 9876, CultureInfo.GetCultureInfo("en-US"), false), //default - new Domain(3, "example.fr", 9876, CultureInfo.GetCultureInfo("fr-FR"), false) + new Domain(2, "example.us", 9876, "en-US", false), //default + new Domain(3, "example.fr", 9876, "fr-FR", false) }; }); domainCache.Setup(x => x.DefaultCulture).Returns(CultureInfo.GetCultureInfo("en-US").Name); diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 21b205de0f..969c267821 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -50,6 +50,7 @@ namespace Umbraco.Web.BackOffice.Controllers // get cultures - new-ing instances to get proper display name, // in the current culture, and not the cached one // (see notes in Language class about culture info names) + // TODO: Fix this requirement, see https://github.com/umbraco/Umbraco-CMS/issues/3623 return CultureInfo.GetCultures(CultureTypes.AllCultures) .Where(x => !x.Name.IsNullOrWhiteSpace()) .Select(x => new CultureInfo(x.Name)) // important! diff --git a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs index a09230a3fc..741583413c 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs @@ -1,7 +1,9 @@ using System.Globalization; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.Options; using Umbraco.Core.Security; namespace Umbraco.Web.Common.Localization @@ -12,6 +14,13 @@ namespace Umbraco.Web.Common.Localization /// public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider { + private readonly RequestLocalizationOptions _localizationOptions; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoBackOfficeIdentityCultureProvider(RequestLocalizationOptions localizationOptions) => _localizationOptions = localizationOptions; + /// public override Task DetermineProviderCultureResult(HttpContext httpContext) { @@ -22,6 +31,17 @@ namespace Umbraco.Web.Common.Localization return NullProviderCultureResult; } + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + var cultureExists = _localizationOptions.SupportedCultures.Contains(culture); + + if (!cultureExists) + { + // add this as a supporting culture + _localizationOptions.SupportedCultures.Add(culture); + _localizationOptions.SupportedUICultures.Add(culture); + } + return Task.FromResult(new ProviderCultureResult(culture.Name)); } } diff --git a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs index cc683848c3..bedf5e73a7 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs @@ -1,8 +1,13 @@ -using System.Globalization; +using System; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Umbraco.Web.Common.Routing; using Umbraco.Web.Routing; @@ -13,19 +18,42 @@ namespace Umbraco.Web.Common.Localization /// public class UmbracoPublishedContentCultureProvider : RequestCultureProvider { + private readonly RequestLocalizationOptions _localizationOptions; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoPublishedContentCultureProvider(RequestLocalizationOptions localizationOptions) => _localizationOptions = localizationOptions; + /// public override Task DetermineProviderCultureResult(HttpContext httpContext) { if (httpContext.GetRouteValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken) is UmbracoRouteValues routeValues) { - CultureInfo culture = routeValues.PublishedRequest?.Culture; + string culture = routeValues.PublishedRequest?.Culture; if (culture != null) { - return Task.FromResult(new ProviderCultureResult(culture.Name)); + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo + // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165 + CultureInfo existingCulture = _localizationOptions.SupportedCultures.FirstOrDefault(supportedCulture => + StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase)); + + if (existingCulture == null) + { + // add this as a supporting culture + var ci = CultureInfo.GetCultureInfo(culture); + _localizationOptions.SupportedCultures.Add(ci); + _localizationOptions.SupportedUICultures.Add(ci); + } + + return Task.FromResult(new ProviderCultureResult(culture)); } } return NullProviderCultureResult; } + } } diff --git a/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs index 1a27798c35..a4c6d117ca 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoRequestLocalizationOptions.cs @@ -30,8 +30,8 @@ namespace Umbraco.Web.Common.Localization options.RequestCultureProviders = new List(); } - options.RequestCultureProviders.Insert(0, new UmbracoBackOfficeIdentityCultureProvider()); - options.RequestCultureProviders.Insert(1, new UmbracoPublishedContentCultureProvider()); + options.RequestCultureProviders.Insert(0, new UmbracoBackOfficeIdentityCultureProvider(options)); + options.RequestCultureProviders.Insert(1, new UmbracoPublishedContentCultureProvider(options)); } } } diff --git a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs index d9b7bf95a4..23b2ba6466 100644 --- a/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs +++ b/src/Umbraco.Web.Common/Templates/TemplateRenderer.cs @@ -87,8 +87,8 @@ namespace Umbraco.Web.Common.Templates var defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault(); requestBuilder.SetCulture(defaultLanguage == null - ? CultureInfo.CurrentUICulture - : defaultLanguage.CultureInfo); + ? CultureInfo.CurrentUICulture.Name + : defaultLanguage.CultureInfo.Name); } else { From 8b80b61e0b116c1f9e0c36f762eb431df5c69730 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Mon, 11 Jan 2021 13:55:27 +1100 Subject: [PATCH 52/88] Update src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs Co-authored-by: Bjarke Berg --- .../Routing/ContentFinderByUrlAndTemplateTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs index 959836d3ff..881cead1fb 100644 --- a/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs +++ b/src/Umbraco.Tests/Routing/ContentFinderByUrlAndTemplateTests.cs @@ -49,8 +49,9 @@ namespace Umbraco.Tests.Routing Assert.IsTrue(result); Assert.IsNotNull(frequest.PublishedContent); - Assert.IsNotNull(frequest.GetTemplateAlias()); - Assert.AreEqual("blah".ToUpperInvariant(), frequest.GetTemplateAlias().ToUpperInvariant()); + var templateAlias = frequest.GetTemplateAlias(); + Assert.IsNotNull(templateAlias ); + Assert.AreEqual("blah".ToUpperInvariant(), templateAlias.ToUpperInvariant()); } } } From c2c42ada3b668b556d18afe189632156103cc2d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 13:55:44 +1100 Subject: [PATCH 53/88] Fixes review notes --- src/Umbraco.Core/Routing/PublishedRequest.cs | 4 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 9 ++- .../Templates/IUmbracoComponentRenderer.cs | 17 ++--- .../Templates/UmbracoComponentRenderer.cs | 70 +++++++------------ src/Umbraco.Web.Website/UmbracoHelper.cs | 12 ++-- src/Umbraco.Web/UmbracoHelper.cs | 12 ++-- 6 files changed, 53 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index badfb27dd2..e42211da49 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool cacheabilityNoCache, bool ignorePublishedContentCollisions) + public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); PublishedContent = publishedContent; @@ -25,7 +25,7 @@ namespace Umbraco.Web.Routing ResponseStatusCode = responseStatusCode; CacheExtensions = cacheExtensions; Headers = headers; - SetNoCacheHeader = cacheabilityNoCache; + SetNoCacheHeader = setNoCacheHeader; IgnorePublishedContentCollisions = ignorePublishedContentCollisions; } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 4d7f0eef82..4e0cda4041 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -665,7 +665,14 @@ namespace Umbraco.Web.Routing var templateId = request.PublishedContent.TemplateId; ITemplate template = GetTemplate(templateId); request.SetTemplate(template); - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); + if (template != null) + { + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); + } + else + { + _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", templateId); + } } else { diff --git a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs index 5444ef3f96..fcfdd815f6 100644 --- a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading.Tasks; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; @@ -12,35 +13,31 @@ namespace Umbraco.Core.Templates /// /// Renders the template for the specified pageId and an optional altTemplateId /// - /// + /// The content id /// If not specified, will use the template assigned to the node - /// - IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null); + Task RenderTemplateAsync(int contentId, int? altTemplateId = null); /// /// Renders the macro with the specified alias. /// - /// + /// The content id /// The alias. - /// IHtmlEncodedString RenderMacro(int contentId, string alias); /// /// Renders the macro with the specified alias, passing in the specified parameters. /// - /// + /// The content id /// The alias. /// The parameters. - /// IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters); /// /// Renders the macro with the specified alias, passing in the specified parameters. /// - /// + /// The content id /// The alias. /// The parameters. - /// IHtmlEncodedString RenderMacro(int contentId, string alias, IDictionary parameters); /// diff --git a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs index 53e856ced4..d0b6972bc3 100644 --- a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.IO; using Umbraco.Web.Templates; @@ -8,6 +8,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Web; using Umbraco.Web.Macros; +using System.Threading.Tasks; namespace Umbraco.Core.Templates { @@ -24,6 +25,9 @@ namespace Umbraco.Core.Templates private readonly IMacroRenderer _macroRenderer; private readonly ITemplateRenderer _templateRenderer; + /// + /// Initializes a new instance of the class. + /// public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer) { _umbracoContextAccessor = umbracoContextAccessor; @@ -31,76 +35,55 @@ namespace Umbraco.Core.Templates _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); } - /// - /// Renders the template for the specified pageId and an optional altTemplateId - /// - /// - /// If not specified, will use the template assigned to the node - /// - public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null) + /// + public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null) { using (var sw = new StringWriter()) { try { - _templateRenderer.RenderAsync(contentId, altTemplateId, sw); + await _templateRenderer.RenderAsync(contentId, altTemplateId, sw); } catch (Exception ex) { sw.Write("", contentId, ex); } + return new HtmlEncodedString(sw.ToString()); } } - /// - /// Renders the macro with the specified alias. - /// - /// - /// The alias. - /// - public IHtmlEncodedString RenderMacro(int contentId, string alias) - { - return RenderMacro(contentId, alias, new { }); - } + /// + public IHtmlEncodedString RenderMacro(int contentId, string alias) => RenderMacro(contentId, alias, new { }); - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// - /// The alias. - /// The parameters. - /// - public IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters) - { - return RenderMacro(contentId, alias, parameters?.ToDictionary()); - } + /// + public IHtmlEncodedString RenderMacro(int contentId, string alias, object parameters) => RenderMacro(contentId, alias, parameters?.ToDictionary()); - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// - /// The alias. - /// The parameters. - /// + /// public IHtmlEncodedString RenderMacro(int contentId, string alias, IDictionary parameters) { if (contentId == default) + { throw new ArgumentException("Invalid content id " + contentId); + } var content = _umbracoContextAccessor.UmbracoContext.Content?.GetById(contentId); if (content == null) + { throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); + } return RenderMacro(content, alias, parameters); } - + /// public IHtmlEncodedString RenderMacroForContent(IPublishedContent content, string alias, IDictionary parameters) { if(content == null) + { throw new InvalidOperationException("Cannot render a macro, IPublishedContent is null"); + } return RenderMacro(content, alias, parameters); } @@ -108,16 +91,15 @@ namespace Umbraco.Core.Templates /// /// Renders the macro with the specified alias, passing in the specified parameters. /// - /// The macro alias. - /// The parameters. - /// The content used for macro rendering - /// private IHtmlEncodedString RenderMacro(IPublishedContent content, string alias, IDictionary parameters) { - if (content == null) throw new ArgumentNullException(nameof(content)); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } // TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten. - //NOTE: the value could have HTML encoded values, so we need to deal with that + // NOTE: the value could have HTML encoded values, so we need to deal with that var macroProps = parameters?.ToDictionary( x => x.Key.ToLowerInvariant(), i => (i.Value is string) ? WebUtility.HtmlDecode(i.Value.ToString()) : i.Value); diff --git a/src/Umbraco.Web.Website/UmbracoHelper.cs b/src/Umbraco.Web.Website/UmbracoHelper.cs index d44ca4e5fe..ed6d5d36b0 100644 --- a/src/Umbraco.Web.Website/UmbracoHelper.cs +++ b/src/Umbraco.Web.Website/UmbracoHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Xml.XPath; using Umbraco.Core; @@ -7,6 +7,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Templates; using Umbraco.Core.Strings; using Umbraco.Core.Xml; +using System.Threading.Tasks; namespace Umbraco.Web.Website { @@ -95,13 +96,10 @@ namespace Umbraco.Web.Website /// /// Renders the template for the specified pageId and an optional altTemplateId /// - /// + /// The content id /// If not specified, will use the template assigned to the node - /// - public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null) - { - return _componentRenderer.RenderTemplate(contentId, altTemplateId); - } + public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null) + => await _componentRenderer.RenderTemplateAsync(contentId, altTemplateId); #region RenderMacro diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 7e00f5bd68..e026dadfa7 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web; @@ -12,6 +12,7 @@ using Umbraco.Core.Xml; using Umbraco.Web.Mvc; using Microsoft.Extensions.Logging; using Umbraco.Web.Security; +using System.Threading.Tasks; namespace Umbraco.Web { @@ -104,13 +105,10 @@ namespace Umbraco.Web /// /// Renders the template for the specified pageId and an optional altTemplateId /// - /// + /// The content id /// If not specified, will use the template assigned to the node - /// - public IHtmlEncodedString RenderTemplate(int contentId, int? altTemplateId = null) - { - return _componentRenderer.RenderTemplate(contentId, altTemplateId); - } + public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null) + => await _componentRenderer.RenderTemplateAsync(contentId, altTemplateId); #region RenderMacro From 501616682973097006fe6eb95a7b8e2ab5aa1f21 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 14:08:20 +1100 Subject: [PATCH 54/88] changed to getawaiter --- src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 9d610b81f0..3ebce0fedb 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -212,7 +212,9 @@ namespace Umbraco.Web.BackOffice.Mapping // NOTE: unfortunately we're not async, we'll use .Result and hope this won't cause a deadlock anywhere for now var urls = source.GetContentUrlsAsync(_publishedRouter, umbracoContext, _localizationService, _localizedTextService, _contentService, _variationContextAccessor, _loggerFactory.CreateLogger(), _uriUtility, _publishedUrlProvider) - .Result + .ConfigureAwait(false) + .GetAwaiter() + .GetResult() .ToArray(); return urls; From f8548543cd890242c98a759698e6668dec53fc2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 14:10:55 +1100 Subject: [PATCH 55/88] Fixes another async/sync issue --- .../Controllers/SectionController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs index 097b5a3310..f3c0415b75 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Umbraco.Core; @@ -49,7 +50,7 @@ namespace Umbraco.Web.BackOffice.Controllers _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; } - public IEnumerable
GetSections() + public async Task> GetSections() { var sections = _sectionService.GetAllowedSections(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -74,7 +75,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (hasDashboards) continue; // get the first tree in the section and get its root node route path - var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; + var sectionRoot = await appTreeController.GetApplicationTrees(section.Alias, null, null); section.RoutePath = GetRoutePathForFirstTree(sectionRoot); } From 4ce3271205cfcf231d65819a8bd5f486af7a7bdf Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 14:16:07 +1100 Subject: [PATCH 56/88] adds doc link on not found result --- .../ActionsResults/PublishedContentNotFoundResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs index 3e90a40f09..dc87f598b3 100644 --- a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs +++ b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Common.ActionsResults await response.WriteAsync("

" + _message + "

"); } - await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); + await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); await response.WriteAsync("

This page is intentionally left ugly ;-)

"); await response.WriteAsync(""); } From 2044b82bb4b9e07566c94ac400dd25d88449b77d Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Jan 2021 16:43:01 +1100 Subject: [PATCH 57/88] turns off the endpoint matching logic - makes it configurable since it shouldn't be needed --- .../Models/WebRoutingSettings.cs | 11 ++++++++ .../Routing/UmbracoRequestPaths.cs | 1 - .../Routing/RoutableDocumentFilterTests.cs | 15 ++++++++--- .../Routing/RoutableDocumentFilter.cs | 26 +++++++------------ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index 9f06046452..d94fdd8496 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -10,6 +10,17 @@ namespace Umbraco.Core.Configuration.Models /// public class WebRoutingSettings { + /// + /// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before + /// the Umbraco dynamic router tries to map the request to an Umbraco content item. + /// + /// + /// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In that case + /// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this is what v7/v8 used + /// to do. + /// + public bool TryMatchingEndpointsForAllPages { get; set; } = false; + /// /// Gets or sets a value indicating whether IIS custom errors should be skipped. /// diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index 6fa3328014..e670930691 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -112,7 +112,6 @@ namespace Umbraco.Core.Routing } // check for special front-end paths - // TODO: These should be constants - will need to update when we do front-end routing if (urlPath.InvariantStartsWith(_surfaceMvcPath) || urlPath.InvariantStartsWith(_apiMvcPath)) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs index b25da2351b..1c9cbc9c26 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -18,6 +18,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { private IOptions GetGlobalSettings() => Options.Create(new GlobalSettings()); + private IOptions GetWebRoutingSettings() => Options.Create(new WebRoutingSettings()); + private IHostingEnvironment GetHostingEnvironment() { var hostingEnv = new Mock(); @@ -35,6 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { var routableDocFilter = new RoutableDocumentFilter( GetGlobalSettings(), + GetWebRoutingSettings(), GetHostingEnvironment(), new DefaultEndpointDataSource()); @@ -44,14 +47,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase("/base/somebasehandler")] [TestCase("/")] - [TestCase("/home.aspx")] + [TestCase("/home")] [TestCase("/umbraco-test")] [TestCase("/install-test")] - [TestCase("/install.aspx")] public void Is_Not_Reserved_Path_Or_Url(string url) { var routableDocFilter = new RoutableDocumentFilter( GetGlobalSettings(), + GetWebRoutingSettings(), GetHostingEnvironment(), new DefaultEndpointDataSource()); @@ -73,6 +76,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing public void Is_Reserved_By_Route(string url, bool isReserved) { var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true }; RouteEndpoint endpoint1 = CreateEndpoint( "Umbraco/RenderMvc/{action?}/{id?}", @@ -88,6 +92,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var routableDocFilter = new RoutableDocumentFilter( Options.Create(globalSettings), + Options.Create(routingSettings), GetHostingEnvironment(), endpointDataSource); @@ -105,16 +110,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing public void Is_Reserved_By_Default_Back_Office_Route(string url, bool isReserved) { var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true }; RouteEndpoint endpoint1 = CreateEndpoint( - "umbraco/{action}/{id?}", - new { controller = "BackOffice", action = "Default" }, + "umbraco/{action?}/{id?}", + new { controller = "BackOffice" }, 0); var endpointDataSource = new DefaultEndpointDataSource(endpoint1); var routableDocFilter = new RoutableDocumentFilter( Options.Create(globalSettings), + Options.Create(routingSettings), GetHostingEnvironment(), endpointDataSource); diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 8f61918121..dee90bbfba 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -24,6 +24,7 @@ namespace Umbraco.Web.Common.Routing { private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly GlobalSettings _globalSettings; + private readonly WebRoutingSettings _routingSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; private readonly object _routeLocker = new object(); @@ -34,9 +35,10 @@ namespace Umbraco.Web.Common.Routing /// /// Initializes a new instance of the class. /// - public RoutableDocumentFilter(IOptions globalSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) + public RoutableDocumentFilter(IOptions globalSettings, IOptions routingSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource) { _globalSettings = globalSettings.Value; + _routingSettings = routingSettings.Value; _hostingEnvironment = hostingEnvironment; _endpointDataSource = endpointDataSource; _endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null); @@ -67,20 +69,17 @@ namespace Umbraco.Web.Common.Routing // a document request should be // /foo/bar/nil // /foo/bar/nil/ - // /foo/bar/nil.aspx // where /foo is not a reserved path - // TODO: Remove aspx checks - - // if the path contains an extension that is not .aspx + // if the path contains an extension // then it cannot be a document request var extension = Path.GetExtension(absPath); - if (maybeDoc && extension.IsNullOrWhiteSpace() == false && !extension.InvariantEquals(".aspx")) + if (maybeDoc && !extension.IsNullOrWhiteSpace()) { maybeDoc = false; } - // at that point, either we have no extension, or it is .aspx + // at that point we have no extension // if the path is reserved then it cannot be a document request if (maybeDoc && IsReservedPathOrUrl(absPath)) @@ -143,16 +142,9 @@ namespace Umbraco.Web.Common.Routing return true; } - // TODO: We have a problem here: - // For every page that is rendered we are storing the URL and if it's routable in _routeChecks which - // is a small memory leak. Not sure how we work around this since routes are all dynamic and we don't want - // to double route everything on each request. Maybe instead of a growing list it's a list with a max capacity? - // BUT... then do we need to do this at all? So long as the catch all route is registered LAST shouldn't all other routes - // just match before it anyways and then we don't need to check? The strange part is that the "/umbraco" route doesn't automatically - // match so we need to investigate that first. - - // check if the current request matches a route, if so then it is reserved. - var hasRoute = _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); + // If configured, check if the current request matches a route, if so then it is reserved, + // else if not configured (default) proceed as normal since we assume the request is for an Umbraco content item. + var hasRoute = _routingSettings.TryMatchingEndpointsForAllPages && _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath)); if (hasRoute) { return true; From 533d56227b2f0ec81d535ac2a3777755d7844c1d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Jan 2021 07:47:24 +0100 Subject: [PATCH 58/88] Change link to tiny link, that we can change without an update of umbraco --- .../ActionsResults/PublishedContentNotFoundResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs index dc87f598b3..a2a752cfd0 100644 --- a/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs +++ b/src/Umbraco.Web.Common/ActionsResults/PublishedContentNotFoundResult.cs @@ -52,7 +52,7 @@ namespace Umbraco.Web.Common.ActionsResults await response.WriteAsync("

" + _message + "

"); } - await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); + await response.WriteAsync("

This page can be replaced with a custom 404. Check the documentation for \"custom 404\".

"); await response.WriteAsync("

This page is intentionally left ugly ;-)

"); await response.WriteAsync(""); } From e66ccc536df8683581545d57fce64df455a773f8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Jan 2021 09:04:05 +0100 Subject: [PATCH 59/88] Netcore: Migrate more tests (#9621) * AB8828 - Migrated ShadowFileSystemTests.cs Note that the underlying behavior of Directory.EnumerateFiles is changed when search pattern is "". "" is not handled like "*" in netcore. * AB8828 - Migrated ScopeEventDispatcherTests.cs * AB8828 - Migrated DistributedCacheBinderTests.cs * AB8828 - Migrated SchemaValidationTest.cs * AB8828 - Migrated LocksTests.cs * AB8828 - Migrated SqlCeTableByTableTest.cs (and renamed to SqlServerTableByTableTest.cs) * AB8828 - Created DatabaseBuilderTests * AB8828 - Fix issues with file systems for linux --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 6 +- src/Umbraco.Core/IO/ShadowWrapper.cs | 6 +- .../Persistence/IEmbeddedDatabaseCreator.cs | 1 + .../NoopEmbeddedDatabaseCreator.cs | 2 + .../SqlCeEmbeddedDatabaseCreator.cs | 3 +- .../Cache/DistributedCacheBinderTests.cs | 154 ++++++++++++++++ .../Umbraco.Core}/IO/ShadowFileSystemTests.cs | 170 +++++++++-------- .../Persistence/DatabaseBuilderTests.cs | 78 ++++++++ .../Persistence/LocksTests.cs | 40 ++-- .../Persistence/SchemaValidationTest.cs | 17 +- .../Persistence/SqlServerTableByTableTest.cs} | 45 +---- .../Umbraco.Tests.Integration.csproj | 1 + .../Scoping/ScopeEventDispatcherTests.cs | 104 ++++++----- .../Persistence/DatabaseContextTests.cs | 36 ++++ .../Cache/DistributedCacheBinderTests.cs | 173 ------------------ .../Persistence/DatabaseContextTests.cs | 140 -------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 10 +- .../UmbracoBuilderExtensions.cs | 13 +- src/umbraco-netcore-only.sln | 8 + 19 files changed, 469 insertions(+), 538 deletions(-) create mode 100644 src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs rename src/{Umbraco.Tests => Umbraco.Tests.Integration/Umbraco.Core}/IO/ShadowFileSystemTests.cs (80%) create mode 100644 src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs rename src/{Umbraco.Tests => Umbraco.Tests.Integration/Umbraco.Infrastructure}/Persistence/LocksTests.cs (91%) rename src/{Umbraco.Tests => Umbraco.Tests.Integration/Umbraco.Infrastructure}/Persistence/SchemaValidationTest.cs (58%) rename src/{Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs => Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs} (91%) rename src/{Umbraco.Tests => Umbraco.Tests.UnitTests/Umbraco.Core}/Scoping/ScopeEventDispatcherTests.cs (83%) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/DatabaseContextTests.cs delete mode 100644 src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs delete mode 100644 src/Umbraco.Tests/Persistence/DatabaseContextTests.cs diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 551be602dd..c8d49e0c19 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -2,10 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using Microsoft.Extensions.Logging; -using Umbraco.Core.Exceptions; -using Umbraco.Core.Composing; using System.Threading; +using Microsoft.Extensions.Logging; using Umbraco.Core.Hosting; namespace Umbraco.Core.IO @@ -177,7 +175,7 @@ namespace Umbraco.Core.IO /// The filesystem-relative path of the directory. /// A filter. /// The filesystem-relative path to the matching files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. + /// Filesystem-relative paths use forward-slashes as directory separators. //TODO check is this is true on linux and windows.. public IEnumerable GetFiles(string path, string filter) { var fullPath = GetFullPath(path); diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index a395938050..1683fb5b4e 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -89,9 +89,9 @@ namespace Umbraco.Core.IO Directory.Delete(dir, true); // shadowPath make be path/to/dir, remove each - dir = dir.Replace("/", "\\"); + dir = dir.Replace('/', Path.DirectorySeparatorChar); var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length; - var pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); + var pos = dir.LastIndexOf(Path.DirectorySeparatorChar); while (pos > min) { dir = dir.Substring(0, pos); @@ -99,7 +99,7 @@ namespace Umbraco.Core.IO Directory.Delete(dir, true); else break; - pos = dir.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); + pos = dir.LastIndexOf(Path.DirectorySeparatorChar); } } catch diff --git a/src/Umbraco.Infrastructure/Persistence/IEmbeddedDatabaseCreator.cs b/src/Umbraco.Infrastructure/Persistence/IEmbeddedDatabaseCreator.cs index 049001b67f..dd1e89a95e 100644 --- a/src/Umbraco.Infrastructure/Persistence/IEmbeddedDatabaseCreator.cs +++ b/src/Umbraco.Infrastructure/Persistence/IEmbeddedDatabaseCreator.cs @@ -3,6 +3,7 @@ public interface IEmbeddedDatabaseCreator { string ProviderName { get; } + string ConnectionString { get; set; } void Create(); } } diff --git a/src/Umbraco.Infrastructure/Persistence/NoopEmbeddedDatabaseCreator.cs b/src/Umbraco.Infrastructure/Persistence/NoopEmbeddedDatabaseCreator.cs index a12819f9f0..8c89f1468f 100644 --- a/src/Umbraco.Infrastructure/Persistence/NoopEmbeddedDatabaseCreator.cs +++ b/src/Umbraco.Infrastructure/Persistence/NoopEmbeddedDatabaseCreator.cs @@ -4,6 +4,8 @@ { public string ProviderName => Constants.DatabaseProviders.SqlServer; + public string ConnectionString { get; set; } + public void Create() { diff --git a/src/Umbraco.Persistance.SqlCe/SqlCeEmbeddedDatabaseCreator.cs b/src/Umbraco.Persistance.SqlCe/SqlCeEmbeddedDatabaseCreator.cs index de6a7ff4d7..24c33acdc9 100644 --- a/src/Umbraco.Persistance.SqlCe/SqlCeEmbeddedDatabaseCreator.cs +++ b/src/Umbraco.Persistance.SqlCe/SqlCeEmbeddedDatabaseCreator.cs @@ -8,9 +8,10 @@ namespace Umbraco.Persistance.SqlCe { public string ProviderName => Constants.DatabaseProviders.SqlCe; + public string ConnectionString { get; set; } = DatabaseBuilder.EmbeddedDatabaseConnectionString; public void Create() { - var engine = new System.Data.SqlServerCe.SqlCeEngine(DatabaseBuilder.EmbeddedDatabaseConnectionString); + var engine = new System.Data.SqlServerCe.SqlCeEngine(ConnectionString); engine.CreateDatabase(); } } diff --git a/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs new file mode 100644 index 0000000000..f986092574 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Cache/DistributedCacheBinderTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; +using Umbraco.Web; +using Umbraco.Web.Cache; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + [UmbracoTest(Boot = true)] + public class DistributedCacheBinderTests : UmbracoIntegrationTest + { + private IUserService UserService => GetRequiredService(); + private ILocalizationService LocalizationService => GetRequiredService(); + private IDataTypeService DataTypeService => GetRequiredService(); + private IFileService FileService => GetRequiredService(); + private IContentTypeService ContentTypeService => GetRequiredService(); + private IMediaTypeService MediaTypeService => GetRequiredService(); + private IDomainService DomainService => GetRequiredService(); + private IMemberTypeService MemberTypeService => GetRequiredService(); + private IMacroService MacroService => GetRequiredService(); + private IMemberService MemberService => GetRequiredService(); + private IMemberGroupService MemberGroupService => GetRequiredService(); + private IMediaService MediaService => GetRequiredService(); + private IContentService ContentService => GetRequiredService(); + private IPublicAccessService PublicAccessService => GetRequiredService(); + private IRelationService RelationService => GetRequiredService(); + private UriUtility UriUtility => GetRequiredService(); + private IUmbracoContextFactory UmbracoContextFactory => GetRequiredService(); + + [Test] + public void Can_Find_All_Event_Handlers() + { + + var definitions = new IEventDefinition[] + { + new EventDefinition>(null, UserService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, UserService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, UserService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, UserService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, LocalizationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, LocalizationService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, DataTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, DataTypeService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, FileService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, FileService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, DomainService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, DomainService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, LocalizationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, LocalizationService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, ContentTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, ContentTypeService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MediaTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MediaTypeService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, MemberTypeService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MemberTypeService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, FileService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, FileService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, MacroService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MacroService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, MemberService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MemberService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, MemberGroupService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, MediaService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MediaService, new DeleteEventArgs(Enumerable.Empty())), + new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), + new EventDefinition>(null, MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), + new EventDefinition(null, MediaService, new RecycleBinEventArgs(Guid.NewGuid())), + + new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "Saved"), + new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "Deleted"), + + // not managed + //new EventDefinition>(null, ContentService, new SaveEventArgs(Enumerable.Empty()), "SavedBlueprint"), + //new EventDefinition>(null, ContentService, new DeleteEventArgs(Enumerable.Empty()), "DeletedBlueprint"), + + new EventDefinition>(null, ContentService, new CopyEventArgs(null, null, -1)), + new EventDefinition>(null, ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), + new EventDefinition(null, ContentService, new RecycleBinEventArgs(Guid.NewGuid())), + new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), + new EventDefinition>(null, ContentService, new PublishEventArgs(Enumerable.Empty()), "Unpublished"), + + new EventDefinition>(null, PublicAccessService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, RelationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, RelationService, new DeleteEventArgs(Enumerable.Empty())), + + new EventDefinition>(null, RelationService, new SaveEventArgs(Enumerable.Empty())), + new EventDefinition>(null, RelationService, new DeleteEventArgs(Enumerable.Empty())), + }; + + var ok = true; + foreach (var definition in definitions) + { + var found = DistributedCacheBinder.FindHandler(definition); + if (found == null) + { + Console.WriteLine("Couldn't find method for " + definition.EventName + " on " + definition.Sender.GetType()); + ok = false; + } + } + Assert.IsTrue(ok, "see log for details"); + } + + [Test] + public void CanHandleEvent() + { + // refreshers.HandleEvents wants a UmbracoContext + // which wants an HttpContext, which we build using a SimpleWorkerRequest + // which requires these to be non-null + var domain = Thread.GetDomain(); + if (domain.GetData(".appPath") == null) + domain.SetData(".appPath", ""); + if (domain.GetData(".appVPath") == null) + domain.SetData(".appVPath", ""); + + // create some event definitions + var definitions = new IEventDefinition[] + { + // works because that event definition maps to an empty handler + new EventDefinition>(null, ContentTypeService, new SaveEventArgs(Enumerable.Empty()), "Saved"), + + }; + + Assert.DoesNotThrow(() => + { + var refreshers = new DistributedCacheBinder(null, UmbracoContextFactory, null); + refreshers.HandleEvents(definitions); + }); + + } + } +} diff --git a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Core/IO/ShadowFileSystemTests.cs similarity index 80% rename from src/Umbraco.Tests/IO/ShadowFileSystemTests.cs rename to src/Umbraco.Tests.Integration/Umbraco.Core/IO/ShadowFileSystemTests.cs index 7c1973a8cf..52d6a34a48 100644 --- a/src/Umbraco.Tests/IO/ShadowFileSystemTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Core/IO/ShadowFileSystemTests.cs @@ -2,37 +2,38 @@ using System.IO; using System.Linq; using System.Text; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; -using Umbraco.Tests.TestHelpers; -using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; +using Umbraco.Tests.Integration.Implementations; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; namespace Umbraco.Tests.IO { [TestFixture] - public class ShadowFileSystemTests + [UmbracoTest] + public class ShadowFileSystemTests : UmbracoIntegrationTest { // tested: // only 1 instance of this class is created // SetUp and TearDown run before/after each test // SetUp does not start before the previous TearDown returns - private readonly ILogger _logger = Mock.Of>(); - private readonly IHostingEnvironment _hostingEnvironment = TestHelper.GetHostingEnvironment(); - private readonly IIOHelper _ioHelper = TestHelper.IOHelper; + private IHostingEnvironment HostingEnvironment => GetRequiredService(); + private ILogger Logger => GetRequiredService>(); [SetUp] public void SetUp() { SafeCallContext.Clear(); - ClearFiles(_hostingEnvironment); + ClearFiles(HostingEnvironment); FileSystems.ResetShadowId(); } @@ -40,11 +41,11 @@ namespace Umbraco.Tests.IO public void TearDown() { SafeCallContext.Clear(); - ClearFiles(_hostingEnvironment); + ClearFiles(HostingEnvironment); FileSystems.ResetShadowId(); } - private static void ClearFiles(IHostingEnvironment hostingEnvironment) + private void ClearFiles(IHostingEnvironment hostingEnvironment) { TestHelper.DeleteDirectory(hostingEnvironment.MapPathContentRoot("FileSysTests")); TestHelper.DeleteDirectory(hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs")); @@ -52,19 +53,19 @@ namespace Umbraco.Tests.IO private static string NormPath(string path) { - return path.ToLowerInvariant().Replace("\\", "/"); + return path.Replace('\\', Path.AltDirectorySeparatorChar); } [Test] public void ShadowDeleteDirectory() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); Directory.CreateDirectory(path + "/ShadowTests/d1"); @@ -92,13 +93,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowDeleteDirectoryInDir() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); Directory.CreateDirectory(path + "/ShadowTests/sub"); @@ -141,13 +142,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowDeleteFile() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); File.WriteAllText(path + "/ShadowTests/f1.txt", "foo"); @@ -180,14 +181,14 @@ namespace Umbraco.Tests.IO [Test] public void ShadowDeleteFileInDir() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); Directory.CreateDirectory(path + "/ShadowTests/sub"); @@ -236,13 +237,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowCantCreateFile() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); Assert.Throws(() => @@ -255,13 +256,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowCreateFile() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper,_hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper,HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); File.WriteAllText(path + "/ShadowTests/f2.txt", "foo"); @@ -294,13 +295,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowCreateFileInDir() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) @@ -334,13 +335,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowAbort() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) @@ -356,13 +357,13 @@ namespace Umbraco.Tests.IO [Test] public void ShadowComplete() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); Directory.CreateDirectory(path + "/ShadowTests/sub/sub"); @@ -393,18 +394,18 @@ namespace Umbraco.Tests.IO public void ShadowScopeComplete() { var loggerFactory = NullLoggerFactory.Instance; - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); - var shadowfs = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); + var shadowfs = HostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); Directory.CreateDirectory(shadowfs); var scopedFileSystems = false; - var phy = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path, "ignore"); + var phy = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path, "ignore"); var container = Mock.Of(); var globalSettings = Options.Create(new GlobalSettings()); - var fileSystems = new FileSystems(container, Mock.Of>(), loggerFactory, _ioHelper, globalSettings, _hostingEnvironment) { IsScoped = () => scopedFileSystems }; + var fileSystems = new FileSystems(container, Mock.Of>(), loggerFactory, IOHelper, globalSettings, HostingEnvironment) { IsScoped = () => scopedFileSystems }; var fs = fileSystems.GetFileSystem(phy); var sw = (ShadowWrapper) fs.InnerFileSystem; @@ -415,7 +416,7 @@ namespace Umbraco.Tests.IO string id; // explicit shadow without scope does not work - sw.Shadow(id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + sw.Shadow(id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -426,7 +427,7 @@ namespace Umbraco.Tests.IO // shadow with scope but no complete does not complete scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f3.txt", ms); @@ -448,7 +449,7 @@ namespace Umbraco.Tests.IO // shadow with scope and complete does complete scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f4.txt", ms); @@ -464,7 +465,7 @@ namespace Umbraco.Tests.IO // test scope for "another thread" scopedFileSystems = true; // pretend we have a scope - scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f5.txt", ms); @@ -487,17 +488,17 @@ namespace Umbraco.Tests.IO [Test] public void ShadowScopeCompleteWithFileConflict() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); - var shadowfs = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); + var shadowfs = HostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var phy = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path, "ignore"); + var phy = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path, "ignore"); var container = Mock.Of(); var globalSettings = Options.Create(new GlobalSettings()); - var fileSystems = new FileSystems(container, Mock.Of>(), NullLoggerFactory.Instance, _ioHelper, globalSettings, _hostingEnvironment) { IsScoped = () => scopedFileSystems }; + var fileSystems = new FileSystems(container, Mock.Of>(), NullLoggerFactory.Instance, IOHelper, globalSettings, HostingEnvironment) { IsScoped = () => scopedFileSystems }; var fs = fileSystems.GetFileSystem( phy); var sw = (ShadowWrapper) fs.InnerFileSystem; @@ -508,7 +509,7 @@ namespace Umbraco.Tests.IO string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -539,17 +540,17 @@ namespace Umbraco.Tests.IO [Test] public void ShadowScopeCompleteWithDirectoryConflict() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); - var shadowfs = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); + var shadowfs = HostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"); Directory.CreateDirectory(path); var scopedFileSystems = false; - var phy = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path, "ignore"); + var phy = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path, "ignore"); var container = Mock.Of(); var globalSettings = Options.Create(new GlobalSettings()); - var fileSystems = new FileSystems(container, Mock.Of>(), NullLoggerFactory.Instance, _ioHelper, globalSettings, _hostingEnvironment) { IsScoped = () => scopedFileSystems }; + var fileSystems = new FileSystems(container, Mock.Of>(), NullLoggerFactory.Instance, IOHelper, globalSettings, HostingEnvironment) { IsScoped = () => scopedFileSystems }; var fs = fileSystems.GetFileSystem( phy); var sw = (ShadowWrapper)fs.InnerFileSystem; @@ -560,7 +561,7 @@ namespace Umbraco.Tests.IO string id; scopedFileSystems = true; // pretend we have a scope - var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(_hostingEnvironment)); + var scope = new ShadowFileSystems(fileSystems, id = ShadowWrapper.CreateShadowId(HostingEnvironment)); Assert.IsTrue(Directory.Exists(shadowfs + "/" + id)); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) sw.AddFile("sub/f2.txt", ms); @@ -608,7 +609,7 @@ namespace Umbraco.Tests.IO [Test] public void GetFilesReturnsChildrenOnly() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); File.WriteAllText(path + "/f1.txt", "foo"); Directory.CreateDirectory(path + "/test"); @@ -630,7 +631,7 @@ namespace Umbraco.Tests.IO [Test] public void DeleteDirectoryAndFiles() { - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); File.WriteAllText(path + "/f1.txt", "foo"); Directory.CreateDirectory(path + "/test"); @@ -651,13 +652,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFiles() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -683,13 +684,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFilesUsingEmptyFilter() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -701,9 +702,6 @@ namespace Umbraco.Tests.IO // ensure we get 2 files from the shadow var getFiles = ss.GetFiles(string.Empty); Assert.AreEqual(2, getFiles.Count()); - // ensure we get 0 files when using a empty filter - var getFilesWithEmptyFilter = ss.GetFiles(string.Empty, ""); - Assert.AreEqual(0, getFilesWithEmptyFilter.Count()); var fsFiles = fs.GetFiles(string.Empty).ToArray(); Assert.AreEqual(1, fsFiles.Length); @@ -718,13 +716,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFilesUsingNullFilter() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -750,13 +748,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFilesUsingWildcardFilter() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -785,13 +783,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFilesUsingSingleCharacterFilter() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -832,13 +830,13 @@ namespace Umbraco.Tests.IO public void ShadowGetFullPath() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -866,13 +864,13 @@ namespace Umbraco.Tests.IO public void ShadowGetRelativePath() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "ignore"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "ignore"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "ignore"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "ignore"); var ss = new ShadowFileSystem(fs, sfs); // Act @@ -905,13 +903,13 @@ namespace Umbraco.Tests.IO public void ShadowGetUrl() { // Arrange - var path = _hostingEnvironment.MapPathContentRoot("FileSysTests"); + var path = HostingEnvironment.MapPathContentRoot("FileSysTests"); Directory.CreateDirectory(path); Directory.CreateDirectory(path + "/ShadowTests"); Directory.CreateDirectory(path + "/ShadowSystem"); - var fs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowTests/", "rootUrl"); - var sfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _logger, path + "/ShadowSystem/", "rootUrl"); + var fs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowTests/", "rootUrl"); + var sfs = new PhysicalFileSystem(IOHelper, HostingEnvironment, Logger, path + "/ShadowSystem/", "rootUrl"); var ss = new ShadowFileSystem(fs, sfs); // Act diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs new file mode 100644 index 0000000000..f6ca0c951f --- /dev/null +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/DatabaseBuilderTests.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using NPoco; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Migrations.Install; +using Umbraco.Core.Persistence; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence +{ + [TestFixture] + [UmbracoTest] + [Platform("Win")] + public class DatabaseBuilderTests : UmbracoIntegrationTest + { + private IDbProviderFactoryCreator DbProviderFactoryCreator => GetRequiredService(); + private IUmbracoDatabaseFactory UmbracoDatabaseFactory => GetRequiredService(); + private IEmbeddedDatabaseCreator EmbeddedDatabaseCreator => GetRequiredService(); + + [Test] + public void CreateDatabase() + { + var path = TestContext.CurrentContext.TestDirectory.Split("bin")[0]; + AppDomain.CurrentDomain.SetData("DataDirectory", path); + const string dbFile = "DatabaseContextTests.sdf"; + // delete database file + // NOTE: using a custom db file for this test since we're re-using the one created with BaseDatabaseFactoryTest + var filePath = string.Concat(path, dbFile); + if (File.Exists(filePath)) + File.Delete(filePath); + + EmbeddedDatabaseCreator.ConnectionString = $"Datasource=|DataDirectory|{dbFile};Flush Interval=1"; + + UmbracoDatabaseFactory.Configure(EmbeddedDatabaseCreator.ConnectionString, Constants.DbProviderNames.SqlCe); + DbProviderFactoryCreator.CreateDatabase(Constants.DbProviderNames.SqlCe); + UmbracoDatabaseFactory.CreateDatabase(); + + // test get database type (requires an actual database) + using (var database = UmbracoDatabaseFactory.CreateDatabase()) + { + var databaseType = database.DatabaseType; + Assert.AreEqual(DatabaseType.SQLCe, databaseType); + } + + // create application context + //var appCtx = new ApplicationContext( + // _databaseFactory, + // new ServiceContext(migrationEntryService: Mock.Of()), + // CacheHelper.CreateDisabledCacheHelper(), + // new ProfilingLogger(Mock.Of(), Mock.Of())); + + // create the umbraco database + DatabaseSchemaCreator schemaHelper; + using (var database = UmbracoDatabaseFactory.CreateDatabase()) + using (var transaction = database.GetTransaction()) + { + schemaHelper = new DatabaseSchemaCreator(database, Mock.Of>(), NullLoggerFactory.Instance, new UmbracoVersion()); + schemaHelper.InitializeDatabaseSchema(); + transaction.Complete(); + } + + var umbracoNodeTable = schemaHelper.TableExists("umbracoNode"); + var umbracoUserTable = schemaHelper.TableExists("umbracoUser"); + var cmsTagsTable = schemaHelper.TableExists("cmsTags"); + + Assert.That(umbracoNodeTable, Is.True); + Assert.That(umbracoUserTable, Is.True); + Assert.That(cmsTagsTable, Is.True); + } + + } +} diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs similarity index 91% rename from src/Umbraco.Tests/Persistence/LocksTests.cs rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs index d4e3d23a70..95387ec97e 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs @@ -1,12 +1,14 @@ using System; -using System.Data.SqlServerCe; +using System.Collections.Generic; +using System.Data.SqlClient; using System.Linq; +using System.Text; using System.Threading; using NPoco; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence.Dtos; -using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Persistence @@ -14,11 +16,11 @@ namespace Umbraco.Tests.Persistence [TestFixture] [Timeout(60000)] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] - public class LocksTests : TestWithDatabaseBase + public class LocksTests : UmbracoIntegrationTest { - protected override void Initialize() + [SetUp] + protected void SetUp() { - base.Initialize(); // create a few lock objects using (var scope = ScopeProvider.CreateScope()) @@ -200,7 +202,7 @@ namespace Umbraco.Tests.Persistence thread2.Join(); Assert.IsNotNull(e1); - Assert.IsInstanceOf(e1); + AssertIsSqlLockException(e1); // the assertion below depends on timing conditions - on a fast enough environment, // thread1 dies (deadlock) and frees thread2, which succeeds - however on a slow @@ -209,9 +211,18 @@ namespace Umbraco.Tests.Persistence // //Assert.IsNull(e2); if (e2 != null) - Assert.IsInstanceOf(e2); + { + AssertIsSqlLockException(e2); + } + } + private void AssertIsSqlLockException(Exception e) + { + var sqlException = e as SqlException; + Assert.IsNotNull(sqlException); + Assert.AreEqual(1222, sqlException.Number); + } private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception) { using (var scope = ScopeProvider.CreateScope()) @@ -304,11 +315,18 @@ namespace Umbraco.Tests.Persistence private void WriteLocks(IDatabaseQuery database) { Console.WriteLine("LOCKS:"); - var info = database.Query("SELECT * FROM sys.lock_information;").ToList(); + var info = database.Query("SELECT * FROM sys.dm_tran_locks;").ToList(); + var sb = new StringBuilder("> "); foreach (var row in info) - Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid, - row.resource_type, row.resource_description, row.request_mode, row.resource_table, - row.resource_table_id, row.request_status)); + { + if (row is IDictionary values) + { + sb.AppendJoin(", ", values); + } + sb.AppendLine(string.Empty); + } + Console.WriteLine(sb.ToString()); + } } } diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs similarity index 58% rename from src/Umbraco.Tests/Persistence/SchemaValidationTest.cs rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs index fe4a1581d1..b04c018a94 100644 --- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SchemaValidationTest.cs @@ -1,21 +1,18 @@ -using System.Linq; -using Microsoft.Extensions.Logging; -using Moq; +using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core.Configuration; using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Persistence { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)] - public class SchemaValidationTest : TestWithDatabaseBase + public class SchemaValidationTest : UmbracoIntegrationTest { + private IUmbracoVersion UmbracoVersion => GetRequiredService(); + [Test] public void DatabaseSchemaCreation_Produces_DatabaseSchemaResult_With_Zero_Errors() { @@ -24,9 +21,7 @@ namespace Umbraco.Tests.Persistence using (var scope = ScopeProvider.CreateScope()) { var schema = new DatabaseSchemaCreator(scope.Database, LoggerFactory.CreateLogger(), LoggerFactory, UmbracoVersion); - result = schema.ValidateSchema( - //TODO: When we remove the xml cache from tests we can remove this too - DatabaseSchemaCreator.OrderedTables.Concat(new []{typeof(ContentXmlDto), typeof(PreviewXmlDto)})); + result = schema.ValidateSchema(DatabaseSchemaCreator.OrderedTables); } // Assert diff --git a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs similarity index 91% rename from src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs rename to src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs index 54d6b828be..59b56c0e51 100644 --- a/src/Umbraco.Tests/Persistence/SqlCeTableByTableTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SqlServerTableByTableTest.cs @@ -1,25 +1,19 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NPoco; using NUnit.Framework; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.LegacyXmlPublishedCache; -using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; namespace Umbraco.Tests.Persistence { [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] - public class SqlCeTableByTableTest : TestWithDatabaseBase + public class SqlServerTableByTableTest : UmbracoIntegrationTest { - public GlobalSettings GlobalSettings => new GlobalSettings(); + private IUmbracoVersion UmbracoVersion => GetRequiredService(); private static ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; [Test] @@ -123,22 +117,6 @@ namespace Umbraco.Tests.Persistence } } - [Test] - public void Can_Create_cmsContentXml_Table() - { - using (var scope = ScopeProvider.CreateScope()) - { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion); - - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - - scope.Complete(); - } - } - [Test] public void Can_Create_cmsDataType_Table() { @@ -329,23 +307,6 @@ namespace Umbraco.Tests.Persistence } } - [Test] - public void Can_Create_cmsPreviewXml_Table() - { - using (var scope = ScopeProvider.CreateScope()) - { - var helper = new DatabaseSchemaCreator(scope.Database, _loggerFactory.CreateLogger(), _loggerFactory, UmbracoVersion); - - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - helper.CreateTable(); - - scope.Complete(); - } - } - [Test] public void Can_Create_PropertyData_Table() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 0614405e41..c1d0fe3758 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -77,6 +77,7 @@ + diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs similarity index 83% rename from src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs index b1851694bc..49440af438 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/ScopeEventDispatcherTests.cs @@ -1,31 +1,26 @@ using System; using System.Linq; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Events; +using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Web; -using Current = Umbraco.Web.Composing.Current; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core.DependencyInjection; +using Umbraco.Tests.Common.Builders; namespace Umbraco.Tests.Scoping { [TestFixture] - public class ScopeEventDispatcherTests //: BaseUmbracoConfigurationTest + public class ScopeEventDispatcherTests { - private TestObjects _testObjects; - [SetUp] public void Setup() { @@ -33,20 +28,10 @@ namespace Umbraco.Tests.Scoping DoThing1 = null; DoThing2 = null; DoThing3 = null; - - var services = TestHelper.GetRegister(); - - var composition = new UmbracoBuilder(services, Mock.Of(), TestHelper.GetMockedTypeLoader()); - - _testObjects = new TestObjects(); - - var globalSettings = new GlobalSettings(); - composition.Services.AddUnique(factory => new FileSystems(factory, factory.GetService>(), factory.GetService(), TestHelper.IOHelper, Microsoft.Extensions.Options.Options.Create(globalSettings), TestHelper.GetHostingEnvironment())); - composition.WithCollectionBuilder(); - - Current.Factory = composition.CreateServiceProvider(); } + + [TestCase(false, true, true)] [TestCase(false, true, false)] [TestCase(false, false, true)] @@ -63,7 +48,7 @@ namespace Umbraco.Tests.Scoping DoThing1 += (sender, args) => { counter1++; if (cancel) args.Cancel = true; }; DoThing2 += (sender, args) => { counter2++; }; - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: passive ? new PassiveEventDispatcher() : null)) { var cancelled = scope.Events.DispatchCancelable(DoThing1, this, new SaveEventArgs("test")); @@ -85,6 +70,27 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(expected2, counter2); } + private ScopeProvider GetScopeProvider(NullLoggerFactory instance) + { + var fileSystems = new FileSystems( + Mock.Of(), + Mock.Of>(), + instance, + Mock.Of(), + Options.Create(new GlobalSettings()), + Mock.Of()); + + return new ScopeProvider( + Mock.Of(), + fileSystems, + Options.Create(new CoreDebugSettings()), + Mock.Of(), + Mock.Of>(), + instance, + Mock.Of() + ); + } + [Test] public void QueueEvents() { @@ -92,7 +98,7 @@ namespace Umbraco.Tests.Scoping DoThing2 += OnDoThingFail; DoThing3 += OnDoThingFail; - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); @@ -123,18 +129,18 @@ namespace Umbraco.Tests.Scoping DoForTestArgs += OnDoThingFail; DoForTestArgs2 += OnDoThingFail; - var contentType = MockedContentTypes.CreateBasicContentType(); + var contentType = ContentTypeBuilder.CreateBasicContentType(); - var content1 = MockedContent.CreateBasicContent(contentType); + var content1 = ContentBuilder.CreateBasicContent(contentType); content1.Id = 123; - var content2 = MockedContent.CreateBasicContent(contentType); + var content2 = ContentBuilder.CreateBasicContent(contentType); content2.Id = 456; - var content3 = MockedContent.CreateBasicContent(contentType); + var content3 = ContentBuilder.CreateBasicContent(contentType); content3.Id = 789; - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { @@ -173,7 +179,7 @@ namespace Umbraco.Tests.Scoping var contentService = Mock.Of(); var content = Mock.Of(); - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new[] { content }), "Unpublished"); @@ -196,18 +202,18 @@ namespace Umbraco.Tests.Scoping DoSaveForContent += OnDoThingFail; var now = DateTime.Now; - var contentType = MockedContentTypes.CreateBasicContentType(); - var content1 = MockedContent.CreateBasicContent(contentType); + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var content1 = ContentBuilder.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); - var content2 = MockedContent.CreateBasicContent(contentType); + var content2 = ContentBuilder.CreateBasicContent(contentType); content2.Id = 123; content2.UpdateDate = now.AddMinutes(2); - var content3 = MockedContent.CreateBasicContent(contentType); + var content3 = ContentBuilder.CreateBasicContent(contentType); content3.Id = 123; content3.UpdateDate = now.AddMinutes(3); - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); @@ -236,18 +242,18 @@ namespace Umbraco.Tests.Scoping DoSaveForContent += OnDoThingFail; var now = DateTime.Now; - var contentType = MockedContentTypes.CreateBasicContentType(); - var content1 = MockedContent.CreateBasicContent(contentType); + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var content1 = ContentBuilder.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); - var content2 = MockedContent.CreateBasicContent(contentType); + var content2 = ContentBuilder.CreateBasicContent(contentType); content2.Id = 123; content1.UpdateDate = now.AddMinutes(2); - var content3 = MockedContent.CreateBasicContent(contentType); + var content3 = ContentBuilder.CreateBasicContent(contentType); content3.Id = 123; content1.UpdateDate = now.AddMinutes(3); - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); @@ -269,18 +275,18 @@ namespace Umbraco.Tests.Scoping DoSaveForContent += OnDoThingFail; var now = DateTime.Now; - var contentType = MockedContentTypes.CreateBasicContentType(); - var content1 = MockedContent.CreateBasicContent(contentType); + var contentType = ContentTypeBuilder.CreateBasicContentType(); + var content1 = ContentBuilder.CreateBasicContent(contentType); content1.Id = 123; content1.UpdateDate = now.AddMinutes(1); - var content2 = MockedContent.CreateBasicContent(contentType); + var content2 = ContentBuilder.CreateBasicContent(contentType); content2.Id = 123; content2.UpdateDate = now.AddMinutes(2); - var content3 = MockedContent.CreateBasicContent(contentType); + var content3 = ContentBuilder.CreateBasicContent(contentType); content3.Id = 123; content3.UpdateDate = now.AddMinutes(3); - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content1)); @@ -304,7 +310,7 @@ namespace Umbraco.Tests.Scoping DoThing2 += OnDoThingFail; DoThing3 += OnDoThingFail; - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { scope.Events.Dispatch(DoThing1, this, new SaveEventArgs("test")); @@ -330,7 +336,7 @@ namespace Umbraco.Tests.Scoping IScopeContext ambientContext = null; Guid value = Guid.Empty; - var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance) as ScopeProvider; + var scopeProvider = GetScopeProvider(NullLoggerFactory.Instance) as ScopeProvider; DoThing1 += (sender, args) => { counter++; }; DoThing2 += (sender, args) => { counter++; }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/DatabaseContextTests.cs new file mode 100644 index 0000000000..8b40ca2ef4 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/DatabaseContextTests.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using Umbraco.Core.Migrations.Install; + +namespace Umbraco.Tests.Persistence +{ + [TestFixture] + public class DatabaseContextTests + { + + [TestCase("MyServer", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("MyServer", "MyDatabase", "MyUser@MyServer", "MyPassword")] + [TestCase("tcp:MyServer", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:MyServer", "MyDatabase", "MyUser@MyServer", "MyPassword")] + [TestCase("tcp:MyServer,1433", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:MyServer,1433", "MyDatabase", "MyUser@MyServer", "MyPassword")] + [TestCase("tcp:MyServer.database.windows.net", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:MyServer.database.windows.net", "MyDatabase", "MyUser@MyServer", "MyPassword")] + [TestCase("tcp:MyServer.database.windows.net,1433", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:MyServer.database.windows.net,1433", "MyDatabase", "MyUser@MyServer", "MyPassword")] + public void Build_Azure_Connection_String_Regular(string server, string databaseName, string userName, string password) + { + var connectionString = DatabaseBuilder.GetAzureConnectionString(server, databaseName, userName, password); + Assert.AreEqual(connectionString, "Server=tcp:MyServer.database.windows.net,1433;Database=MyDatabase;User ID=MyUser@MyServer;Password=MyPassword"); + } + + [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433", "MyDatabase", "MyUser@kzeej5z8ty", "MyPassword")] + [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com", "MyDatabase", "MyUser", "MyPassword")] + [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com", "MyDatabase", "MyUser@kzeej5z8ty", "MyPassword")] + public void Build_Azure_Connection_String_CustomServer(string server, string databaseName, string userName, string password) + { + var connectionString = DatabaseBuilder.GetAzureConnectionString(server, databaseName, userName, password); + Assert.AreEqual(connectionString, "Server=tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433;Database=MyDatabase;User ID=MyUser@kzeej5z8ty;Password=MyPassword"); + } + } +} diff --git a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs b/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs deleted file mode 100644 index f18aacf18b..0000000000 --- a/src/Umbraco.Tests/Cache/DistributedCacheBinderTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Moq; -using NUnit.Framework; -using System; -using System.Linq; -using System.Threading; -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Services; -using Umbraco.Tests.Common; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Web; -using Umbraco.Web.Cache; -using Umbraco.Web.PublishedCache; -using Umbraco.Web.Routing; - -namespace Umbraco.Tests.Cache -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class DistributedCacheBinderTests : UmbracoTestBase - { - protected override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - // refreshers.HandleEvents wants a UmbracoContext - // which wants these - builder.Services.AddUnique(Mock.Of()); - builder.WithCollectionBuilder(); - } - - [Test] - public void Can_Find_All_Event_Handlers() - { - // FIXME: cannot work with mocks - // because the events are defined on actual static classes, not on the interfaces, so name matching fails - // we should really refactor events entirely - in the meantime, let it be an UmbracoTestBase ;( - //var testObjects = new TestObjects(null); - //var serviceContext = testObjects.GetServiceContextMock(); - var serviceContext = ServiceContext; - - var definitions = new IEventDefinition[] - { - //I would test these but they are legacy events and we don't need them for deploy, when we migrate to new/better events we can wire up the check - //Permission.New += PermissionNew; - //Permission.Updated += PermissionUpdated; - //Permission.Deleted += PermissionDeleted; - //PermissionRepository.AssignedPermissions += CacheRefresherEventHandler_AssignedPermissions; - - new EventDefinition>(null, serviceContext.UserService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.UserService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.UserService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.DataTypeService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.DataTypeService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.FileService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.FileService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.DomainService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.DomainService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.LocalizationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.LocalizationService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.ContentTypeService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MediaTypeService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MediaTypeService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.MemberTypeService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MemberTypeService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.FileService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.FileService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.MacroService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MacroService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.MemberService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MemberService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.MemberGroupService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MemberGroupService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.MediaService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MediaService, new DeleteEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Moved"), - new EventDefinition>(null, serviceContext.MediaService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, serviceContext.MediaService, new RecycleBinEventArgs(Guid.NewGuid())), - - new EventDefinition>(null, serviceContext.ContentService, new SaveEventArgs(Enumerable.Empty()), "Saved"), - new EventDefinition>(null, serviceContext.ContentService, new DeleteEventArgs(Enumerable.Empty()), "Deleted"), - - // not managed - //new EventDefinition>(null, serviceContext.ContentService, new SaveEventArgs(Enumerable.Empty()), "SavedBlueprint"), - //new EventDefinition>(null, serviceContext.ContentService, new DeleteEventArgs(Enumerable.Empty()), "DeletedBlueprint"), - - new EventDefinition>(null, serviceContext.ContentService, new CopyEventArgs(null, null, -1)), - new EventDefinition>(null, serviceContext.ContentService, new MoveEventArgs(new MoveEventInfo(null, "", -1)), "Trashed"), - new EventDefinition(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid())), - new EventDefinition>(null, serviceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "Published"), - new EventDefinition>(null, serviceContext.ContentService, new PublishEventArgs(Enumerable.Empty()), "Unpublished"), - - new EventDefinition>(null, serviceContext.PublicAccessService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.PublicAccessService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.RelationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.RelationService, new DeleteEventArgs(Enumerable.Empty())), - - new EventDefinition>(null, serviceContext.RelationService, new SaveEventArgs(Enumerable.Empty())), - new EventDefinition>(null, serviceContext.RelationService, new DeleteEventArgs(Enumerable.Empty())), - }; - - var ok = true; - foreach (var definition in definitions) - { - var found = DistributedCacheBinder.FindHandler(definition); - if (found == null) - { - Console.WriteLine("Couldn't find method for " + definition.EventName + " on " + definition.Sender.GetType()); - ok = false; - } - } - Assert.IsTrue(ok, "see log for details"); - } - - [Test] - public void CanHandleEvent() - { - // refreshers.HandleEvents wants a UmbracoContext - // which wants an HttpContext, which we build using a SimpleWorkerRequest - // which requires these to be non-null - var domain = Thread.GetDomain(); - if (domain.GetData(".appPath") == null) - domain.SetData(".appPath", ""); - if (domain.GetData(".appVPath") == null) - domain.SetData(".appVPath", ""); - - // create some event definitions - var definitions = new IEventDefinition[] - { - // works because that event definition maps to an empty handler - new EventDefinition>(null, ServiceContext.ContentTypeService, new SaveEventArgs(Enumerable.Empty()), "Saved"), - - }; - - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbracoContextFactory = new UmbracoContextFactory( - new TestUmbracoContextAccessor(), - Mock.Of(), - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - TestObjects.GetGlobalSettings(), - Mock.Of(), - TestHelper.GetHostingEnvironment(), - UriUtility, - httpContextAccessor, - new AspNetCookieManager(httpContextAccessor)); - - // just assert it does not throw - var refreshers = new DistributedCacheBinder(null, umbracoContextFactory, null); - refreshers.HandleEvents(definitions); - } - } -} diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs deleted file mode 100644 index a69eaf849f..0000000000 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Configuration; -using System.Data.SqlServerCe; -using System.IO; -using System.Threading; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using NPoco; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Migrations.Install; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Persistance.SqlCe; -using Umbraco.Tests.TestHelpers; - -namespace Umbraco.Tests.Persistence -{ - [TestFixture] - [Apartment(ApartmentState.STA)] - public class DatabaseContextTests - { - private IUmbracoDatabaseFactory _databaseFactory; - private ILogger _logger; - private ILoggerFactory _loggerFactory; - private SqlCeSyntaxProvider _sqlCeSyntaxProvider; - private ISqlSyntaxProvider[] _sqlSyntaxProviders; - private IUmbracoVersion _umbracoVersion; - - [SetUp] - public void Setup() - { - // create the database factory and database context - _sqlCeSyntaxProvider = new SqlCeSyntaxProvider(); - _sqlSyntaxProviders = new[] { (ISqlSyntaxProvider) _sqlCeSyntaxProvider }; - _logger = Mock.Of>(); - _loggerFactory = NullLoggerFactory.Instance; - _umbracoVersion = TestHelper.GetUmbracoVersion(); - var globalSettings = new GlobalSettings(); - var connectionStrings = new ConnectionStrings(); - _databaseFactory = new UmbracoDatabaseFactory(_logger, _loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy(() => Mock.Of()), TestHelper.DbProviderFactoryCreator); - } - - [TearDown] - public void TearDown() - { - _databaseFactory = null; - } - - [Test] - public void CreateDatabase() // FIXME: move to DatabaseBuilderTest! - { - var path = TestHelper.WorkingDirectory; - AppDomain.CurrentDomain.SetData("DataDirectory", path); - - // delete database file - // NOTE: using a custom db file for this test since we're re-using the one created with BaseDatabaseFactoryTest - var filePath = string.Concat(path, "\\DatabaseContextTests.sdf"); - if (File.Exists(filePath)) - File.Delete(filePath); - - // get the connectionstring settings from config - var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName]; - - // by default the conn string is: Datasource=|DataDirectory|UmbracoNPocoTests.sdf;Flush Interval=1; - // replace the SDF file with our own and create the sql ce database - var connString = settings.ConnectionString.Replace("UmbracoNPocoTests", "DatabaseContextTests"); - using (var engine = new SqlCeEngine(connString)) - { - engine.CreateDatabase(); - } - - // re-create the database factory and database context with proper connection string - _databaseFactory = new UmbracoDatabaseFactory(_logger, NullLoggerFactory.Instance, connString, Constants.DbProviderNames.SqlCe, new Lazy(() => Mock.Of()), TestHelper.DbProviderFactoryCreator); - - // test get database type (requires an actual database) - using (var database = _databaseFactory.CreateDatabase()) - { - var databaseType = database.DatabaseType; - Assert.AreEqual(DatabaseType.SQLCe, databaseType); - } - - // create application context - //var appCtx = new ApplicationContext( - // _databaseFactory, - // new ServiceContext(migrationEntryService: Mock.Of()), - // CacheHelper.CreateDisabledCacheHelper(), - // new ProfilingLogger(Mock.Of(), Mock.Of())); - - // create the umbraco database - DatabaseSchemaCreator schemaHelper; - using (var database = _databaseFactory.CreateDatabase()) - using (var transaction = database.GetTransaction()) - { - schemaHelper = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger(), _loggerFactory, _umbracoVersion); - schemaHelper.InitializeDatabaseSchema(); - transaction.Complete(); - } - - var umbracoNodeTable = schemaHelper.TableExists("umbracoNode"); - var umbracoUserTable = schemaHelper.TableExists("umbracoUser"); - var cmsTagsTable = schemaHelper.TableExists("cmsTags"); - - Assert.That(umbracoNodeTable, Is.True); - Assert.That(umbracoUserTable, Is.True); - Assert.That(cmsTagsTable, Is.True); - } - - [TestCase("MyServer", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("MyServer", "MyDatabase", "MyUser@MyServer", "MyPassword")] - [TestCase("tcp:MyServer", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:MyServer", "MyDatabase", "MyUser@MyServer", "MyPassword")] - [TestCase("tcp:MyServer,1433", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:MyServer,1433", "MyDatabase", "MyUser@MyServer", "MyPassword")] - [TestCase("tcp:MyServer.database.windows.net", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:MyServer.database.windows.net", "MyDatabase", "MyUser@MyServer", "MyPassword")] - [TestCase("tcp:MyServer.database.windows.net,1433", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:MyServer.database.windows.net,1433", "MyDatabase", "MyUser@MyServer", "MyPassword")] - public void Build_Azure_Connection_String_Regular(string server, string databaseName, string userName, string password) - { - var connectionString = DatabaseBuilder.GetAzureConnectionString(server, databaseName, userName, password); - Assert.AreEqual(connectionString, "Server=tcp:MyServer.database.windows.net,1433;Database=MyDatabase;User ID=MyUser@MyServer;Password=MyPassword"); - } - - [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433", "MyDatabase", "MyUser@kzeej5z8ty", "MyPassword")] - [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com", "MyDatabase", "MyUser", "MyPassword")] - [TestCase("tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com", "MyDatabase", "MyUser@kzeej5z8ty", "MyPassword")] - public void Build_Azure_Connection_String_CustomServer(string server, string databaseName, string userName, string password) - { - var connectionString = DatabaseBuilder.GetAzureConnectionString(server, databaseName, userName, password); - Assert.AreEqual(connectionString, "Server=tcp:kzeej5z8ty.ssmsawacluster4.windowsazure.mscds.com,1433;Database=MyDatabase;User ID=MyUser@kzeej5z8ty;Password=MyPassword"); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index cb3df3b905..70bf7b3eb9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -128,8 +128,6 @@ - - @@ -137,7 +135,6 @@ - @@ -179,7 +176,6 @@ - @@ -202,14 +198,11 @@ - - - @@ -314,6 +307,9 @@ + + + $(NuGetPackageFolders.Split(';')[0]) diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 8a6f44f456..30a5b4d53e 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -15,37 +13,29 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Serilog; using Smidge; using Smidge.Nuglify; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Diagnostics; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Runtime; using Umbraco.Core.Security; using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.HostedServices; using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Infrastructure.PublishedCache.DependencyInjection; -using Umbraco.Infrastructure.Runtime; using Umbraco.Net; -using Umbraco.Web.Cache; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Common.Install; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.Localization; @@ -132,8 +122,9 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.AddHostedService(factory => factory.GetRequiredService()); // Add supported databases - builder.AddUmbracoSqlCeSupport(); builder.AddUmbracoSqlServerSupport(); + builder.AddUmbracoSqlCeSupport(); + // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( diff --git a/src/umbraco-netcore-only.sln b/src/umbraco-netcore-only.sln index d7d3385dc8..3994d852cf 100644 --- a/src/umbraco-netcore-only.sln +++ b/src/umbraco-netcore-only.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{2849E9D4 ..\linting\codeanalysis.tests.ruleset = ..\linting\codeanalysis.tests.ruleset ..\Directory.Build.props = ..\Directory.Build.props ..\Directory.Build.targets = ..\Directory.Build.targets + ..\build\azure-pipelines.yml = ..\build\azure-pipelines.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{FD962632-184C-4005-A5F3-E705D92FC645}" @@ -141,6 +142,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Tests.UnitTests", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Integration", "Umbraco.Tests.Integration\Umbraco.Tests.Integration.csproj", "{1B885D2F-1599-4557-A4EC-474CC74DEB10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,6 +194,10 @@ Global {79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Release|Any CPU.ActiveCfg = Release|Any CPU {79E4293D-C92C-4649-AEC8-F1EFD95BDEB1}.Release|Any CPU.Build.0 = Release|Any CPU + {1B885D2F-1599-4557-A4EC-474CC74DEB10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B885D2F-1599-4557-A4EC-474CC74DEB10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B885D2F-1599-4557-A4EC-474CC74DEB10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B885D2F-1599-4557-A4EC-474CC74DEB10}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -204,6 +211,7 @@ Global {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} {A499779C-1B3B-48A8-B551-458E582E6E96} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {9102ABDF-E537-4E46-B525-C9ED4833EED0} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} + {1B885D2F-1599-4557-A4EC-474CC74DEB10} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC} From 3722a9bff53ab8b30c50dfdec8794278fba5a2b6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Jan 2021 11:14:43 +0100 Subject: [PATCH 60/88] Cleanup + fixed tests --- .../Objects/TestUmbracoContextFactory.cs | 4 +- .../Routing/UmbracoRequestPathsTests.cs | 9 ++-- .../Security/BackOfficeCookieManagerTests.cs | 4 +- .../Controllers/SurfaceControllerTests.cs | 53 ++----------------- .../Security/BackOfficeSessionIdValidator.cs | 10 ++-- 5 files changed, 19 insertions(+), 61 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs index 37b96b9947..d1450552c3 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Tests.Common; +using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.PublishedCache; @@ -55,7 +56,8 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects var snapshotService = new Mock(); snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot.Object); - IHostingEnvironment hostingEnvironment = Mock.Of(); + IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); + var backofficeSecurityAccessorMock = new Mock(); backofficeSecurityAccessorMock.Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs index da2ea985d0..722376d61a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Moq; @@ -8,6 +6,7 @@ using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Routing; using Umbraco.Web.Common.AspNetCore; +using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing { @@ -39,7 +38,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("/home.aspx", false)] public void Is_Client_Side_Request(string url, bool assert) { - var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + IHostingEnvironment hostingEnvironment = CreateHostingEnvironment(); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); var uri = new Uri("http://test.com" + url); var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); @@ -49,7 +49,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [Test] public void Is_Client_Side_Request_InvalidPath_ReturnFalse() { - var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + IHostingEnvironment hostingEnvironment = CreateHostingEnvironment(); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); // This URL is invalid. Default to false when the extension cannot be determined var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index 569c79faef..974254179d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; @@ -10,6 +9,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Routing; using Umbraco.Extensions; +using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Security; @@ -28,7 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - new UmbracoRequestPaths(Options.Create(globalSettings), Mock.Of())); + new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment())); var result = mgr.ShouldAuthenticateRequest("/umbraco"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index 1ea3e99b54..e286603f1c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -5,25 +5,22 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Tests.Common; using Umbraco.Tests.Testing; +using Umbraco.Tests.UnitTests.TestHelpers.Objects; using Umbraco.Web; using Umbraco.Web.Common.Routing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Website; using Umbraco.Web.Website.Controllers; -using Umbraco.Web.Website.Routing; using CoreConstants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers @@ -45,17 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); var globalSettings = new GlobalSettings(); - var umbracoContextFactory = new UmbracoContextFactory( - _umbracoContextAccessor, - Mock.Of(), - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), - hostingEnvironment, - new UriUtility(hostingEnvironment), - Mock.Of(), - Mock.Of(), - backofficeSecurityAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; @@ -76,17 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers IHostingEnvironment hostingEnvironment = Mock.Of(); IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); - var umbracoContextFactory = new UmbracoContextFactory( - _umbracoContextAccessor, - Mock.Of(), - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), - hostingEnvironment, - new UriUtility(hostingEnvironment), - Mock.Of(), - Mock.Of(), - backofficeSecurityAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); IUmbracoContext umbCtx = umbracoContextReference.UmbracoContext; @@ -111,17 +88,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers IHostingEnvironment hostingEnvironment = Mock.Of(); var globalSettings = new GlobalSettings(); - var umbracoContextFactory = new UmbracoContextFactory( - _umbracoContextAccessor, - publishedSnapshotService.Object, - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), - hostingEnvironment, - new UriUtility(hostingEnvironment), - Mock.Of(), - Mock.Of(), - backofficeSecurityAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; @@ -145,17 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers IHostingEnvironment hostingEnvironment = Mock.Of(); IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); - var umbracoContextFactory = new UmbracoContextFactory( - _umbracoContextAccessor, - Mock.Of(), - new TestVariationContextAccessor(), - new TestDefaultCultureAccessor(), - new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), - hostingEnvironment, - new UriUtility(hostingEnvironment), - Mock.Of(), - Mock.Of(), - backofficeSecurityAccessor); + var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor); UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs index 5efbf65b78..709416c420 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs @@ -1,3 +1,5 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. using System; using System.Security.Claims; @@ -13,10 +15,6 @@ using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security { -#pragma warning disable IDE0065 // Misplaced using directive - using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager; -#pragma warning restore IDE0065 // Misplaced using directive - /// /// Used to validate a cookie against a user's session id /// @@ -68,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Security private async Task ValidateSessionAsync( TimeSpan validateInterval, HttpContext httpContext, - ICookieManager cookieManager, + Microsoft.AspNetCore.Authentication.Cookies.ICookieManager cookieManager, ISystemClock systemClock, DateTimeOffset? authTicketIssueDate, ClaimsIdentity currentIdentity) @@ -118,7 +116,7 @@ namespace Umbraco.Web.BackOffice.Security var userId = currentIdentity.GetUserId(); var user = await _userManager.FindByIdAsync(userId); if (user == null) - { + { return false; } From 84206d7625446ba2cef7d3f10e8ad832cc053ba7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Jan 2021 10:37:21 +1100 Subject: [PATCH 61/88] removes ext checks since we don't have any server side requests with ext anymore --- .../Routing/UmbracoRequestPaths.cs | 47 +++++-------------- .../Routing/UmbracoRequestPathsTests.cs | 11 ++--- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index e670930691..8e8541cb2c 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -22,9 +22,7 @@ namespace Umbraco.Core.Routing private readonly string _apiMvcPath; private readonly string _installPath; private readonly string _appPath; - private readonly List _aspLegacyJsExt = new List { ".asmx/", ".aspx/", ".ashx/", ".axd/", ".svc/" }; - private readonly List _aspLegacyExt = new List { ".asmx", ".aspx", ".ashx", ".axd", ".svc" }; - + private readonly List _defaultUmbPaths; /// /// Initializes a new instance of the class. @@ -38,7 +36,7 @@ namespace Umbraco.Core.Routing .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - + _defaultUmbPaths = new List { "/" + _mvcArea, "/" + _mvcArea + "/" }; _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; _previewMvcPath = "/" + _mvcArea + "/Preview/"; _surfaceMvcPath = "/" + _mvcArea + "/Surface/"; @@ -50,22 +48,25 @@ namespace Umbraco.Core.Routing /// Checks if the current uri is a back office request /// /// + /// /// There are some special routes we need to check to properly determine this: - /// - /// If any route has an extension in the path like .aspx = back office - /// + /// + /// /// These are def back office: /// /Umbraco/BackOffice = back office /// /Umbraco/Preview = back office - /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end + /// + /// + /// If it's not any of the above then we cannot determine if it's back office or front-end /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// + /// + /// /// These are def front-end: /// /Umbraco/Surface = front-end /// /Umbraco/Api = front-end /// But if we've got this far we'll just have to assume it's front-end anyways. - /// + /// /// public bool IsBackOfficeRequest(string absPath) { @@ -82,24 +83,7 @@ namespace Umbraco.Core.Routing } // if its the normal /umbraco path - if (urlPath.InvariantEquals("/" + _mvcArea) - || urlPath.InvariantEquals("/" + _mvcArea + "/")) - { - return true; - } - - // check for a file extension - var extension = Path.GetExtension(absPath); - - // has an extension, def back office - if (extension.IsNullOrWhiteSpace() == false) - { - return true; - } - - // check for special case asp.net calls like: - // /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension - if (_aspLegacyJsExt.Any(x => urlPath.InvariantContains(x))) + if (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x))) { return true; } @@ -143,12 +127,7 @@ namespace Umbraco.Core.Routing public bool IsClientSideRequest(string absPath) { var ext = Path.GetExtension(absPath); - if (ext.IsNullOrWhiteSpace()) - { - return false; - } - - return _aspLegacyExt.Any(ext.InvariantEquals) == false; + return !ext.IsNullOrWhiteSpace(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs index 722376d61a..24f0b04080 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -35,7 +35,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)] [TestCase("/base/somebasehandler", false)] [TestCase("/", false)] - [TestCase("/home.aspx", false)] + [TestCase("/home.aspx", true)] // has ext, assume client side + [TestCase("http://www.domain.com/Umbraco/test/test.aspx", true)] // has ext, assume client side + [TestCase("http://www.domain.com/umbraco/test/test.js", true)] public void Is_Client_Side_Request(string url, bool assert) { IHostingEnvironment hostingEnvironment = CreateHostingEnvironment(); @@ -63,9 +65,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://www.domain.com/Umbraco/", "", true)] [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] [TestCase("http://www.domain.com/umbraco/test/test", "", false)] - [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] - [TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)] - [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] + [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] [TestCase("http://www.domain.com/umbrac", "", false)] [TestCase("http://www.domain.com/test", "", false)] [TestCase("http://www.domain.com/test/umbraco", "", false)] @@ -77,7 +77,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] - [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] public void Is_Back_Office_Request(string input, string virtualPath, bool expected) { var source = new Uri(input); @@ -85,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); } - + [TestCase("http://www.domain.com/install", true)] [TestCase("http://www.domain.com/Install/", true)] [TestCase("http://www.domain.com/install/default.aspx", true)] From a83cbc00ef0415ab50844bca596eee917fd18a7d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Jan 2021 16:28:00 +1100 Subject: [PATCH 62/88] Adding tests for HijackedRouteEvaluator --- .../Routing/HijackedRouteEvaluatorTests.cs | 95 +++++++++++++++++++ .../Routing/HijackedRouteEvaluator.cs | 12 ++- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs new file mode 100644 index 0000000000..fecbe76d55 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Primitives; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Extensions; +using Umbraco.Web; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Website.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing +{ + [TestFixture] + public class HijackedRouteEvaluatorTests + { + private class TestActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider + { + private readonly IEnumerable _actions; + + public TestActionDescriptorCollectionProvider(IEnumerable actions) => _actions = actions; + + public override ActionDescriptorCollection ActionDescriptors => new ActionDescriptorCollection(_actions.ToList(), 1); + + public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; + } + + private IActionDescriptorCollectionProvider GetActionDescriptors() => new TestActionDescriptorCollectionProvider( + new ActionDescriptor[] + { + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(RenderController).GetTypeInfo() + }, + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render1Controller).GetTypeInfo() + }, + new ControllerActionDescriptor + { + ActionName = "Index", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render2Controller).GetTypeInfo() + } + }); + + private class Render1Controller : ControllerBase, IRenderController + { + public IActionResult Index => Content("hello world"); + } + + private class Render2Controller : RenderController + { + public Render2Controller(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + } + + [TestCase("index", "Render", "Index", true)] + [TestCase("Index", "Render1", "Index", true)] + [TestCase("Index", "render2", "Index", true)] + [TestCase("NotFound", "Render", "Index", true)] + [TestCase("NotFound", "Render1", "Index", true)] + [TestCase("NotFound", "Render2", "Index", true)] + public void Matches_Controller(string action, string controller, string resultAction, bool matches) + { + var evaluator = new HijackedRouteEvaluator( + new NullLogger(), + GetActionDescriptors()); + + HijackedRouteResult result = evaluator.Evaluate(controller, action); + Assert.AreEqual(matches, result.Success); + if (matches) + { + Assert.IsTrue(result.ActionName.InvariantEquals(resultAction), "expected {0} does not match resulting action {1}", resultAction, result.ActionName); + Assert.IsTrue(result.ControllerName.InvariantEquals(controller), "expected {0} does not match resulting controller {1}", controller, result.ControllerName); + } + } + } +} diff --git a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs index a72268d298..79036a01e1 100644 --- a/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs +++ b/src/Umbraco.Web.Website/Routing/HijackedRouteEvaluator.cs @@ -50,14 +50,22 @@ namespace Umbraco.Web.Website.Routing && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) { // now check if the custom action matches - var customActionExists = action != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(action)); + var resultingAction = DefaultActionName; + if (action != null) + { + var found = customControllerCandidates.FirstOrDefault(x => x.ActionName.InvariantEquals(action))?.ActionName; + if (found != null) + { + resultingAction = found; + } + } // it's a hijacked route with a custom controller, so return the the values return new HijackedRouteResult( true, controllerDescriptor.ControllerName, controllerDescriptor.ControllerTypeInfo, - customActionExists ? action : DefaultActionName); + resultingAction); } else { From 456cb00a2d0b6b7eafbe2c6844768d38b33d73f4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 Jan 2021 17:06:37 +1100 Subject: [PATCH 63/88] Add test for UmbracoRouteValuesFactoryTests --- .../Routing/HijackedRouteEvaluatorTests.cs | 13 ++- .../Routing/UmbracoRouteValuesFactoryTests.cs | 90 +++++++++++++++++++ .../Routing/UmbracoRouteValuesFactory.cs | 8 +- 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs index fecbe76d55..5543a8920a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; @@ -21,6 +19,7 @@ using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing { + [TestFixture] public class HijackedRouteEvaluatorTests { @@ -51,6 +50,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing ControllerTypeInfo = typeof(Render1Controller).GetTypeInfo() }, new ControllerActionDescriptor + { + ActionName = "Custom", + ControllerName = ControllerExtensions.GetControllerName(), + ControllerTypeInfo = typeof(Render1Controller).GetTypeInfo() + }, + new ControllerActionDescriptor { ActionName = "Index", ControllerName = ControllerExtensions.GetControllerName(), @@ -61,6 +66,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing private class Render1Controller : ControllerBase, IRenderController { public IActionResult Index => Content("hello world"); + + public IActionResult Custom => Content("hello world"); } private class Render2Controller : RenderController @@ -71,12 +78,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing } } + [TestCase("Index", "RenderNotFound", null, false)] [TestCase("index", "Render", "Index", true)] [TestCase("Index", "Render1", "Index", true)] [TestCase("Index", "render2", "Index", true)] [TestCase("NotFound", "Render", "Index", true)] [TestCase("NotFound", "Render1", "Index", true)] [TestCase("NotFound", "Render2", "Index", true)] + [TestCase("Custom", "Render1", "Custom", true)] public void Matches_Controller(string action, string controller, string resultAction, bool matches) { var evaluator = new HijackedRouteEvaluator( diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs new file mode 100644 index 0000000000..ca5329a3f7 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs @@ -0,0 +1,90 @@ +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Features; +using Umbraco.Web.Routing; +using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing +{ + [TestFixture] + public class UmbracoRouteValuesFactoryTests + { + private UmbracoRouteValuesFactory GetFactory(IPublishedRouter router, out UmbracoRenderingDefaults renderingDefaults) + { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(Mock.Of()); + IPublishedRequest request = builder.Build(); + + var publishedRouter = new Mock(); + publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) + .Returns((IPublishedRequest r) => builder) + .Verifiable(); + + renderingDefaults = new UmbracoRenderingDefaults(); + + var factory = new UmbracoRouteValuesFactory( + renderingDefaults, + Mock.Of(), + new UmbracoFeatures(), + new HijackedRouteEvaluator( + new NullLogger(), + Mock.Of()), + publishedRouter.Object); + + return factory; + } + + [Test] + public void Update_Request_To_Not_Found_When_No_Template() + { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(Mock.Of()); + IPublishedRequest request = builder.Build(); + + var publishedRouter = new Mock(); + publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) + .Returns((IPublishedRequest r) => builder) + .Verifiable(); + + UmbracoRouteValuesFactory factory = GetFactory(publishedRouter.Object, out _); + + UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), new RouteValueDictionary(), request); + + // The request has content, no template, no hijacked route and no disabled template features so UpdateRequestToNotFound will be called + publishedRouter.Verify(m => m.UpdateRequestToNotFound(It.IsAny()), Times.Once); + } + + [Test] + public void Adds_Result_To_Route_Value_Dictionary() + { + var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); + builder.SetPublishedContent(Mock.Of()); + builder.SetTemplate(Mock.Of()); + IPublishedRequest request = builder.Build(); + + UmbracoRouteValuesFactory factory = GetFactory(Mock.Of(), out UmbracoRenderingDefaults renderingDefaults); + + var routeVals = new RouteValueDictionary(); + UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), routeVals, request); + + Assert.IsNotNull(result); + Assert.IsTrue(routeVals.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)); + Assert.AreEqual(result, routeVals[Constants.Web.UmbracoRouteDefinitionDataToken]); + Assert.AreEqual(renderingDefaults.DefaultControllerType, result.ControllerType); + Assert.AreEqual(UmbracoRouteValues.DefaultActionName, result.ActionName); + Assert.IsNull(result.TemplateName); + } + } +} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index fd92f7f11e..d26216204e 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -103,7 +103,7 @@ namespace Umbraco.Web.Website.Routing { IPublishedRequest request = def.PublishedRequest; - var customControllerName = request.PublishedContent?.ContentType.Alias; + var customControllerName = request.PublishedContent?.ContentType?.Alias; if (customControllerName != null) { HijackedRouteResult hijackedResult = _hijackedRouteEvaluator.Evaluate(customControllerName, def.TemplateName); @@ -144,6 +144,12 @@ namespace Umbraco.Web.Website.Routing // We then need to re-run this through the pipeline for the last // chance finders to work. IPublishedRequestBuilder builder = _publishedRouter.UpdateRequestToNotFound(request); + + if (builder == null) + { + throw new InvalidOperationException($"The call to {nameof(IPublishedRouter.UpdateRequestToNotFound)} cannot return null"); + } + request = builder.Build(); def = new UmbracoRouteValues( From cbe2214afc97822118237912447500dcd729025b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Jan 2021 13:58:48 +0100 Subject: [PATCH 64/88] AB#9725 - Removed IUmbracoApplicationLifetimeManager.cs and IUmbracoApplicationLifetime.ApplicationInit Migrated some of the components to UmbracoBuilder and fixed issues with the way asp.net core need to append the profiler html --- .../DependencyInjection/UmbracoBuilder.cs | 3 +- .../Hosting/IUmbracoApplicationLifetime.cs | 5 -- .../IUmbracoApplicationLifetimeManager.cs | 8 --- .../NoopUmbracoApplicationLifetimeManager.cs | 7 -- .../Compose/ModelsBuilderComponent.cs | 11 +-- .../Scoping/ScopedNuCacheTests.cs | 5 +- .../UmbracoBuilderExtensions.cs | 2 +- .../AspNetCoreUmbracoApplicationLifetime.cs | 12 +--- .../AspNetCore/UmbracoViewPage.cs | 24 +++---- .../UmbracoBuilderExtensions.cs | 10 ++- .../Profiler/InitializeWebProfiling.cs | 56 +++++++++++++++ .../Profiler/WebProfilerComponent.cs | 69 ------------------- .../Profiler/WebProfilerComposer.cs | 15 ---- .../Runtime/AspNetCoreComponent.cs | 37 ---------- .../Runtime/AspNetCoreComposer.cs | 12 ---- .../AspNetUmbracoApplicationLifetime.cs | 11 +-- 16 files changed, 81 insertions(+), 206 deletions(-) delete mode 100644 src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs delete mode 100644 src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs create mode 100644 src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs delete mode 100644 src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs delete mode 100644 src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs delete mode 100644 src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs delete mode 100644 src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 260ec5487f..0deac29761 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -114,9 +114,8 @@ namespace Umbraco.Core.DependencyInjection // Adds no-op registrations as many core services require these dependencies but these // dependencies cannot be fulfilled in the Core project Services.AddUnique(); - Services.AddUnique(); + //Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs index a4368a2634..50b7727ecf 100644 --- a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs @@ -1,5 +1,3 @@ -using System; - namespace Umbraco.Core.Hosting { public interface IUmbracoApplicationLifetime @@ -13,8 +11,5 @@ namespace Umbraco.Core.Hosting /// Terminates the current application. The application restarts the next time a request is received for it. /// void Restart(); - - // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications - event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs deleted file mode 100644 index 778edc24dd..0000000000 --- a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetimeManager.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Core.Hosting -{ - // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications - public interface IUmbracoApplicationLifetimeManager - { - void InvokeApplicationInit(); - } -} diff --git a/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs b/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs deleted file mode 100644 index 7833fd1224..0000000000 --- a/src/Umbraco.Core/Hosting/NoopUmbracoApplicationLifetimeManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Core.Hosting -{ - internal class NoopUmbracoApplicationLifetimeManager : IUmbracoApplicationLifetimeManager - { - public void InvokeApplicationInit() { } - } -} diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 7afb166069..9a139014fa 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Options; using Umbraco.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; @@ -26,12 +25,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose 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) + IUmbracoRequestLifetime umbracoRequestLifetime) { _config = config.Value; _shortStringHelper = shortStringHelper; @@ -40,7 +38,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose _shortStringHelper = shortStringHelper; _linkGenerator = linkGenerator; _umbracoRequestLifetime = umbracoRequestLifetime; - _umbracoApplicationLifetime = umbracoApplicationLifetime; } public void Initialize() @@ -48,7 +45,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose // 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; @@ -69,10 +66,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose FileService.SavingTemplate -= FileService_SavingTemplate; } - private void InitializeApplication(object sender, EventArgs args) - { - _umbracoRequestLifetime.RequestEnd += (sender, context) => _liveModelsProvider.AppEndRequest(context); - } private void InstallServerVars() { diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 71809d063a..cef471b2d4 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -25,10 +25,9 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.Cache; +using Umbraco.Web.Composing; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; -using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.Scoping { @@ -106,7 +105,7 @@ namespace Umbraco.Tests.Scoping hostingEnvironment, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); - lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); + //lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); return snapshotService; } diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 0d12fae687..92b604caaf 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies() - .AddMiniProfiler() + .AddUmbracoProfiler() .AddMvcAndRazor() .AddWebServer() .AddPreviewSupport() diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs index cdba8273a0..3854f92f8c 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreUmbracoApplicationLifetime.cs @@ -1,11 +1,9 @@ -using System; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; using Umbraco.Core.Hosting; namespace Umbraco.Web.Common.AspNetCore { - public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager + public class AspNetCoreUmbracoApplicationLifetime : IUmbracoApplicationLifetime { private readonly IHostApplicationLifetime _hostApplicationLifetime; @@ -21,13 +19,5 @@ namespace Umbraco.Web.Common.AspNetCore IsRestarting = true; _hostApplicationLifetime.StopApplication(); } - - public void InvokeApplicationInit() - { - ApplicationInit?.Invoke(this, EventArgs.Empty); - } - - // TODO: Should be killed and replaced with UmbracoApplicationStarting notifications - public event EventHandler ApplicationInit; } } diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index 3afc8978b6..512f56179a 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -1,12 +1,11 @@ using System; -using System.Text; -using System.Threading.Tasks; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -69,6 +68,11 @@ namespace Umbraco.Web.Common.AspNetCore { WriteLiteral(htmlEncodedString.ToHtmlString()); } + else if (value is TagHelperOutput tagHelperOutput) + { + WriteUmbracoContent(tagHelperOutput); + base.Write(value); + } else { base.Write(value); @@ -76,18 +80,16 @@ namespace Umbraco.Web.Common.AspNetCore } /// - public override void WriteLiteral(object value) + public void WriteUmbracoContent(TagHelperOutput tagHelperOutput) { // filter / add preview banner // ASP.NET default value is text/html - if (Context.Response.ContentType.InvariantEquals("text/html")) + if (Context.Response.ContentType.InvariantContains("text/html")) { if (UmbracoContext.IsDebug || UmbracoContext.InPreviewMode) { - var text = value.ToString(); - var pos = text.IndexOf("", StringComparison.InvariantCultureIgnoreCase); - if (pos > -1) + if (tagHelperOutput.TagName.Equals("body", StringComparison.InvariantCultureIgnoreCase)) { string markupToInject; @@ -107,16 +109,10 @@ namespace Umbraco.Web.Common.AspNetCore markupToInject = ProfilerHtml.Render(); } - var sb = new StringBuilder(text); - sb.Insert(pos, markupToInject); - - WriteLiteral(sb.ToString()); - return; + tagHelperOutput.Content.AppendHtml(markupToInject); } } } - - base.WriteLiteral(value); } /// diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 30a5b4d53e..58a0690bfc 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -22,6 +22,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Diagnostics; +using Umbraco.Core.Events; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -137,7 +138,7 @@ namespace Umbraco.Web.Common.DependencyInjection builder.AddCoreInitialServices(); // aspnet app lifetime mgmt - builder.Services.AddMultipleUnique(); + builder.Services.AddUnique(); builder.Services.AddUnique(); return builder; @@ -166,15 +167,18 @@ namespace Umbraco.Web.Common.DependencyInjection } /// - /// Adds mini profiler services for Umbraco + /// Adds the Umbraco request profiler /// - public static IUmbracoBuilder AddMiniProfiler(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddUmbracoProfiler(this IUmbracoBuilder builder) { + builder.Services.AddUnique(); + builder.Services.AddMiniProfiler(options => // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile options.ShouldProfile = request => false); + builder.AddNotificationHandler(); return builder; } diff --git a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs new file mode 100644 index 0000000000..ff4b2293ed --- /dev/null +++ b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Umbraco.Core.Events; +using Umbraco.Core.Logging; +using Umbraco.Web.Common.Lifetime; + +namespace Umbraco.Web.Common.Profiler +{ + public class InitializeWebProfiling : INotificationHandler + { + private readonly bool _profile; + private readonly WebProfiler _profiler; + private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; + private readonly List _terminate = new List(); + public InitializeWebProfiling(IProfiler profiler, IUmbracoRequestLifetime umbracoRequestLifetime, ILogger logger) + { + _umbracoRequestLifetime = umbracoRequestLifetime; + _profile = true; + + // although registered in WebRuntime.Compose, ensure that we have not + // been replaced by another component, and we are still "the" profiler + _profiler = profiler as WebProfiler; + if (_profiler != null) return; + + // if VoidProfiler was registered, let it be known + if (profiler is NoopProfiler) + logger.LogInformation( + "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + _profile = false; + } + + /// + public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + { + if (_profile) + { + void requestStart(object sender, HttpContext context) => _profiler.UmbracoApplicationBeginRequest(context); + _umbracoRequestLifetime.RequestStart += requestStart; + _terminate.Add(() => _umbracoRequestLifetime.RequestStart -= requestStart); + + void requestEnd(object sender, HttpContext context) => _profiler.UmbracoApplicationEndRequest(context); + _umbracoRequestLifetime.RequestEnd += requestEnd; + _terminate.Add(() => _umbracoRequestLifetime.RequestEnd -= requestEnd); + + // Stop the profiling of the booting process + _profiler.StopBoot(); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs deleted file mode 100644 index 498b550c1a..0000000000 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Web.Common.Lifetime; -using Umbraco.Web.Common.Middleware; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.Common.Profiler -{ - internal sealed class WebProfilerComponent : IComponent - { - private readonly bool _profile; - private readonly WebProfiler _profiler; - private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; - private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; - private readonly List _terminate = new List(); - - public WebProfilerComponent(IProfiler profiler, ILogger logger, IUmbracoRequestLifetime umbracoRequestLifetime, - IUmbracoApplicationLifetime umbracoApplicationLifetime) - { - _umbracoRequestLifetime = umbracoRequestLifetime; - _umbracoApplicationLifetime = umbracoApplicationLifetime; - _profile = true; - - // although registered in WebRuntime.Compose, ensure that we have not - // been replaced by another component, and we are still "the" profiler - _profiler = profiler as WebProfiler; - if (_profiler != null) return; - - // if VoidProfiler was registered, let it be known - if (profiler is NoopProfiler) - logger.LogInformation( - "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); - _profile = false; - } - - public void Initialize() - { - if (!_profile) return; - - // bind to ApplicationInit - ie execute the application initialization for *each* application - // it would be a mistake to try and bind to the current application events - _umbracoApplicationLifetime.ApplicationInit += InitializeApplication; - } - - public void Terminate() - { - _umbracoApplicationLifetime.ApplicationInit -= InitializeApplication; - foreach (var t in _terminate) t(); - } - - private void InitializeApplication(object sender, EventArgs args) - { - void requestStart(object sender, HttpContext context) => _profiler.UmbracoApplicationBeginRequest(context); - _umbracoRequestLifetime.RequestStart += requestStart; - _terminate.Add(() => _umbracoRequestLifetime.RequestStart -= requestStart); - - void requestEnd(object sender, HttpContext context) => _profiler.UmbracoApplicationEndRequest(context); - _umbracoRequestLifetime.RequestEnd += requestEnd; - _terminate.Add(() => _umbracoRequestLifetime.RequestEnd -= requestEnd); - - // Stop the profiling of the booting process - _profiler.StopBoot(); - } - } -} diff --git a/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs deleted file mode 100644 index eac3e058c2..0000000000 --- a/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; - -namespace Umbraco.Web.Common.Profiler -{ - internal class WebProfilerComposer : ComponentComposer, ICoreComposer - { - public override void Compose(IUmbracoBuilder builder) - { - base.Compose(builder); - - builder.Services.AddUnique(); - } - } -} diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs deleted file mode 100644 index 5c7e47cf3f..0000000000 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Hosting; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Hosting; -using Umbraco.Web.Common.Lifetime; - -namespace Umbraco.Web.Common.Runtime -{ - public sealed class AspNetCoreComponent : IComponent - { - private readonly IHostApplicationLifetime _hostApplicationLifetime; - private readonly IUmbracoApplicationLifetimeManager _umbracoApplicationLifetimeManager; - - public AspNetCoreComponent( - IHostApplicationLifetime hostApplicationLifetime, - IUmbracoApplicationLifetimeManager umbracoApplicationLifetimeManager) - { - _hostApplicationLifetime = hostApplicationLifetime; - _umbracoApplicationLifetimeManager = umbracoApplicationLifetimeManager; - } - - public void Initialize() - { - _hostApplicationLifetime.ApplicationStarted.Register(() => { - _umbracoApplicationLifetimeManager.InvokeApplicationInit(); - }); - } - - - - public void Terminate() - { - } - } -} diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs deleted file mode 100644 index 1eda1cc23a..0000000000 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Umbraco.Core.Composing; -namespace Umbraco.Web.Common.Runtime -{ - - /// - /// Adds/replaces AspNetCore specific services - /// - [ComposeBefore(typeof(ICoreComposer))] - public class AspNetCoreComposer : ComponentComposer, IComposer - { - } -} diff --git a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs index 90261b1a5a..107c7e41c5 100644 --- a/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Web/AspNet/AspNetUmbracoApplicationLifetime.cs @@ -1,19 +1,16 @@ -using System; using System.Threading; using System.Web; using Umbraco.Core.Hosting; namespace Umbraco.Web.AspNet { - public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime, IUmbracoApplicationLifetimeManager + public class AspNetUmbracoApplicationLifetime : IUmbracoApplicationLifetime { private readonly IHttpContextAccessor _httpContextAccessor; public AspNetUmbracoApplicationLifetime(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; - - UmbracoApplicationBase.ApplicationInit += ApplicationInit; } public bool IsRestarting { get; set; } @@ -33,11 +30,5 @@ namespace Umbraco.Web.AspNet Thread.CurrentPrincipal = null; HttpRuntime.UnloadAppDomain(); } - - public event EventHandler ApplicationInit; - public void InvokeApplicationInit() - { - ApplicationInit?.Invoke(this, EventArgs.Empty); - } } } From b0f4f9f440ede17904ee88c6fd02b3628d6b3b84 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 Jan 2021 14:00:14 +0100 Subject: [PATCH 65/88] Refactoring the use of ValidationErrorResult with the build-in types for status response results --- .../Controllers/AuthenticationController.cs | 2 +- .../Controllers/CodeFileController.cs | 5 +-- .../Controllers/ContentController.cs | 15 ++++---- .../Controllers/ContentTypeController.cs | 13 +++---- .../Controllers/ContentTypeControllerBase.cs | 9 ++--- .../Controllers/DataTypeController.cs | 16 ++++---- .../Controllers/EntityController.cs | 38 +++++++++---------- .../Controllers/MacroRenderingController.cs | 6 +-- .../Controllers/MacrosController.cs | 2 +- .../Controllers/MediaController.cs | 15 ++++---- .../Controllers/MediaTypeController.cs | 16 ++++---- .../Controllers/MemberController.cs | 8 ++-- .../Controllers/MemberGroupController.cs | 14 +++---- .../Controllers/MemberTypeController.cs | 16 ++++---- .../Controllers/PackageController.cs | 5 +-- .../Controllers/RelationTypeController.cs | 10 ++--- .../Controllers/TemplateController.cs | 16 ++++---- .../Controllers/UsersController.cs | 32 ++++++++-------- 18 files changed, 108 insertions(+), 130 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index ef12f98120..1463c19bb5 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -349,7 +349,7 @@ namespace Umbraco.Web.BackOffice.Controllers // by our angular helper because it thinks that we need to re-perform the request once we are // authorized and we don't want to return a 403 because angular will show a warning message indicating // that the user doesn't have access to perform this function, we just want to return a normal invalid message. - return new ValidationErrorResult(null); + return BadRequest(); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index a945ad3b2b..e2ec2fecd7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Net; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -258,10 +257,10 @@ namespace Umbraco.Web.BackOffice.Controllers snippets = _fileService.GetPartialViewSnippetNames(); break; default: - return new ValidationErrorResult(type, StatusCodes.Status404NotFound); + return NotFound(); } - return snippets.Select(snippet => new SnippetDisplay() {Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet}).ToList(); + return snippets.Select(snippet => new SnippetDisplay() { Name = snippet.SplitPascalCasing(_shortStringHelper).ToFirstUpperInvariant(), FileName = snippet }).ToList(); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 5f1d283b14..cc9a787ee9 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -6,7 +6,6 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -384,7 +383,7 @@ namespace Umbraco.Web.BackOffice.Controllers return GetById(guidUdi.Guid); } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } /// @@ -399,7 +398,7 @@ namespace Umbraco.Web.BackOffice.Controllers var contentType = _contentTypeService.Get(contentTypeAlias); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } return GetEmpty(contentType, parentId); @@ -417,7 +416,7 @@ namespace Umbraco.Web.BackOffice.Controllers var contentType = _contentTypeService.Get(contentTypeKey); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } return GetEmpty(contentType, parentId); @@ -446,7 +445,7 @@ namespace Umbraco.Web.BackOffice.Controllers var blueprint = _contentService.GetBlueprintById(blueprintId); if (blueprint == null) { - return new ValidationErrorResult(blueprint, StatusCodes.Status404NotFound); + return NotFound(); } blueprint.Id = 0; @@ -2022,14 +2021,14 @@ namespace Umbraco.Web.BackOffice.Controllers { if (model == null) { - return new ValidationErrorResult(model, StatusCodes.Status404NotFound); + return NotFound(); } var contentService = _contentService; var toMove = contentService.GetById(model.Id); if (toMove == null) { - return new ValidationErrorResult(toMove, StatusCodes.Status404NotFound); + return NotFound(); } if (model.ParentId < 0) { @@ -2045,7 +2044,7 @@ namespace Umbraco.Web.BackOffice.Controllers var parent = contentService.GetById(model.ParentId); if (parent == null) { - return new ValidationErrorResult(parent, StatusCodes.Status404NotFound); + return NotFound(); } var parentContentType = _contentTypeService.Get(parent.ContentTypeId); diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 864a3e7369..67e6a6332c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Mime; using System.Text; using System.Xml; @@ -121,7 +120,7 @@ namespace Umbraco.Web.BackOffice.Controllers var ct = _contentTypeService.Get(id); if (ct == null) { - return new ValidationErrorResult(ct, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(ct); @@ -138,7 +137,7 @@ namespace Umbraco.Web.BackOffice.Controllers var contentType = _contentTypeService.Get(id); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(contentType); @@ -154,12 +153,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var contentType = _contentTypeService.Get(guidUdi.Guid); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(contentType); @@ -177,7 +176,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _contentTypeService.Get(id); if (foundType == null) { - return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); + return NotFound(); } _contentTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -254,7 +253,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (dataTypeDiff == null) { - return new ValidationErrorResult(dataTypeDiff, StatusCodes.Status404NotFound); + return NotFound(); } var configuration = _dataTypeService.GetDataType(id).Configuration; diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 2e1f711984..9b70d2fb49 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net; using System.Net.Mime; using System.Text; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Core; @@ -92,7 +91,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source = ContentTypeService.Get(contentTypeId); - if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); + if (source == null) return NotFound(); } allContentTypes = ContentTypeService.GetAll().Cast().ToArray(); break; @@ -101,7 +100,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source =MediaTypeService.Get(contentTypeId); - if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); + if (source == null) return NotFound(); } allContentTypes =MediaTypeService.GetAll().Cast().ToArray(); break; @@ -110,7 +109,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentTypeId > 0) { source = MemberTypeService.Get(contentTypeId); - if (source == null) return new ValidationErrorResult(source, StatusCodes.Status404NotFound); + if (source == null) return NotFound(); } allContentTypes = MemberTypeService.GetAll().Cast().ToArray(); break; @@ -205,7 +204,7 @@ namespace Umbraco.Web.BackOffice.Controllers } if (source == null) - return new ValidationErrorResult(source, StatusCodes.Status404NotFound); + return NotFound(); id = source.Id; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index a099953a08..452ff8b5e0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -2,11 +2,9 @@ using System; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Net; using System.Net.Mime; using System.Text; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -97,7 +95,7 @@ namespace Umbraco.Web.BackOffice.Controllers var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { - return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(dataType); @@ -114,7 +112,7 @@ namespace Umbraco.Web.BackOffice.Controllers var dataType = _dataTypeService.GetDataType(id); if (dataType == null) { - return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(dataType); @@ -130,12 +128,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var dataType = _dataTypeService.GetDataType(guidUdi.Guid); if (dataType == null) { - return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(dataType); @@ -153,7 +151,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _dataTypeService.GetDataType(id); if (foundType == null) { - return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); + return NotFound(); } var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; _dataTypeService.Delete(foundType, currentUser.Id); @@ -179,7 +177,7 @@ namespace Umbraco.Web.BackOffice.Controllers var dt = _dataTypeService.GetDataType(Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias); if (dt == null) { - return new ValidationErrorResult(dt, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(dt); @@ -229,7 +227,7 @@ namespace Umbraco.Web.BackOffice.Controllers var dataType = _dataTypeService.GetDataType(dataTypeId); if (dataType == null) { - return new ValidationErrorResult(dataType, StatusCodes.Status404NotFound); + return NotFound(); } //now, lets check if the data type has the current editor selected, if that is true diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 600134df8e..34031a5dba 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -17,7 +17,6 @@ using Umbraco.Core.Trees; using Umbraco.Core.Xml; using Umbraco.Extensions; using Umbraco.Web.BackOffice.ModelBinders; -using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models; @@ -237,7 +236,8 @@ namespace Umbraco.Web.BackOffice.Controllers { return new ActionResult>(GetPath(guidUdi.Guid, type)); } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + + return NotFound(); } /// @@ -251,7 +251,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var intId = _entityService.GetId(udi); if (!intId.Success) - return new ValidationErrorResult(intId.Result, StatusCodes.Status404NotFound); + return NotFound(); UmbracoEntityTypes entityType; switch (udi.EntityType) { @@ -265,7 +265,7 @@ namespace Umbraco.Web.BackOffice.Controllers entityType = UmbracoEntityTypes.Member; break; default: - return new ValidationErrorResult(udi.EntityType, StatusCodes.Status404NotFound); + return NotFound(); } return GetUrl(intId.Result, entityType, culture); } @@ -357,7 +357,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var intId = _entityService.GetId(id); if (!intId.Success) - return new ValidationErrorResult(intId.Result, StatusCodes.Status404NotFound); + return NotFound(); return GetUrlAndAnchors(intId.Result, culture); } @@ -422,7 +422,7 @@ namespace Umbraco.Web.BackOffice.Controllers return GetResultForKey(guidUdi.Guid, type); } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } #endregion @@ -443,7 +443,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ids == null) { - return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); + return NotFound(); } return new ActionResult>(GetResultForIds(ids, type)); @@ -465,7 +465,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ids == null) { - return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); + return NotFound(); } return new ActionResult>(GetResultForKeys(ids, type)); @@ -489,7 +489,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ids == null) { - return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); + return NotFound(); } if (ids.Length == 0) @@ -506,7 +506,7 @@ namespace Umbraco.Web.BackOffice.Controllers return new ActionResult>(GetResultForKeys(ids.Select(x => ((GuidUdi)x).Guid).ToArray(), type)); } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } #endregion @@ -580,13 +580,13 @@ namespace Umbraco.Web.BackOffice.Controllers if (Guid.TryParse(id, out _)) { //Not supported currently - return new ValidationErrorResult(id, StatusCodes.Status404NotFound); + return NotFound(); } if (UdiParser.TryParse(id, out _)) { //Not supported currently - return new ValidationErrorResult(id, StatusCodes.Status404NotFound); + return NotFound(); } //so we don't have an INT, GUID or UDI, it's just a string, so now need to check if it's a special id or a member type @@ -635,9 +635,9 @@ namespace Umbraco.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) - return new ValidationErrorResult(pageNumber, StatusCodes.Status404NotFound); + return NotFound(); if (pageSize <= 0) - return new ValidationErrorResult(pageSize, StatusCodes.Status404NotFound); + return NotFound(); var objectType = ConvertToObjectType(type); if (objectType.HasValue) @@ -735,9 +735,9 @@ namespace Umbraco.Web.BackOffice.Controllers Guid? dataTypeKey = null) { if (pageNumber <= 0) - return new ValidationErrorResult(pageNumber, StatusCodes.Status404NotFound); + return NotFound(); if (pageSize <= 0) - return new ValidationErrorResult(pageSize, StatusCodes.Status404NotFound); + return NotFound(); // re-normalize since NULL can be passed in filter = filter ?? string.Empty; @@ -980,7 +980,7 @@ namespace Umbraco.Web.BackOffice.Controllers var found = _entityService.Get(key, objectType.Value); if (found == null) { - return new ValidationErrorResult(found, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(found); } @@ -1012,7 +1012,7 @@ namespace Umbraco.Web.BackOffice.Controllers var found = _entityService.Get(id, objectType.Value); if (found == null) { - return new ValidationErrorResult(found, StatusCodes.Status404NotFound); + return NotFound(); } return MapEntity(found); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs index b3d0eefad4..739ef9942e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacroRenderingController.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Net; using System.Text; using System.Threading; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core; @@ -68,7 +66,7 @@ namespace Umbraco.Web.BackOffice.Controllers var macro = _macroService.GetById(macroId); if (macro == null) { - return new ValidationErrorResult(macro, StatusCodes.Status404NotFound); + return NotFound(); } return new ActionResult>(_umbracoMapper.Map>(macro).OrderBy(x => x.SortOrder)); @@ -116,7 +114,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var m = _macroService.GetByAlias(macroAlias); if (m == null) - return new ValidationErrorResult(m, StatusCodes.Status404NotFound); + return NotFound(); var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); var publishedContent = umbracoContext.Content.GetById(true, pageId); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs index 598fe15bf4..1ad7442289 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs @@ -160,7 +160,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [HttpPost] - public ActionResult DeleteById(int id) + public IActionResult DeleteById(int id) { var macro = _macroService.GetById(id); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index e43b40f97e..49f16f41ac 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Mime; using System.Text; using System.Threading.Tasks; @@ -126,7 +125,7 @@ namespace Umbraco.Web.BackOffice.Controllers var contentType = _mediaTypeService.Get(contentTypeAlias); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } var emptyContent = _mediaService.CreateMedia("", parentId, contentType.Alias, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(Constants.Security.SuperUserId)); @@ -221,7 +220,7 @@ namespace Umbraco.Web.BackOffice.Controllers return GetById(guidUdi.Guid); } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } /// @@ -392,7 +391,7 @@ namespace Umbraco.Web.BackOffice.Controllers return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter); } - return new ValidationErrorResult(entity, StatusCodes.Status404NotFound); + return NotFound(); } /// @@ -426,7 +425,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } #endregion @@ -906,14 +905,14 @@ namespace Umbraco.Web.BackOffice.Controllers { if (model == null) { - return new ValidationErrorResult(model, StatusCodes.Status404NotFound); + return NotFound(); } var toMove = _mediaService.GetById(model.Id); if (toMove == null) { - return new ValidationErrorResult(toMove, StatusCodes.Status404NotFound); + return NotFound(); } if (model.ParentId < 0) { @@ -932,7 +931,7 @@ namespace Umbraco.Web.BackOffice.Controllers var parent = _mediaService.GetById(model.ParentId); if (parent == null) { - return new ValidationErrorResult(parent, StatusCodes.Status404NotFound); + return NotFound(); } //check if the item is allowed under this one diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index 2446d480ef..1010615368 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Dictionary; @@ -83,7 +81,7 @@ namespace Umbraco.Web.BackOffice.Controllers var ct = _mediaTypeService.Get(id); if (ct == null) { - return new ValidationErrorResult(ct, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(ct); @@ -102,7 +100,7 @@ namespace Umbraco.Web.BackOffice.Controllers var mediaType = _mediaTypeService.Get(id); if (mediaType == null) { - return new ValidationErrorResult(mediaType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(mediaType); @@ -120,12 +118,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var mediaType = _mediaTypeService.Get(guidUdi.Guid); if (mediaType == null) { - return new ValidationErrorResult(mediaType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(mediaType); @@ -145,7 +143,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _mediaTypeService.Get(id); if (foundType == null) { - return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); + return NotFound(); } _mediaTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -383,7 +381,7 @@ namespace Umbraco.Web.BackOffice.Controllers return new ActionResult>(GetAllowedChildren(entity.Id)); } - return new ValidationErrorResult(entity, StatusCodes.Status404NotFound); + return NotFound(); } /// @@ -404,7 +402,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); } #endregion diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 47487d6c5b..23e6e30d81 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -2,13 +2,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -173,13 +171,13 @@ namespace Umbraco.Web.BackOffice.Controllers IMember emptyContent; if (contentTypeAlias == null) { - return new ValidationErrorResult(contentTypeAlias, StatusCodes.Status404NotFound); + return NotFound(); } var contentType = _memberTypeService.Get(contentTypeAlias); if (contentType == null) { - return new ValidationErrorResult(contentType, StatusCodes.Status404NotFound); + return NotFound(); } var passwordGenerator = new PasswordGenerator(_passwordConfig); @@ -241,7 +239,7 @@ namespace Umbraco.Web.BackOffice.Controllers break; default: //we don't support anything else for members - return new ValidationErrorResult(contentItem.Action, StatusCodes.Status404NotFound); + return NotFound(); } //TODO: There's 3 things saved here and we should do this all in one transaction, which we can do here by wrapping in a scope diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs index 6e8c002b7d..43df969ef5 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberGroupController.cs @@ -2,13 +2,11 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; -using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; @@ -50,7 +48,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(memberGroup); @@ -69,7 +67,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(memberGroup); @@ -85,12 +83,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var memberGroup = _memberGroupService.GetById(guidUdi.Guid); if (memberGroup == null) { - return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(memberGroup); @@ -109,7 +107,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberGroup = _memberGroupService.GetById(id); if (memberGroup == null) { - return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); + return NotFound(); } _memberGroupService.Delete(memberGroup); @@ -135,7 +133,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberGroup = id > 0 ? _memberGroupService.GetById(id) : new MemberGroup(); if (memberGroup == null) { - return new ValidationErrorResult(memberGroup, StatusCodes.Status404NotFound); + return NotFound(); } memberGroup.Name = saveModel.Name; diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 9f01987470..6e95680110 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -14,8 +14,6 @@ using Umbraco.Core.Security; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Editors; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers @@ -70,7 +68,7 @@ namespace Umbraco.Web.BackOffice.Controllers var mt = _memberTypeService.Get(id); if (mt == null) { - return new ValidationErrorResult(mt, StatusCodes.Status404NotFound); + return NotFound(); } var dto =_umbracoMapper.Map(mt); @@ -88,7 +86,7 @@ namespace Umbraco.Web.BackOffice.Controllers var memberType = _memberTypeService.Get(id); if (memberType == null) { - return new ValidationErrorResult(memberType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(memberType); @@ -105,12 +103,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var memberType = _memberTypeService.Get(guidUdi.Guid); if (memberType == null) { - return new ValidationErrorResult(memberType, StatusCodes.Status404NotFound); + return NotFound(); } var dto = _umbracoMapper.Map(memberType); @@ -129,7 +127,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundType = _memberTypeService.Get(id); if (foundType == null) { - return new ValidationErrorResult(foundType, StatusCodes.Status404NotFound); + return NotFound(); } _memberTypeService.Delete(foundType, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -212,7 +210,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ct.IsSensitiveProperty(foundOnContentType.Alias) && prop.IsSensitiveData == false) { //if these don't match, then we cannot continue, this user is not allowed to change this value - return new ValidationErrorResult(ct, StatusCodes.Status403Forbidden); + return Forbid(); } } } @@ -221,7 +219,7 @@ namespace Umbraco.Web.BackOffice.Controllers //if it is new, then we can just verify if any property has sensitive data turned on which is not allowed if (props.Any(prop => prop.IsSensitiveData)) { - return new ValidationErrorResult(props, StatusCodes.Status403Forbidden); + return Forbid(); } } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 47944428d7..57f49bdc23 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Net; using System.Text; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Semver; @@ -50,7 +49,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var package = _packagingService.GetCreatedPackageById(id); if (package == null) - return new ValidationErrorResult(package, StatusCodes.Status404NotFound); + return NotFound(); return package; } @@ -130,7 +129,7 @@ namespace Umbraco.Web.BackOffice.Controllers public ActionResult GetInstalledPackageById(int id) { var pack = _packagingService.GetInstalledPackageById(id); - if (pack == null) return new ValidationErrorResult(pack, StatusCodes.Status404NotFound); + if (pack == null) return NotFound(); return pack; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs index 2a8a148555..0a6d174bdd 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/RelationTypeController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Net.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -14,7 +13,6 @@ using Constants = Umbraco.Core.Constants; using Umbraco.Core.Mapping; using Umbraco.Web.Common.Attributes; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; @@ -56,7 +54,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (relationType == null) { - return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); + return NotFound(); } var display = _umbracoMapper.Map(relationType); @@ -74,7 +72,7 @@ namespace Umbraco.Web.BackOffice.Controllers var relationType = _relationService.GetRelationTypeById(id); if (relationType == null) { - return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(relationType); } @@ -89,12 +87,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var relationType = _relationService.GetRelationTypeById(guidUdi.Guid); if (relationType == null) { - return new ValidationErrorResult(relationType, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(relationType); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs index 4ba9306bd0..560e3bc78c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/TemplateController.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.IO; @@ -10,7 +9,6 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; @@ -66,7 +64,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var template = _fileService.GetTemplate(id); if (template == null) - return new ValidationErrorResult(template, StatusCodes.Status404NotFound); + return NotFound(); return _umbracoMapper.Map(template); } @@ -82,7 +80,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var template = _fileService.GetTemplate(id); if (template == null) - return new ValidationErrorResult(template, StatusCodes.Status404NotFound); + return NotFound(); return _umbracoMapper.Map(template); } @@ -97,12 +95,12 @@ namespace Umbraco.Web.BackOffice.Controllers { var guidUdi = id as GuidUdi; if (guidUdi == null) - return new ValidationErrorResult(guidUdi, StatusCodes.Status404NotFound); + return NotFound(); var template = _fileService.GetTemplate(guidUdi.Guid); if (template == null) { - return new ValidationErrorResult(template, StatusCodes.Status404NotFound); + return NotFound(); } return _umbracoMapper.Map(template); @@ -119,7 +117,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var template = _fileService.GetTemplate(id); if (template == null) - return new ValidationErrorResult(template, StatusCodes.Status404NotFound); + return NotFound(); _fileService.DeleteTemplate(template.Alias); return Ok(); @@ -166,7 +164,7 @@ namespace Umbraco.Web.BackOffice.Controllers // update var template = _fileService.GetTemplate(display.Id); if (template == null) - return new ValidationErrorResult(template, StatusCodes.Status404NotFound); + return NotFound(); var changeMaster = template.MasterTemplateAlias != display.MasterTemplateAlias; var changeAlias = template.Alias != display.Alias; @@ -238,7 +236,7 @@ namespace Umbraco.Web.BackOffice.Controllers { master = _fileService.GetTemplate(display.MasterTemplateAlias); if (master == null) - return new ValidationErrorResult(master, StatusCodes.Status404NotFound); + return NotFound(); } // we need to pass the template name as alias to keep the template file casing consistent with templates created with content diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 8475773caa..a8be6bd125 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -125,7 +125,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator); if (urls == null) - return new ValidationErrorResult("Could not access Gravatar endpoint"); + return BadRequest("Could not access Gravatar endpoint"); return urls; } @@ -141,7 +141,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (files is null) { - return new ValidationErrorResult(files, StatusCodes.Status415UnsupportedMediaType); + return new UnsupportedMediaTypeResult(); } var root = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads); @@ -229,7 +229,7 @@ namespace Umbraco.Web.BackOffice.Controllers var user = _userService.GetUserById(id); if (user == null) { - return new ValidationErrorResult(user, StatusCodes.Status404NotFound); + return NotFound(); } var result = _umbracoMapper.Map(user); return result; @@ -246,7 +246,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ids == null) { - return new ValidationErrorResult(ids, StatusCodes.Status404NotFound); + return NotFound(); } if (ids.Length == 0) @@ -255,7 +255,7 @@ namespace Umbraco.Web.BackOffice.Controllers var users = _userService.GetUsersById(ids); if (users == null) { - return new ValidationErrorResult(users, StatusCodes.Status404NotFound); + return NotFound(); } var result = _umbracoMapper.MapEnumerable(users); @@ -342,7 +342,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); } if (_securitySettings.UsernameIsEmail) @@ -361,7 +361,7 @@ namespace Umbraco.Web.BackOffice.Controllers var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) { - return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized); + return Unauthorized(canSaveUser.Result); } //we want to create the user with the UserManager, this ensures the 'empty' (special) password @@ -419,7 +419,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); } IUser user; @@ -518,7 +518,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (user != null && (extraCheck == null || extraCheck(user))) { ModelState.AddModelError("Email", "A user with the email already exists"); - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); } return new ActionResult(user); @@ -532,7 +532,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError( _securitySettings.UsernameIsEmail ? "Email" : "Username", "A user with the username already exists"); - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); } return new ActionResult(user); @@ -594,13 +594,13 @@ namespace Umbraco.Web.BackOffice.Controllers var found = _userService.GetUserById(intId.Result); if (found == null) - return new ValidationErrorResult(found, StatusCodes.Status404NotFound); + return NotFound(); //Perform authorization here to see if the current user can actually save this user with the info being requested var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, found, userSave.StartContentIds, userSave.StartMediaIds, userSave.UserGroups); if (canSaveUser == false) { - return new ValidationErrorResult(canSaveUser.Result, StatusCodes.Status401Unauthorized); + return Unauthorized(canSaveUser.Result); } var hasErrors = false; @@ -647,7 +647,7 @@ namespace Umbraco.Web.BackOffice.Controllers } if (hasErrors) - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); //merge the save data onto the user var user = _umbracoMapper.Map(userSave, found); @@ -671,19 +671,19 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - return new ValidationErrorResult(ModelState); + return BadRequest(ModelState); } var intId = changingPasswordModel.Id.TryConvertTo(); if (intId.Success == false) { - return new ValidationErrorResult(intId, StatusCodes.Status404NotFound); + return NotFound(); } var found = _userService.GetUserById(intId.Result); if (found == null) { - return new ValidationErrorResult(found, StatusCodes.Status404NotFound); + return NotFound(); } // TODO: Why don't we inject this? Then we can just inject a logger From 5feb273c3a5b8564a450d2df63f4f4ee65f8198d Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 Jan 2021 16:01:09 +0100 Subject: [PATCH 66/88] Some more refactoring --- src/Umbraco.Web.BackOffice/Controllers/ContentController.cs | 4 ++-- .../Controllers/ContentTypeControllerBase.cs | 2 +- src/Umbraco.Web.BackOffice/Controllers/MediaController.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index cc9a787ee9..845104719e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -621,7 +621,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// [FileUploadCleanupFilter] [ContentSaveValidation] - public async Task PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) + public async Task> PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { var contentItemDisplay = await PostSaveInternal( contentItem, @@ -641,7 +641,7 @@ namespace Umbraco.Web.BackOffice.Controllers return display; }); - return contentItemDisplay.Value; + return contentItemDisplay; } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 9b70d2fb49..90b4329d68 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -557,7 +557,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (invalidCompositionException != null) { AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases); - throw CreateModelStateValidationException(ctId, contentTypeSave, ct); + return CreateModelStateValidationException(ctId, contentTypeSave, ct); } return null; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 49f16f41ac..8e5d1307fc 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -663,7 +663,7 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task> PostAddFolder(PostedFolder folder) { - var parentId = GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true).Result.Value; + var parentId = (await GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true)).Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); @@ -695,7 +695,7 @@ namespace Umbraco.Web.BackOffice.Controllers } //get the string json from the request - var parentId = GetParentIdAsIntAsync(currentFolder, validatePermissions: true).Result.Value; + var parentId = (await GetParentIdAsIntAsync(currentFolder, validatePermissions: true)).Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); From fe016dd103ef1ba066926593ffa5b48e2fc4ab03 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 12 Jan 2021 16:15:19 +0100 Subject: [PATCH 67/88] Netcore: Fixes issues with user invites (#9616) * AB9629 Fixes issues with user invites - Issue with the generated link in the invite email - Allow anonymous access to CurrentUserController.PostSetInvitedUserPassword, as it is used by users not logged in - Allow anonymous access to AuthenticationController.GetPasswordConfig, as this is used to set a password for newly invited users, before they login * Fix issues with invite flow * Fix minor typos * Fixed issue with validation response and remove/change avatar * Fix issue with disable users, after all enums are handled like strings * Fix tests * Fix other validation issue * Fix yet another validation issue Co-authored-by: Elitsa Marinovska --- .../Models/SimpleValidationModel.cs | 16 ++ .../AppendUserModifiedHeaderAttributeTests.cs | 9 +- .../Controllers/AuthenticationController.cs | 11 +- .../Controllers/CurrentUserController.cs | 12 +- .../Controllers/UsersController.cs | 35 +++-- .../AppendUserModifiedHeaderAttribute.cs | 5 +- .../Security/BackofficeSecurity.cs | 30 ++-- .../Security/BackofficeSecurityFactory.cs | 12 +- src/Umbraco.Web.UI.Client/package-lock.json | 146 +++++++++--------- .../src/views/users/user.controller.js | 12 +- .../users/views/users/users.controller.js | 12 +- .../appsettings.Development.json | 6 +- 12 files changed, 162 insertions(+), 144 deletions(-) create mode 100644 src/Umbraco.Core/Models/SimpleValidationModel.cs diff --git a/src/Umbraco.Core/Models/SimpleValidationModel.cs b/src/Umbraco.Core/Models/SimpleValidationModel.cs new file mode 100644 index 0000000000..cca44613fb --- /dev/null +++ b/src/Umbraco.Core/Models/SimpleValidationModel.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Models +{ + public class SimpleValidationModel + { + public SimpleValidationModel(IDictionary modelState, string message = "The request is invalid.") + { + Message = message; + ModelState = modelState; + } + + public string Message { get; } + public IDictionary ModelState { get; } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs index fe442a023b..fcace95718 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs @@ -99,10 +99,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters .SetupGet(x => x.CurrentUser) .Returns(currentUserMock.Object); + var backofficeSecurityAccessorMock = new Mock(); + backofficeSecurityAccessorMock + .SetupGet(x => x.BackOfficeSecurity) + .Returns(backofficeSecurityMock.Object); + var serviceProviderMock = new Mock(); serviceProviderMock - .Setup(x => x.GetService(typeof(IBackOfficeSecurity))) - .Returns(backofficeSecurityMock.Object); + .Setup(x => x.GetService(typeof(IBackOfficeSecurityAccessor))) + .Returns(backofficeSecurityAccessorMock.Object); httpContext.RequestServices = serviceProviderMock.Object; diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 502ffbcba2..89260b1f24 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -33,7 +33,6 @@ using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -117,11 +116,15 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Returns the configuration for the backoffice user membership provider - used to configure the change password dialog /// - /// - [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] + [AllowAnonymous] // Needed for users that are invited when they use the link from the mail they are not authorized + [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] // Needed to enforce the principle set on the request, if one exists. public IDictionary GetPasswordConfig(int userId) { - return _passwordConfiguration.GetConfiguration(userId != _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); + Attempt currentUserId = _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId(); + return _passwordConfiguration.GetConfiguration( + currentUserId.Success + ? currentUserId.Result != userId + : true); } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs index d156551c26..174c184a8f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs @@ -16,12 +16,14 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Mapping; using Umbraco.Core.Media; +using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; @@ -170,7 +172,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// This only works when the user is logged in (partially) /// - [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] // TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController + [AllowAnonymous] public async Task PostSetInvitedUserPassword([FromBody]string newPassword) { var user = await _backOfficeUserManager.FindByIdAsync(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0).ToString()); @@ -201,10 +203,10 @@ namespace Umbraco.Web.BackOffice.Controllers } [AppendUserModifiedHeader] - public IActionResult PostSetAvatar(IList files) + public IActionResult PostSetAvatar(IList file) { //borrow the logic from the user controller - return UsersController.PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); + return UsersController.PostSetAvatarInternal(file, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); } /// @@ -214,7 +216,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// If the password is being reset it will return the newly reset password, otherwise will return an empty value /// - public async Task> PostChangePassword(ChangingPasswordModel data) + public async Task>> PostChangePassword(ChangingPasswordModel data) { // TODO: Why don't we inject this? Then we can just inject a logger var passwordChanger = new PasswordChanger(_loggerFactory.CreateLogger()); @@ -233,7 +235,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } // TODO: Why is this necessary? This inherits from UmbracoAuthorizedApiController diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index fca8c49004..8bfaca88ab 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.IO; @@ -39,9 +38,6 @@ using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Editors; using Umbraco.Web.Models; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; -using IUser = Umbraco.Core.Models.Membership.IUser; -using Task = System.Threading.Tasks.Task; namespace Umbraco.Web.BackOffice.Controllers { @@ -133,9 +129,9 @@ namespace Umbraco.Web.BackOffice.Controllers [AppendUserModifiedHeader("id")] [Authorize(Policy = AuthorizationPolicies.AdminUserEditsRequireAdmin)] - public IActionResult PostSetAvatar(int id, IList files) + public IActionResult PostSetAvatar(int id, IList file) { - return PostSetAvatarInternal(files, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id); + return PostSetAvatarInternal(file, _userService, _appCaches.RuntimeCache, _mediaFileSystem, _shortStringHelper, _contentSettings, _hostingEnvironment, _imageUrlGenerator, id); } internal static IActionResult PostSetAvatarInternal(IList files, IUserService userService, IAppCache cache, IMediaFileSystem mediaFileSystem, IShortStringHelper shortStringHelper, ContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IImageUrlGenerator imageUrlGenerator, int id) @@ -337,13 +333,13 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public async Task PostCreateUser(UserInvite userSave) + public async Task> PostCreateUser(UserInvite userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); if (ModelState.IsValid == false) { - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } if (_securitySettings.UsernameIsEmail) @@ -358,6 +354,11 @@ namespace Umbraco.Web.BackOffice.Controllers } CheckUniqueEmail(userSave.Email, null); + if (ModelState.IsValid == false) + { + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); + } + //Perform authorization here to see if the current user can actually save this user with the info being requested var canSaveUser = _userEditorAuthorizationHelper.IsAuthorized(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, null, null, null, userSave.UserGroups); if (canSaveUser == false) @@ -418,11 +419,6 @@ namespace Umbraco.Web.BackOffice.Controllers if (userSave.Message.IsNullOrWhiteSpace()) ModelState.AddModelError("Message", "Message cannot be empty"); - if (ModelState.IsValid == false) - { - return new ValidationErrorResult(ModelState); - } - IUser user; if (_securitySettings.UsernameIsEmail) { @@ -436,6 +432,11 @@ namespace Umbraco.Web.BackOffice.Controllers } user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + if (ModelState.IsValid == false) + { + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); + } + if (!EmailSender.CanSendRequiredEmail(_globalSettings) && !_userManager.HasSendingUserInviteEventHandler) { return new ValidationErrorResult("No Email server is configured"); @@ -519,7 +520,6 @@ namespace Umbraco.Web.BackOffice.Controllers if (user != null && (extraCheck == null || extraCheck(user))) { ModelState.AddModelError("Email", "A user with the email already exists"); - throw new HttpResponseException(HttpStatusCode.BadRequest, ModelState); } return user; } @@ -548,9 +548,12 @@ namespace Umbraco.Web.BackOffice.Controllers token.ToUrlBase64()); // Get an mvc helper to get the URL - var action = _linkGenerator.GetPathByAction("VerifyInvite", "BackOffice", new + var action = _linkGenerator.GetPathByAction( + nameof(BackOfficeController.VerifyInvite), + ControllerExtensions.GetControllerName(), + new { - area = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment), + area = Constants.Web.Mvc.BackOfficeArea, invite = inviteToken }); diff --git a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs index 04adab4a27..c33d416cc7 100644 --- a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; using Umbraco.Core.Security; -using Umbraco.Web.Security; namespace Umbraco.Web.BackOffice.Filters { @@ -44,8 +43,8 @@ namespace Umbraco.Web.BackOffice.Filters throw new InvalidOperationException($"No argument found for the current action with the name: {_userIdParameter}"); } - var backofficeSecurity = context.HttpContext.RequestServices.GetService(); - var user = backofficeSecurity.CurrentUser; + var backofficeSecurityAccessor = context.HttpContext.RequestServices.GetService(); + var user = backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; if (user == null) { return; diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs index 1ea44b1596..9d8fdd9174 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurity.cs @@ -1,10 +1,5 @@ -using System; -using System.Security; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Http; using Umbraco.Core; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; @@ -18,34 +13,39 @@ namespace Umbraco.Web.Common.Security public class BackOfficeSecurity : IBackOfficeSecurity { private readonly IUserService _userService; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; + private object _currentUserLock = new object(); + private IUser _currentUser; + public BackOfficeSecurity( IUserService userService, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor) { _userService = userService; - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; _httpContextAccessor = httpContextAccessor; } - private IUser _currentUser; + /// public IUser CurrentUser { get { + //only load it once per instance! (but make sure groups are loaded) if (_currentUser == null) { - var id = GetUserId(); - _currentUser = id ? _userService.GetUserById(id.Result) : null; + lock (_currentUserLock) + { + //Check again + if (_currentUser == null) + { + var id = GetUserId(); + _currentUser = id ? _userService.GetUserById(id.Result) : null; + } + } } return _currentUser; diff --git a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs index d212f5a1e3..528f2f564c 100644 --- a/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs +++ b/src/Umbraco.Web.Common/Security/BackofficeSecurityFactory.cs @@ -1,9 +1,5 @@ using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -15,21 +11,15 @@ namespace Umbraco.Web.Common.Security { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IUserService _userService; - private readonly IOptions _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IHttpContextAccessor _httpContextAccessor; public BackOfficeSecurityFactory( IBackOfficeSecurityAccessor backofficeSecurityAccessor, IUserService userService, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor) { _backOfficeSecurityAccessor = backofficeSecurityAccessor; _userService = userService; - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnvironment; _httpContextAccessor = httpContextAccessor; } @@ -37,7 +27,7 @@ namespace Umbraco.Web.Common.Security { if (_backOfficeSecurityAccessor.BackOfficeSecurity is null) { - _backOfficeSecurityAccessor.BackOfficeSecurity = new BackOfficeSecurity(_userService, _globalSettings, _hostingEnvironment, _httpContextAccessor); + _backOfficeSecurityAccessor.BackOfficeSecurity = new BackOfficeSecurity(_userService, _httpContextAccessor); } } diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index dfbdbc34d4..dea27a0d69 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -948,7 +948,7 @@ "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "integrity": "sha1-mgb08TfuhNffBGDB/bETX/psUP0=", "dev": true, "optional": true }, @@ -970,7 +970,7 @@ "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "integrity": "sha1-PcoOPzOyAPx9ETnAzZbBJoyt/Z0=", "dev": true }, "@types/node": { @@ -1593,7 +1593,7 @@ "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "integrity": "sha1-t5hCCtvrHego2ErNii4j0+/oXo0=", "dev": true }, "array-uniq": { @@ -1877,7 +1877,7 @@ "bin-build": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", - "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", + "integrity": "sha1-xXgKJaip+WbYJEIX5sH1CCoUOGE=", "dev": true, "optional": true, "requires": { @@ -1928,7 +1928,7 @@ "bin-check": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", - "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "integrity": "sha1-/ElZcL3Ii7HVo1/BfmXEoUn8Skk=", "dev": true, "optional": true, "requires": { @@ -1976,7 +1976,7 @@ "bin-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", - "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "integrity": "sha1-WwnrKAdSsb0o8MnbP5by9DtsCDk=", "dev": true, "optional": true, "requires": { @@ -1987,7 +1987,7 @@ "bin-version-check": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", - "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "integrity": "sha1-fYGcYklpkfgNiT5uAqMDI2Fgj3E=", "dev": true, "optional": true, "requires": { @@ -1999,7 +1999,7 @@ "bin-wrapper": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", - "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "integrity": "sha1-mTSPLPhQMePvfvzn5TAK6q6WBgU=", "dev": true, "optional": true, "requires": { @@ -2014,7 +2014,7 @@ "download": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", - "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "integrity": "sha1-kFmqnXC1A+52oTKJe+beyOVYcjM=", "dev": true, "optional": true, "requires": { @@ -2044,7 +2044,7 @@ "file-type": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", - "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "integrity": "sha1-JE87fvZBu+DMoZbHJ25LMyOZ9ow=", "dev": true, "optional": true }, @@ -2058,7 +2058,7 @@ "got": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "integrity": "sha1-HSP2Q5Dpf3dsrFLluTbl9RTS6Tc=", "dev": true, "optional": true, "requires": { @@ -2093,7 +2093,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -2112,14 +2112,14 @@ "p-cancelable": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "integrity": "sha1-NfNj1n1SCByNlYXje8zrfgu8sqA=", "dev": true, "optional": true }, "p-event": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", - "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "integrity": "sha1-WWJ57xaassPgyuiMHPuwgHmZPvY=", "dev": true, "optional": true, "requires": { @@ -2129,7 +2129,7 @@ "p-timeout": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "integrity": "sha1-2N0ZeVldLcATnh/ka4tkbLPN8Dg=", "dev": true, "optional": true, "requires": { @@ -2139,7 +2139,7 @@ "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=", "dev": true, "optional": true }, @@ -2204,7 +2204,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -2460,7 +2460,7 @@ "normalize-url": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "integrity": "sha1-g1qdoVUfom9w6SMpBpojqmV01+Y=", "dev": true, "optional": true, "requires": { @@ -2570,7 +2570,7 @@ "caw": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "integrity": "sha1-bDygcfwZRyCIPC3F2psHS/x+npU=", "dev": true, "optional": true, "requires": { @@ -3095,7 +3095,7 @@ "config-chain": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "integrity": "sha1-D96NCRIA616AjK8l/mGMAvSOTvo=", "dev": true, "optional": true, "requires": { @@ -3151,7 +3151,7 @@ "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", "dev": true, "optional": true, "requires": { @@ -3610,7 +3610,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -3641,7 +3641,7 @@ "decompress-tar": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", - "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "integrity": "sha1-cYy9P8sWIJcW5womuE57pFkuWvE=", "dev": true, "optional": true, "requires": { @@ -3662,7 +3662,7 @@ "decompress-tarbz2": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", - "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "integrity": "sha1-MIKluIDqQEOBY0nzeLVsUWvho5s=", "dev": true, "optional": true, "requires": { @@ -3676,7 +3676,7 @@ "file-type": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", - "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "integrity": "sha1-5QzXXTVv/tTjBtxPW89Sp5kDqRk=", "dev": true, "optional": true } @@ -3685,7 +3685,7 @@ "decompress-targz": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", - "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "integrity": "sha1-wJvDXE0R894J8tLaU+neI+fOHu4=", "dev": true, "optional": true, "requires": { @@ -3875,7 +3875,7 @@ "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "integrity": "sha1-Vtv3PZkqSpO6FYT0U0Bj/S5BcX8=", "dev": true, "requires": { "path-type": "^4.0.0" @@ -3884,7 +3884,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "integrity": "sha1-hO0BwKe6OAr+CdkKjBgNzZ0DBDs=", "dev": true } } @@ -3982,7 +3982,7 @@ "download": { "version": "6.2.5", "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", - "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "integrity": "sha1-rNalQuTNC7Qspwz8mMnkOwcDlxQ=", "dev": true, "optional": true, "requires": { @@ -4016,7 +4016,7 @@ "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "integrity": "sha1-ecEDO4BRW9bSTsmTPoYMp17ifww=", "dev": true, "optional": true, "requires": { @@ -4631,7 +4631,7 @@ "exec-buffer": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/exec-buffer/-/exec-buffer-3.2.0.tgz", - "integrity": "sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==", + "integrity": "sha1-sWhtvZBMfPmC5lLB9aebHlVzCCs=", "dev": true, "optional": true, "requires": { @@ -4705,7 +4705,7 @@ "executable": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "integrity": "sha1-QVMr/zYdPlevTXY7cFgtsY9dEzw=", "dev": true, "optional": true, "requires": { @@ -4831,7 +4831,7 @@ "ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", - "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "integrity": "sha1-C5jmTtgvWs8PKTG6v2khLvUt3Tc=", "dev": true, "optional": true, "requires": { @@ -4841,7 +4841,7 @@ "ext-name": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", - "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "integrity": "sha1-cHgZgdGD7hXROZPIgiBFxQbI8KY=", "dev": true, "optional": true, "requires": { @@ -4990,7 +4990,7 @@ "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", "dev": true, "requires": { "fill-range": "^7.0.1" @@ -4999,7 +4999,7 @@ "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -5026,13 +5026,13 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", "dev": true }, "micromatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "integrity": "sha1-T8sJmb+fvC/L3SEvbWKbmlbDklk=", "dev": true, "requires": { "braces": "^3.0.1", @@ -5048,7 +5048,7 @@ "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", "dev": true, "requires": { "is-number": "^7.0.0" @@ -5126,7 +5126,7 @@ "filenamify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", - "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "integrity": "sha1-iPr0lfsbR6v9YSMAACoWIoxnfuk=", "dev": true, "optional": true, "requires": { @@ -5476,7 +5476,7 @@ "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "integrity": "sha1-a+Dem+mYzhavivwkSXue6bfM2a0=", "dev": true, "optional": true }, @@ -6074,7 +6074,7 @@ "get-proxy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", - "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "integrity": "sha1-NJ8rTZHUTE1NTpy6KtkBQ/rF75M=", "dev": true, "optional": true, "requires": { @@ -6222,7 +6222,7 @@ "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=", "dev": true, "optional": true, "requires": { @@ -6475,7 +6475,7 @@ "got": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "integrity": "sha1-BUUP2ECU5rvqVvRRpDqcKJFmOFo=", "dev": true, "optional": true, "requires": { @@ -7321,7 +7321,7 @@ "has-symbol-support-x": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "integrity": "sha1-FAn5i8ACR9pF2mfO4KNvKC/yZFU=", "dev": true, "optional": true }, @@ -7334,7 +7334,7 @@ "has-to-string-tag-x": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "integrity": "sha1-oEWrOD17SyASoAFIqwql8pAETU0=", "dev": true, "optional": true, "requires": { @@ -7476,7 +7476,7 @@ "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "integrity": "sha1-ObDhat2bYFvwqe89nar0hDtMrNI=", "dev": true, "optional": true }, @@ -7608,7 +7608,7 @@ "imagemin-optipng": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/imagemin-optipng/-/imagemin-optipng-7.1.0.tgz", - "integrity": "sha512-JNORTZ6j6untH7e5gF4aWdhDCxe3ODsSLKs/f7Grewy3ebZpl1ZsU+VUTPY4rzeHgaFA8GSWOoA8V2M3OixWZQ==", + "integrity": "sha1-IiXILDXlwpt/qY1Pns7hFhpo6Ig=", "dev": true, "optional": true, "requires": { @@ -7741,7 +7741,7 @@ "import-lazy": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", - "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "integrity": "sha1-iRJ5ICyKIoD9vWZ029jaGh38Z8w=", "dev": true, "optional": true }, @@ -7915,7 +7915,7 @@ "irregular-plurals": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "integrity": "sha1-OdQPBbAPZW0Lf6RxIw3TtxSvKHI=", "dev": true }, "is": { @@ -8098,7 +8098,7 @@ "is-gif": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-gif/-/is-gif-3.0.0.tgz", - "integrity": "sha512-IqJ/jlbw5WJSNfwQ/lHEDXF8rxhRgF6ythk2oiEvhpG29F704eX9NO6TvPfMiq9DrbwgcEDnETYNcZDPewQoVw==", + "integrity": "sha1-xL5gsmowHWlbuDOyDZtdZsbPg7E=", "dev": true, "optional": true, "requires": { @@ -8108,7 +8108,7 @@ "file-type": { "version": "10.11.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz", - "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", + "integrity": "sha1-KWHQnkZ1ufuaPua2npzSP0P9GJA=", "dev": true, "optional": true } @@ -8195,7 +8195,7 @@ "is-png": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-png/-/is-png-2.0.0.tgz", - "integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==", + "integrity": "sha1-7oy8npsFBCXO3utKb7dKZJsKSo0=", "dev": true, "optional": true }, @@ -8250,7 +8250,7 @@ "is-retry-allowed": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "integrity": "sha1-13hIi9CkZmo76KFIK58rqv7eqLQ=", "dev": true, "optional": true }, @@ -8352,7 +8352,7 @@ "isurl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "integrity": "sha1-sn9PSfPNqj6kSgpbfzRi5u3DnWc=", "dev": true, "optional": true, "requires": { @@ -8590,7 +8590,7 @@ "junk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "integrity": "sha1-MUmQmNkCt+mMXZucgPQ0V6iKv6E=", "dev": true }, "just-debounce": { @@ -8798,7 +8798,7 @@ "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "integrity": "sha1-RJI7o55osSp87H32wyaMAx8u83M=", "dev": true, "optional": true, "requires": { @@ -9249,7 +9249,7 @@ "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "integrity": "sha1-b54wtHCE2XGnyCD/FabFFnt0wm8=", "dev": true, "optional": true }, @@ -9297,7 +9297,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=", "dev": true } } @@ -9489,7 +9489,7 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "integrity": "sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=", "dev": true, "optional": true }, @@ -12836,7 +12836,7 @@ "npm-conf": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", - "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "integrity": "sha1-JWzEe9DiGMJZxOlVC/QTvCGSr/k=", "dev": true, "optional": true, "requires": { @@ -13123,7 +13123,7 @@ "optipng-bin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/optipng-bin/-/optipng-bin-6.0.0.tgz", - "integrity": "sha512-95bB4y8IaTsa/8x6QH4bLUuyvyOoGBCLDA7wOgDL8UFqJpSUh1Hob8JRJhit+wC1ZLN3tQ7mFt7KuBj0x8F2Wg==", + "integrity": "sha1-N2Eg+nnV5x7uL1JBdu/dOl6r0xY=", "dev": true, "optional": true, "requires": { @@ -13184,7 +13184,7 @@ "os-filter-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", - "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "integrity": "sha1-HAti1fOiRCdJotE55t3e5ugdjRY=", "dev": true, "optional": true, "requires": { @@ -13209,7 +13209,7 @@ "p-cancelable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "integrity": "sha1-ueEjgAvOu3rBOkeb4ZW1B7mNMPo=", "dev": true, "optional": true }, @@ -13506,7 +13506,7 @@ "plur": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "integrity": "sha1-YCZ5Z4ZqjYEVBP5Y8vqrojdUals=", "dev": true, "requires": { "irregular-plurals": "^2.0.0" @@ -14031,7 +14031,7 @@ "query-string": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "integrity": "sha1-p4wBK3HBfgXy4/ojGd0zBoLvs8s=", "dev": true, "optional": true, "requires": { @@ -14488,7 +14488,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "integrity": "sha1-kNo4Kx4SbvwCFG6QhFqI2xKSXXY=", "dev": true }, "rfdc": { @@ -14706,7 +14706,7 @@ "semver-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", - "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "integrity": "sha1-qTwsWERTmncCMzeRB7OMe0rJ0zg=", "dev": true, "optional": true }, @@ -15411,7 +15411,7 @@ "strip-dirs": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", - "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "integrity": "sha1-SYdzYmT8NEzyD2w0rKnRPR1O1sU=", "dev": true, "optional": true, "requires": { @@ -15451,7 +15451,7 @@ "strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "integrity": "sha1-sv0qv2YEudHmATBXGV34Nrip1jE=", "dev": true, "optional": true, "requires": { @@ -15577,7 +15577,7 @@ "tar-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "integrity": "sha1-jqVdqzeXIlPZqa+Q/c1VmuQ1xVU=", "dev": true, "optional": true, "requires": { @@ -15706,7 +15706,7 @@ "through2-concurrent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/through2-concurrent/-/through2-concurrent-2.0.0.tgz", - "integrity": "sha512-R5/jLkfMvdmDD+seLwN7vB+mhbqzWop5fAjx5IX8/yQq7VhBhzDmhXgaHAOnhnWkCpRMM7gToYHycB0CS/pd+A==", + "integrity": "sha1-yd0sFGUE7Jli28hqUWi2PWYmafo=", "dev": true, "requires": { "through2": "^2.0.0" @@ -15789,7 +15789,7 @@ "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "integrity": "sha1-STvUj2LXxD/N7TE6A9ytsuEhOoA=", "dev": true, "optional": true }, diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index a65f82f525..684ce6d2f0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -94,7 +94,7 @@ vm.changePasswordModel.config = data; //the user has a password if they are not states: Invited, NoCredentials - vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + vm.changePasswordModel.config.hasPassword = vm.user.userState !== "Invited" && vm.user.userState !== "Inactive"; vm.changePasswordModel.config.disableToggle = true; @@ -207,7 +207,7 @@ vm.changePasswordModel.value = {}; //the user has a password if they are not states: Invited, NoCredentials - vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4; + vm.changePasswordModel.config.hasPassword = vm.user.userState !== "Invited" && vm.user.userState !== "Inactive"; }, err => { contentEditingHelper.handleSaveError({ err: err, @@ -363,7 +363,7 @@ function disableUser() { vm.disableUserButtonState = "busy"; usersResource.disableUsers([vm.user.id]).then(function (data) { - vm.user.userState = 1; + vm.user.userState = "Disabled"; setUserDisplayState(); vm.disableUserButtonState = "success"; @@ -376,7 +376,7 @@ function enableUser() { vm.enableUserButtonState = "busy"; usersResource.enableUsers([vm.user.id]).then(function (data) { - vm.user.userState = 0; + vm.user.userState = "Active"; setUserDisplayState(); vm.enableUserButtonState = "success"; }, function (error) { @@ -387,7 +387,7 @@ function unlockUser() { vm.unlockUserButtonState = "busy"; usersResource.unlockUsers([vm.user.id]).then(function (data) { - vm.user.userState = 0; + vm.user.userState = "Active"; vm.user.failedPasswordAttempts = 0; setUserDisplayState(); vm.unlockUserButtonState = "success"; @@ -540,7 +540,7 @@ } function setUserDisplayState() { - vm.user.userDisplayState = usersHelper.getUserStateFromValue(vm.user.userState); + vm.user.userDisplayState = usersHelper.getUserStateByKey(vm.user.userState); } function formatDatesToLocal(user) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index 70102c9418..d62ed4507f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -104,7 +104,7 @@ vm.defaultButton = getCreateUserButton(); } - + vm.toggleFilter = toggleFilter; vm.setUsersViewState = setUsersViewState; @@ -319,7 +319,7 @@ vm.selection.forEach(function (userId) { var user = getUserFromArrayById(userId, vm.users); if (user) { - user.userState = 1; + user.userState = "Disabled"; } }); // show the correct badges @@ -340,7 +340,7 @@ vm.selection.forEach(function (userId) { var user = getUserFromArrayById(userId, vm.users); if (user) { - user.userState = 0; + user.userState = "Active"; } }); // show the correct badges @@ -359,7 +359,7 @@ vm.selection.forEach(function (userId) { var user = getUserFromArrayById(userId, vm.users); if (user) { - user.userState = 0; + user.userState = "Active"; } }); // show the correct badges @@ -452,7 +452,7 @@ } function areAllSelected() { - // we need to check if the current user is part of the selection and + // we need to check if the current user is part of the selection and // subtract the user from the total selection to find out if all users are selected var includesCurrentUser = vm.users.some(function (user) { return user.isCurrentUser === true; }); @@ -727,7 +727,7 @@ function setUserDisplayState(users) { users.forEach(function (user) { - user.userDisplayState = usersHelper.getUserStateFromValue(user.userState); + user.userDisplayState = usersHelper.getUserStateByKey(user.userState); }); } diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json index 72ff9076e9..983b157ef1 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.Development.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.Development.json @@ -20,9 +20,9 @@ "CMS": { "Global": { "Smtp": { - // "From": "your@email.here", - // "Host": "localhost", - // "Port": "25" +// "From": "your@email.here", +// "Host": "localhost", +// "Port": "25" } }, "Hosting": { From 0b9f02630f012ba9a64e01973cd5bce718ab4a3e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 Jan 2021 16:24:50 +0100 Subject: [PATCH 68/88] Continue replacing HttpResponseException --- .../Controllers/ContentTypeController.cs | 2 +- .../Controllers/ContentTypeControllerBase.cs | 4 ++-- .../Controllers/MediaTypeController.cs | 2 +- .../Controllers/MemberTypeController.cs | 2 +- .../Controllers/SectionController.cs | 4 ++-- .../Controllers/UserGroupsController.cs | 17 ++++++++-------- .../Trees/ApplicationTreeController.cs | 20 +++++++++---------- .../Trees/ContentTreeController.cs | 7 ++++--- .../Trees/ContentTreeControllerBase.cs | 8 ++++---- 9 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 67e6a6332c..e27968face 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -404,7 +404,7 @@ namespace Umbraco.Web.BackOffice.Controllers } }); - var display = _umbracoMapper.Map(savedCt); + var display = _umbracoMapper.Map(savedCt.Value); display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/contentTypeSavedHeader"), diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 90b4329d68..965059b83f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -253,7 +253,7 @@ namespace Umbraco.Web.BackOffice.Controllers return CultureDictionary[text].IfNullOrWhiteSpace(text); } - protected TContentType PerformPostSave( + protected ActionResult PerformPostSave( TContentTypeSave contentTypeSave, Func getContentType, Action saveContentType, @@ -264,7 +264,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var ctId = Convert.ToInt32(contentTypeSave.Id); var ct = ctId > 0 ? getContentType(ctId) : null; - if (ctId > 0 && ct == null) throw new HttpResponseException(HttpStatusCode.NotFound); + if (ctId > 0 && ct == null) return NotFound(); //Validate that there's no other ct with the same alias // it in fact cannot be the same as any content type alias (member, content or media) because diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index 1010615368..deab42db8d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -280,7 +280,7 @@ namespace Umbraco.Web.BackOffice.Controllers i => _mediaTypeService.Get(i), type => _mediaTypeService.Save(type)); - var display = _umbracoMapper.Map(savedCt); + var display = _umbracoMapper.Map(savedCt.Value); display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/mediaTypeSavedHeader"), diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 6e95680110..7598c0d449 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -230,7 +230,7 @@ namespace Umbraco.Web.BackOffice.Controllers getContentType: i => ct, saveContentType: type => _memberTypeService.Save(type)); - var display =_umbracoMapper.Map(savedCt); + var display =_umbracoMapper.Map(savedCt.Value); display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/memberTypeSavedHeader"), diff --git a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs index 097b5a3310..5b7119f754 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -75,7 +75,7 @@ namespace Umbraco.Web.BackOffice.Controllers // get the first tree in the section and get its root node route path var sectionRoot = appTreeController.GetApplicationTrees(section.Alias, null, null).Result; - section.RoutePath = GetRoutePathForFirstTree(sectionRoot); + section.RoutePath = GetRoutePathForFirstTree(sectionRoot.Value); } return sectionModels; diff --git a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs index 64aef74257..ff5ade53c1 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UserGroupsController.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -9,15 +10,13 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.BackOffice.ActionResults; +using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Controllers { @@ -52,7 +51,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [UserGroupValidate] - public UserGroupDisplay PostSaveUserGroup(UserGroupSave userGroupSave) + public ActionResult PostSaveUserGroup(UserGroupSave userGroupSave) { if (userGroupSave == null) throw new ArgumentNullException(nameof(userGroupSave)); @@ -62,14 +61,14 @@ namespace Umbraco.Web.BackOffice.Controllers var isAuthorized = authHelper.AuthorizeGroupAccess(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, userGroupSave.Alias); if (isAuthorized == false) - throw new HttpResponseException(HttpStatusCode.Unauthorized, isAuthorized.Result); + return Unauthorized(isAuthorized.Result); //if sections were added we need to check that the current user has access to that section isAuthorized = authHelper.AuthorizeSectionChanges(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, userGroupSave.PersistedUserGroup.AllowedSections, userGroupSave.Sections); if (isAuthorized == false) - throw new HttpResponseException(HttpStatusCode.Unauthorized, isAuthorized.Result); + return Unauthorized(isAuthorized.Result); //if start nodes were changed we need to check that the current user has access to them isAuthorized = authHelper.AuthorizeStartNodeChanges(_backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser, @@ -78,7 +77,7 @@ namespace Umbraco.Web.BackOffice.Controllers userGroupSave.PersistedUserGroup.StartMediaId, userGroupSave.StartMediaId); if (isAuthorized == false) - throw new HttpResponseException(HttpStatusCode.Unauthorized, isAuthorized.Result); + return Unauthorized(isAuthorized.Result); //need to ensure current user is in a group if not an admin to avoid a 401 EnsureNonAdminUserIsInSavedUserGroup(userGroupSave); diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 000740e27e..c19229b1e6 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -62,16 +62,16 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Tree use. /// - public async Task GetApplicationTrees(string application, string tree, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings, TreeUse use = TreeUse.Main) + public async Task> GetApplicationTrees(string application, string tree, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings, TreeUse use = TreeUse.Main) { application = application.CleanForXss(); if (string.IsNullOrEmpty(application)) - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); var section = _sectionService.GetByAlias(application); if (section == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); //find all tree definitions that have the current application alias var groupedTrees = _treeService.GetBySectionGrouped(application, use); @@ -93,13 +93,13 @@ namespace Umbraco.Web.BackOffice.Trees : allTrees.FirstOrDefault(x => x.TreeAlias == tree); if (t == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, queryStrings); if (treeRootNode != null) return treeRootNode; - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } // handle requests for all trees @@ -219,7 +219,7 @@ namespace Umbraco.Web.BackOffice.Trees if (tree == null) throw new ArgumentNullException(nameof(tree)); - var controller = (TreeControllerBase)await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring); + var controller = (TreeControllerBase)(await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring)).Value; var rootNode = controller.GetRootNode(querystring); if (rootNode == null) throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); @@ -241,7 +241,7 @@ namespace Umbraco.Web.BackOffice.Trees d["id"] = StringValues.Empty; var proxyQuerystring = new FormCollection(d); - var controller = (TreeControllerBase)await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring); + var controller = (TreeControllerBase)(await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring)).Value; return controller.GetNodes(id.ToInvariantString(), querystring); } @@ -257,7 +257,7 @@ namespace Umbraco.Web.BackOffice.Trees /// and context etc. so it can execute the specified . Runs the authorization /// filters for that action, to ensure that the user has permission to execute it. /// - private async Task GetApiControllerProxy(Type controllerType, string action, FormCollection querystring) + private async Task> GetApiControllerProxy(Type controllerType, string action, FormCollection querystring) { // note: this is all required in order to execute the auth-filters for the sub request, we // need to "trick" mvc into thinking that it is actually executing the proxied controller. @@ -289,11 +289,9 @@ namespace Umbraco.Web.BackOffice.Trees var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext); if (!isAllowed) - throw new HttpResponseException(HttpStatusCode.Forbidden); + return Forbid(); return controller; } - - } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 404ebfdb3a..5cdd356296 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -21,6 +21,7 @@ using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; using Umbraco.Web.Trees; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; using Umbraco.Core.Trees; @@ -236,7 +237,7 @@ namespace Umbraco.Web.BackOffice.Trees return HasPathAccess(entity, queryStrings); } - protected override IEnumerable GetChildEntities(string id, FormCollection queryStrings) + protected override ActionResult> GetChildEntities(string id, FormCollection queryStrings) { var result = base.GetChildEntities(id, queryStrings); var culture = queryStrings["culture"].TryConvertTo(); @@ -245,7 +246,7 @@ namespace Umbraco.Web.BackOffice.Trees var cultureVal = (culture.Success ? culture.Result : null) ?? _localizationService.GetDefaultLanguageIsoCode(); // set names according to variations - foreach (var entity in result) + foreach (var entity in result.Value) { EnsureName(entity, cultureVal); } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 53a6f02a79..047778720b 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -211,7 +211,7 @@ namespace Umbraco.Web.BackOffice.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id, queryStrings).ToList(); + var entities = GetChildEntities(id, queryStrings).Value.ToList(); //get the current user start node/paths GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); @@ -257,7 +257,7 @@ namespace Umbraco.Web.BackOffice.Trees protected abstract UmbracoObjectTypes UmbracoObjectType { get; } - protected virtual IEnumerable GetChildEntities(string id, FormCollection queryStrings) + protected virtual ActionResult> GetChildEntities(string id, FormCollection queryStrings) { // try to parse id as an integer else use GetEntityFromId // which will grok Guids, Udis, etc and let use obtain the id @@ -265,7 +265,7 @@ namespace Umbraco.Web.BackOffice.Trees { var entity = GetEntityFromId(id); if (entity == null) - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); entityId = entity.Id; } From f691dcef91ef84bdc1fe157067d3745ac082ab7e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 12 Jan 2021 16:32:24 +0100 Subject: [PATCH 69/88] Changing return type --- .../Controllers/EntityController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 34031a5dba..32d09e9a5c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -319,7 +319,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public EntityBasic GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) + public ActionResult GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) { // TODO: Rename this!!! It's misleading, it should be GetByXPath @@ -390,9 +390,9 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public EntityBasic GetById(int id, UmbracoEntityTypes type) + public ActionResult GetById(int id, UmbracoEntityTypes type) { - return GetResultForId(id, type).Value; + return GetResultForId(id, type); } /// @@ -402,9 +402,9 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public EntityBasic GetById(Guid id, UmbracoEntityTypes type) + public ActionResult GetById(Guid id, UmbracoEntityTypes type) { - return GetResultForKey(id, type).Value; + return GetResultForKey(id, type); } /// From d1df6c471981129823a290b2e7327cfbf0ccd23e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Jan 2021 11:08:48 +1100 Subject: [PATCH 70/88] less allocations with AbsolutePathDecoded. removes more aspx checks, adds UmbracoRouteValueTransformerTests, adds interface for IRoutableDocumentFilter --- .../Routing/ContentFinderByIdPath.cs | 2 +- .../Routing/ContentFinderByRedirectUrl.cs | 4 +- .../Routing/ContentFinderByUrl.cs | 4 +- .../Routing/ContentFinderByUrlAlias.cs | 2 +- .../Routing/ContentFinderByUrlAndTemplate.cs | 2 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 4 +- .../Routing/IPublishedRequestBuilder.cs | 5 + .../Routing/PublishedRequestBuilder.cs | 5 + src/Umbraco.Core/Routing/UriUtility.cs | 18 +- src/Umbraco.Core/UriExtensions.cs | 2 + .../Routing/ContentFinderByConfigured404.cs | 2 +- .../Umbraco.Tests.Common.csproj | 2 +- .../Umbraco.Tests.Integration.csproj | 2 +- .../Routing/HijackedRouteEvaluatorTests.cs | 14 +- .../UmbracoRouteValueTransformerTests.cs | 170 ++++++++++++++++++ .../Routing/UmbracoRouteValuesFactoryTests.cs | 16 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../AspNetCore/AspNetCoreRequestAccessor.cs | 4 +- .../Routing/IRoutableDocumentFilter.cs | 7 + .../Routing/RoutableDocumentFilter.cs | 2 +- .../UmbracoContext/UmbracoContext.cs | 27 +-- .../UmbracoBuilderExtensions.cs | 2 +- .../Routing/UmbracoRouteValueTransformer.cs | 28 +-- src/Umbraco.Web/UmbracoContext.cs | 1 - 24 files changed, 255 insertions(+), 74 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs create mode 100644 src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 46571f5d65..500bd65f82 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Routing } IPublishedContent node = null; - var path = frequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.AbsolutePathDecoded; var nodeId = -1; diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 38f04d1ddb..e3c5b28a2a 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -51,8 +51,8 @@ namespace Umbraco.Web.Routing } var route = frequest.Domain != null - ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()) - : frequest.Uri.GetAbsolutePathDecoded(); + ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded) + : frequest.AbsolutePathDecoded; IRedirectUrl redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index 27893cd3de..c20cf9fd85 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -44,11 +44,11 @@ namespace Umbraco.Web.Routing string route; if (frequest.Domain != null) { - route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.Uri.GetAbsolutePathDecoded()); + route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded); } else { - route = frequest.Uri.GetAbsolutePathDecoded(); + route = frequest.AbsolutePathDecoded; } IPublishedContent node = FindContent(frequest, route); diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 770fdf4003..4745ea8cd3 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -60,7 +60,7 @@ namespace Umbraco.Web.Routing umbCtx.Content, frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture, - frequest.Uri.GetAbsolutePathDecoded()); + frequest.AbsolutePathDecoded); if (node != null) { diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index c6bd4f383d..2e69446d68 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -49,7 +49,7 @@ namespace Umbraco.Web.Routing /// If successful, also assigns the template. public override bool TryFindContent(IPublishedRequestBuilder frequest) { - var path = frequest.Uri.GetAbsolutePathDecoded(); + var path = frequest.AbsolutePathDecoded; if (frequest.Domain != null) { diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index fa5d84836d..0d14b26396 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -364,9 +364,7 @@ namespace Umbraco.Web.Routing /// The path part relative to the uri of the domain. /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. public static string PathRelativeToDomain(Uri domainUri, string path) - { - return path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); - } + => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); #endregion } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index ced443a89c..180b6825f2 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -18,6 +18,11 @@ namespace Umbraco.Web.Routing /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. Uri Uri { get; } + /// + /// Gets the decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + /// /// Gets the assigned (if any) /// diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index faa793c7ff..9ed8a1ee10 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -26,12 +27,16 @@ namespace Umbraco.Web.Routing public PublishedRequestBuilder(Uri uri, IFileService fileService) { Uri = uri; + AbsolutePathDecoded = uri.GetAbsolutePathDecoded(); _fileService = fileService; } /// public Uri Uri { get; } + /// + public string AbsolutePathDecoded { get; } + /// public DomainAndUri Domain { get; private set; } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 8de78dfbf5..96325d1289 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -88,6 +88,10 @@ namespace Umbraco.Web // ie no virtual directory, no .aspx, lowercase... public Uri UriToUmbraco(Uri uri) { + // TODO: Ideally we do this witout so many string allocations, we can use + // techniques like StringSegment and Span. This is critical code that executes on every request. + // not really sure we need ToLower. + // note: no need to decode uri here because we're returning a uri // so it will be re-encoded anyway var path = uri.GetSafeAbsolutePath(); @@ -95,23 +99,11 @@ namespace Umbraco.Web path = path.ToLower(); path = ToAppRelative(path); // strip vdir if any - //we need to check if the path is /default.aspx because this will occur when using a - //web server pre IIS 7 when requesting the root document - //if this is the case we need to change it to '/' - if (path.StartsWith("/default.aspx", StringComparison.InvariantCultureIgnoreCase)) - { - string rempath = path.Substring("/default.aspx".Length, path.Length - "/default.aspx".Length); - path = rempath.StartsWith("/") ? rempath : "/" + rempath; - } if (path != "/") { path = path.TrimEnd('/'); } - //if any part of the path contains .aspx, replace it with nothing. - //sometimes .aspx is not at the end since we might have /home/sub1.aspx/customtemplate - path = path.Replace(".aspx", ""); - return uri.Rewrite(path); } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 497159309a..53bf2d6d92 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -61,7 +61,9 @@ namespace Umbraco.Core public static string GetSafeAbsolutePath(this Uri uri) { if (uri.IsAbsoluteUri) + { return uri.AbsolutePath; + } // cannot get .AbsolutePath on relative uri (InvalidOperation) var s = uri.OriginalString; diff --git a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs index 5634fa4a93..bc9f9f3857 100644 --- a/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs +++ b/src/Umbraco.Infrastructure/Routing/ContentFinderByConfigured404.cs @@ -61,7 +61,7 @@ namespace Umbraco.Web.Routing } else { - var route = frequest.Uri.GetAbsolutePathDecoded(); + var route = frequest.AbsolutePathDecoded; var pos = route.LastIndexOf('/'); IPublishedContent node = null; while (pos > 1) diff --git a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj index aa39070cc7..bdb703753d 100644 --- a/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj +++ b/src/Umbraco.Tests.Common/Umbraco.Tests.Common.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index c1d0fe3758..dbb3fa1137 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -59,7 +59,7 @@ - + all diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs index 5543a8920a..2d96476b30 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/HijackedRouteEvaluatorTests.cs @@ -79,13 +79,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing } [TestCase("Index", "RenderNotFound", null, false)] - [TestCase("index", "Render", "Index", true)] - [TestCase("Index", "Render1", "Index", true)] - [TestCase("Index", "render2", "Index", true)] - [TestCase("NotFound", "Render", "Index", true)] - [TestCase("NotFound", "Render1", "Index", true)] - [TestCase("NotFound", "Render2", "Index", true)] - [TestCase("Custom", "Render1", "Custom", true)] + [TestCase("index", "Render", nameof(RenderController.Index), true)] + [TestCase("Index", "Render1", nameof(RenderController.Index), true)] + [TestCase("Index", "render2", nameof(Render2Controller.Index), true)] + [TestCase("NotFound", "Render", nameof(RenderController.Index), true)] + [TestCase("NotFound", "Render1", nameof(Render1Controller.Index), true)] + [TestCase("NotFound", "Render2", nameof(Render2Controller.Index), true)] + [TestCase("Custom", "Render1", nameof(Render1Controller.Custom), true)] public void Matches_Controller(string action, string controller, string resultAction, bool matches) { var evaluator = new HijackedRouteEvaluator( diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs new file mode 100644 index 0000000000..0b9e1d6420 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -0,0 +1,170 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Extensions; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.Routing; +using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.Routing; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing +{ + [TestFixture] + public class UmbracoRouteValueTransformerTests + { + private IOptions GetGlobalSettings() => Options.Create(new GlobalSettings()); + + private UmbracoRouteValueTransformer GetTransformerWithRunState( + IUmbracoContextAccessor ctx, + IRoutableDocumentFilter filter = null, + IPublishedRouter router = null, + IUmbracoRouteValuesFactory routeValuesFactory = null) + => GetTransformer(ctx, Mock.Of(x => x.Level == RuntimeLevel.Run), filter, router, routeValuesFactory); + + private UmbracoRouteValueTransformer GetTransformer( + IUmbracoContextAccessor ctx, + IRuntimeState state, + IRoutableDocumentFilter filter = null, + IPublishedRouter router = null, + IUmbracoRouteValuesFactory routeValuesFactory = null) + { + var transformer = new UmbracoRouteValueTransformer( + new NullLogger(), + ctx, + router ?? Mock.Of(), + GetGlobalSettings(), + TestHelper.GetHostingEnvironment(), + state, + routeValuesFactory ?? Mock.Of(), + filter ?? Mock.Of(x => x.IsDocumentRequest(It.IsAny()) == true)); + + return transformer; + } + + private IUmbracoContext GetUmbracoContext(bool hasContent) + { + IPublishedContentCache publishedContent = Mock.Of(x => x.HasContent() == hasContent); + var uri = new Uri("http://example.com"); + + IUmbracoContext umbracoContext = Mock.Of(x => + x.Content == publishedContent + && x.OriginalRequestUrl == uri + && x.CleanedUmbracoUrl == uri); + + return umbracoContext; + } + + private UmbracoRouteValues GetRouteValues(IPublishedRequest request) + => new UmbracoRouteValues( + request, + ControllerExtensions.GetControllerName(), + typeof(TestController)); + + private IUmbracoRouteValuesFactory GetRouteValuesFactory(IPublishedRequest request) + => Mock.Of(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()) == GetRouteValues(request)); + + private IPublishedRouter GetRouter(IPublishedRequest request) + => Mock.Of(x => x.RouteRequestAsync(It.IsAny(), It.IsAny()) == Task.FromResult(request)); + + [Test] + public async Task Noop_When_Runtime_Level_Not_Run() + { + UmbracoRouteValueTransformer transformer = GetTransformer( + Mock.Of(x => x.UmbracoContext == null), + Mock.Of()); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task Noop_When_No_Umbraco_Context() + { + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == null)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task Noop_When_Not_Document_Request() + { + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == Mock.Of()), + Mock.Of(x => x.IsDocumentRequest(It.IsAny()) == false)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(0, result.Count); + } + + [Test] + public async Task NoContentController_Values_When_No_Content() + { + IUmbracoContext umbracoContext = GetUmbracoContext(false); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(ControllerExtensions.GetControllerName(), result["controller"]); + Assert.AreEqual(nameof(RenderNoContentController.Index), result["action"]); + } + + [Test] + public async Task Assigns_PublishedRequest_To_UmbracoContext() + { + IUmbracoContext umbracoContext = GetUmbracoContext(true); + IPublishedRequest request = Mock.Of(); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext), + router: GetRouter(request), + routeValuesFactory: GetRouteValuesFactory(request)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + Assert.AreEqual(request, umbracoContext.PublishedRequest); + } + + [Test] + public async Task Assigns_Values_To_RouteValueDictionary() + { + IUmbracoContext umbracoContext = GetUmbracoContext(true); + IPublishedRequest request = Mock.Of(); + UmbracoRouteValues routeValues = GetRouteValues(request); + + UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( + Mock.Of(x => x.UmbracoContext == umbracoContext), + router: GetRouter(request), + routeValuesFactory: GetRouteValuesFactory(request)); + + RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); + + Assert.AreEqual(routeValues.ControllerName, result["controller"]); + Assert.AreEqual(routeValues.ActionName, result["action"]); + } + + private class TestController : RenderController + { + public TestController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs index ca5329a3f7..17ce59862f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactoryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; @@ -18,16 +18,17 @@ using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing { + [TestFixture] public class UmbracoRouteValuesFactoryTests { - private UmbracoRouteValuesFactory GetFactory(IPublishedRouter router, out UmbracoRenderingDefaults renderingDefaults) + private UmbracoRouteValuesFactory GetFactory(out Mock publishedRouter, out UmbracoRenderingDefaults renderingDefaults) { var builder = new PublishedRequestBuilder(new Uri("https://example.com"), Mock.Of()); builder.SetPublishedContent(Mock.Of()); IPublishedRequest request = builder.Build(); - var publishedRouter = new Mock(); + publishedRouter = new Mock(); publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) .Returns((IPublishedRequest r) => builder) .Verifiable(); @@ -53,12 +54,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing builder.SetPublishedContent(Mock.Of()); IPublishedRequest request = builder.Build(); - var publishedRouter = new Mock(); - publishedRouter.Setup(x => x.UpdateRequestToNotFound(It.IsAny())) - .Returns((IPublishedRequest r) => builder) - .Verifiable(); - - UmbracoRouteValuesFactory factory = GetFactory(publishedRouter.Object, out _); + UmbracoRouteValuesFactory factory = GetFactory(out Mock publishedRouter, out _); UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), new RouteValueDictionary(), request); @@ -74,7 +70,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing builder.SetTemplate(Mock.Of()); IPublishedRequest request = builder.Build(); - UmbracoRouteValuesFactory factory = GetFactory(Mock.Of(), out UmbracoRenderingDefaults renderingDefaults); + UmbracoRouteValuesFactory factory = GetFactory(out _, out UmbracoRenderingDefaults renderingDefaults); var routeVals = new RouteValueDictionary(); UmbracoRouteValues result = factory.Create(new DefaultHttpContext(), routeVals, request); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e1f57262ff..fe9d41f681 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -112,7 +112,7 @@ - + @@ -307,7 +307,7 @@ - + diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs index c5104c0fdc..cd38712aa0 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreRequestAccessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -61,7 +61,7 @@ namespace Umbraco.Web.Common.AspNetCore public Uri GetApplicationUrl() { - //Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that + // Fixme: This causes problems with site swap on azure because azure pre-warms a site by calling into `localhost` and when it does that // it changes the URL to `localhost:80` which actually doesn't work for pinging itself, it only works internally in Azure. The ironic part // about this is that this is here specifically for the slot swap scenario https://issues.umbraco.org/issue/U4-10626 diff --git a/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs new file mode 100644 index 0000000000..b921918bf6 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/IRoutableDocumentFilter.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Web.Common.Routing +{ + public interface IRoutableDocumentFilter + { + bool IsDocumentRequest(string absPath); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index dee90bbfba..18190f9ad9 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Routing /// /// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes. /// - public sealed class RoutableDocumentFilter + public sealed class RoutableDocumentFilter : IRoutableDocumentFilter { private readonly ConcurrentDictionary _routeChecks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly GlobalSettings _globalSettings; diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index f7d3e61664..e89a874d71 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -16,6 +16,7 @@ namespace Umbraco.Web public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { private readonly IHostingEnvironment _hostingEnvironment; + private readonly UriUtility _uriUtility; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; private readonly Lazy _publishedSnapshot; @@ -23,6 +24,8 @@ namespace Umbraco.Web private bool? _previewing; private readonly IBackOfficeSecurity _backofficeSecurity; private readonly UmbracoRequestPaths _umbracoRequestPaths; + private Uri _originalRequestUrl; + private Uri _cleanedUmbracoUrl; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -43,8 +46,8 @@ namespace Umbraco.Web throw new ArgumentNullException(nameof(publishedSnapshotService)); } - VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - + VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + _uriUtility = uriUtility; _hostingEnvironment = hostingEnvironment; _cookieManager = cookieManager; _requestAccessor = requestAccessor; @@ -56,15 +59,6 @@ namespace Umbraco.Web // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); - - // set the urls... - // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this - // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people - // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get - // the current domain during application startup. - // see: http://issues.umbraco.org/issue/U4-1890 - OriginalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost"); - CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); } /// @@ -79,10 +73,17 @@ namespace Umbraco.Web internal Guid UmbracoRequestId { get; } /// - public Uri OriginalRequestUrl { get; } + // set the urls lazily, no need to allocate until they are needed... + // NOTE: The request will not be available during app startup so we can only set this to an absolute URL of localhost, this + // is a work around to being able to access the UmbracoContext during application startup and this will also ensure that people + // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get + // the current domain during application startup. + // see: http://issues.umbraco.org/issue/U4-1890 + public Uri OriginalRequestUrl => _originalRequestUrl ?? (_originalRequestUrl = _requestAccessor.GetRequestUrl() ?? new Uri("http://localhost")); /// - public Uri CleanedUmbracoUrl { get; } + // set the urls lazily, no need to allocate until they are needed... + public Uri CleanedUmbracoUrl => _cleanedUmbracoUrl ?? (_cleanedUmbracoUrl = _uriUtility.UriToUmbraco(OriginalRequestUrl)); /// public IPublishedSnapshot PublishedSnapshot => _publishedSnapshot.Value; diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index a641f32235..ca2f9e6161 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Website.DependencyInjection builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.AddDistributedCache(); diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 5d0c564df3..af23105099 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.Website.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeState _runtime; private readonly IUmbracoRouteValuesFactory _routeValuesFactory; - private readonly RoutableDocumentFilter _routableDocumentFilter; + private readonly IRoutableDocumentFilter _routableDocumentFilter; /// /// Initializes a new instance of the class. @@ -47,16 +47,21 @@ namespace Umbraco.Web.Website.Routing IHostingEnvironment hostingEnvironment, IRuntimeState runtime, IUmbracoRouteValuesFactory routeValuesFactory, - RoutableDocumentFilter routableDocumentFilter) + IRoutableDocumentFilter routableDocumentFilter) { - _logger = logger; - _umbracoContextAccessor = umbracoContextAccessor; - _publishedRouter = publishedRouter; + if (globalSettings is null) + { + throw new System.ArgumentNullException(nameof(globalSettings)); + } + + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _publishedRouter = publishedRouter ?? throw new System.ArgumentNullException(nameof(publishedRouter)); _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - _runtime = runtime; - _routeValuesFactory = routeValuesFactory; - _routableDocumentFilter = routableDocumentFilter; + _hostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment)); + _runtime = runtime ?? throw new System.ArgumentNullException(nameof(runtime)); + _routeValuesFactory = routeValuesFactory ?? throw new System.ArgumentNullException(nameof(routeValuesFactory)); + _routableDocumentFilter = routableDocumentFilter ?? throw new System.ArgumentNullException(nameof(routableDocumentFilter)); } /// @@ -113,9 +118,10 @@ namespace Umbraco.Web.Website.Routing // an immutable object. The only way to make this better would be to have a RouteRequest // as part of UmbracoContext but then it will require a PublishedRouter dependency so not sure that's worth it. // Maybe could be a one-time Set method instead? - IPublishedRequest publishedRequest = umbracoContext.PublishedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); + IPublishedRequest routedRequest = await _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)); + umbracoContext.PublishedRequest = routedRequest; - return publishedRequest; + return routedRequest; } } } diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 7461364d3f..92a480bc45 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -62,7 +62,6 @@ namespace Umbraco.Web // 'could' still generate URLs during startup BUT any domain driven URL generation will not work because it is NOT possible to get // the current domain during application startup. // see: http://issues.umbraco.org/issue/U4-1890 - // OriginalRequestUrl = GetRequestFromContext()?.Url ?? new Uri("http://localhost"); CleanedUmbracoUrl = uriUtility.UriToUmbraco(OriginalRequestUrl); } From 0c012d007c7ef0a5b30e9ce377511f38ccbd3804 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Jan 2021 11:15:23 +1100 Subject: [PATCH 71/88] Adds AbsolutePathDecoded to IPublishedRequest too --- src/Umbraco.Core/Routing/IPublishedRequest.cs | 5 +++++ src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs | 2 +- src/Umbraco.Core/Routing/PublishedRequest.cs | 6 +++++- src/Umbraco.Core/Routing/PublishedRequestBuilder.cs | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index fedfd69dc3..58523d12e4 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -17,6 +17,11 @@ namespace Umbraco.Web.Routing /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. Uri Uri { get; } + /// + /// Gets the URI decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + /// /// Gets a value indicating the requested content. /// diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index 180b6825f2..bd5b5625a3 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Routing Uri Uri { get; } /// - /// Gets the decoded absolute path of the + /// Gets the URI decoded absolute path of the /// string AbsolutePathDecoded { get; } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index e42211da49..0c129bf279 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -13,9 +13,10 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) + public PublishedRequest(Uri uri, string absPathDecoded, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + AbsolutePathDecoded = absPathDecoded ?? throw new ArgumentNullException(nameof(absPathDecoded)); PublishedContent = publishedContent; IsInternalRedirect = isInternalRedirect; Template = template; @@ -32,6 +33,9 @@ namespace Umbraco.Web.Routing /// public Uri Uri { get; } + /// + public string AbsolutePathDecoded { get; } + /// public bool IgnorePublishedContentCollisions { get; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 9ed8a1ee10..606031564b 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -67,6 +67,7 @@ namespace Umbraco.Web.Routing /// public IPublishedRequest Build() => new PublishedRequest( Uri, + AbsolutePathDecoded, PublishedContent, IsInternalRedirect, Template, From 52642a39146009c3da1a8fca3a7c403ed5520b25 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Jan 2021 13:54:20 +1100 Subject: [PATCH 72/88] remove aspx tests --- .../Umbraco.Core/Routing/UriUtilityTests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs index dee487621f..4789698fcd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs @@ -23,28 +23,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://LocalHost/Home/Sub1", "http://localhost/home/sub1")] [TestCase("http://LocalHost/Home/Sub1?x=y", "http://localhost/home/sub1?x=y")] - // same with .aspx - [TestCase("http://LocalHost/Home.aspx", "http://localhost/home")] - [TestCase("http://LocalHost/Home.aspx?x=y", "http://localhost/home?x=y")] - [TestCase("http://LocalHost/Home/Sub1.aspx", "http://localhost/home/sub1")] - [TestCase("http://LocalHost/Home/Sub1.aspx?x=y", "http://localhost/home/sub1?x=y")] - // test that the trailing slash goes but not on hostname [TestCase("http://LocalHost/", "http://localhost/")] [TestCase("http://LocalHost/Home/", "http://localhost/home")] [TestCase("http://LocalHost/Home/?x=y", "http://localhost/home?x=y")] [TestCase("http://LocalHost/Home/Sub1/", "http://localhost/home/sub1")] [TestCase("http://LocalHost/Home/Sub1/?x=y", "http://localhost/home/sub1?x=y")] - - // test that default.aspx goes, even with parameters - [TestCase("http://LocalHost/deFault.aspx", "http://localhost/")] - [TestCase("http://LocalHost/deFault.aspx?x=y", "http://localhost/?x=y")] - - // test with inner .aspx - [TestCase("http://Localhost/Home/Sub1.aspx/Sub2", "http://localhost/home/sub1/sub2")] - [TestCase("http://Localhost/Home/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] - [TestCase("http://Localhost/Home.aspx/Sub1.aspx/Sub2?x=y", "http://localhost/home/sub1/sub2?x=y")] - [TestCase("http://Localhost/deFault.aspx/Home.aspx/deFault.aspx/Sub1.aspx", "http://localhost/home/default/sub1")] public void Uri_To_Umbraco(string sourceUrl, string expectedUrl) { // Arrange From 5afa49f170b14bd017f81da6cd93dacc84cea71a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Jan 2021 13:59:34 +1100 Subject: [PATCH 73/88] adds notes --- src/Umbraco.Core/Routing/UriUtility.cs | 11 ++++++++--- src/Umbraco.Core/UriExtensions.cs | 2 ++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 96325d1289..43a36db101 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -55,9 +55,15 @@ namespace Umbraco.Web { if (virtualPath.InvariantStartsWith(_appPathPrefix) && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) + { virtualPath = virtualPath.Substring(_appPathPrefix.Length); + } + if (virtualPath.Length == 0) + { virtualPath = "/"; + } + return virtualPath; } @@ -88,9 +94,8 @@ namespace Umbraco.Web // ie no virtual directory, no .aspx, lowercase... public Uri UriToUmbraco(Uri uri) { - // TODO: Ideally we do this witout so many string allocations, we can use - // techniques like StringSegment and Span. This is critical code that executes on every request. - // not really sure we need ToLower. + // TODO: This is critical code that executes on every request, we should + // look into if all of this is necessary? not really sure we need ToLower? // note: no need to decode uri here because we're returning a uri // so it will be re-encoded anyway diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 53bf2d6d92..26580fab84 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -67,6 +67,8 @@ namespace Umbraco.Core // cannot get .AbsolutePath on relative uri (InvalidOperation) var s = uri.OriginalString; + + // TODO: Shouldn't this just use Uri.GetLeftPart? var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); var pos = posq > 0 ? posq : (posf > 0 ? posf : 0); From 62f3a80dc6219cdae2cb1ffbcb498e5e3aa238ce Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 13 Jan 2021 14:46:59 +1100 Subject: [PATCH 74/88] adds locks to culture providers when manipulating supported cultures --- ...mbracoBackOfficeIdentityCultureProvider.cs | 24 +++++++++------- .../UmbracoPublishedContentCultureProvider.cs | 28 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs index 741583413c..e2a7c35daa 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoBackOfficeIdentityCultureProvider.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web.Common.Localization public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider { private readonly RequestLocalizationOptions _localizationOptions; + private readonly object _locker = new object(); /// /// Initializes a new instance of the class. @@ -31,18 +32,21 @@ namespace Umbraco.Web.Common.Localization return NullProviderCultureResult; } - // We need to dynamically change the supported cultures since we won't ever know what languages are used since - // they are dynamic within Umbraco. - var cultureExists = _localizationOptions.SupportedCultures.Contains(culture); - - if (!cultureExists) + lock(_locker) { - // add this as a supporting culture - _localizationOptions.SupportedCultures.Add(culture); - _localizationOptions.SupportedUICultures.Add(culture); - } + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + var cultureExists = _localizationOptions.SupportedCultures.Contains(culture); - return Task.FromResult(new ProviderCultureResult(culture.Name)); + if (!cultureExists) + { + // add this as a supporting culture + _localizationOptions.SupportedCultures.Add(culture); + _localizationOptions.SupportedUICultures.Add(culture); + } + + return Task.FromResult(new ProviderCultureResult(culture.Name)); + } } } } diff --git a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs index bedf5e73a7..7322ad2869 100644 --- a/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs +++ b/src/Umbraco.Web.Common/Localization/UmbracoPublishedContentCultureProvider.cs @@ -19,6 +19,7 @@ namespace Umbraco.Web.Common.Localization public class UmbracoPublishedContentCultureProvider : RequestCultureProvider { private readonly RequestLocalizationOptions _localizationOptions; + private readonly object _locker = new object(); /// /// Initializes a new instance of the class. @@ -33,19 +34,22 @@ namespace Umbraco.Web.Common.Localization string culture = routeValues.PublishedRequest?.Culture; if (culture != null) { - // We need to dynamically change the supported cultures since we won't ever know what languages are used since - // they are dynamic within Umbraco. - // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo - // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165 - CultureInfo existingCulture = _localizationOptions.SupportedCultures.FirstOrDefault(supportedCulture => - StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase)); - - if (existingCulture == null) + lock (_locker) { - // add this as a supporting culture - var ci = CultureInfo.GetCultureInfo(culture); - _localizationOptions.SupportedCultures.Add(ci); - _localizationOptions.SupportedUICultures.Add(ci); + // We need to dynamically change the supported cultures since we won't ever know what languages are used since + // they are dynamic within Umbraco. + // This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo + // https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165 + CultureInfo existingCulture = _localizationOptions.SupportedCultures.FirstOrDefault(supportedCulture => + StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase)); + + if (existingCulture == null) + { + // add this as a supporting culture + var ci = CultureInfo.GetCultureInfo(culture); + _localizationOptions.SupportedCultures.Add(ci); + _localizationOptions.SupportedUICultures.Add(ci); + } } return Task.FromResult(new ProviderCultureResult(culture)); From 8c8871a7990a9cc99557a1a839c919209ffc063c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 13 Jan 2021 08:30:35 +0100 Subject: [PATCH 75/88] Cleanup --- .../DependencyInjection/UmbracoBuilder.cs | 1 - .../Scoping/ScopedNuCacheTests.cs | 2 -- .../Profiler/InitializeWebProfiling.cs | 32 ++++++++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 0deac29761..54e5982a58 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -114,7 +114,6 @@ namespace Umbraco.Core.DependencyInjection // Adds no-op registrations as many core services require these dependencies but these // dependencies cannot be fulfilled in the Core project Services.AddUnique(); - //Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index cef471b2d4..63b3b13e13 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -105,8 +105,6 @@ namespace Umbraco.Tests.Scoping hostingEnvironment, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); - //lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); - return snapshotService; } diff --git a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs index ff4b2293ed..fede88e14f 100644 --- a/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs +++ b/src/Umbraco.Web.Common/Profiler/InitializeWebProfiling.cs @@ -1,8 +1,8 @@ -using System; -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Umbraco.Core.Events; using Umbraco.Core.Logging; @@ -10,26 +10,38 @@ using Umbraco.Web.Common.Lifetime; namespace Umbraco.Web.Common.Profiler { + /// + /// Initialized the web profiling. Ensures the boot process profiling is stopped. + /// public class InitializeWebProfiling : INotificationHandler { private readonly bool _profile; private readonly WebProfiler _profiler; private readonly IUmbracoRequestLifetime _umbracoRequestLifetime; - private readonly List _terminate = new List(); + + /// + /// Initializes a new instance of the class. + /// public InitializeWebProfiling(IProfiler profiler, IUmbracoRequestLifetime umbracoRequestLifetime, ILogger logger) { _umbracoRequestLifetime = umbracoRequestLifetime; _profile = true; - // although registered in WebRuntime.Compose, ensure that we have not + // although registered in UmbracoBuilderExtensions.AddUmbraco, ensure that we have not // been replaced by another component, and we are still "the" profiler _profiler = profiler as WebProfiler; - if (_profiler != null) return; + if (_profiler != null) + { + return; + } // if VoidProfiler was registered, let it be known if (profiler is NoopProfiler) + { logger.LogInformation( "Profiler is VoidProfiler, not profiling (must run debug mode to profile)."); + } + _profile = false; } @@ -38,13 +50,9 @@ namespace Umbraco.Web.Common.Profiler { if (_profile) { - void requestStart(object sender, HttpContext context) => _profiler.UmbracoApplicationBeginRequest(context); - _umbracoRequestLifetime.RequestStart += requestStart; - _terminate.Add(() => _umbracoRequestLifetime.RequestStart -= requestStart); + _umbracoRequestLifetime.RequestStart += (sender, context) => _profiler.UmbracoApplicationBeginRequest(context); - void requestEnd(object sender, HttpContext context) => _profiler.UmbracoApplicationEndRequest(context); - _umbracoRequestLifetime.RequestEnd += requestEnd; - _terminate.Add(() => _umbracoRequestLifetime.RequestEnd -= requestEnd); + _umbracoRequestLifetime.RequestEnd += (sender, context) => _profiler.UmbracoApplicationEndRequest(context); // Stop the profiling of the booting process _profiler.StopBoot(); From d6e90c7e7ef05d3d89b8ba02195c74a1d861291f Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 10:57:49 +0100 Subject: [PATCH 76/88] Fixing ExamineManagementController with another way of replacing the usage of HttpResponseException --- .../Controllers/ExamineManagementController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs index c692f45ac2..3174c3ca4a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ExamineManagementController.cs @@ -68,7 +68,7 @@ namespace Umbraco.Web.BackOffice.Controllers var msg = ValidateSearcher(searcherName, out var searcher); if (!msg.IsSuccessStatusCode()) - return new ValidationErrorResult(msg); + return msg; // NativeQuery will work for a single word/phrase too (but depends on the implementation) the lucene one will work. var results = searcher.CreateQuery().NativeQuery(query).Execute(maxResults: pageSize * (pageIndex + 1)); @@ -104,11 +104,11 @@ namespace Umbraco.Web.BackOffice.Controllers var validate = ValidateIndex(indexName, out var index); if (!validate.IsSuccessStatusCode()) - return new ValidationErrorResult(validate); + return validate; validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode()) - return new ValidationErrorResult(validate); + return validate; var cacheKey = "temp_indexing_op_" + indexName; var found = _runtimeCache.Get(cacheKey); @@ -129,11 +129,11 @@ namespace Umbraco.Web.BackOffice.Controllers { var validate = ValidateIndex(indexName, out var index); if (!validate.IsSuccessStatusCode()) - return new ValidationErrorResult(validate); + return validate; validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode()) - return new ValidationErrorResult(validate); + return validate; _logger.LogInformation("Rebuilding index '{IndexName}'", indexName); From 2c81d09d0eac1364fa6a9c1eca65a1a54acc60ec Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 10:59:27 +0100 Subject: [PATCH 77/88] Replacing HttpResponseException in Attributes --- .../Filters/DataTypeValidateAttribute.cs | 5 ++--- .../Filters/UserGroupValidateAttribute.cs | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs index b67cd17afc..26c3b419ba 100644 --- a/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/DataTypeValidateAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net; using Microsoft.AspNetCore.Mvc; @@ -10,7 +10,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Filters @@ -106,7 +105,7 @@ namespace Umbraco.Web.BackOffice.Filters if (context.ModelState.IsValid == false) { // if it is not valid, do not continue and return the model state - throw HttpResponseException.CreateValidationErrorResponse(context.ModelState); + context.Result = new ValidationErrorResult(context.ModelState); } } } diff --git a/src/Umbraco.Web.BackOffice/Filters/UserGroupValidateAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/UserGroupValidateAttribute.cs index 8c6e85a44c..f6647ea1d7 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UserGroupValidateAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UserGroupValidateAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -7,7 +7,7 @@ using Umbraco.Core.Mapping; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.ActionResults; -using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Filters @@ -86,7 +86,7 @@ namespace Umbraco.Web.BackOffice.Filters if (context.ModelState.IsValid == false) { //if it is not valid, do not continue and return the model state - throw HttpResponseException.CreateValidationErrorResponse(context.ModelState); + context.Result = new ValidationErrorResult(context.ModelState); } } From 2204a57a8d2b0d143e473e4c9e50540d34e718a7 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 11:02:29 +0100 Subject: [PATCH 78/88] Signature changes of GetMenuForNode() & PerformGetMenuForNode() --- .../Trees/ContentBlueprintTreeController.cs | 5 +++-- .../Trees/ContentTreeController.cs | 6 +++--- .../Trees/ContentTreeControllerBase.cs | 4 ++-- .../Trees/ContentTypeTreeController.cs | 5 +++-- .../Trees/DataTypeTreeController.cs | 5 +++-- .../Trees/DictionaryTreeController.cs | 5 +++-- .../Trees/FileSystemTreeController.cs | 5 +++-- .../Trees/LanguageTreeController.cs | 3 ++- .../Trees/LogViewerTreeController.cs | 5 +++-- src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs | 5 +++-- src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs | 9 +++++---- .../Trees/MediaTypeTreeController.cs | 5 +++-- src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs | 4 ++-- .../Trees/MemberTypeAndGroupTreeControllerBase.cs | 5 +++-- .../Trees/PackagesTreeController.cs | 5 +++-- .../Trees/RelationTypeTreeController.cs | 5 +++-- .../Trees/TemplatesTreeController.cs | 3 ++- src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs | 8 ++++---- src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs | 5 +++-- 19 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs index e232bf03b9..f1ea8329b8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -112,7 +113,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 5cdd356296..8ec0bfe449 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -143,7 +143,7 @@ namespace Umbraco.Web.BackOffice.Trees return null; } - protected override MenuItemCollection PerformGetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult PerformGetMenuForNode(string id, FormCollection queryStrings) { if (id == Constants.System.RootString) { @@ -187,12 +187,12 @@ namespace Umbraco.Web.BackOffice.Trees int iid; if (int.TryParse(id, out iid) == false) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } var item = _entityService.Get(iid, UmbracoObjectTypes.Document); if (item == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } //if the user has no path access for this node, all they can do is refresh diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 047778720b..14120825c9 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -253,7 +253,7 @@ namespace Umbraco.Web.BackOffice.Trees return parts.Length >= 2 && int.TryParse(parts[1], out id) ? id : 0; } - protected abstract MenuItemCollection PerformGetMenuForNode(string id, FormCollection queryStrings); + protected abstract ActionResult PerformGetMenuForNode(string id, FormCollection queryStrings); protected abstract UmbracoObjectTypes UmbracoObjectType { get; } @@ -432,7 +432,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - protected sealed override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected sealed override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { if (RecycleBinId.ToInvariantString() == id) { diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index 25c48b94bd..defe628123 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -97,7 +98,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 30389fb1be..9a8c94e686 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; @@ -15,6 +15,7 @@ using Umbraco.Web.Common.Attributes; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; using Umbraco.Core.Trees; @@ -119,7 +120,7 @@ namespace Umbraco.Web.BackOffice.Trees }; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs index 2dd145c654..ae9131c9e7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -109,7 +110,7 @@ namespace Umbraco.Web.BackOffice.Trees /// All of the query string parameters passed from jsTree /// /// - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs index 74d2ee90f8..54c3462fde 100644 --- a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.IO; using System.Linq; using System.Net; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.IO; using Umbraco.Core.Services; @@ -148,7 +149,7 @@ namespace Umbraco.Web.BackOffice.Trees return menu; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { //if root node no need to visit the filesystem so lets just create the menu and return it if (id == Constants.System.RootString) diff --git a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs index ecd1c954ac..44f0111f13 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -30,7 +31,7 @@ namespace Umbraco.Web.BackOffice.Trees return new TreeNodeCollection(); } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI return null; diff --git a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs index b03b2d9926..e3b76b8af1 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs @@ -1,5 +1,6 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -30,7 +31,7 @@ namespace Umbraco.Web.BackOffice.Trees return new TreeNodeCollection(); } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { //We don't have any menu item options (such as create/delete/reload) & only use the root node to load a custom UI return null; diff --git a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs index 518c1b5495..57d2169be5 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Microsoft.AspNetCore.Http; using Umbraco.Core.Services; using Umbraco.Web.Models.Trees; @@ -9,6 +9,7 @@ using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Trees @@ -57,7 +58,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs index d284624999..1354fc3d7c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -21,6 +21,7 @@ using Umbraco.Web.Security; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; using Umbraco.Core.Trees; @@ -97,7 +98,7 @@ namespace Umbraco.Web.BackOffice.Trees return node; } - protected override MenuItemCollection PerformGetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult PerformGetMenuForNode(string id, FormCollection queryStrings) { var menu = MenuItemCollectionFactory.Create(); @@ -122,12 +123,12 @@ namespace Umbraco.Web.BackOffice.Trees if (int.TryParse(id, out var iid) == false) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } var item = _entityService.Get(iid, UmbracoObjectTypes.Media); if (item == null) { - throw new HttpResponseException(HttpStatusCode.NotFound); + return NotFound(); } //if the user has no path access for this node, all they can do is refresh diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index 424a0b2451..7555895483 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; @@ -14,6 +14,7 @@ using Umbraco.Web.Common.Attributes; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; using Umbraco.Core.Trees; @@ -81,7 +82,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index 378a90da83..b480e36422 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; @@ -125,7 +125,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs index d758d8b7f9..b9f5ed7ede 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Actions; @@ -32,7 +33,7 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = MenuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs index 5c96bb4d64..66c7e4a7d1 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs @@ -1,5 +1,6 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Services; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; @@ -52,7 +53,7 @@ namespace Umbraco.Web.BackOffice.Trees return TreeNodeCollection.Empty; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { //doesn't have a menu, this is a full screen app without tree nodes return _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs index a36c2f36a9..c394af26aa 100644 --- a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Microsoft.AspNetCore.Http; using Umbraco.Web.Models.Trees; using Umbraco.Core; @@ -10,6 +10,7 @@ using Umbraco.Web.Common.Attributes; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Trees @@ -34,7 +35,7 @@ namespace Umbraco.Web.BackOffice.Trees _relationService = relationService; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs index eb08dbe629..b3178f8657 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -93,7 +94,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { var menu = _menuItemCollectionFactory.Create(); diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index ad4266e5e5..3d9bf989b3 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -55,7 +55,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - protected abstract MenuItemCollection GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); + protected abstract ActionResult GetMenuForNode(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// The name to display on the root node @@ -148,12 +148,12 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - public MenuItemCollection GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; var menu = GetMenuForNode(id, queryStrings); //raise the event - OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); + OnMenuRendering(this, new MenuRenderingEventArgs(id, menu.Value, queryStrings)); return menu; } diff --git a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs index 960ed76ac5..45bb1cdf83 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs @@ -1,5 +1,6 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core.Services; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; @@ -49,7 +50,7 @@ namespace Umbraco.Web.BackOffice.Trees return TreeNodeCollection.Empty; } - protected override MenuItemCollection GetMenuForNode(string id, FormCollection queryStrings) + protected override ActionResult GetMenuForNode(string id, FormCollection queryStrings) { //doesn't have a menu, this is a full screen app without tree nodes return _menuItemCollectionFactory.Create(); From 2e62a6c5f253b1012d24c6518c5fb86d896bb9d4 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 11:29:56 +0100 Subject: [PATCH 79/88] Reverting the change of BadRequest() back to ValidationErrorResult --- .../Controllers/UsersController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 17b845c9e0..6a7d8a468c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -121,7 +121,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var urls = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileSystem, _imageUrlGenerator); if (urls == null) - return BadRequest("Could not access Gravatar endpoint"); + return new ValidationErrorResult("Could not access Gravatar endpoint"); return urls; } @@ -531,7 +531,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError( _securitySettings.UsernameIsEmail ? "Email" : "Username", "A user with the username already exists"); - return BadRequest(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } return new ActionResult(user); @@ -586,7 +586,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - return BadRequest(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } var intId = userSave.Id.TryConvertTo(); @@ -649,7 +649,7 @@ namespace Umbraco.Web.BackOffice.Controllers } if (hasErrors) - return BadRequest(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); //merge the save data onto the user var user = _umbracoMapper.Map(userSave, found); @@ -673,7 +673,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - return BadRequest(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } var intId = changingPasswordModel.Id.TryConvertTo(); From b572cf6809ee2abbec415b05f2c16759f0a7948e Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 11:39:44 +0100 Subject: [PATCH 80/88] Surrounding ModelState with the newly introduced SimpleValidationModel type --- .../Controllers/AuthenticationController.cs | 4 ++-- .../Controllers/CurrentUserController.cs | 2 +- .../Controllers/DataTypeController.cs | 3 ++- .../Controllers/DictionaryController.cs | 3 ++- .../Controllers/LanguageController.cs | 13 +++++++------ .../Controllers/PackageController.cs | 4 +++- .../Controllers/UsersController.cs | 2 +- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 9e4d9b5d2b..11931b5f47 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -209,7 +209,7 @@ namespace Umbraco.Web.BackOffice.Controllers else { AddModelErrors(result); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } } @@ -470,7 +470,7 @@ namespace Umbraco.Web.BackOffice.Controllers { if (ModelState.IsValid == false) { - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs index 3a3734160d..36f1a7455f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CurrentUserController.cs @@ -185,7 +185,7 @@ namespace Umbraco.Web.BackOffice.Controllers // so that is why it is being used here. ModelState.AddModelError("value", result.Errors.ToErrorMessage()); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } //They've successfully set their password, we can now update their user account to be approved diff --git a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs index 452ff8b5e0..4cc1f80f9e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs @@ -15,6 +15,7 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; using Umbraco.Core.Serialization; using Umbraco.Core.Services; +using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; @@ -302,7 +303,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (DuplicateNameException ex) { ModelState.AddModelError("Name", ex.Message); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } // map back to display model, and return diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index 94ada7e3aa..6a03c6ef89 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -15,6 +15,7 @@ using Constants = Umbraco.Core.Constants; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Authorization; +using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Authorization; @@ -224,7 +225,7 @@ namespace Umbraco.Web.BackOffice.Controllers userCulture, new Dictionary { { "0", dictionary.Name } }); ModelState.AddModelError("Name", message); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } dictionaryItem.ItemKey = dictionary.Name; diff --git a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs index 7fbc018a1e..c011f67279 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/LanguageController.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; @@ -114,7 +115,7 @@ namespace Umbraco.Web.BackOffice.Controllers public ActionResult SaveLanguage(Language language) { if (!ModelState.IsValid) - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); // this is prone to race conditions but the service will not let us proceed anyways var existingByCulture = _localizationService.GetLanguageByIsoCode(language.IsoCode); @@ -130,7 +131,7 @@ namespace Umbraco.Web.BackOffice.Controllers { //someone is trying to create a language that already exist ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } var existingById = language.Id != default ? _localizationService.GetLanguageById(language.Id) : null; @@ -147,7 +148,7 @@ namespace Umbraco.Web.BackOffice.Controllers catch (CultureNotFoundException) { ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } // create it (creating a new language cannot create a fallback cycle) @@ -170,7 +171,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (existingById.IsDefault && !language.IsDefault) { ModelState.AddModelError("IsDefault", "Cannot un-default the default language."); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } existingById.IsDefault = language.IsDefault; @@ -185,12 +186,12 @@ namespace Umbraco.Web.BackOffice.Controllers if (!languages.ContainsKey(existingById.FallbackLanguageId.Value)) { ModelState.AddModelError("FallbackLanguage", "The selected fall back language does not exist."); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } if (CreatesCycle(existingById, languages)) { ModelState.AddModelError("FallbackLanguage", $"The selected fall back language {languages[existingById.FallbackLanguageId.Value].IsoCode} would create a circular path."); - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs index 57f49bdc23..d900076e03 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PackageController.cs @@ -10,9 +10,11 @@ using Microsoft.Net.Http.Headers; using Semver; using Umbraco.Core; using Umbraco.Core.Hosting; +using Umbraco.Core.Models; using Umbraco.Core.Models.Packaging; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Extensions; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; @@ -67,7 +69,7 @@ namespace Umbraco.Web.BackOffice.Controllers public ActionResult PostSavePackage(PackageDefinition model) { if (ModelState.IsValid == false) - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); //save it if (!_packagingService.SaveCreatedPackage(model)) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 6a7d8a468c..fd2623ceab 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -704,7 +704,7 @@ namespace Umbraco.Web.BackOffice.Controllers ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } - return new ValidationErrorResult(ModelState); + return new ValidationErrorResult(new SimpleValidationModel(ModelState.ToErrorDictionary())); } From f496dadf2307fe8da99ff7c5fdd29b0a6d4c3732 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 13 Jan 2021 12:35:05 +0100 Subject: [PATCH 81/88] Fix tests --- .../Routing/UmbracoRouteValueTransformerTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs index 0b9e1d6420..a531c77fe1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -10,7 +10,6 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.PublishedContent; using Umbraco.Extensions; using Umbraco.Tests.TestHelpers; using Umbraco.Web; @@ -84,7 +83,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing public async Task Noop_When_Runtime_Level_Not_Run() { UmbracoRouteValueTransformer transformer = GetTransformer( - Mock.Of(x => x.UmbracoContext == null), + Mock.Of(), Mock.Of()); RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); @@ -95,7 +94,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing public async Task Noop_When_No_Umbraco_Context() { UmbracoRouteValueTransformer transformer = GetTransformerWithRunState( - Mock.Of(x => x.UmbracoContext == null)); + Mock.Of()); RouteValueDictionary result = await transformer.TransformAsync(new DefaultHttpContext(), new RouteValueDictionary()); Assert.AreEqual(0, result.Count); From f5be1016fc2e48cdd07f9d7ff6ec6545df918366 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 13 Jan 2021 12:39:34 +0100 Subject: [PATCH 82/88] Param refactor --- src/Umbraco.Core/Routing/PublishedRequest.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 0c129bf279..7a3d44149d 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -13,10 +11,10 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, string absPathDecoded, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) + public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent publishedContent, bool isInternalRedirect, ITemplate template, DomainAndUri domain, string culture, string redirectUrl, int? responseStatusCode, IReadOnlyList cacheExtensions, IReadOnlyDictionary headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); - AbsolutePathDecoded = absPathDecoded ?? throw new ArgumentNullException(nameof(absPathDecoded)); + AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded)); PublishedContent = publishedContent; IsInternalRedirect = isInternalRedirect; Template = template; From ccfb9155842a8e6518530e1de93314b3029ba811 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 16:08:00 +0100 Subject: [PATCH 83/88] Getting rid of HttpResponseExceptionFilter --- .../Controllers/UmbracoApiControllerBase.cs | 3 +- .../Filters/HttpResponseExceptionFilter.cs | 31 ------------------- .../Install/InstallApiController.cs | 1 - 3 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs b/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs index 9f9e2b19be..364c3c1211 100644 --- a/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; @@ -15,7 +15,6 @@ namespace Umbraco.Web.Common.Controllers /// The base class is which are netcore API controllers without any view support /// [Authorize(Policy = AuthorizationPolicies.UmbracoFeatureEnabled)] // TODO: This could be part of our conventions - [TypeFilter(typeof(HttpResponseExceptionFilter))] // TODO: This could be part of our conventions [UmbracoApiController] public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature { diff --git a/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs b/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs deleted file mode 100644 index 6a5f6eaa47..0000000000 --- a/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Umbraco.Web.Common.Exceptions; - -namespace Umbraco.Web.Common.Filters -{ - public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter - { - public int Order { get; set; } = int.MaxValue - 10; - - public void OnActionExecuting(ActionExecutingContext context) { } - - public void OnActionExecuted(ActionExecutedContext context) - { - if (context.Exception is HttpResponseException exception) - { - context.Result = new ObjectResult(exception.Value) - { - StatusCode = (int)exception.Status, - }; - - foreach (var (key,value) in exception.AdditionalHeaders) - { - context.HttpContext.Response.Headers[key] = value; - } - - context.ExceptionHandled = true; - } - } - } -} diff --git a/src/Umbraco.Web.Common/Install/InstallApiController.cs b/src/Umbraco.Web.Common/Install/InstallApiController.cs index 58bd95413f..ab96707f94 100644 --- a/src/Umbraco.Web.Common/Install/InstallApiController.cs +++ b/src/Umbraco.Web.Common/Install/InstallApiController.cs @@ -19,7 +19,6 @@ using Umbraco.Web.Install.Models; namespace Umbraco.Web.Common.Install { [UmbracoApiController] - [TypeFilter(typeof(HttpResponseExceptionFilter))] [AngularJsonOnlyConfiguration] [InstallAuthorize] [Area(Umbraco.Core.Constants.Web.Mvc.InstallArea)] From 3a519653583397308134241073de1a37bf45a47b Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 16:14:20 +0100 Subject: [PATCH 84/88] Placing the code for checking if a ModelState is invalid directly --- .../Controllers/ContentControllerBase.cs | 16 ---------------- .../Controllers/MediaController.cs | 6 +++++- .../Controllers/MemberController.cs | 7 ++++++- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index 50012c7921..28047ea119 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -1,22 +1,16 @@ using System; using System.Linq; -using System.Net; -using System.Net.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Dictionary; using Umbraco.Core.Events; -using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Editors; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Extensions; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Models.ContentEditing; @@ -151,16 +145,6 @@ namespace Umbraco.Web.BackOffice.Controllers } } - protected virtual void HandleInvalidModelState(IErrorModel display) - { - //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 - if (!ModelState.IsValid) - { - display.Errors = ModelState.ToErrorDictionary(); - throw HttpResponseException.CreateValidationErrorResponse(display); - } - } - /// /// A helper method to attempt to get the instance from the request storage if it can be found there, /// otherwise gets it from the callback specified diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 8e5d1307fc..3af2d7da2a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -570,7 +570,11 @@ namespace Umbraco.Web.BackOffice.Controllers var display = _umbracoMapper.Map(contentItem.PersistedContent); //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 - HandleInvalidModelState(display); + if (!ModelState.IsValid) + { + display.Errors = ModelState.ToErrorDictionary(); + return new ValidationErrorResult(display, StatusCodes.Status403Forbidden); + } //put the correct msgs in switch (contentItem.Action) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 23e6e30d81..ba0e22b2bf 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -7,6 +7,7 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -266,7 +267,11 @@ namespace Umbraco.Web.BackOffice.Controllers var display = _umbracoMapper.Map(contentItem.PersistedContent); //lastly, if it is not valid, add the model state to the outgoing object and throw a 403 - HandleInvalidModelState(display); + if (!ModelState.IsValid) + { + display.Errors = ModelState.ToErrorDictionary(); + return new ValidationErrorResult(display, StatusCodes.Status403Forbidden); + } var localizedTextService = _localizedTextService; //put the correct messages in From 7c5e0458684cacfe239c640ac734abdd0e75faf2 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 16:16:11 +0100 Subject: [PATCH 85/88] Commenting out 2 GetModelFromMultipartRequest() and ReadAsMultipart() as for the moment they are not used and we need to rethink how to handle them --- .../WebApi/HttpActionContextExtensions.cs | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs b/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs index 477585640d..6601497a3f 100644 --- a/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpActionContextExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net; using System.Net.Http; @@ -23,29 +23,29 @@ namespace Umbraco.Web.WebApi /// /// /// - public static T GetModelFromMultipartRequest(this HttpActionContext actionContext, MultipartFormDataStreamProvider result, string requestKey, string validationKeyPrefix = "") - { - if (result.FormData[requestKey/*"contentItem"*/] == null) - { - var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest); - response.ReasonPhrase = $"The request was not formatted correctly and is missing the '{requestKey}' parameter"; - throw new HttpResponseException(response); - } + //public static T GetModelFromMultipartRequest(this HttpActionContext actionContext, MultipartFormDataStreamProvider result, string requestKey, string validationKeyPrefix = "") + //{ + // if (result.FormData[requestKey/*"contentItem"*/] == null) + // { + // var response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest); + // response.ReasonPhrase = $"The request was not formatted correctly and is missing the '{requestKey}' parameter"; + // throw new HttpResponseException(response); + // } - //get the string json from the request - var contentItem = result.FormData[requestKey]; + // //get the string json from the request + // var contentItem = result.FormData[requestKey]; - //deserialize into our model - var model = JsonConvert.DeserializeObject(contentItem); + // //deserialize into our model + // var model = JsonConvert.DeserializeObject(contentItem); - //get the default body validator and validate the object - var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator(); - var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider(); - //by default all validation errors will not contain a prefix (empty string) unless specified - bodyValidator.Validate(model, typeof(T), metadataProvider, actionContext, validationKeyPrefix); + // //get the default body validator and validate the object + // var bodyValidator = actionContext.ControllerContext.Configuration.Services.GetBodyModelValidator(); + // var metadataProvider = actionContext.ControllerContext.Configuration.Services.GetModelMetadataProvider(); + // //by default all validation errors will not contain a prefix (empty string) unless specified + // bodyValidator.Validate(model, typeof(T), metadataProvider, actionContext, validationKeyPrefix); - return model; - } + // return model; + //} /// /// Helper method to get the from the request in a non-async manner @@ -53,54 +53,54 @@ namespace Umbraco.Web.WebApi /// /// /// - public static MultipartFormDataStreamProvider ReadAsMultipart(this HttpActionContext actionContext, string rootVirtualPath) - { - if (actionContext.Request.Content.IsMimeMultipartContent() == false) - { - throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); - } + //public static MultipartFormDataStreamProvider ReadAsMultipart(this HttpActionContext actionContext, string rootVirtualPath) + //{ + // if (actionContext.Request.Content.IsMimeMultipartContent() == false) + // { + // throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + // } - var hostingEnvironment = Current.Factory.GetRequiredService(); - var root = hostingEnvironment.MapPathContentRoot(rootVirtualPath); - //ensure it exists - Directory.CreateDirectory(root); - var provider = new MultipartFormDataStreamProvider(root); + // var hostingEnvironment = Current.Factory.GetRequiredService(); + // var root = hostingEnvironment.MapPathContentRoot(rootVirtualPath); + // //ensure it exists + // Directory.CreateDirectory(root); + // var provider = new MultipartFormDataStreamProvider(root); - var request = actionContext.Request; - var content = request.Content; + // var request = actionContext.Request; + // var content = request.Content; - // Note: YES this is super strange, ugly, and weird. - // One would think that you could just do: - // - //var result = content.ReadAsMultipartAsync(provider).Result; - // - // But it deadlocks. See https://stackoverflow.com/questions/15201255 for details, which - // points to https://msdn.microsoft.com/en-us/magazine/jj991977.aspx which contains more - // details under "Async All the Way" - see also https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/ - // which contains a simplified explanation: ReadAsMultipartAsync is meant to be awaited, - // not used in the non-async .Result way, and there is nothing we can do about it. - // - // Alas, model binders cannot be async "all the way", so we have to wrap in a task, to - // force proper threading, and then it works. + // // Note: YES this is super strange, ugly, and weird. + // // One would think that you could just do: + // // + // //var result = content.ReadAsMultipartAsync(provider).Result; + // // + // // But it deadlocks. See https://stackoverflow.com/questions/15201255 for details, which + // // points to https://msdn.microsoft.com/en-us/magazine/jj991977.aspx which contains more + // // details under "Async All the Way" - see also https://olitee.com/2015/01/c-async-await-common-deadlock-scenario/ + // // which contains a simplified explanation: ReadAsMultipartAsync is meant to be awaited, + // // not used in the non-async .Result way, and there is nothing we can do about it. + // // + // // Alas, model binders cannot be async "all the way", so we have to wrap in a task, to + // // force proper threading, and then it works. - MultipartFormDataStreamProvider result = null; - var task = Task.Run(() => content.ReadAsMultipartAsync(provider)) - .ContinueWith(x => - { - if (x.IsFaulted && x.Exception != null) - { - throw x.Exception; - } - result = x.ConfigureAwait(false).GetAwaiter().GetResult(); - }, - // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html - TaskScheduler.Default); - task.Wait(); + // MultipartFormDataStreamProvider result = null; + // var task = Task.Run(() => content.ReadAsMultipartAsync(provider)) + // .ContinueWith(x => + // { + // if (x.IsFaulted && x.Exception != null) + // { + // throw x.Exception; + // } + // result = x.ConfigureAwait(false).GetAwaiter().GetResult(); + // }, + // // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + // TaskScheduler.Default); + // task.Wait(); - if (result == null) - throw new InvalidOperationException("Could not read multi-part message"); + // if (result == null) + // throw new InvalidOperationException("Could not read multi-part message"); - return result; - } + // return result; + //} } } From ded3a061701775a34f144eedc5c964fe97e71da3 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 16:17:39 +0100 Subject: [PATCH 86/88] Special case where the user is not authorized --- .../ModelBinders/ContentModelBinderHelper.cs | 4 +-- .../Trees/ApplicationTreeController.cs | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/ModelBinders/ContentModelBinderHelper.cs b/src/Umbraco.Web.BackOffice/ModelBinders/ContentModelBinderHelper.cs index adea4dcc3c..0ed360214b 100644 --- a/src/Umbraco.Web.BackOffice/ModelBinders/ContentModelBinderHelper.cs +++ b/src/Umbraco.Web.BackOffice/ModelBinders/ContentModelBinderHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net; using System.Threading.Tasks; @@ -60,7 +60,7 @@ namespace Umbraco.Web.BackOffice.ModelBinders if (parts.Length < 2) { bindingContext.HttpContext.SetReasonPhrase( "The request was not formatted correctly the file name's must be underscore delimited"); - throw new HttpResponseException(HttpStatusCode.BadRequest); + return null; } var propAlias = parts[1]; diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index c19229b1e6..1a5aa688d4 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -160,25 +160,17 @@ namespace Umbraco.Web.BackOffice.Trees /// Tries to get the root node of a tree. /// /// - /// Returns null if the root node could not be obtained due to an HttpResponseException, - /// which probably indicates that the user isn't authorized to view that tree. + /// Returns null if the root node could not be obtained due to that + /// the user isn't authorized to view that tree. In this case since we are + /// loading multiple trees we will just return null so that it's not added + /// to the list /// private async Task TryGetRootNode(Tree tree, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); - try - { - return await GetRootNode(tree, querystring); - } - catch (HttpResponseException) - { - // if this occurs its because the user isn't authorized to view that tree, - // in this case since we are loading multiple trees we will just return - // null so that it's not added to the list. - return null; - } + return await GetRootNode(tree, querystring); } /// @@ -219,7 +211,15 @@ namespace Umbraco.Web.BackOffice.Trees if (tree == null) throw new ArgumentNullException(nameof(tree)); - var controller = (TreeControllerBase)(await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring)).Value; + var result = await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring); + + // return null if the user isn't authorized to view that tree + if (!((ForbidResult)result.Result is null)) + { + return null; + } + + var controller = (TreeControllerBase)result.Value; var rootNode = controller.GetRootNode(querystring); if (rootNode == null) throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); From 680f8c4d96c68fb7c7b3f69b27122feb2bcf07ad Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Wed, 13 Jan 2021 16:25:21 +0100 Subject: [PATCH 87/88] Other special cases --- .../Controllers/ContentController.cs | 23 +++++++++++----- .../Controllers/ContentTypeControllerBase.cs | 26 +++++++++++-------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 845104719e..c1f08f5a85 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -6,6 +6,7 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -606,14 +607,16 @@ namespace Umbraco.Web.BackOffice.Controllers return notificationModel; } - private void EnsureUniqueName(string name, IContent content, string modelName) + private bool EnsureUniqueName(string name, IContent content, string modelName) { var existing = _contentService.GetBlueprintsForContentTypes(content.ContentTypeId); if (existing.Any(x => x.Name == name && x.Id != content.Id)) { ModelState.AddModelError(modelName, _localizedTextService.Localize("blueprints/duplicateBlueprintMessage")); - throw HttpResponseException.CreateValidationErrorResponse(ModelState); + return false; } + + return true; } /// @@ -627,7 +630,10 @@ namespace Umbraco.Web.BackOffice.Controllers contentItem, content => { - EnsureUniqueName(content.Name, content, "Name"); + if (!EnsureUniqueName(content.Name, content, "Name")) + { + return OperationResult.Cancel(new EventMessages()); + } _contentService.SaveBlueprint(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); @@ -839,9 +845,15 @@ namespace Umbraco.Web.BackOffice.Controllers v.Notifications.AddRange(n.Notifications); } - //lastly, if it is not valid, add the model state to the outgoing object and throw a 400 HandleInvalidModelState(display, cultureForInvariantErrors); + //lastly, if it is not valid, add the model state to the outgoing object and throw a 400 + if (!ModelState.IsValid) + { + display.Errors = ModelState.ToErrorDictionary(); + return new ValidationErrorResult(display); + } + if (wasCancelled) { AddCancelMessage(display); @@ -1915,9 +1927,6 @@ namespace Umbraco.Web.BackOffice.Controllers AddVariantValidationError(culture, segment, "speechBubbles/contentCultureValidationError"); } } - - base.HandleInvalidModelState(display); - } /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs index 965059b83f..f5934a9583 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs @@ -282,7 +282,8 @@ namespace Umbraco.Web.BackOffice.Controllers if (ModelState.IsValid == false) { - throw CreateModelStateValidationException(ctId, contentTypeSave, ct); + var err = CreateModelStateValidationEror(ctId, contentTypeSave, ct); + return new ValidationErrorResult(err); } //filter out empty properties @@ -304,11 +305,11 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId); - if (responseEx != null) throw responseEx; + if (responseEx != null) return new ValidationErrorResult(responseEx); } var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, ct); - if (exResult != null) throw exResult; + if (exResult != null) return new ValidationErrorResult(exResult); saveContentType(ct); @@ -344,11 +345,14 @@ namespace Umbraco.Web.BackOffice.Controllers catch (Exception ex) { var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId); - throw responseEx ?? ex; + if (responseEx is null) + throw ex; + + return new ValidationErrorResult(responseEx); } var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, newCt); - if (exResult != null) throw exResult; + if (exResult != null) return new ValidationErrorResult(exResult); //set id to null to ensure its handled as a new type contentTypeSave.Id = null; @@ -472,7 +476,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) + private TContentTypeDisplay CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, TContentType composition) where TContentTypeSave : ContentTypeSave where TPropertyType : PropertyTypeBasic where TContentTypeDisplay : ContentTypeCompositionDisplay @@ -490,7 +494,7 @@ namespace Umbraco.Web.BackOffice.Controllers //map the 'save' data on top display = UmbracoMapper.Map(contentTypeSave, display); display.Errors = ModelState.ToErrorDictionary(); - throw HttpResponseException.CreateValidationErrorResponse(display); + return display; } return null; } @@ -539,7 +543,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - private HttpResponseException CreateInvalidCompositionResponseException( + private TContentTypeDisplay CreateInvalidCompositionResponseException( Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId) where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave @@ -557,7 +561,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (invalidCompositionException != null) { AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases); - return CreateModelStateValidationException(ctId, contentTypeSave, ct); + return CreateModelStateValidationEror(ctId, contentTypeSave, ct); } return null; } @@ -570,7 +574,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - private HttpResponseException CreateModelStateValidationException(int ctId, TContentTypeSave contentTypeSave, TContentType ct) + private TContentTypeDisplay CreateModelStateValidationEror(int ctId, TContentTypeSave contentTypeSave, TContentType ct) where TContentTypeDisplay : ContentTypeCompositionDisplay where TContentTypeSave : ContentTypeSave { @@ -589,7 +593,7 @@ namespace Umbraco.Web.BackOffice.Controllers } forDisplay.Errors = ModelState.ToErrorDictionary(); - return HttpResponseException.CreateValidationErrorResponse(forDisplay); + return forDisplay; } } } From 04bb4e99b6ce7f2b32f4051422006522a9a4ad83 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 14 Jan 2021 19:41:32 +0100 Subject: [PATCH 88/88] Fixed possible null reference exceptions --- .../Controllers/ContentController.cs | 44 ++++++---- .../Controllers/ContentControllerBase.cs | 7 +- .../Controllers/ContentTypeController.cs | 21 ++++- .../Controllers/EntityController.cs | 27 ++++-- .../Controllers/MediaController.cs | 27 +++++- .../Controllers/MediaTypeController.cs | 21 ++++- .../Controllers/MemberController.cs | 3 +- .../Controllers/MemberTypeController.cs | 27 ++++-- .../Controllers/SectionController.cs | 8 +- .../Controllers/UsersController.cs | 8 +- .../Trees/ApplicationTreeController.cs | 64 ++++++++++---- .../Trees/ContentBlueprintTreeController.cs | 13 +-- .../Trees/ContentTreeController.cs | 35 ++++---- .../Trees/ContentTreeControllerBase.cs | 48 +++++++---- .../Trees/ContentTypeTreeController.cs | 12 ++- .../Trees/DataTypeTreeController.cs | 18 ++-- .../Trees/DictionaryTreeController.cs | 11 ++- .../Trees/FileSystemTreeController.cs | 19 ++++- .../Trees/LanguageTreeController.cs | 14 ++-- .../Trees/LogViewerTreeController.cs | 14 ++-- .../Trees/MacrosTreeController.cs | 23 +++-- .../Trees/MediaTypeTreeController.cs | 17 ++-- .../Trees/MemberGroupTreeController.cs | 12 ++- .../Trees/MemberTreeController.cs | 14 ++-- .../MemberTypeAndGroupTreeControllerBase.cs | 2 +- .../Trees/MemberTypeTreeController.cs | 13 ++- .../Trees/PackagesTreeController.cs | 15 ++-- .../Trees/RelationTypeTreeController.cs | 11 ++- .../Trees/TemplatesTreeController.cs | 14 ++-- .../Trees/TreeControllerBase.cs | 36 ++++++-- .../Trees/UserTreeController.cs | 13 ++- .../Exceptions/HttpResponseException.cs | 84 ------------------- 32 files changed, 411 insertions(+), 284 deletions(-) delete mode 100644 src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index c1f08f5a85..52273b92ee 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -6,7 +6,6 @@ using System.Net.Mime; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -34,7 +33,6 @@ using Umbraco.Web.BackOffice.ModelBinders; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; @@ -335,13 +333,12 @@ namespace Umbraco.Web.BackOffice.Controllers [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] [DetermineAmbiguousActionByPassingParameters] - public ContentItemDisplay GetById(int id) + public ActionResult GetById(int id) { var foundContent = GetObjectFromRequest(() => _contentService.GetById(id)); if (foundContent == null) { - HandleContentNotFound(id); - return null;//irrelevant since the above throws + return HandleContentNotFound(id); } var content = MapToDisplay(foundContent); return content; @@ -355,13 +352,12 @@ namespace Umbraco.Web.BackOffice.Controllers [OutgoingEditorModelEvent] [Authorize(Policy = AuthorizationPolicies.ContentPermissionBrowseById)] [DetermineAmbiguousActionByPassingParameters] - public ContentItemDisplay GetById(Guid id) + public ActionResult GetById(Guid id) { var foundContent = GetObjectFromRequest(() => _contentService.GetById(id)); if (foundContent == null) { - HandleContentNotFound(id); - return null;//irrelevant since the above throws + return HandleContentNotFound(id); } var content = MapToDisplay(foundContent); @@ -590,9 +586,14 @@ namespace Umbraco.Web.BackOffice.Controllers var content = _contentService.GetById(contentId); if (content == null) + { return NotFound(); + } - EnsureUniqueName(name, content, nameof(name)); + if (!EnsureUniqueName(name, content, nameof(name))) + { + return new ValidationErrorResult(ModelState.ToErrorDictionary()); + } var blueprint = _contentService.CreateContentFromBlueprint(content, name, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -1484,7 +1485,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (foundContent == null) { - return HandleContentNotFound(id, false); + return HandleContentNotFound(id); } var publishResult = _contentService.SaveAndPublish(foundContent, userId: _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -1507,7 +1508,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (found == null) { - return HandleContentNotFound(id, false); + return HandleContentNotFound(id); } _contentService.DeleteBlueprint(found); @@ -1533,7 +1534,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (foundContent == null) { - return HandleContentNotFound(id, false); + return HandleContentNotFound(id); } //if the current item is in the recycle bin @@ -1639,7 +1640,12 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toMove = ValidateMoveOrCopy(move).Value; + var toMoveResult = ValidateMoveOrCopy(move); + if (!(toMoveResult is null)) + { + return toMoveResult.Result; + } + var toMove = toMoveResult.Value; _contentService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -1651,7 +1657,7 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// /// - public async Task PostCopy(MoveOrCopy copy) + public async Task> PostCopy(MoveOrCopy copy) { // Authorize... var resource = new ContentPermissionsResource(_contentService.GetById(copy.ParentId), ActionCopy.ActionLetter); @@ -1661,8 +1667,12 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toCopy = ValidateMoveOrCopy(copy).Value; - + var toCopyResult = ValidateMoveOrCopy(copy); + if ((toCopyResult.Result is null)) + { + return toCopyResult.Result; + } + var toCopy = toCopyResult.Value; var c = _contentService.Copy(toCopy, copy.ParentId, copy.RelateToOriginal, copy.Recursive, _backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); return Content(c.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); @@ -1680,7 +1690,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (foundContent == null) { - HandleContentNotFound(model.Id); + return HandleContentNotFound(model.Id); } // Authorize... diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index 28047ea119..75d62f1863 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -11,7 +11,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Models.ContentEditing; @@ -71,14 +70,10 @@ namespace Umbraco.Web.BackOffice.Controllers /// protected ILocalizedTextService LocalizedTextService { get; } - protected NotFoundObjectResult HandleContentNotFound(object id, bool throwException = true) + protected NotFoundObjectResult HandleContentNotFound(object id) { ModelState.AddModelError("id", $"content with id: {id} was not found"); var errorResponse = NotFound(ModelState); - if (throwException) - { - throw new HttpResponseException(errorResponse); - } return errorResponse; } diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index e27968face..ea34005a87 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -208,9 +208,18 @@ namespace Umbraco.Web.BackOffice.Controllers /// [HttpPost] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] - public IActionResult GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) + public ActionResult GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) { - var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement).Value + var actionResult = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, + UmbracoObjectTypes.DocumentType, filter.FilterContentTypes, filter.FilterPropertyTypes, + filter.IsElement); + + if (!(actionResult.Result is null)) + { + return actionResult.Result; + } + + var result = actionResult.Value .Select(x => new { contentType = x.Item1, @@ -361,7 +370,7 @@ namespace Umbraco.Web.BackOffice.Controllers } [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] - public DocumentTypeDisplay PostSave(DocumentTypeSave contentTypeSave) + public ActionResult PostSave(DocumentTypeSave contentTypeSave) { //Before we send this model into this saving/mapping pipeline, we need to do some cleanup on variations. //If the doc type does not allow content variations, we need to update all of it's property types to not allow this either @@ -404,8 +413,14 @@ namespace Umbraco.Web.BackOffice.Controllers } }); + if (!(savedCt.Result is null)) + { + return savedCt.Result; + } + var display = _umbracoMapper.Map(savedCt.Value); + display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/contentTypeSavedHeader"), string.Empty); diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 32d09e9a5c..13ef66fa15 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Umbraco.Core; using Umbraco.Core.Mapping; using Umbraco.Core.Models; @@ -201,11 +202,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetPath(int id, UmbracoEntityTypes type) + public IConvertToActionResult GetPath(int id, UmbracoEntityTypes type) { - var foundContent = GetResultForId(id, type).Value; + var foundContentResult = GetResultForId(id, type); + var foundContent = foundContentResult.Value; + if (foundContent is null) + { + return foundContentResult; + } - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + return new ActionResult>(foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse)); } /// @@ -215,11 +221,16 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public IEnumerable GetPath(Guid id, UmbracoEntityTypes type) + public IConvertToActionResult GetPath(Guid id, UmbracoEntityTypes type) { - var foundContent = GetResultForKey(id, type).Value; + var foundContentResult = GetResultForKey(id, type); + var foundContent = foundContentResult.Value; + if (foundContent is null) + { + return foundContentResult; + } - return foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse); + return new ActionResult>(foundContent.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse)); } /// @@ -229,12 +240,12 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// [DetermineAmbiguousActionByPassingParameters] - public ActionResult> GetPath(Udi id, UmbracoEntityTypes type) + public IActionResult GetPath(Udi id, UmbracoEntityTypes type) { var guidUdi = id as GuidUdi; if (guidUdi != null) { - return new ActionResult>(GetPath(guidUdi.Guid, type)); + return GetPath(guidUdi.Guid, type).Convert(); } return NotFound(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index 3af2d7da2a..e822e7df84 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -443,7 +444,7 @@ namespace Umbraco.Web.BackOffice.Controllers if (foundMedia == null) { - return HandleContentNotFound(id, false); + return HandleContentNotFound(id); } //if the current item is in the recycle bin @@ -486,7 +487,13 @@ namespace Umbraco.Web.BackOffice.Controllers return Forbid(); } - var toMove = ValidateMoveOrCopy(move).Value; + var toMoveResult = ValidateMoveOrCopy(move); + var toMove = toMoveResult.Value; + if (toMove is null && toMoveResult is IConvertToActionResult convertToActionResult) + { + return convertToActionResult.Convert(); + } + var destinationParentID = move.ParentId; var sourceParentID = toMove.ParentId; @@ -667,7 +674,12 @@ namespace Umbraco.Web.BackOffice.Controllers public async Task> PostAddFolder(PostedFolder folder) { - var parentId = (await GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true)).Value; + var parentIdResult = await GetParentIdAsIntAsync(folder.ParentId, validatePermissions:true); + if (!(parentIdResult.Result is null)) + { + return new ActionResult(parentIdResult.Result); + } + var parentId = parentIdResult.Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); @@ -699,11 +711,18 @@ namespace Umbraco.Web.BackOffice.Controllers } //get the string json from the request - var parentId = (await GetParentIdAsIntAsync(currentFolder, validatePermissions: true)).Value; + var parentIdResult = await GetParentIdAsIntAsync(currentFolder, validatePermissions:true); + if (!(parentIdResult.Result is null)) + { + return parentIdResult.Result; + } + + var parentId = parentIdResult.Value; if (!parentId.HasValue) { return NotFound("The passed id doesn't exist"); } + var tempFiles = new PostedFiles(); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs index deab42db8d..b8952e580f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaTypeController.cs @@ -176,9 +176,16 @@ namespace Umbraco.Web.BackOffice.Controllers [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] public IActionResult GetAvailableCompositeMediaTypes(GetAvailableCompositionsFilter filter) { - var result = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, UmbracoObjectTypes.MediaType, - filter.FilterContentTypes, filter.FilterPropertyTypes, filter.IsElement).Value - .Select(x => new + var actionResult = PerformGetAvailableCompositeContentTypes(filter.ContentTypeId, + UmbracoObjectTypes.MediaType, filter.FilterContentTypes, filter.FilterPropertyTypes, + filter.IsElement); + + if (!(actionResult.Result is null)) + { + return actionResult.Result; + } + + var result = actionResult.Value.Select(x => new { contentType = x.Item1, allowed = x.Item2 @@ -273,15 +280,21 @@ namespace Umbraco.Web.BackOffice.Controllers } [Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)] - public MediaTypeDisplay PostSave(MediaTypeSave contentTypeSave) + public ActionResult PostSave(MediaTypeSave contentTypeSave) { var savedCt = PerformPostSave( contentTypeSave, i => _mediaTypeService.Get(i), type => _mediaTypeService.Save(type)); + if (!(savedCt.Result is null)) + { + return savedCt.Result; + } + var display = _umbracoMapper.Map(savedCt.Value); + display.AddSuccessNotification( _localizedTextService.Localize("speechBubbles/mediaTypeSavedHeader"), string.Empty); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index ba0e22b2bf..26d84756bd 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -33,7 +33,6 @@ using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Filters; using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { @@ -453,7 +452,7 @@ namespace Umbraco.Web.BackOffice.Controllers var foundMember = _memberService.GetByKey(key); if (foundMember == null) { - return HandleContentNotFound(key, false); + return HandleContentNotFound(key); } _memberService.Delete(foundMember); diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs index 7598c0d449..7944da1f0a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberTypeController.cs @@ -1,20 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Dictionary; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; +using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Web.Models.ContentEditing; -using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Mapping; -using Umbraco.Core.Security; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Editors; -using Microsoft.AspNetCore.Authorization; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.BackOffice.Controllers { @@ -152,7 +151,16 @@ namespace Umbraco.Web.BackOffice.Controllers [FromQuery]string[] filterContentTypes, [FromQuery]string[] filterPropertyTypes) { - var result = PerformGetAvailableCompositeContentTypes(contentTypeId, UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes, false).Value + var actionResult = PerformGetAvailableCompositeContentTypes(contentTypeId, + UmbracoObjectTypes.MemberType, filterContentTypes, filterPropertyTypes, + false); + + if (!(actionResult.Result is null)) + { + return actionResult.Result; + } + + var result = actionResult.Value .Select(x => new { contentType = x.Item1, @@ -230,6 +238,11 @@ namespace Umbraco.Web.BackOffice.Controllers getContentType: i => ct, saveContentType: type => _memberTypeService.Save(type)); + if (!(savedCt.Result is null)) + { + return savedCt.Result; + } + var display =_umbracoMapper.Map(savedCt.Value); display.AddSuccessNotification( diff --git a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs index 41cc6e99ff..5b1e5fb18a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/SectionController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Umbraco.Core; @@ -48,7 +49,7 @@ namespace Umbraco.Web.BackOffice.Controllers _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; } - public async Task> GetSections() + public async Task>> GetSections() { var sections = _sectionService.GetAllowedSections(_backofficeSecurityAccessor.BackOfficeSecurity.GetUserId().ResultOr(0)); @@ -74,6 +75,11 @@ namespace Umbraco.Web.BackOffice.Controllers // get the first tree in the section and get its root node route path var sectionRoot = await appTreeController.GetApplicationTrees(section.Alias, null, null); + + if (!(sectionRoot.Result is null)) + { + return sectionRoot.Result; + } section.RoutePath = GetRoutePathForFirstTree(sectionRoot.Value); } diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index fd2623ceab..5e84b7cd65 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -427,7 +427,13 @@ namespace Umbraco.Web.BackOffice.Controllers else { //first validate the username if we're showing it - user = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue).Value; + var userResult = CheckUniqueUsername(userSave.Username, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); + if (!(userResult.Result is null)) + { + return userResult.Result; + } + + user = userResult.Value; } user = CheckUniqueEmail(userSave.Email, u => u.LastLoginDate != default || u.EmailConfirmedDate.HasValue); diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 1a5aa688d4..22667f0c30 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -15,13 +13,11 @@ using Umbraco.Core.Services; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models.Trees; using Umbraco.Web.Services; using Umbraco.Web.Trees; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -96,8 +92,11 @@ namespace Umbraco.Web.BackOffice.Trees return NotFound(); var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, queryStrings); + if (treeRootNode != null) + { return treeRootNode; + } return NotFound(); } @@ -109,7 +108,12 @@ namespace Umbraco.Web.BackOffice.Trees var nodes = new TreeNodeCollection(); foreach (var t in allTrees) { - var node = await TryGetRootNode(t, queryStrings); + var nodeResult = await TryGetRootNode(t, queryStrings); + if (!(nodeResult.Result is null)) + { + return nodeResult.Result; + } + var node = nodeResult.Value; if (node != null) nodes.Add(node); } @@ -135,7 +139,13 @@ namespace Umbraco.Web.BackOffice.Trees var nodes = new TreeNodeCollection(); foreach (var t in trees) { - var node = await TryGetRootNode(t, queryStrings); + var nodeResult = await TryGetRootNode(t, queryStrings); + if (!(nodeResult.Result is null)) + { + return nodeResult.Result; + } + var node = nodeResult.Value; + if (node != null) nodes.Add(node); } @@ -165,7 +175,7 @@ namespace Umbraco.Web.BackOffice.Trees /// loading multiple trees we will just return null so that it's not added /// to the list /// - private async Task TryGetRootNode(Tree tree, FormCollection querystring) + private async Task> TryGetRootNode(Tree tree, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -176,13 +186,26 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Get the tree root node of a tree. /// - private async Task GetTreeRootNode(Tree tree, int id, FormCollection querystring) + private async Task> GetTreeRootNode(Tree tree, int id, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); - var children = await GetChildren(tree, id, querystring); - var rootNode = await GetRootNode(tree, querystring); + var childrenResult = await GetChildren(tree, id, querystring); + if (!(childrenResult.Result is null)) + { + return new ActionResult(childrenResult.Result); + } + + var children = childrenResult.Value; + var rootNodeResult = await GetRootNode(tree, querystring); + if (!(rootNodeResult.Result is null)) + { + return rootNodeResult.Result; + } + + var rootNode = rootNodeResult.Value; + var sectionRoot = TreeRootNode.CreateSingleTreeRoot( Constants.System.RootString, @@ -206,7 +229,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Gets the root node of a tree. /// - private async Task GetRootNode(Tree tree, FormCollection querystring) + private async Task> GetRootNode(Tree tree, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -220,7 +243,14 @@ namespace Umbraco.Web.BackOffice.Trees } var controller = (TreeControllerBase)result.Value; - var rootNode = controller.GetRootNode(querystring); + var rootNodeResult = controller.GetRootNode(querystring); + if (!(rootNodeResult.Result is null)) + { + return rootNodeResult.Result; + } + + var rootNode = rootNodeResult.Value; + if (rootNode == null) throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); return rootNode; @@ -229,7 +259,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Get the child nodes of a tree node. /// - private async Task GetChildren(Tree tree, int id, FormCollection querystring) + private async Task> GetChildren(Tree tree, int id, FormCollection querystring) { if (tree == null) throw new ArgumentNullException(nameof(tree)); @@ -241,7 +271,13 @@ namespace Umbraco.Web.BackOffice.Trees d["id"] = StringValues.Empty; var proxyQuerystring = new FormCollection(d); - var controller = (TreeControllerBase)(await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring)).Value; + var controllerResult = await GetApiControllerProxy(tree.TreeControllerType, "GetNodes", proxyQuerystring); + if (!(controllerResult.Result is null)) + { + return new ActionResult(controllerResult.Result); + } + + var controller = (TreeControllerBase)controllerResult.Value; return controller.GetNodes(id.ToInvariantString(), querystring); } diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs index f1ea8329b8..c0396b68e6 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentBlueprintTreeController.cs @@ -8,13 +8,11 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -50,9 +48,14 @@ namespace Umbraco.Web.BackOffice.Trees _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.ContentBlueprints}/intro"; @@ -62,7 +65,7 @@ namespace Umbraco.Web.BackOffice.Trees return root; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs index 8ec0bfe449..4c139847f0 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs @@ -1,29 +1,26 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Umbraco.Core; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Services; -using Umbraco.Web.Actions; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Search; using Umbraco.Core.Security; -using Constants = Umbraco.Core.Constants; -using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Exceptions; -using Umbraco.Web.WebApi; -using Umbraco.Core.Configuration.Models; -using Microsoft.Extensions.Options; -using Umbraco.Web.Trees; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Common.Authorization; +using Umbraco.Core.Services; using Umbraco.Core.Trees; +using Umbraco.Web.Actions; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; +using Umbraco.Web.Trees; +using Umbraco.Web.WebApi; namespace Umbraco.Web.BackOffice.Trees { @@ -240,6 +237,12 @@ namespace Umbraco.Web.BackOffice.Trees protected override ActionResult> GetChildEntities(string id, FormCollection queryStrings) { var result = base.GetChildEntities(id, queryStrings); + + if (!(result.Result is null)) + { + return result.Result; + } + var culture = queryStrings["culture"].TryConvertTo(); //if this is null we'll set it to the default. diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs index 14120825c9..212b4dd890 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs @@ -2,21 +2,18 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Net; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Core.Services; -using Umbraco.Core.Models; -using Umbraco.Web.Models.Trees; -using Umbraco.Core.Models.Entities; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Actions; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Security; +using Umbraco.Core.Services; using Umbraco.Extensions; -using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Actions; using Umbraco.Web.Common.ModelBinders; -using Umbraco.Web.Security; +using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; @@ -98,9 +95,14 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var node = base.CreateRootNode(queryStrings); + var nodeResult = base.CreateRootNode(queryStrings); + if ((nodeResult.Result is null)) + { + return nodeResult; + } + var node = nodeResult.Value; if (IsDialog(queryStrings) && UserStartNodes.Contains(Constants.System.Root) == false && IgnoreUserStartNodes(queryStrings) == false) { @@ -174,7 +176,7 @@ namespace Umbraco.Web.BackOffice.Trees /// protected abstract int[] UserStartNodes { get; } - protected virtual TreeNodeCollection PerformGetTreeNodes(string id, FormCollection queryStrings) + protected virtual ActionResult PerformGetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); @@ -211,7 +213,13 @@ namespace Umbraco.Web.BackOffice.Trees // get child entities - if id is root, but user's start nodes do not contain the // root node, this returns the start nodes instead of root's children - var entities = GetChildEntities(id, queryStrings).Value.ToList(); + var entitiesResult = GetChildEntities(id, queryStrings); + if (!(entitiesResult.Result is null)) + { + return entitiesResult.Result; + } + + var entities = entitiesResult.Value.ToList(); //get the current user start node/paths GetUserStartNodes(out var userStartNodes, out var userStartNodePaths); @@ -325,7 +333,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// This method is overwritten strictly to render the recycle bin, it should serve no other purpose /// - protected sealed override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected sealed override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //check if we're rendering the root if (id == Constants.System.RootString && UserStartNodes.Contains(Constants.System.Root)) @@ -341,7 +349,13 @@ namespace Umbraco.Web.BackOffice.Trees id = altStartId; } - var nodes = GetTreeNodesInternal(id, queryStrings); + var nodesResult = GetTreeNodesInternal(id, queryStrings); + if (!(nodesResult.Result is null)) + { + return nodesResult.Result; + } + + var nodes = nodesResult.Value; //only render the recycle bin if we are not in dialog and the start id is still the root //we need to check for the "application" key in the queryString because its value is required here, @@ -410,7 +424,7 @@ namespace Umbraco.Web.BackOffice.Trees /// /// Currently this just checks if it is a container type, if it is we cannot render children. In the future this might check for other things. /// - private TreeNodeCollection GetTreeNodesInternal(string id, FormCollection queryStrings) + private ActionResult GetTreeNodesInternal(string id, FormCollection queryStrings) { var current = GetEntityFromId(id); diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index defe628123..f115e3e923 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Trees; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; @@ -39,15 +38,20 @@ namespace Umbraco.Web.BackOffice.Trees _entityService = entityService; } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; //check if there are any types root.HasChildren = _contentTypeService.GetAll().Any(); return root; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var intId = id.TryConvertTo(); if (intId == false) throw new InvalidOperationException("Id must be an integer"); diff --git a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs index 9a8c94e686..79cfee1ce7 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs @@ -1,23 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Web.Models.Trees; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Web.Actions; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Search; -using Constants = Umbraco.Core.Constants; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Common.Authorization; -using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { @@ -41,7 +39,7 @@ namespace Umbraco.Web.BackOffice.Trees _dataTypeService = dataTypeService; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var intId = id.TryConvertTo(); if (intId == false) throw new InvalidOperationException("Id must be an integer"); diff --git a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs index ae9131c9e7..87f7a1508f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/DictionaryTreeController.cs @@ -33,9 +33,14 @@ namespace Umbraco.Web.BackOffice.Trees _localizationService = localizationService; } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; // the default section is settings, falling back to this if we can't // figure out where we are from the querystring parameters @@ -60,7 +65,7 @@ namespace Umbraco.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var intId = id.TryConvertTo(); if (intId == false) diff --git a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs index 54c3462fde..ab77e00067 100644 --- a/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.BackOffice.Trees treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);"; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var path = string.IsNullOrEmpty(id) == false && id != Constants.System.RootString ? WebUtility.UrlDecode(id).TrimStart("/") @@ -93,11 +93,22 @@ namespace Umbraco.Web.BackOffice.Trees return nodes; } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //check if there are any children - root.HasChildren = GetTreeNodes(Constants.System.RootString, queryStrings).Any(); + var treeNodesResult = GetTreeNodes(Constants.System.RootString, queryStrings); + if (!(treeNodesResult.Result is null)) + { + return treeNodesResult.Result; + } + root.HasChildren = treeNodesResult.Value.Any(); return root; } diff --git a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs index 44f0111f13..3dcbbc9da8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LanguageTreeController.cs @@ -1,14 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -25,7 +24,7 @@ namespace Umbraco.Web.BackOffice.Trees : base(textService, umbracoApiControllerTypeCollection) { } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //We don't have any child nodes & only use the root node to load a custom UI return new TreeNodeCollection(); @@ -41,9 +40,14 @@ namespace Umbraco.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Settings}/{Constants.Trees.Languages}/overview"; diff --git a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs index e3b76b8af1..91b89cee69 100644 --- a/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/LogViewerTreeController.cs @@ -1,14 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -25,7 +24,7 @@ namespace Umbraco.Web.BackOffice.Trees { } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //We don't have any child nodes & only use the root node to load a custom UI return new TreeNodeCollection(); @@ -41,9 +40,14 @@ namespace Umbraco.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Settings, Constants.Trees.LogViewer, "overview"); diff --git a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs index 57d2169be5..d59c5c8d3a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MacrosTreeController.cs @@ -1,16 +1,15 @@ using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Web.Models.Trees; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Trees { @@ -29,15 +28,21 @@ namespace Umbraco.Web.BackOffice.Trees _macroService = macroService; } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //check if there are any macros root.HasChildren = _macroService.GetAll().Any(); return root; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs index 7555895483..ff53a82219 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs @@ -1,22 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Web.Models.Trees; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Web.Actions; -using Umbraco.Web.Models.ContentEditing; -using Umbraco.Web.Search; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Search; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Common.Authorization; -using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { @@ -39,7 +38,7 @@ namespace Umbraco.Web.BackOffice.Trees _entityService = entityService; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var intId = id.TryConvertTo(); if (intId == false) throw new InvalidOperationException("Id must be an integer"); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs index 817b32f301..5184325db8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberGroupTreeController.cs @@ -2,9 +2,9 @@ using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; @@ -38,9 +38,15 @@ namespace Umbraco.Web.BackOffice.Trees .Select(dt => CreateTreeNode(dt.Id.ToString(), id, queryStrings, dt.Name, Constants.Icons.MemberGroup, false)); } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //check if there are any groups root.HasChildren = _memberGroupService.GetAll().Any(); return root; diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs index b480e36422..0a68c36e08 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTreeController.cs @@ -1,28 +1,24 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Security; using Umbraco.Core.Services; +using Umbraco.Core.Trees; using Umbraco.Extensions; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; -using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.ModelBinders; -using Umbraco.Web.Models.Trees; using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.Models.Trees; using Umbraco.Web.Search; -using Constants = Umbraco.Core.Constants; -using Umbraco.Web.Security; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Microsoft.AspNetCore.Authorization; -using Umbraco.Web.Common.Authorization; -using Umbraco.Core.Trees; namespace Umbraco.Web.BackOffice.Trees { @@ -101,7 +97,7 @@ namespace Umbraco.Web.BackOffice.Trees return node; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs index b9f5ed7ede..0804e78c8a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeAndGroupTreeControllerBase.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.BackOffice.Trees MenuItemCollectionFactory = menuItemCollectionFactory; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); nodes.AddRange(GetTreeNodesFromService(id, queryStrings)); diff --git a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs index 8a8fe7b11e..5d44d7c832 100644 --- a/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/MemberTypeTreeController.cs @@ -2,12 +2,11 @@ using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Trees; -using Umbraco.Web.BackOffice.Filters; -using Umbraco.Web.BackOffice.Trees; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; @@ -40,9 +39,15 @@ namespace Umbraco.Web.BackOffice.Trees } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //check if there are any member types root.HasChildren = _memberTypeService.GetAll().Any(); return root; diff --git a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs index 66c7e4a7d1..9b80782725 100644 --- a/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/PackagesTreeController.cs @@ -1,14 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Services; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -34,9 +33,15 @@ namespace Umbraco.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Packages}/{Constants.Trees.Packages}/repo"; @@ -47,7 +52,7 @@ namespace Umbraco.Web.BackOffice.Trees } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //full screen app without tree nodes return TreeNodeCollection.Empty; diff --git a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs index c394af26aa..2e200e8b0a 100644 --- a/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/RelationTypeTreeController.cs @@ -1,17 +1,16 @@ using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Umbraco.Web.Models.Trees; +using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Common.Authorization; namespace Umbraco.Web.BackOffice.Trees { @@ -61,7 +60,7 @@ namespace Umbraco.Web.BackOffice.Trees return menu; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs index b3178f8657..a8ebc71581 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TemplatesTreeController.cs @@ -11,7 +11,6 @@ using Umbraco.Core.Services; using Umbraco.Core.Trees; using Umbraco.Extensions; using Umbraco.Web.Actions; -using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.ContentEditing; @@ -19,7 +18,6 @@ using Umbraco.Web.Models.Trees; using Umbraco.Web.Search; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -46,9 +44,15 @@ namespace Umbraco.Web.BackOffice.Trees _fileService = fileService; } - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; + //check if there are any templates root.HasChildren = _fileService.GetTemplates(-1).Any(); return root; @@ -65,7 +69,7 @@ namespace Umbraco.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be pased in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { var nodes = new TreeNodeCollection(); diff --git a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs index 3d9bf989b3..5c6f8a7fe8 100644 --- a/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Trees/TreeControllerBase.cs @@ -6,7 +6,6 @@ using Umbraco.Core; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence; using Umbraco.Core.Trees; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; @@ -47,7 +46,7 @@ namespace Umbraco.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - protected abstract TreeNodeCollection GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); + protected abstract ActionResult GetTreeNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings); /// /// Returns the menu structure for the node @@ -88,10 +87,16 @@ namespace Umbraco.Web.BackOffice.Trees /// /// /// - public TreeNode GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult GetRootNode([ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; - var node = CreateRootNode(queryStrings); + var nodeResult = CreateRootNode(queryStrings); + if (!(nodeResult.Result is null)) + { + return nodeResult.Result; + } + + var node = nodeResult.Value; //add the tree alias to the root node.AdditionalData["treeAlias"] = TreeAlias; @@ -123,10 +128,17 @@ namespace Umbraco.Web.BackOffice.Trees /// We are allowing an arbitrary number of query strings to be passed in so that developers are able to persist custom data from the front-end /// to the back end to be used in the query for model data. /// - public TreeNodeCollection GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) + public ActionResult GetNodes(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; - var nodes = GetTreeNodes(id, queryStrings); + var nodesResult = GetTreeNodes(id, queryStrings); + + if (!(nodesResult.Result is null)) + { + return nodesResult.Result; + } + + var nodes = nodesResult.Value; foreach (var node in nodes) AddQueryStringsToAdditionalData(node, queryStrings); @@ -151,9 +163,15 @@ namespace Umbraco.Web.BackOffice.Trees public ActionResult GetMenu(string id, [ModelBinder(typeof(HttpQueryStringModelBinder))]FormCollection queryStrings) { if (queryStrings == null) queryStrings = FormCollection.Empty; - var menu = GetMenuForNode(id, queryStrings); + var menuResult = GetMenuForNode(id, queryStrings); + if (!(menuResult.Result is null)) + { + return menuResult.Result; + } + + var menu = menuResult.Value; //raise the event - OnMenuRendering(this, new MenuRenderingEventArgs(id, menu.Value, queryStrings)); + OnMenuRendering(this, new MenuRenderingEventArgs(id, menu, queryStrings)); return menu; } @@ -161,7 +179,7 @@ namespace Umbraco.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected virtual TreeNode CreateRootNode(FormCollection queryStrings) + protected virtual ActionResult CreateRootNode(FormCollection queryStrings) { var rootNodeAsString = Constants.System.RootString; queryStrings.TryGetValue(TreeQueryStringParameters.Application, out var currApp); diff --git a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs index 45bb1cdf83..f43247c09c 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UserTreeController.cs @@ -1,13 +1,13 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Core; using Umbraco.Core.Services; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Models.Trees; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; -using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Trees { @@ -32,9 +32,14 @@ namespace Umbraco.Web.BackOffice.Trees /// Helper method to create a root model for a tree /// /// - protected override TreeNode CreateRootNode(FormCollection queryStrings) + protected override ActionResult CreateRootNode(FormCollection queryStrings) { - var root = base.CreateRootNode(queryStrings); + var rootResult = base.CreateRootNode(queryStrings); + if (!(rootResult.Result is null)) + { + return rootResult; + } + var root = rootResult.Value; //this will load in a custom UI instead of the dashboard for the root node root.RoutePath = $"{Constants.Applications.Users}/{Constants.Trees.Users}/users"; @@ -44,7 +49,7 @@ namespace Umbraco.Web.BackOffice.Trees return root; } - protected override TreeNodeCollection GetTreeNodes(string id, FormCollection queryStrings) + protected override ActionResult GetTreeNodes(string id, FormCollection queryStrings) { //full screen app without tree nodes return TreeNodeCollection.Empty; diff --git a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs b/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs deleted file mode 100644 index cb14a5a546..0000000000 --- a/src/Umbraco.Web.Common/Exceptions/HttpResponseException.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Runtime.Serialization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Common.Exceptions -{ - [Serializable] - public class HttpResponseException : Exception - { - public HttpResponseException(HttpStatusCode status = HttpStatusCode.InternalServerError, object value = null) - { - Status = status; - Value = value; - } - - public HttpResponseException(ActionResult actionResult) - { - - Status = actionResult switch - { - IStatusCodeActionResult x => (HttpStatusCode)x.StatusCode.GetValueOrDefault((int)HttpStatusCode.InternalServerError), - _ => HttpStatusCode.InternalServerError - }; - - Value = actionResult switch - { - ObjectResult x => x.Value, - _ => null - }; - } - - public HttpStatusCode Status { get; set; } - public object Value { get; set; } - - public IDictionary AdditionalHeaders { get; } = new Dictionary(); - - - /// - /// When overridden in a derived class, sets the with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(Status), Enum.GetName(typeof(HttpStatusCode), Status)); - info.AddValue(nameof(Value), Value); - info.AddValue(nameof(AdditionalHeaders), AdditionalHeaders); - - base.GetObjectData(info, context); - } - - public static HttpResponseException CreateValidationErrorResponse(object model) - { - return new HttpResponseException(HttpStatusCode.BadRequest, model) - { - AdditionalHeaders = - { - ["X-Status-Reason"] = "Validation failed" - } - }; - } - - public static HttpResponseException CreateNotificationValidationErrorResponse(string errorMessage) - { - var notificationModel = new SimpleNotificationModel - { - Message = errorMessage - }; - notificationModel.AddErrorNotification(errorMessage, string.Empty); - return CreateValidationErrorResponse(notificationModel); - } - - } -}