From cdd39050469b8bb20020724104914998cb519015 Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 2 Dec 2020 10:10:48 +0100 Subject: [PATCH 001/127] 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 002/127] 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 003/127] 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 e3be4009c02e941f2a89411598c91a8cc3321a4e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 8 Dec 2020 16:33:50 +1100 Subject: [PATCH 004/127] Getting front end routing poc going --- src/Umbraco.Core/Routing/PublishedRouter.cs | 7 +- .../{ => Web}/HybridUmbracoContextAccessor.cs | 0 src/Umbraco.Core/{ => Web}/IUmbracoContext.cs | 0 .../{ => Web}/IUmbracoContextAccessor.cs | 0 .../{ => Web}/IUmbracoContextFactory.cs | 0 ...RenderIndexActionSelectorAttributeTests.cs | 17 +- .../Controllers/SurfaceControllerTests.cs | 1 + .../Controllers/BackOfficeController.cs | 2 +- .../BackOfficeApplicationBuilderExtensions.cs | 49 ++-- .../PreviewAuthenticationMiddleware.cs | 6 +- .../Trees/ApplicationTreeController.cs | 34 +-- .../Controllers/IRenderController.cs | 6 +- .../Controllers/IRenderMvcController.cs | 19 -- .../Controllers/RenderController.cs | 10 - .../Controllers/UmbracoController.cs | 11 +- .../ApplicationBuilderExtensions.cs | 110 ++++++--- ...racoInstallApplicationBuilderExtensions.cs | 17 +- ...tialViewMacroViewContextFilterAttribute.cs | 35 +-- .../PreRenderViewActionFilterAttribute.cs | 43 ---- .../Middleware/UmbracoRequestMiddleware.cs | 22 +- src/Umbraco.Web.Common/Routing/IAreaRoutes.cs | 6 +- src/Umbraco.Web.UI.NetCore/Startup.cs | 1 + .../Controllers/IUmbracoRenderingDefaults.cs | 15 ++ ...erMvcController.cs => RenderController.cs} | 34 ++- .../RenderIndexActionSelectorAttribute.cs | 23 +- .../Controllers/SurfaceController.cs | 3 +- .../Controllers/UmbracoRenderingDefaults.cs | 13 + .../Extensions/UmbracoBuilderExtensions.cs | 17 +- ...racoWebsiteApplicationBuilderExtensions.cs | 31 ++- .../Routing/NoContentRoutes.cs | 19 +- .../{ => Routing}/RouteDefinition.cs | 4 +- .../Routing/UmbracoRouteValueTransformer.cs | 230 ++++++++++++++++++ .../Mvc/RenderControllerFactory.cs | 45 ---- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 10 +- src/Umbraco.Web/Mvc/RouteDefinition.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/UmbracoInjectedModule.cs | 9 +- 37 files changed, 543 insertions(+), 315 deletions(-) rename src/Umbraco.Core/{ => Web}/HybridUmbracoContextAccessor.cs (100%) rename src/Umbraco.Core/{ => Web}/IUmbracoContext.cs (100%) rename src/Umbraco.Core/{ => Web}/IUmbracoContextAccessor.cs (100%) rename src/Umbraco.Core/{ => Web}/IUmbracoContextFactory.cs (100%) delete mode 100644 src/Umbraco.Web.Common/Controllers/IRenderMvcController.cs delete mode 100644 src/Umbraco.Web.Common/Controllers/RenderController.cs delete mode 100644 src/Umbraco.Web.Common/Filters/PreRenderViewActionFilterAttribute.cs create mode 100644 src/Umbraco.Web.Website/Controllers/IUmbracoRenderingDefaults.cs rename src/Umbraco.Web.Website/Controllers/{RenderMvcController.cs => RenderController.cs} (78%) create mode 100644 src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs rename src/Umbraco.Web.Website/{ => Routing}/RouteDefinition.cs (94%) create mode 100644 src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs delete mode 100644 src/Umbraco.Web/Mvc/RenderControllerFactory.cs diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 9b7354de74..10986b941a 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -138,7 +138,7 @@ namespace Umbraco.Web.Routing Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture; SetVariationContext(request.Culture.Name); - //find the published content if it's not assigned. This could be manually assigned with a custom route handler, or + // 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) @@ -156,15 +156,13 @@ namespace Umbraco.Web.Routing // 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 - //complete the PCR and assign the remaining values + // complete the PCR and assign the remaining values return ConfigureRequest(request); } @@ -201,7 +199,6 @@ namespace Umbraco.Web.Routing // 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; } diff --git a/src/Umbraco.Core/HybridUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs similarity index 100% rename from src/Umbraco.Core/HybridUmbracoContextAccessor.cs rename to src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs diff --git a/src/Umbraco.Core/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs similarity index 100% rename from src/Umbraco.Core/IUmbracoContext.cs rename to src/Umbraco.Core/Web/IUmbracoContext.cs diff --git a/src/Umbraco.Core/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs similarity index 100% rename from src/Umbraco.Core/IUmbracoContextAccessor.cs rename to src/Umbraco.Core/Web/IUmbracoContextAccessor.cs diff --git a/src/Umbraco.Core/IUmbracoContextFactory.cs b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs similarity index 100% rename from src/Umbraco.Core/IUmbracoContextFactory.cs rename to src/Umbraco.Core/Web/IUmbracoContextFactory.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs index 3a987fb038..bf5c422bd8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs @@ -16,6 +16,7 @@ using Moq; using NUnit.Framework; using Umbraco.Web.Models; using Umbraco.Web.Mvc; +using Umbraco.Web.Website.Controllers; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers { @@ -118,48 +119,48 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers public ActionDescriptorCollection ActionDescriptors { get; } } - private class MatchesDefaultIndexController : RenderMvcController + private class MatchesDefaultIndexController : RenderController { - public MatchesDefaultIndexController(ILogger logger, + public MatchesDefaultIndexController(ILogger logger, ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) { } } - private class MatchesOverriddenIndexController : RenderMvcController + private class MatchesOverriddenIndexController : RenderController { public override IActionResult Index(ContentModel model) { return base.Index(model); } - public MatchesOverriddenIndexController(ILogger logger, + public MatchesOverriddenIndexController(ILogger logger, ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) { } } - private class MatchesCustomIndexController : RenderMvcController + private class MatchesCustomIndexController : RenderController { public IActionResult Index(ContentModel model, int page) { return base.Index(model); } - public MatchesCustomIndexController(ILogger logger, + public MatchesCustomIndexController(ILogger logger, ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) { } } - private class MatchesAsyncIndexController : RenderMvcController + private class MatchesAsyncIndexController : RenderController { public new async Task Index(ContentModel model) { return await Task.FromResult(base.Index(model)); } - public MatchesAsyncIndexController(ILogger logger, + public MatchesAsyncIndexController(ILogger logger, ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) { } 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 82bd6719d4..d1ffc2044e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -19,6 +19,7 @@ using Umbraco.Web.Routing; using Umbraco.Web.Security; 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 diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 19fb6aa2df..34d3a96ca3 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -30,10 +30,10 @@ using Umbraco.Web.BackOffice.Security; using Umbraco.Web.Common.ActionsResults; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; -using Umbraco.Web.Mvc; using Umbraco.Web.WebAssets; using Constants = Umbraco.Core.Constants; diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index 6ff42a5737..fe4951bc2b 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -8,60 +8,49 @@ using Umbraco.Web.Common.Security; namespace Umbraco.Extensions { + /// + /// extensions for Umbraco + /// public static class BackOfficeApplicationBuilderExtensions { - public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - app.UseStatusCodePages(); - app.UseRouting(); - - app.UseUmbracoCore(); - app.UseUmbracoRouting(); - app.UseRequestLocalization(); - app.UseUmbracoRequestLogging(); - app.UseUmbracoBackOffice(); - app.UseUmbracoPreview(); - app.UseUmbracoInstaller(); - - return app; - } - public static IApplicationBuilder UseUmbracoBackOffice(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + // NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } app.UseBackOfficeUserManagerAuditing(); - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. - // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? - app.UseImageSharp(); - app.UseStaticFiles(); - - // Must be called after UseRouting and before UseEndpoints - app.UseSession(); - - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + return app; + } app.UseEndpoints(endpoints => { - var backOfficeRoutes = app.ApplicationServices.GetRequiredService(); + BackOfficeAreaRoutes backOfficeRoutes = app.ApplicationServices.GetRequiredService(); backOfficeRoutes.CreateRoutes(endpoints); }); app.UseUmbracoRuntimeMinification(); - app.UseMiddleware(); app.UseMiddleware(); + app.UseUmbracoPreview(); + return app; } public static IApplicationBuilder UseUmbracoPreview(this IApplicationBuilder app) { + // TODO: I'm unsure this middleware will execute before the endpoint, we'll have to see + app.UseMiddleware(); + app.UseEndpoints(endpoints => { - var previewRoutes = app.ApplicationServices.GetRequiredService(); + PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService(); previewRoutes.CreateRoutes(endpoints); }); diff --git a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs index 284dbbc913..06715b4ad1 100644 --- a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -45,7 +45,7 @@ namespace Umbraco.Web.BackOffice.Middleware 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. + // 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 // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered. if (request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie)) @@ -55,7 +55,7 @@ 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 + // 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. diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 2b9370fedd..000740e27e 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -46,13 +46,13 @@ namespace Umbraco.Web.BackOffice.Trees IControllerFactory controllerFactory, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider ) - { + { _treeService = treeService; _sectionService = sectionService; _localizedTextService = localizedTextService; _controllerFactory = controllerFactory; _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; - } + } /// /// Returns the tree nodes for an application @@ -62,7 +62,7 @@ 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(); @@ -165,7 +165,8 @@ namespace Umbraco.Web.BackOffice.Trees /// private async Task TryGetRootNode(Tree tree, FormCollection querystring) { - if (tree == null) throw new ArgumentNullException(nameof(tree)); + if (tree == null) + throw new ArgumentNullException(nameof(tree)); try { @@ -185,7 +186,8 @@ namespace Umbraco.Web.BackOffice.Trees /// private async Task GetTreeRootNode(Tree tree, int id, FormCollection querystring) { - if (tree == null) throw new ArgumentNullException(nameof(tree)); + if (tree == null) + throw new ArgumentNullException(nameof(tree)); var children = await GetChildren(tree, id, querystring); var rootNode = await GetRootNode(tree, querystring); @@ -214,9 +216,10 @@ namespace Umbraco.Web.BackOffice.Trees /// private async Task GetRootNode(Tree tree, FormCollection querystring) { - if (tree == null) throw new ArgumentNullException(nameof(tree)); + 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); var rootNode = controller.GetRootNode(querystring); if (rootNode == null) throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); @@ -228,7 +231,8 @@ namespace Umbraco.Web.BackOffice.Trees /// private async Task GetChildren(Tree tree, int id, FormCollection querystring) { - if (tree == null) throw new ArgumentNullException(nameof(tree)); + if (tree == null) + throw new ArgumentNullException(nameof(tree)); // the method we proxy has an 'id' parameter which is *not* in the querystring, // we need to add it for the proxy to work (else, it does not find the method, @@ -237,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); return controller.GetNodes(id.ToInvariantString(), querystring); } @@ -267,7 +271,7 @@ namespace Umbraco.Web.BackOffice.Trees }); if (!(querystring is null)) { - foreach (var (key,value) in querystring) + foreach (var (key, value) in querystring) { routeData.Values[key] = value; } @@ -281,11 +285,11 @@ namespace Umbraco.Web.BackOffice.Trees var actionContext = new ActionContext(HttpContext, routeData, actionDescriptor); var proxyControllerContext = new ControllerContext(actionContext); - var controller = (TreeController) _controllerFactory.CreateController(proxyControllerContext); + var controller = (TreeController)_controllerFactory.CreateController(proxyControllerContext); - var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext); - if (!isAllowed) - throw new HttpResponseException(HttpStatusCode.Forbidden); + var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext); + if (!isAllowed) + throw new HttpResponseException(HttpStatusCode.Forbidden); return controller; } diff --git a/src/Umbraco.Web.Common/Controllers/IRenderController.cs b/src/Umbraco.Web.Common/Controllers/IRenderController.cs index 7534abc9b4..26a1286afa 100644 --- a/src/Umbraco.Web.Common/Controllers/IRenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/IRenderController.cs @@ -1,9 +1,11 @@ -namespace Umbraco.Web.Mvc +using Umbraco.Core.Composing; + +namespace Umbraco.Web.Common.Controllers { /// /// A marker interface to designate that a controller will be used for Umbraco front-end requests and/or route hijacking /// - public interface IRenderController + public interface IRenderController : IDiscoverable { } diff --git a/src/Umbraco.Web.Common/Controllers/IRenderMvcController.cs b/src/Umbraco.Web.Common/Controllers/IRenderMvcController.cs deleted file mode 100644 index 8727918bf4..0000000000 --- a/src/Umbraco.Web.Common/Controllers/IRenderMvcController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Umbraco.Core.Composing; -using Umbraco.Web.Models; - -namespace Umbraco.Web.Mvc -{ - /// - /// The interface that must be implemented for a controller to be designated to execute for route hijacking - /// - public interface IRenderMvcController : IRenderController, IDiscoverable - { - /// - /// The default action to render the front-end view - /// - /// - /// - IActionResult Index(ContentModel model); - } -} diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs deleted file mode 100644 index b95859ccbe..0000000000 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Umbraco.Web.Mvc; - -namespace Umbraco.Web.Common.Controllers -{ - public abstract class RenderController : Controller, IRenderController - { - - } -} diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoController.cs b/src/Umbraco.Web.Common/Controllers/UmbracoController.cs index 50ec620741..22bef0da69 100644 --- a/src/Umbraco.Web.Common/Controllers/UmbracoController.cs +++ b/src/Umbraco.Web.Common/Controllers/UmbracoController.cs @@ -1,14 +1,7 @@ -using System; +using System; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Services; -using Umbraco.Web.Security; -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Common.Controllers { /// /// Provides a base class for Umbraco controllers. diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index caf4132664..99a2b2aa3f 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog.Context; +using SixLabors.ImageSharp.Web.DependencyInjection; using Smidge; using Smidge.Nuglify; using StackExchange.Profiling; @@ -14,16 +15,61 @@ using Umbraco.Web.Common.Middleware; namespace Umbraco.Extensions { + /// + /// extensions for Umbraco + /// public static class ApplicationBuilderExtensions { + /// + /// Configures and use services required for using Umbraco + /// + public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + app.UseUmbracoCore(); + app.UseUmbracoRequestLogging(); + + // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed + // before endpoint routing middleware. + app.UseUmbracoRouting(); + + app.UseStatusCodePages(); + + // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. + // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? + app.UseImageSharp(); + app.UseStaticFiles(); + + // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one + // will execute after endpoint routing. The ordering of everything is quite important here, see + // https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-5.0 + // where we need to have UseAuthentication and UseAuthorization proceeding this call but before + // endpoints are defined. + app.UseRouting(); + app.UseRequestLocalization(); + app.UseAuthentication(); + app.UseAuthorization(); + + // Must be called after UseRouting and before UseEndpoints + app.UseSession(); + + // Must come after the above! + app.UseUmbracoInstaller(); + + return app; + } + /// /// Returns true if Umbraco is greater than /// - /// - /// public static bool UmbracoCanBoot(this IApplicationBuilder app) { - var runtime = app.ApplicationServices.GetRequiredService(); + IRuntime runtime = app.ApplicationServices.GetRequiredService(); + // can't continue if boot failed return runtime.State.Level > RuntimeLevel.BootFailed; } @@ -31,26 +77,30 @@ namespace Umbraco.Extensions /// /// Start Umbraco /// - /// - /// public static IApplicationBuilder UseUmbracoCore(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + return app; + } - var hostingEnvironment = app.ApplicationServices.GetRequiredService(); - AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data)); + IHostingEnvironment hostingEnvironment = app.ApplicationServices.GetRequiredService(); + AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data)); - var runtime = app.ApplicationServices.GetRequiredService(); + IRuntime runtime = app.ApplicationServices.GetRequiredService(); // Register a listener for application shutdown in order to terminate the runtime - var hostLifetime = app.ApplicationServices.GetRequiredService(); + IApplicationShutdownRegistry hostLifetime = app.ApplicationServices.GetRequiredService(); var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime); hostLifetime.RegisterObject(runtimeShutdown); // Register our global threadabort enricher for logging - var threadAbortEnricher = app.ApplicationServices.GetRequiredService(); + ThreadAbortExceptionEnricher threadAbortEnricher = app.ApplicationServices.GetRequiredService(); LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context StaticApplicationLogging.Initialize(app.ApplicationServices.GetRequiredService()); @@ -64,12 +114,16 @@ namespace Umbraco.Extensions /// /// Enables middlewares required to run Umbraco /// - /// - /// - // TODO: Could be internal or part of another call - this is a required system so should't be 'opt-in' + /// + /// Must occur before UseRouting + /// public static IApplicationBuilder UseUmbracoRouting(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + // TODO: This method could be internal or part of another call - this is a required system so should't be 'opt-in' + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } if (!app.UmbracoCanBoot()) { @@ -79,11 +133,6 @@ namespace Umbraco.Extensions { app.UseMiddleware(); app.UseMiddleware(); - - // TODO: Both of these need to be done before any endpoints but after UmbracoRequestMiddleware - // because they rely on an UmbracoContext. But should they be here? - app.UseAuthentication(); - app.UseAuthorization(); } return app; @@ -92,11 +141,12 @@ namespace Umbraco.Extensions /// /// Adds request based serilog enrichers to the LogContext for each request /// - /// - /// public static IApplicationBuilder UseUmbracoRequestLogging(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } if (!app.UmbracoCanBoot()) return app; @@ -108,13 +158,17 @@ namespace Umbraco.Extensions /// /// Enables runtime minification for Umbraco /// - /// - /// public static IApplicationBuilder UseUmbracoRuntimeMinification(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + return app; + } app.UseSmidge(); app.UseSmidgeNuglify(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs index 3350af756e..ac5d787911 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoInstallApplicationBuilderExtensions.cs @@ -1,30 +1,31 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Umbraco.Web.Common.Install; namespace Umbraco.Extensions { + /// + /// extensions for Umbraco installer + /// public static class UmbracoInstallApplicationBuilderExtensions { /// /// Enables the Umbraco installer /// - /// - /// public static IApplicationBuilder UseUmbracoInstaller(this IApplicationBuilder app) { - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + return app; + } app.UseEndpoints(endpoints => { - var installerRoutes = app.ApplicationServices.GetRequiredService(); + InstallAreaRoutes installerRoutes = app.ApplicationServices.GetRequiredService(); installerRoutes.CreateRoutes(endpoints); }); return app; } - - } - } diff --git a/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs index 360396fe04..c53c367689 100644 --- a/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/EnsurePartialViewMacroViewContextFilterAttribute.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Umbraco.Web.Common.Constants; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Mvc; namespace Umbraco.Web.Common.Filters { @@ -35,13 +34,18 @@ namespace Umbraco.Web.Common.Filters /// this ensures that any calls to GetPropertyValue with regards to RTE or Grid editors can still /// render any PartialViewMacro with a form and maintain ModelState /// - /// public override void OnActionExecuting(ActionExecutingContext context) { - if (!(context.Controller is Controller controller)) return; + if (!(context.Controller is Controller controller)) + { + return; + } - //ignore anything that is not IRenderController - if (!(controller is IRenderController)) return; + // ignore anything that is not IRenderController + if (!(controller is IRenderController)) + { + return; + } SetViewContext(context, controller); } @@ -54,10 +58,16 @@ namespace Umbraco.Web.Common.Filters /// The filter context. public override void OnResultExecuting(ResultExecutingContext context) { - if (!(context.Controller is Controller controller)) return; + if (!(context.Controller is Controller controller)) + { + return; + } - //ignore anything that is not IRenderController - if (!(controller is RenderController)) return; + // ignore anything that is not IRenderController + if (!(controller is IRenderController)) + { + return; + } SetViewContext(context, controller); } @@ -72,16 +82,13 @@ namespace Umbraco.Web.Common.Filters new StringWriter(), new HtmlHelperOptions()); - //set the special data token + // set the special data token context.RouteData.DataTokens[ViewConstants.DataTokenCurrentViewContext] = viewCtx; } private class DummyView : IView { - public Task RenderAsync(ViewContext context) - { - return Task.CompletedTask; - } + public Task RenderAsync(ViewContext context) => Task.CompletedTask; public string Path { get; } } diff --git a/src/Umbraco.Web.Common/Filters/PreRenderViewActionFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/PreRenderViewActionFilterAttribute.cs deleted file mode 100644 index 2ba58a8fd7..0000000000 --- a/src/Umbraco.Web.Common/Filters/PreRenderViewActionFilterAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Umbraco.Web.Common.Events; - -namespace Umbraco.Web.Common.Filters -{ - public class PreRenderViewActionFilterAttribute : ActionFilterAttribute - { - public override void OnActionExecuted(ActionExecutedContext context) - { - if (!(context.Controller is Controller umbController) || !(context.Result is ViewResult result)) - { - return; - } - - var model = result.Model; - if (model == null) - { - return; - } - - var args = new ActionExecutedEventArgs(umbController, model); - OnActionExecuted(args); - - if (args.Model != model) - { - result.ViewData.Model = args.Model; - } - - base.OnActionExecuted(context); - } - - - public static event EventHandler ActionExecuted; - - private static void OnActionExecuted(ActionExecutedEventArgs e) - { - var handler = ActionExecuted; - handler?.Invoke(null, e); - } - } -} diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index e274e479b7..6b5d305a64 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -1,15 +1,13 @@ -using System; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; -using Umbraco.Web.Common.Lifetime; using Umbraco.Core; -using Umbraco.Core.Logging; -using System.Threading; using Umbraco.Core.Cache; -using System.Collections.Generic; -using Umbraco.Core.Security; +using Umbraco.Core.Logging; +using Umbraco.Web.Common.Lifetime; namespace Umbraco.Web.Common.Middleware { @@ -28,6 +26,9 @@ namespace Umbraco.Web.Common.Middleware private readonly IRequestCache _requestCache; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; + /// + /// Initializes a new instance of the class. + /// public UmbracoRequestMiddleware( ILogger logger, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager, @@ -42,6 +43,7 @@ namespace Umbraco.Web.Common.Middleware _backofficeSecurityFactory = backofficeSecurityFactory; } + /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); @@ -52,16 +54,16 @@ namespace Umbraco.Web.Common.Middleware await next(context); return; } - _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext - var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext + UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); try { if (umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest) { - LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); - _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); + LogHttpRequest.TryGetCurrentHttpRequestId(out Guid httpRequestId, _requestCache); + _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); } try diff --git a/src/Umbraco.Web.Common/Routing/IAreaRoutes.cs b/src/Umbraco.Web.Common/Routing/IAreaRoutes.cs index b561abc4dd..b01f703016 100644 --- a/src/Umbraco.Web.Common/Routing/IAreaRoutes.cs +++ b/src/Umbraco.Web.Common/Routing/IAreaRoutes.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing; namespace Umbraco.Web.Common.Routing { @@ -11,6 +11,10 @@ namespace Umbraco.Web.Common.Routing // on individual ext methods. This would reduce the amount of code in Startup, but could also mean there's less control over startup // if someone wanted that. Maybe we can just have both. + /// + /// Create routes for an area + /// + /// The endpoint route builder void CreateRoutes(IEndpointRouteBuilder endpoints); } } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index d496aadfd3..6b57c8af9d 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -54,6 +54,7 @@ namespace Umbraco.Web.UI.NetCore } app.UseUmbraco(); + app.UseUmbracoBackOffice(); app.UseUmbracoWebsite(); } } diff --git a/src/Umbraco.Web.Website/Controllers/IUmbracoRenderingDefaults.cs b/src/Umbraco.Web.Website/Controllers/IUmbracoRenderingDefaults.cs new file mode 100644 index 0000000000..507b8c4a04 --- /dev/null +++ b/src/Umbraco.Web.Website/Controllers/IUmbracoRenderingDefaults.cs @@ -0,0 +1,15 @@ +using System; + +namespace Umbraco.Web.Website.Controllers +{ + /// + /// The defaults used for rendering Umbraco front-end pages + /// + public interface IUmbracoRenderingDefaults + { + /// + /// Gets the default umbraco render controller type + /// + Type DefaultControllerType { get; } + } +} diff --git a/src/Umbraco.Web.Website/Controllers/RenderMvcController.cs b/src/Umbraco.Web.Website/Controllers/RenderController.cs similarity index 78% rename from src/Umbraco.Web.Website/Controllers/RenderMvcController.cs rename to src/Umbraco.Web.Website/Controllers/RenderController.cs index 62ffb010ea..071560d860 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderMvcController.cs +++ b/src/Umbraco.Web.Website/Controllers/RenderController.cs @@ -3,25 +3,28 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Logging; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Filters; using Umbraco.Web.Models; using Umbraco.Web.Routing; -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Website.Controllers { /// /// Represents the default front-end rendering controller. /// - [PreRenderViewActionFilter] [TypeFilter(typeof(ModelBindingExceptionFilter))] - public class RenderMvcController : UmbracoController, IRenderMvcController + public class RenderController : UmbracoController, IRenderController { private IPublishedRequest _publishedRequest; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; - public RenderMvcController(ILogger logger, ICompositeViewEngine compositeViewEngine) + /// + /// Initializes a new instance of the class. + /// + public RenderController(ILogger logger, ICompositeViewEngine compositeViewEngine) { _logger = logger; _compositeViewEngine = compositeViewEngine; @@ -40,11 +43,15 @@ namespace Umbraco.Web.Mvc get { if (_publishedRequest != null) + { return _publishedRequest; + } + if (RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken) == false) { throw new InvalidOperationException("DataTokens must contain an 'umbraco-doc-request' key with a PublishedRequest object"); } + _publishedRequest = (IPublishedRequest)RouteData.DataTokens[Core.Constants.Web.PublishedDocumentRequestDataToken]; return _publishedRequest; } @@ -56,8 +63,11 @@ namespace Umbraco.Web.Mvc /// The view name. protected bool EnsurePhsyicalViewExists(string template) { - var result = _compositeViewEngine.FindView(ControllerContext, template, false); - if (result.View != null) return true; + ViewEngineResult result = _compositeViewEngine.FindView(ControllerContext, template, false); + if (result.View != null) + { + return true; + } _logger.LogWarning("No physical template file was found for template {Template}", template); return false; @@ -74,19 +84,17 @@ namespace Umbraco.Web.Mvc { var template = ControllerContext.RouteData.Values["action"].ToString(); if (EnsurePhsyicalViewExists(template) == false) + { throw new InvalidOperationException("No physical template file was found for template " + template); + } + return View(template, model); } /// /// The default action to render the front-end view. /// - /// - /// [RenderIndexActionSelector] - public virtual IActionResult Index(ContentModel model) - { - return CurrentTemplate(model); - } + public virtual IActionResult Index(ContentModel model) => CurrentTemplate(model); } } diff --git a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs b/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs index f1ea65e983..0027132c23 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs +++ b/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -namespace Umbraco.Web.Mvc +namespace Umbraco.Web.Website.Controllers { /// /// A custom ActionMethodSelector which will ensure that the RenderMvcController.Index(ContentModel model) action will be executed @@ -35,17 +35,18 @@ namespace Umbraco.Web.Mvc var baseType = controllerAction.ControllerTypeInfo.BaseType; //It's the same type, so this must be the Index action to use - if (currType == baseType) return true; + if (currType == baseType) + return true; - var actions = _controllerActionsCache.GetOrAdd(currType, type => - { - var actionDescriptors = routeContext.HttpContext.RequestServices - .GetRequiredService().ActionDescriptors.Items - .Where(x=>x is ControllerActionDescriptor).Cast() - .Where(x => x.ControllerTypeInfo == controllerAction.ControllerTypeInfo); + var actions = _controllerActionsCache.GetOrAdd(currType, type => + { + var actionDescriptors = routeContext.HttpContext.RequestServices + .GetRequiredService().ActionDescriptors.Items + .Where(x => x is ControllerActionDescriptor).Cast() + .Where(x => x.ControllerTypeInfo == controllerAction.ControllerTypeInfo); - return actionDescriptors; - }); + return actionDescriptors; + }); //If there are more than one Index action for this controller, then // this base class one should not be matched diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs index 4e0517754c..1d3e4c5626 100644 --- a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs +++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Specialized; using Microsoft.AspNetCore.Http; using Umbraco.Core; @@ -10,6 +10,7 @@ using Umbraco.Core.Services; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Routing; using Umbraco.Web.Website.ActionResults; +using Umbraco.Web.Website.Routing; namespace Umbraco.Web.Website.Controllers { diff --git a/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs new file mode 100644 index 0000000000..669e1835d4 --- /dev/null +++ b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs @@ -0,0 +1,13 @@ +using System; + +namespace Umbraco.Web.Website.Controllers +{ + /// + /// The defaults used for rendering Umbraco front-end pages + /// + public class UmbracoRenderingDefaults : IUmbracoRenderingDefaults + { + /// + public Type DefaultControllerType => typeof(RenderController); + } +} diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs index 72d12809e2..cbfa0c659e 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs @@ -1,14 +1,21 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; -using Umbraco.Core.DependencyInjection; +using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.Routing; using Umbraco.Web.Website.ViewEngines; namespace Umbraco.Extensions { + /// + /// extensions for umbraco front-end website + /// public static class UmbracoBuilderExtensions { + /// + /// Add services for the umbraco front-end website + /// public static IUmbracoBuilder AddUmbracoWebsite(this IUmbracoBuilder builder) { // Set the render & plugin view engines (Super complicated, but this allows us to use the IServiceCollection @@ -21,12 +28,14 @@ namespace Umbraco.Extensions // Wraps all existing view engines in a ProfilerViewEngine builder.Services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>(); - //TODO figure out if we need more to work on load balanced setups + // TODO figure out if we need more to work on load balanced setups builder.Services.AddDataProtection(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + return builder; } - } } diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs index ef99d67373..32d84088c1 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs @@ -6,28 +6,41 @@ using Umbraco.Web.Website.Routing; namespace Umbraco.Extensions { + /// + /// extensions for the umbraco front-end website + /// public static class UmbracoWebsiteApplicationBuilderExtensions { + /// + /// Sets up services and routes for the front-end umbraco website + /// public static IApplicationBuilder UseUmbracoWebsite(this IApplicationBuilder app) { - if (app == null) throw new ArgumentNullException(nameof(app)); + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } - if (!app.UmbracoCanBoot()) return app; + if (!app.UmbracoCanBoot()) + { + return app; + } - // Important we handle image manipulations before the static files, otherwise the querystring is just ignored. - // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? - app.UseImageSharp(); - app.UseStaticFiles(); - app.UseUmbracoNoContentPage(); + app.UseUmbracoRoutes(); return app; } - public static IApplicationBuilder UseUmbracoNoContentPage(this IApplicationBuilder app) + /// + /// Sets up routes for the umbraco front-end + /// + public static IApplicationBuilder UseUmbracoRoutes(this IApplicationBuilder app) { app.UseEndpoints(endpoints => { - var noContentRoutes = app.ApplicationServices.GetRequiredService(); + endpoints.MapDynamicControllerRoute("/{**slug}"); + + NoContentRoutes noContentRoutes = app.ApplicationServices.GetRequiredService(); noContentRoutes.CreateRoutes(endpoints); }); diff --git a/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs b/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs index 58885bcd96..f2f2e8dfe3 100644 --- a/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs +++ b/src/Umbraco.Web.Website/Routing/NoContentRoutes.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using Umbraco.Core; @@ -17,6 +17,9 @@ namespace Umbraco.Web.Website.Routing private readonly IRuntimeState _runtimeState; private readonly string _umbracoPathSegment; + /// + /// Initializes a new instance of the class. + /// public NoContentRoutes( IOptions globalSettings, IHostingEnvironment hostingEnvironment, @@ -26,6 +29,7 @@ namespace Umbraco.Web.Website.Routing _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); } + /// public void CreateRoutes(IEndpointRouteBuilder endpoints) { switch (_runtimeState.Level) @@ -35,13 +39,16 @@ namespace Umbraco.Web.Website.Routing 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( - // named consistently - Constants.Web.NoContentRouteName, + Constants.Web.NoContentRouteName, // named consistently _umbracoPathSegment + "/UmbNoContent", - new { controller = "RenderNoContent", action = "Index" } - ); - break; + new { controller = "RenderNoContent", action = "Index" }); + break; case RuntimeLevel.BootFailed: case RuntimeLevel.Unknown: case RuntimeLevel.Boot: diff --git a/src/Umbraco.Web.Website/RouteDefinition.cs b/src/Umbraco.Web.Website/Routing/RouteDefinition.cs similarity index 94% rename from src/Umbraco.Web.Website/RouteDefinition.cs rename to src/Umbraco.Web.Website/Routing/RouteDefinition.cs index 02eab6ae77..47206bd0c3 100644 --- a/src/Umbraco.Web.Website/RouteDefinition.cs +++ b/src/Umbraco.Web.Website/Routing/RouteDefinition.cs @@ -1,7 +1,7 @@ -using System; +using System; using Umbraco.Web.Routing; -namespace Umbraco.Web.Website +namespace Umbraco.Web.Website.Routing { /// /// Represents the data required to route to a specific controller/action during an Umbraco request diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs new file mode 100644 index 0000000000..a6582f03ae --- /dev/null +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -0,0 +1,230 @@ +using System; +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 Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Strings; +using Umbraco.Extensions; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Middleware; +using Umbraco.Web.Models; +using Umbraco.Web.Routing; +using Umbraco.Web.Website.Controllers; + +namespace Umbraco.Web.Website.Routing +{ + /// + /// The route value transformer for Umbraco front-end routes + /// + public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer + { + private readonly ILogger _logger; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUmbracoRenderingDefaults _renderingDefaults; + private readonly IShortStringHelper _shortStringHelper; + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private readonly IPublishedRouter _publishedRouter; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRouteValueTransformer( + ILogger logger, + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoRenderingDefaults renderingDefaults, + IShortStringHelper shortStringHelper, + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + IPublishedRouter publishedRouter) + { + _logger = logger; + _umbracoContextAccessor = umbracoContextAccessor; + _renderingDefaults = renderingDefaults; + _shortStringHelper = shortStringHelper; + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + _publishedRouter = publishedRouter; + } + + /// + public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) + { + if (_umbracoContextAccessor.UmbracoContext == null) + { + throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); + } + + bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext); + if (!routed) + { + // TODO: Deal with it not being routable, perhaps this should be an enum result? + } + + IPublishedRequest request = _umbracoContextAccessor.UmbracoContext.PublishedRequest; // This cannot be null here + + SetupRouteDataForRequest( + new ContentModel(request.PublishedContent), + request, + values); + + RouteDefinition routeDef = GetUmbracoRouteDefinition(httpContext, values, request); + values["controller"] = routeDef.ControllerName; + if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) + { + values["action"] = routeDef.ActionName; + } + + return await Task.FromResult(values); + } + + /// + /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views + /// + private void SetupRouteDataForRequest(ContentModel contentModel, IPublishedRequest frequest, RouteValueDictionary values) + { + // put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine + + // required for the ContentModelBinder and view engine. + // TODO: Are we sure, seems strange to need this in netcore + values.TryAdd(Constants.Web.UmbracoDataToken, contentModel); + + // required for RenderMvcController + // TODO: Are we sure, seems strange to need this in netcore + values.TryAdd(Constants.Web.PublishedDocumentRequestDataToken, frequest); + + // required for UmbracoViewPage + // TODO: Are we sure, seems strange to need this in netcore + values.TryAdd(Constants.Web.UmbracoContextDataToken, _umbracoContextAccessor.UmbracoContext); + } + + /// + /// Returns a object based on the current content request + /// + private RouteDefinition 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); + + // 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(), + ActionName = "Index", + 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.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); + def.ActionName = templateName; + } + + // check if there's a custom controller assigned, base on the document type alias. + Type controllerType = FindControllerType(request.PublishedContent.ContentType.Alias); + + // check if that controller exists + if (controllerType != null) + { + // ensure the controller is of type IRenderController 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 + { + _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 + values.TryAdd(Constants.Web.UmbracoRouteDefinitionDataToken, def); + + return def; + } + + private Type FindControllerType(string controllerName) + { + ControllerActionDescriptor descriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items + .Cast() + .First(x => + x.ControllerName.Equals(controllerName)); + + return descriptor?.ControllerTypeInfo; + } + + private bool RouteRequest(IUmbracoContext umbracoContext) + { + // TODO: I suspect one day this will be async + + // 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 + IPublishedRequest request = _publishedRouter.CreateRequest(umbracoContext); + umbracoContext.PublishedRequest = request; // TODO: This is ugly + bool prepared = _publishedRouter.PrepareRequest(request); + return prepared && request.HasPublishedContent; + + // // 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); + // } + } + } +} diff --git a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs deleted file mode 100644 index 091ace4d98..0000000000 --- a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs +++ /dev/null @@ -1,45 +0,0 @@ -// using System.Web.Mvc; -// using System.Web.Routing; -// -// namespace Umbraco.Web.Mvc -// { -// /// -// /// A controller factory for the render pipeline of Umbraco. This controller factory tries to create a controller with the supplied -// /// name, and falls back to UmbracoController if none was found. -// /// -// /// -// public class RenderControllerFactory : UmbracoControllerFactory -// { -// /// -// /// Determines whether this instance can handle the specified request. -// /// -// /// The request. -// /// true if this instance can handle the specified request; otherwise, false. -// /// -// public override bool CanHandle(RequestContext request) -// { -// var dataToken = request.RouteData.DataTokens["area"]; -// return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); -// } -// -// /// -// /// Creates the controller -// /// -// /// -// /// -// /// -// /// -// /// We always set the correct ActionInvoker on our custom created controller, this is very important for route hijacking! -// /// -// public override IController CreateController(RequestContext requestContext, string controllerName) -// { -// var instance = base.CreateController(requestContext, controllerName); -// if (instance is Controller controllerInstance) -// { -// //set the action invoker! -// controllerInstance.ActionInvoker = new RenderActionInvoker(); -// } -// return instance; -// } -// } -// } diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 163364602d..19e1b79c89 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -338,10 +338,10 @@ namespace Umbraco.Web.Mvc } - //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. + // 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) { @@ -370,7 +370,7 @@ namespace Umbraco.Web.Mvc routeDef = GetUmbracoRouteDefinition(requestContext, request); } - //no post values, just route to the controller/action required (local) + // no post values, just route to the controller/action required (local) requestContext.RouteData.Values["controller"] = routeDef.ControllerName; if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs index 45e759fd66..2977c49cb5 100644 --- a/src/Umbraco.Web/Mvc/RouteDefinition.cs +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -1,13 +1,9 @@ -using System; -using System.Web.Mvc; +using System; using Umbraco.Web.Routing; namespace Umbraco.Web.Mvc { - /// - /// Represents the data required to route to a specific controller/action during an Umbraco request - /// - /// Migrated already to .Net Core + // TODO: Migrated already to .Net Core public class RouteDefinition { public string ControllerName { get; set; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ad7dd86730..8867971657 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -241,7 +241,6 @@ - diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index e9a1cc1436..5c7468ce95 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Routing; using Microsoft.Extensions.Logging; @@ -157,9 +157,6 @@ namespace Umbraco.Web /// /// Checks the current request and ensures that it is routable based on the structure of the request and URI /// - /// - /// - /// internal Attempt EnsureUmbracoRoutablePage(IUmbracoContext context, HttpContextBase httpContext) { var uri = context.OriginalRequestUrl; @@ -186,8 +183,8 @@ namespace Umbraco.Web return Attempt.If(reason == EnsureRoutableOutcome.IsRoutable, reason); } - - + // TODO: Where should this execute in netcore? This will have to be a middleware + // executing before UseRouting so that it is done before any endpoint routing takes place. private bool EnsureRuntime(HttpContextBase httpContext, Uri uri) { var level = _runtime.Level; From 4b85f8eb20525884c11b8684b81ce7a850884409 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 9 Dec 2020 22:43:49 +1100 Subject: [PATCH 005/127] Big refactor or PublishedSnapshotService to split up so that there's a service and repository responsible for the data querying and persistence --- .../IPublishedSnapshotService.cs | 30 +- .../PublishedSnapshotServiceBase.cs | 43 +- .../Persistence/Dtos/ContentNuDto.cs | 2 +- .../Implement/ContentRepositoryBase.cs | 73 +- .../Implement/EntityRepository.cs | 35 +- ...fTIdTEntity.cs => EntityRepositoryBase.cs} | 187 ++-- .../Implement/NPocoRepositoryBase.cs | 61 +- .../Repositories/Implement/RepositoryBase.cs | 81 ++ .../ContentTypeServiceBaseOfTItemTService.cs | 23 +- .../Compose/ModelsBuilderComposer.cs | 3 +- .../ContentCache.cs | 2 +- .../DataSource/ContentNestedData.cs | 6 +- .../DataSource/DatabaseDataSource.cs | 325 ------ .../DataSource/IDataSource.cs | 77 -- .../DataSource/PropertyData.cs | 4 +- .../NuCacheComponent.cs | 18 - .../NuCacheComposer.cs | 26 +- .../Persistence/INuCacheContentRepository.cs | 46 + .../Persistence/INuCacheContentService.cs | 96 ++ .../Persistence/NuCacheContentRepository.cs | 735 ++++++++++++++ .../Persistence/NuCacheContentService.cs | 107 ++ .../PublishedSnapshotService.cs | 944 ++++-------------- .../PublishedSnapshotServiceEventHandler.cs | 187 ++++ .../Repositories/DocumentRepositoryTest.cs | 4 +- .../Repositories/EntityRepositoryTest.cs | 5 +- .../Repositories/MediaRepositoryTest.cs | 4 +- .../Repositories/RelationRepositoryTest.cs | 4 +- .../Repositories/TemplateRepositoryTest.cs | 4 +- .../Services/EntityServiceTests.cs | 9 +- .../PublishedContent/NuCacheChildrenTests.cs | 17 +- .../PublishedContent/NuCacheTests.cs | 17 +- .../Scoping/ScopedNuCacheTests.cs | 15 +- .../Testing/Objects/TestDataSource.cs | 50 +- .../ApplicationBuilderExtensions.cs | 12 + .../Middleware/UmbracoRequestMiddleware.cs | 2 +- .../Umbraco.Web.Common.csproj | 1 + .../Routing/UmbracoRouteValueTransformer.cs | 2 +- 37 files changed, 1766 insertions(+), 1491 deletions(-) rename src/Umbraco.Infrastructure/Persistence/Repositories/Implement/{RepositoryBaseOfTIdTEntity.cs => EntityRepositoryBase.cs} (63%) create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs delete mode 100644 src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs delete mode 100644 src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs delete mode 100644 src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 68b2367ce0..73ce858b52 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -11,8 +11,6 @@ namespace Umbraco.Web.PublishedCache /// public interface IPublishedSnapshotService : IDisposable { - #region PublishedSnapshot - /* Various places (such as Node) want to access the XML content, today as an XmlDocument * but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need * to find out how to get that navigator. @@ -25,6 +23,8 @@ namespace Umbraco.Web.PublishedCache * */ + void LoadCachesOnStartup(); + /// /// Creates a published snapshot. /// @@ -47,20 +47,26 @@ namespace Umbraco.Web.PublishedCache /// A value indicating whether the published snapshot has the proper environment to run. bool EnsureEnvironment(out IEnumerable errors); - #endregion - #region Rebuild /// /// Rebuilds internal caches (but does not reload). /// + /// The operation batch size to process the items + /// If not null will process content for the matching content types, if empty will process all content + /// If not null will process content for the matching media types, if empty will process all media + /// If not null will process content for the matching members types, if empty will process all members /// /// Forces the snapshot service to rebuild its internal caches. For instance, some caches /// may rely on a database table to store pre-serialized version of documents. /// This does *not* reload the caches. Caches need to be reloaded, for instance via /// RefreshAllPublishedSnapshot method. /// - void Rebuild(); + void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null); #endregion @@ -84,11 +90,11 @@ namespace Umbraco.Web.PublishedCache /// A preview token. /// /// Tells the caches that they should prepare any data that they would be keeping - /// in order to provide preview to a give user. In the Xml cache this means creating the Xml + /// in order to provide preview to a given user. In the Xml cache this means creating the Xml /// file, though other caches may do things differently. /// Does not handle the preview token storage (cookie, etc) that must be handled separately. /// - string EnterPreview(IUser user, int contentId); + string EnterPreview(IUser user, int contentId); // TODO: Remove this, it is not needed and is legacy from the XML cache /// /// Refreshes preview for a specified content. @@ -98,7 +104,7 @@ namespace Umbraco.Web.PublishedCache /// Tells the caches that they should update any data that they would be keeping /// in order to provide preview to a given user. In the Xml cache this means updating the Xml /// file, though other caches may do things differently. - void RefreshPreview(string previewToken, int contentId); + void RefreshPreview(string previewToken, int contentId); // TODO: Remove this, it is not needed and is legacy from the XML cache /// /// Exits preview for a specified preview token. @@ -110,7 +116,7 @@ namespace Umbraco.Web.PublishedCache /// though other caches may do things differently. /// Does not handle the preview token storage (cookie, etc) that must be handled separately. /// - void ExitPreview(string previewToken); + void ExitPreview(string previewToken); // TODO: Remove this, it is not needed and is legacy from the XML cache #endregion @@ -162,14 +168,12 @@ namespace Umbraco.Web.PublishedCache #endregion - #region Status - + // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus? string GetStatus(); + // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus? string StatusUrl { get; } - #endregion - void Collect(); } } diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs index 9c71bdc04b..6a8324cc27 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; @@ -8,44 +8,83 @@ namespace Umbraco.Web.PublishedCache { public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService { + /// + /// Initializes a new instance of the class. + /// protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) { PublishedSnapshotAccessor = publishedSnapshotAccessor; VariationContextAccessor = variationContextAccessor; } + /// public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } + + /// + /// Gets the + /// public IVariationContextAccessor VariationContextAccessor { get; } // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the // responsibility of the caller to manage what the 'current' facade is + + /// public abstract IPublishedSnapshot CreatePublishedSnapshot(string previewToken); protected IPublishedSnapshot CurrentPublishedSnapshot => PublishedSnapshotAccessor.PublishedSnapshot; + /// public abstract bool EnsureEnvironment(out IEnumerable errors); + /// public abstract string EnterPreview(IUser user, int contentId); + + /// public abstract void RefreshPreview(string previewToken, int contentId); + + /// public abstract void ExitPreview(string previewToken); + + /// public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); + + /// public abstract void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); + + /// public abstract void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); + + /// public abstract void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); + + /// public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads); - public virtual void Rebuild() + // TODO: Why is this virtual? + + /// + public virtual void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null) { } + /// public virtual void Dispose() { } + /// public abstract string GetStatus(); + /// public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html"; + /// public virtual void Collect() { } + + public abstract void LoadCachesOnStartup(); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs index a611186021..2aa450b7b9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs @@ -1,4 +1,4 @@ -using System.Data; +using System.Data; using NPoco; using Umbraco.Core.Persistence.DatabaseAnnotations; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 2533eaea8e..7ce363e446 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -767,8 +767,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region UnitOfWork Events - // TODO: The reason these events are in the repository is for legacy, the events should exist at the service - // level now since we can fire these events within the transaction... so move the events to service level + /* + * TODO: The reason these events are in the repository is for legacy, the events should exist at the service + * level now since we can fire these events within the transaction... + * The reason these events 'need' to fire in the transaction is to ensure data consistency with Nucache (currently + * the only thing that uses them). For example, if the transaction succeeds and NuCache listened to ContentService.Saved + * and then NuCache failed at persisting data after the trans completed, then NuCache would be out of sync. This way + * the entire trans is rolled back if NuCache files. That said, I'm unsure this is really required because there + * are other systems that rely on the "ed" (i.e. Saved) events like Examine which would be inconsistent if it failed + * too. I'm just not sure this is totally necessary especially. + * So these events can be moved to the service level. However, see the notes below, it seems the only event we + * really need is the ScopedEntityRefresh. The only tricky part with moving that to the service level is that the + * handlers of that event will need to deal with the data a little differently because it seems that the + * "Published" flag on the content item matters and this event is raised before that flag is switched. Weird. + * We have the ability with IContent to see if something "WasPublished", etc.. so i think we could still use that. + */ public class ScopedEntityEventArgs : EventArgs { @@ -784,6 +797,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public class ScopedVersionEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// public ScopedVersionEventArgs(IScope scope, int entityId, int versionId) { Scope = scope; @@ -791,13 +807,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement VersionId = versionId; } + /// + /// Gets the current + /// public IScope Scope { get; } + + /// + /// Gets the entity id + /// public int EntityId { get; } + + /// + /// Gets the version id + /// public int VersionId { get; } } + /// + /// Occurs when an is created or updated from within the (transaction) + /// public static event TypedEventHandler ScopedEntityRefresh; + + /// + /// Occurs when an is being deleted from within the (transaction) + /// + /// + /// TODO: This doesn't seem to be necessary at all, the service "Deleting" events for this would work just fine + /// since they are raised before the item is actually deleted just like this event. + /// public static event TypedEventHandler ScopeEntityRemove; + + /// + /// Occurs when a version for an is being deleted from within the (transaction) + /// + /// + /// TODO: This doesn't seem to be necessary at all, the service "DeletingVersions" events for this would work just fine + /// since they are raised before the item is actually deleted just like this event. + /// public static event TypedEventHandler ScopeVersionRemove; // used by tests to clear events @@ -808,20 +854,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ScopeVersionRemove = null; } + /// + /// Raises the event + /// protected void OnUowRefreshedEntity(ScopedEntityEventArgs args) - { - ScopedEntityRefresh.RaiseEvent(args, This); - } + => ScopedEntityRefresh.RaiseEvent(args, This); + /// + /// Raises the event + /// protected void OnUowRemovingEntity(ScopedEntityEventArgs args) - { - ScopeEntityRemove.RaiseEvent(args, This); - } + => ScopeEntityRemove.RaiseEvent(args, This); + /// + /// Raises the event + /// protected void OnUowRemovingVersion(ScopedVersionEventArgs args) - { - ScopeVersionRemove.RaiseEvent(args, This); - } + => ScopeVersionRemove.RaiseEvent(args, This); #endregion diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs index 61ced57149..41f6a065d4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs @@ -1,15 +1,16 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NPoco; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Scoping; -using static Umbraco.Core.Persistence.SqlExtensionsStatics; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.SqlExtensionsStatics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -20,21 +21,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// Limited to objects that have a corresponding node (in umbracoNode table). /// Returns objects, i.e. lightweight representation of entities. /// - internal class EntityRepository : IEntityRepository + internal class EntityRepository : RepositoryBase, IEntityRepository { - private readonly IScopeAccessor _scopeAccessor; - - public EntityRepository(IScopeAccessor scopeAccessor) + public EntityRepository(IScopeAccessor scopeAccessor, AppCaches appCaches) + : base(scopeAccessor, appCaches) { - _scopeAccessor = scopeAccessor; } - protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database; - protected Sql Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql(); - protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax; - #region Repository - + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { @@ -49,17 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media); var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member); - var sql = GetBaseWhere(isContent, isMedia, isMember, false, s => + Sql sql = GetBaseWhere(isContent, isMedia, isMember, false, s => { sqlCustomization?.Invoke(s); if (filter != null) { - foreach (var filterClause in filter.GetWhereClauses()) + foreach (Tuple filterClause in filter.GetWhereClauses()) + { s.Where(filterClause.Item1, filterClause.Item2); + } } - - }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); @@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently - //no matter what we always must have node id ordered at the end + // no matter what we always must have node id ordered at the end sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names @@ -102,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia, bool isMember) { - //isContent is going to return a 1:M result now with the variants so we need to do different things + // isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { var cdtos = Database.Fetch(sql); @@ -164,7 +159,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia, bool isMember) { - //isContent is going to return a 1:M result now with the variants so we need to do different things + // isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { var cdtos = Database.Fetch(sql); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs similarity index 63% rename from src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs rename to src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index a9e8f4bb16..d7f6e63c55 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Querying; @@ -9,53 +10,38 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { + /// - /// Provides a base class to all repositories. + /// Provides a base class to all based repositories. /// - /// The type of the entity managed by this repository. /// The type of the entity's unique identifier. - public abstract class RepositoryBase : IReadWriteQueryRepository + /// The type of the entity managed by this repository. + public abstract class EntityRepositoryBase : RepositoryBase, IReadWriteQueryRepository where TEntity : class, IEntity { private IRepositoryCachePolicy _cachePolicy; - - protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger) - { - ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); - } - - protected ILogger> Logger { get; } - - protected AppCaches AppCaches { get; } - - protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); - - protected IScopeAccessor ScopeAccessor { get; } - - protected IScope AmbientScope - { - get - { - var scope = ScopeAccessor.AmbientScope; - if (scope == null) - throw new InvalidOperationException("Cannot run a repository without an ambient scope."); - return scope; - } - } - - #region Static Queries - private IQuery _hasIdQuery; + private static RepositoryCachePolicyOptions s_defaultOptions; - #endregion - - protected virtual TId GetEntityId(TEntity entity) + /// + /// Initializes a new instance of the class. + /// + protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger) + : base(scopeAccessor, appCaches) { - return (TId) (object) entity.Id; + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + /// + /// Gets the logger + /// + protected ILogger> Logger { get; } + + /// + /// Gets the isolated cache for the + /// + protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate(); + /// /// Gets the isolated cache. /// @@ -78,30 +64,34 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } - // ReSharper disable once StaticMemberInGenericType - private static RepositoryCachePolicyOptions _defaultOptions; - // ReSharper disable once InconsistentNaming - protected virtual RepositoryCachePolicyOptions DefaultOptions - { - get - { - return _defaultOptions ?? (_defaultOptions + /// + /// Gets the default + /// + protected virtual RepositoryCachePolicyOptions DefaultOptions => s_defaultOptions ?? (s_defaultOptions = new RepositoryCachePolicyOptions(() => { // get count of all entities of current type (TEntity) to ensure cached result is correct // create query once if it is needed (no need for locking here) - query is static! - var query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); + IQuery query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0)); return PerformCount(query); })); - } - } + /// + /// Gets the node object type for the repository's entity + /// + protected abstract Guid NodeObjectTypeId { get; } + + /// + /// Gets the repository cache policy + /// protected IRepositoryCachePolicy CachePolicy { get { if (AppCaches == AppCaches.NoCache) + { return NoCacheRepositoryCachePolicy.Instance; + } // create the cache policy using IsolatedCache which is either global // or scoped depending on the repository cache mode for the current scope @@ -122,66 +112,101 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } + /// + /// Get the entity id for the + /// + protected virtual TId GetEntityId(TEntity entity) + => (TId)(object)entity.Id; + + /// + /// Create the repository cache policy + /// protected virtual IRepositoryCachePolicy CreateCachePolicy() - { - return new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); - } + => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); /// /// Adds or Updates an entity of type TEntity /// /// This method is backed by an cache - /// public virtual void Save(TEntity entity) { if (entity.HasIdentity == false) + { CachePolicy.Create(entity, PersistNewItem); + } else + { CachePolicy.Update(entity, PersistUpdatedItem); + } } /// /// Deletes the passed in entity /// - /// public virtual void Delete(TEntity entity) - { - CachePolicy.Delete(entity, PersistDeletedItem); - } + => CachePolicy.Delete(entity, PersistDeletedItem); protected abstract TEntity PerformGet(TId id); + protected abstract IEnumerable PerformGetAll(params TId[] ids); + protected abstract IEnumerable PerformGetByQuery(IQuery query); - protected abstract bool PerformExists(TId id); - protected abstract int PerformCount(IQuery query); protected abstract void PersistNewItem(TEntity item); - protected abstract void PersistUpdatedItem(TEntity item); - protected abstract void PersistDeletedItem(TEntity item); + protected abstract void PersistUpdatedItem(TEntity item); + + protected abstract Sql GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere + + protected abstract string GetBaseWhereClause(); + + protected abstract IEnumerable GetDeleteClauses(); + + protected virtual bool PerformExists(TId id) + { + var sql = GetBaseQuery(true); + sql.Where(GetBaseWhereClause(), new { id = id }); + var count = Database.ExecuteScalar(sql); + return count == 1; + } + + protected virtual int PerformCount(IQuery query) + { + var sqlClause = GetBaseQuery(true); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + return Database.ExecuteScalar(sql); + } + + protected virtual void PersistDeletedItem(TEntity entity) + { + var deletes = GetDeleteClauses(); + foreach (var delete in deletes) + { + Database.Execute(delete, new { id = GetEntityId(entity) }); + } + + entity.DeleteDate = DateTime.Now; + } /// /// Gets an entity by the passed in Id utilizing the repository's cache policy /// - /// - /// public TEntity Get(TId id) - { - return CachePolicy.Get(id, PerformGet, PerformGetAll); - } + => CachePolicy.Get(id, PerformGet, PerformGetAll); /// /// Gets all entities of type TEntity or a list according to the passed in Ids /// - /// - /// public IEnumerable GetMany(params TId[] ids) { - //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries + // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries ids = ids.Distinct() - //don't query by anything that is a default of T (like a zero) + + // don't query by anything that is a default of T (like a zero) // TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids - //.Where(x => Equals(x, default(TId)) == false) + // .Where(x => Equals(x, default(TId)) == false) .ToArray(); // can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities, @@ -197,39 +222,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll)); } + return entities; } /// /// Gets a list of entities by the passed in query /// - /// - /// public IEnumerable Get(IQuery query) - { - return PerformGetByQuery(query) - //ensure we don't include any null refs in the returned collection! - .WhereNotNull(); - } + => PerformGetByQuery(query) + .WhereNotNull(); // ensure we don't include any null refs in the returned collection! /// /// Returns a boolean indicating whether an entity with the passed Id exists /// - /// - /// public bool Exists(TId id) - { - return CachePolicy.Exists(id, PerformExists, PerformGetAll); - } + => CachePolicy.Exists(id, PerformExists, PerformGetAll); /// /// Returns an integer with the count of entities found with the passed in query /// - /// - /// public int Count(IQuery query) - { - return PerformCount(query); - } + => PerformCount(query); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs index 392e7bdf1f..ff820da577 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Collections.Generic; -using NPoco; using Microsoft.Extensions.Logging; +using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Querying; @@ -13,9 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represent an abstract Repository for NPoco based repositories /// - /// - /// - public abstract class NPocoRepositoryBase : RepositoryBase + public abstract class NPocoRepositoryBase : EntityRepositoryBase where TEntity : class, IEntity { /// @@ -24,58 +22,5 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) : base(scopeAccessor, cache, logger) { } - - /// - /// Gets the repository's database. - /// - protected IUmbracoDatabase Database => AmbientScope.Database; - - /// - /// Gets the Sql context. - /// - protected ISqlContext SqlContext=> AmbientScope.SqlContext; - - protected Sql Sql() => SqlContext.Sql(); - protected Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args); - protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; - protected IQuery Query() => SqlContext.Query(); - - #region Abstract Methods - - protected abstract Sql GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere - protected abstract string GetBaseWhereClause(); - protected abstract IEnumerable GetDeleteClauses(); - protected abstract Guid NodeObjectTypeId { get; } - protected abstract override void PersistNewItem(TEntity entity); - protected abstract override void PersistUpdatedItem(TEntity entity); - - #endregion - - protected override bool PerformExists(TId id) - { - var sql = GetBaseQuery(true); - sql.Where(GetBaseWhereClause(), new { id = id}); - var count = Database.ExecuteScalar(sql); - return count == 1; - } - - protected override int PerformCount(IQuery query) - { - var sqlClause = GetBaseQuery(true); - var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); - - return Database.ExecuteScalar(sql); - } - - protected override void PersistDeletedItem(TEntity entity) - { - var deletes = GetDeleteClauses(); - foreach (var delete in deletes) - { - Database.Execute(delete, new { id = GetEntityId(entity) }); - } - entity.DeleteDate = DateTime.Now; - } } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs new file mode 100644 index 0000000000..8b9d8fe77c --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs @@ -0,0 +1,81 @@ +using System; +using NPoco; +using Umbraco.Core.Cache; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Scoping; + +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + /// + /// Base repository class for all instances + /// + public abstract class RepositoryBase : IRepository + { + /// + /// Initializes a new instance of the class. + /// + protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches) + { + ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); + AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches)); + } + + /// + /// Gets the + /// + protected AppCaches AppCaches { get; } + + /// + /// Gets the + /// + protected IScopeAccessor ScopeAccessor { get; } + + /// + /// Gets the AmbientScope + /// + protected IScope AmbientScope + { + get + { + IScope scope = ScopeAccessor.AmbientScope; + if (scope == null) + { + throw new InvalidOperationException("Cannot run a repository without an ambient scope."); + } + + return scope; + } + } + + /// + /// Gets the repository's database. + /// + protected IUmbracoDatabase Database => AmbientScope.Database; + + /// + /// Gets the Sql context. + /// + protected ISqlContext SqlContext => AmbientScope.SqlContext; + + /// + /// Gets the + /// + protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; + + /// + /// Creates an expression + /// + protected Sql Sql() => SqlContext.Sql(); + + /// + /// Creates a expression + /// + protected Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args); + + /// + /// Creates a new query expression + /// + protected IQuery Query() => SqlContext.Query(); + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index 3241fa9d0e..1bdd00f576 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Scoping; @@ -16,10 +16,23 @@ namespace Umbraco.Core.Services.Implement protected abstract TService This { get; } - // that one must be dispatched + /// + /// Raised when a is changed + /// + /// + /// This event is dispatched after the trans is completed. Used by event refreshers. + /// public static event TypedEventHandler.EventArgs> Changed; - // that one is always immediate (transactional) + /// + /// Occurs when an is created or updated from within the (transaction) + /// + /// + /// The purpose of this event being raised within the transaction is so that listeners can perform database + /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong + /// the entire transaction can be rolled back. This is used by Nucache. + /// TODO: See remarks in ContentRepositoryBase about these types of events. Not sure we need/want them. + /// public static event TypedEventHandler.EventArgs> ScopedRefreshedEntity; // used by tests to clear events @@ -45,9 +58,11 @@ namespace Umbraco.Core.Services.Implement scope.Events.Dispatch(Changed, This, args, nameof(Changed)); } + /// + /// Raises the event during the (transaction) + /// protected void OnUowRefreshedEntity(ContentTypeChange.EventArgs args) { - // that one is always immediate (not dispatched, transactional) ScopedRefreshedEntity.RaiseEvent(args, This); } diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index 81869a9261..344e1b025a 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Configuration; using Umbraco.Core; @@ -11,6 +11,7 @@ using Umbraco.Core.DependencyInjection; namespace Umbraco.ModelsBuilder.Embedded.Compose { + // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there [ComposeBefore(typeof(IPublishedCacheComposer))] public sealed class ModelsBuilderComposer : ICoreComposer { diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs index 7cdc7e72e1..f1c4f1d85b 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs index ec5424ad9a..98d423680b 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System.Collections.Generic; using Umbraco.Core.Serialization; @@ -9,7 +9,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource /// internal class ContentNestedData { - //dont serialize empty properties + // dont serialize empty properties [JsonProperty("pd")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary PropertyData { get; set; } @@ -21,7 +21,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource [JsonProperty("us")] public string UrlSegment { get; set; } - //Legacy properties used to deserialize existing nucache db entries + // Legacy properties used to deserialize existing nucache db entries [JsonProperty("properties")] [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] private Dictionary LegacyPropertyData { set { PropertyData = value; } } diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs deleted file mode 100644 index bdcd8fe3e3..0000000000 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using NPoco; -using Umbraco.Core; -using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Scoping; -using Umbraco.Core.Serialization; -using static Umbraco.Core.Persistence.SqlExtensionsStatics; - -namespace Umbraco.Web.PublishedCache.NuCache.DataSource -{ - // TODO: use SqlTemplate for these queries else it's going to be horribly slow! - - // provides efficient database access for NuCache - internal class DatabaseDataSource : IDataSource - { - private const int PageSize = 500; - - private readonly ILogger _logger; - - public DatabaseDataSource(ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - // we want arrays, we want them all loaded, not an enumerable - - private Sql ContentSourcesSelect(IScope scope, Func, Sql> joins = null) - { - var sql = scope.SqlContext.Sql() - - .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), - x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), - x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) - .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) - .AndSelect(x => Alias(x.Published, "Published"), x => Alias(x.Edited, "Edited")) - - .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) - .AndSelect(x => Alias(x.TemplateId, "EditTemplateId")) - - .AndSelect("pcver", x => Alias(x.Id, "PublishedVersionId"), x => Alias(x.Text, "PubName"), x => Alias(x.VersionDate, "PubVersionDate"), x => Alias(x.UserId, "PubWriterId")) - .AndSelect("pdver", x => Alias(x.TemplateId, "PubTemplateId")) - - .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) - .AndSelect("nuPub", x => Alias(x.Data, "PubData")) - - .From(); - - if (joins != null) - sql = joins(sql); - - sql = sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .InnerJoin().On((left, right) => left.Id == right.Id) - - .LeftJoin(j => - j.InnerJoin("pdver").On((left, right) => left.Id == right.Id && right.Published, "pcver", "pdver"), "pcver") - .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcver") - - .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit") - .LeftJoin("nuPub").On((left, right) => left.NodeId == right.NodeId && right.Published, aliasRight: "nuPub"); - - return sql; - } - - public ContentNodeKit GetContentSource(IScope scope, int id) - { - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto); - } - - public IEnumerable GetAllContentSources(IScope scope) - { - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateContentNodeKit(row); - } - - public IEnumerable GetBranchContentSources(IScope scope, int id) - { - var syntax = scope.SqlContext.SqlSyntax; - var sql = ContentSourcesSelect(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .Where(x => x.NodeId == id, "x") - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateContentNodeKit(row); - } - - public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) - { - if (!ids.Any()) yield break; - - var sql = ContentSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) - .WhereIn(x => x.ContentTypeId, ids) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateContentNodeKit(row); - } - - private Sql MediaSourcesSelect(IScope scope, Func, Sql> joins = null) - { - var sql = scope.SqlContext.Sql() - - .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), - x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), - x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) - .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) - .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) - .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) - .From(); - - if (joins != null) - sql = joins(sql); - - sql = sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); - - return sql; - } - - public ContentNodeKit GetMediaSource(IScope scope, int id) - { - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && x.NodeId == id && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - var dto = scope.Database.Fetch(sql).FirstOrDefault(); - return dto == null ? new ContentNodeKit() : CreateMediaNodeKit(dto); - } - - public IEnumerable GetAllMediaSources(IScope scope) - { - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateMediaNodeKit(row); - } - - public IEnumerable GetBranchMediaSources(IScope scope, int id) - { - var syntax = scope.SqlContext.SqlSyntax; - var sql = MediaSourcesSelect(scope, - s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .Where(x => x.NodeId == id, "x") - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateMediaNodeKit(row); - } - - public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) - { - if (!ids.Any()) yield break; - - var sql = MediaSourcesSelect(scope) - .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) - .WhereIn(x => x.ContentTypeId, ids) - .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); - - // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. - // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. - - foreach (var row in scope.Database.QueryPaged(PageSize, sql)) - yield return CreateMediaNodeKit(row); - } - - private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto) - { - ContentData d = null; - ContentData p = null; - - if (dto.Edited) - { - if (dto.EditData == null) - { - if (Debugger.IsAttached) - throw new InvalidOperationException("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); - _logger.LogWarning("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id); - } - else - { - var nested = DeserializeNestedData(dto.EditData); - - d = new ContentData - { - Name = dto.EditName, - Published = false, - TemplateId = dto.EditTemplateId, - VersionId = dto.VersionId, - VersionDate = dto.EditVersionDate, - WriterId = dto.EditWriterId, - Properties = nested.PropertyData, - CultureInfos = nested.CultureData, - UrlSegment = nested.UrlSegment - }; - } - } - - if (dto.Published) - { - if (dto.PubData == null) - { - if (Debugger.IsAttached) - throw new InvalidOperationException("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); - _logger.LogWarning("Missing cmsContentNu published content for node {NodeId}, consider rebuilding.", dto.Id); - } - else - { - var nested = DeserializeNestedData(dto.PubData); - - p = new ContentData - { - Name = dto.PubName, - UrlSegment = nested.UrlSegment, - Published = true, - TemplateId = dto.PubTemplateId, - VersionId = dto.VersionId, - VersionDate = dto.PubVersionDate, - WriterId = dto.PubWriterId, - Properties = nested.PropertyData, - CultureInfos = nested.CultureData - }; - } - } - - var n = new ContentNode(dto.Id, dto.Uid, - dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); - - var s = new ContentNodeKit - { - Node = n, - ContentTypeId = dto.ContentTypeId, - DraftData = d, - PublishedData = p - }; - - return s; - } - - private static ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto) - { - if (dto.EditData == null) - throw new InvalidOperationException("No data for media " + dto.Id); - - var nested = DeserializeNestedData(dto.EditData); - - var p = new ContentData - { - Name = dto.EditName, - Published = true, - TemplateId = -1, - VersionId = dto.VersionId, - VersionDate = dto.EditVersionDate, - WriterId = dto.CreatorId, // what-else? - Properties = nested.PropertyData, - CultureInfos = nested.CultureData - }; - - var n = new ContentNode(dto.Id, dto.Uid, - dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); - - var s = new ContentNodeKit - { - Node = n, - ContentTypeId = dto.ContentTypeId, - PublishedData = p - }; - - return s; - } - - private static ContentNestedData DeserializeNestedData(string data) - { - // by default JsonConvert will deserialize our numeric values as Int64 - // which is bad, because they were Int32 in the database - take care - - var settings = new JsonSerializerSettings - { - Converters = new List { new ForceInt32Converter() } - }; - - return JsonConvert.DeserializeObject(data, settings); - } - } -} diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs deleted file mode 100644 index ec3ab38e84..0000000000 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using Umbraco.Core.Scoping; - -namespace Umbraco.Web.PublishedCache.NuCache.DataSource -{ - /// - /// Defines a data source for NuCache. - /// - internal interface IDataSource - { - //TODO: For these required sort orders, would sorting on Path 'just work'? - - ContentNodeKit GetContentSource(IScope scope, int id); - - /// - /// Returns all content ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetAllContentSources(IScope scope); - - /// - /// Returns branch for content ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetBranchContentSources(IScope scope, int id); - - /// - /// Returns content by Ids ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids); - - ContentNodeKit GetMediaSource(IScope scope, int id); - - /// - /// Returns all media ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetAllMediaSources(IScope scope); - - /// - /// Returns branch for media ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetBranchMediaSources(IScope scope, int id); // must order by level, sortOrder - - /// - /// Returns media by Ids ordered by level + sortOrder - /// - /// - /// - /// - /// MUST be ordered by level + parentId + sortOrder! - /// - IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids); - } -} diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs index cf7ab95360..42e038c744 100644 --- a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs +++ b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using Newtonsoft.Json; @@ -29,7 +29,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource public object Value { get; set; } - //Legacy properties used to deserialize existing nucache db entries + // Legacy properties used to deserialize existing nucache db entries [JsonProperty("culture")] private string LegacyCulture { diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs b/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs deleted file mode 100644 index fba133a2aa..0000000000 --- a/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Core.Composing; - -namespace Umbraco.Web.PublishedCache.NuCache -{ - public sealed class NuCacheComponent : IComponent - { - public NuCacheComponent(IPublishedSnapshotService service) - { - // nothing - this just ensures that the service is created at boot time - } - - public void Initialize() - { } - - public void Terminate() - { } - } -} diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs index 17e707effd..5e618d361c 100644 --- a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs +++ b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs @@ -1,23 +1,26 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Composing; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Infrastructure.PublishedCache; -using Umbraco.Web.PublishedCache.NuCache.DataSource; +using Umbraco.Infrastructure.PublishedCache.Persistence; namespace Umbraco.Web.PublishedCache.NuCache { - public class NuCacheComposer : ComponentComposer, IPublishedCacheComposer + // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there, + // see comment in ModelsBuilderComposer which requires this weird IPublishedCacheComposer + public class NuCacheComposer : IComposer, IPublishedCacheComposer { - public override void Compose(IUmbracoBuilder builder) + /// + public void Compose(IUmbracoBuilder builder) { - base.Compose(builder); - // register the NuCache database data source - builder.Services.AddTransient(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // register the NuCache published snapshot service // must register default options, required in the service ctor @@ -26,21 +29,22 @@ namespace Umbraco.Web.PublishedCache.NuCache // replace this service since we want to improve the content/media // mapping lookups if we are using nucache. + // TODO: Gotta wonder how much this does actually improve perf? It's a lot of weird code to make this happen so hope it's worth it builder.Services.AddUnique(factory => { var idkSvc = new IdKeyMap(factory.GetRequiredService()); - var publishedSnapshotService = factory.GetRequiredService() as PublishedSnapshotService; - if (publishedSnapshotService != null) + if (factory.GetRequiredService() is PublishedSnapshotService publishedSnapshotService) { idkSvc.SetMapper(UmbracoObjectTypes.Document, id => publishedSnapshotService.GetDocumentUid(id), uid => publishedSnapshotService.GetDocumentId(uid)); idkSvc.SetMapper(UmbracoObjectTypes.Media, id => publishedSnapshotService.GetMediaUid(id), uid => publishedSnapshotService.GetMediaId(uid)); } + return idkSvc; }); // add the NuCache health check (hidden from type finder) // TODO: no NuCache health check yet - //composition.HealthChecks().Add(); + // composition.HealthChecks().Add(); } } } diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs new file mode 100644 index 0000000000..7bce5e138c --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Web.PublishedCache.NuCache; + +namespace Umbraco.Infrastructure.PublishedCache.Persistence +{ + public interface INuCacheContentRepository + { + void DeleteContentItem(IContentBase item); + IEnumerable GetAllContentSources(); + IEnumerable GetAllMediaSources(); + IEnumerable GetBranchContentSources(int id); + IEnumerable GetBranchMediaSources(int id); + ContentNodeKit GetContentSource(int id); + ContentNodeKit GetMediaSource(int id); + IEnumerable GetTypeContentSources(IEnumerable ids); + IEnumerable GetTypeMediaSources(IEnumerable ids); + + /// + /// Refreshes the nucache database row for the + /// + void RefreshContent(IContent content); + + /// + /// Refreshes the nucache database row for the (used for media/members) + /// + void RefreshEntity(IContentBase content); + + /// + /// Rebuilds the caches for content, media and/or members based on the content type ids specified + /// + /// The operation batch size to process the items + /// If not null will process content for the matching content types, if empty will process all content + /// If not null will process content for the matching media types, if empty will process all media + /// If not null will process content for the matching members types, if empty will process all members + void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null); + + bool VerifyContentDbCache(); + bool VerifyMediaDbCache(); + bool VerifyMemberDbCache(); + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs new file mode 100644 index 0000000000..0ac3939742 --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Web.PublishedCache.NuCache; + +namespace Umbraco.Infrastructure.PublishedCache.Persistence +{ + /// + /// Defines a data source for NuCache. + /// + public interface INuCacheContentService + { + // TODO: For these required sort orders, would sorting on Path 'just work'? + ContentNodeKit GetContentSource(int id); + + /// + /// Returns all content ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetAllContentSources(); + + /// + /// Returns branch for content ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetBranchContentSources(int id); + + /// + /// Returns content by Ids ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetTypeContentSources(IEnumerable ids); + + ContentNodeKit GetMediaSource(int id); + + /// + /// Returns all media ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetAllMediaSources(); + + /// + /// Returns branch for media ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetBranchMediaSources(int id); // must order by level, sortOrder + + /// + /// Returns media by Ids ordered by level + sortOrder + /// + /// + /// MUST be ordered by level + parentId + sortOrder! + /// + IEnumerable GetTypeMediaSources(IEnumerable ids); + + void DeleteContentItem(IContentBase item); + + /// + /// Refreshes the nucache database row for the + /// + void RefreshContent(IContent content); + + /// + /// Refreshes the nucache database row for the (used for media/members) + /// + void RefreshEntity(IContentBase content); + + /// + /// Rebuilds the caches for content, media and/or members based on the content type ids specified + /// + /// The operation batch size to process the items + /// If not null will process content for the matching content types, if empty will process all content + /// If not null will process content for the matching media types, if empty will process all media + /// If not null will process content for the matching members types, if empty will process all members + void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null); + + bool VerifyContentDbCache(); + + bool VerifyMediaDbCache(); + + bool VerifyMemberDbCache(); + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs new file mode 100644 index 0000000000..60370e9be8 --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs @@ -0,0 +1,735 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using NPoco; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Core.Serialization; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.PublishedCache.NuCache; +using Umbraco.Web.PublishedCache.NuCache.DataSource; +using static Umbraco.Core.Persistence.SqlExtensionsStatics; + +namespace Umbraco.Infrastructure.PublishedCache.Persistence +{ + public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepository + { + private const int PageSize = 500; + private readonly ILogger _logger; + private readonly IMemberRepository _memberRepository; + private readonly IDocumentRepository _documentRepository; + private readonly IMediaRepository _mediaRepository; + private readonly IShortStringHelper _shortStringHelper; + private readonly UrlSegmentProviderCollection _urlSegmentProviders; + + /// + /// Initializes a new instance of the class. + /// + public NuCacheContentRepository( + IScopeAccessor scopeAccessor, + AppCaches appCaches, + ILogger logger, + IMemberRepository memberRepository, + IDocumentRepository documentRepository, + IMediaRepository mediaRepository, + IShortStringHelper shortStringHelper, + UrlSegmentProviderCollection urlSegmentProviders) + : base(scopeAccessor, appCaches) + { + _logger = logger; + _memberRepository = memberRepository; + _documentRepository = documentRepository; + _mediaRepository = mediaRepository; + _shortStringHelper = shortStringHelper; + _urlSegmentProviders = urlSegmentProviders; + } + + public void DeleteContentItem(IContentBase item) + => Database.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id", new { id = item.Id }); + + public void RefreshContent(IContent content) + { + // always refresh the edited data + OnRepositoryRefreshed(content, false); + + if (content.PublishedState == PublishedState.Unpublishing) + { + // if unpublishing, remove published data from table + Database.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id AND published=1", new { id = content.Id }); + } + else if (content.PublishedState == PublishedState.Publishing) + { + // if publishing, refresh the published data + OnRepositoryRefreshed(content, true); + } + } + + public void RefreshEntity(IContentBase content) + => OnRepositoryRefreshed(content, false); + + private void OnRepositoryRefreshed(IContentBase content, bool published) + { + // use a custom SQL to update row version on each update + // db.InsertOrUpdate(dto); + ContentNuDto dto = GetDto(content, published); + + Database.InsertOrUpdate( + dto, + "SET data=@data, rv=rv+1 WHERE nodeId=@id AND published=@published", + new + { + data = dto.Data, + id = dto.NodeId, + published = dto.Published + }); + } + + public void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null) + { + if (contentTypeIds != null) + { + RebuildContentDbCache(groupSize, contentTypeIds); + } + + if (mediaTypeIds != null) + { + RebuildContentDbCache(groupSize, mediaTypeIds); + } + + if (memberTypeIds != null) + { + RebuildContentDbCache(groupSize, memberTypeIds); + } + } + + // assumes content tree lock + private void RebuildContentDbCache(int groupSize, IReadOnlyCollection contentTypeIds) + { + Guid contentObjectType = Constants.ObjectTypes.Document; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIds.Count == 0) + { + // must support SQL-CE + Database.Execute( + @"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = contentObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + Database.Execute( + $@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = contentObjectType, ctypes = contentTypeIds }); + } + + // insert back - if anything fails the transaction will rollback + IQuery query = SqlContext.Query(); + if (contentTypeIds != null && contentTypeIds.Count > 0) + { + query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...) + } + + long pageIndex = 0; + long processed = 0; + long total; + do + { + // the tree is locked, counting and comparing to total is safe + IEnumerable descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); + var items = new List(); + var count = 0; + foreach (IContent c in descendants) + { + // always the edited version + items.Add(GetDto(c, false)); + + // and also the published version if it makes any sense + if (c.Published) + { + items.Add(GetDto(c, true)); + } + + count++; + } + + Database.BulkInsertRecords(items); + processed += count; + } while (processed < total); + } + + // assumes media tree lock + private void RebuildMediaDbCache(int groupSize, IReadOnlyCollection contentTypeIds) + { + var mediaObjectType = Constants.ObjectTypes.Media; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIds.Count == 0) + { + // must support SQL-CE + Database.Execute( + @"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = mediaObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + Database.Execute( + $@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = mediaObjectType, ctypes = contentTypeIds }); + } + + // insert back - if anything fails the transaction will rollback + var query = SqlContext.Query(); + if (contentTypeIds != null && contentTypeIds.Count > 0) + { + query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...) + } + + long pageIndex = 0; + long processed = 0; + long total; + do + { + // the tree is locked, counting and comparing to total is safe + var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); + var items = descendants.Select(m => GetDto(m, false)).ToList(); + Database.BulkInsertRecords(items); + processed += items.Count; + } while (processed < total); + } + + // assumes member tree lock + private void RebuildMemberDbCache(int groupSize, IReadOnlyCollection contentTypeIds) + { + Guid memberObjectType = Constants.ObjectTypes.Member; + + // remove all - if anything fails the transaction will rollback + if (contentTypeIds == null || contentTypeIds.Count == 0) + { + // must support SQL-CE + Database.Execute( + @"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType +)", + new { objType = memberObjectType }); + } + else + { + // assume number of ctypes won't blow IN(...) + // must support SQL-CE + Database.Execute( + $@"DELETE FROM cmsContentNu +WHERE cmsContentNu.nodeId IN ( + SELECT id FROM umbracoNode + JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id + WHERE umbracoNode.nodeObjectType=@objType + AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) +)", + new { objType = memberObjectType, ctypes = contentTypeIds }); + } + + // insert back - if anything fails the transaction will rollback + IQuery query = SqlContext.Query(); + if (contentTypeIds != null && contentTypeIds.Count > 0) + { + query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...) + } + + long pageIndex = 0; + long processed = 0; + long total; + do + { + IEnumerable descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); + ContentNuDto[] items = descendants.Select(m => GetDto(m, false)).ToArray(); + Database.BulkInsertRecords(items); + processed += items.Length; + } while (processed < total); + } + + // assumes content tree lock + public bool VerifyContentDbCache() + { + // every document should have a corresponding row for edited properties + // and if published, may have a corresponding row for published properties + Guid contentObjectType = Constants.ObjectTypes.Document; + + var count = Database.ExecuteScalar( + $@"SELECT COUNT(*) +FROM umbracoNode +JOIN {Constants.DatabaseSchema.Tables.Document} ON umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId +LEFT JOIN cmsContentNu nuEdited ON (umbracoNode.id=nuEdited.nodeId AND nuEdited.published=0) +LEFT JOIN cmsContentNu nuPublished ON (umbracoNode.id=nuPublished.nodeId AND nuPublished.published=1) +WHERE umbracoNode.nodeObjectType=@objType +AND nuEdited.nodeId IS NULL OR ({Constants.DatabaseSchema.Tables.Document}.published=1 AND nuPublished.nodeId IS NULL);", + new { objType = contentObjectType }); + + return count == 0; + } + + // assumes media tree lock + public bool VerifyMediaDbCache() + { + // every media item should have a corresponding row for edited properties + Guid mediaObjectType = Constants.ObjectTypes.Media; + + var count = Database.ExecuteScalar( + @"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentNu.nodeId IS NULL +", new { objType = mediaObjectType }); + + return count == 0; + } + + // assumes member tree lock + public bool VerifyMemberDbCache() + { + // every member item should have a corresponding row for edited properties + var memberObjectType = Constants.ObjectTypes.Member; + + var count = Database.ExecuteScalar( + @"SELECT COUNT(*) +FROM umbracoNode +LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) +WHERE umbracoNode.nodeObjectType=@objType +AND cmsContentNu.nodeId IS NULL +", new { objType = memberObjectType }); + + return count == 0; + } + + private ContentNuDto GetDto(IContentBase content, bool published) + { + // should inject these in ctor + // BUT for the time being we decide not to support ConvertDbToXml/String + // var propertyEditorResolver = PropertyEditorResolver.Current; + // var dataTypeService = ApplicationContext.Current.Services.DataTypeService; + var propertyData = new Dictionary(); + foreach (IProperty prop in content.Properties) + { + var pdatas = new List(); + foreach (IPropertyValue pvalue in prop.Values) + { + // sanitize - properties should be ok but ... never knows + if (!prop.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment)) + { + continue; + } + + // note: at service level, invariant is 'null', but here invariant becomes 'string.Empty' + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + if (value != null) + { + pdatas.Add(new PropertyData { Culture = pvalue.Culture ?? string.Empty, Segment = pvalue.Segment ?? string.Empty, Value = value }); + } + } + + propertyData[prop.Alias] = pdatas.ToArray(); + } + + var cultureData = new Dictionary(); + + // sanitize - names should be ok but ... never knows + if (content.ContentType.VariesByCulture()) + { + ContentCultureInfosCollection infos = content is IContent document + ? published + ? document.PublishCultureInfos + : document.CultureInfos + : content.CultureInfos; + + // ReSharper disable once UseDeconstruction + foreach (ContentCultureInfos cultureInfo in infos) + { + var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(cultureInfo.Culture); + cultureData[cultureInfo.Culture] = new CultureVariation + { + Name = cultureInfo.Name, + UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders, cultureInfo.Culture), + Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue, + IsDraft = cultureIsDraft + }; + } + } + + // the dictionary that will be serialized + var nestedData = new ContentNestedData + { + PropertyData = propertyData, + CultureData = cultureData, + UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders) + }; + + var dto = new ContentNuDto + { + NodeId = content.Id, + Published = published, + + // note that numeric values (which are Int32) are serialized without their + // type (eg "value":1234) and JsonConvert by default deserializes them as Int64 + Data = JsonConvert.SerializeObject(nestedData) + }; + + return dto; + } + + // we want arrays, we want them all loaded, not an enumerable + private Sql ContentSourcesSelect(Func, Sql> joins = null) + { + var sql = Sql() + + .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), + x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), + x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) + .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) + .AndSelect(x => Alias(x.Published, "Published"), x => Alias(x.Edited, "Edited")) + + .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) + .AndSelect(x => Alias(x.TemplateId, "EditTemplateId")) + + .AndSelect("pcver", x => Alias(x.Id, "PublishedVersionId"), x => Alias(x.Text, "PubName"), x => Alias(x.VersionDate, "PubVersionDate"), x => Alias(x.UserId, "PubWriterId")) + .AndSelect("pdver", x => Alias(x.TemplateId, "PubTemplateId")) + + .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) + .AndSelect("nuPub", x => Alias(x.Data, "PubData")) + + .From(); + + if (joins != null) + { + sql = joins(sql); + } + + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .InnerJoin().On((left, right) => left.Id == right.Id) + + .LeftJoin(j => + j.InnerJoin("pdver").On((left, right) => left.Id == right.Id && right.Published, "pcver", "pdver"), "pcver") + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcver") + + .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit") + .LeftJoin("nuPub").On((left, right) => left.NodeId == right.NodeId && right.Published, aliasRight: "nuPub"); + + return sql; + } + + public ContentNodeKit GetContentSource(int id) + { + var sql = ContentSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id && !x.Trashed) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto); + } + + public IEnumerable GetAllContentSources() + { + var sql = ContentSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateContentNodeKit(row); + } + } + + public IEnumerable GetBranchContentSources(int id) + { + var syntax = SqlSyntax; + var sql = ContentSourcesSelect( + s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + .Where(x => x.NodeId == id, "x") + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateContentNodeKit(row); + } + } + + public IEnumerable GetTypeContentSources(IEnumerable ids) + { + if (!ids.Any()) + yield break; + + var sql = ContentSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed) + .WhereIn(x => x.ContentTypeId, ids) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateContentNodeKit(row); + } + } + + private Sql MediaSourcesSelect(Func, Sql> joins = null) + { + var sql = Sql() + + .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"), + x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"), + x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId")) + .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId")) + .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId")) + .AndSelect("nuEdit", x => Alias(x.Data, "EditData")) + .From(); + + if (joins != null) + { + sql = joins(sql); + } + + sql = sql + .InnerJoin().On((left, right) => left.NodeId == right.NodeId) + .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit"); + + return sql; + } + + public ContentNodeKit GetMediaSource(int id) + { + var sql = MediaSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && x.NodeId == id && !x.Trashed) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + var dto = Database.Fetch(sql).FirstOrDefault(); + return dto == null ? new ContentNodeKit() : CreateMediaNodeKit(dto); + } + + public IEnumerable GetAllMediaSources() + { + var sql = MediaSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateMediaNodeKit(row); + } + } + + public IEnumerable GetBranchMediaSources(int id) + { + var syntax = SqlSyntax; + var sql = MediaSourcesSelect( + s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x")) + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) + .Where(x => x.NodeId == id, "x") + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateMediaNodeKit(row); + } + } + + public IEnumerable GetTypeMediaSources(IEnumerable ids) + { + if (!ids.Any()) + { + yield break; + } + + var sql = MediaSourcesSelect() + .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed) + .WhereIn(x => x.ContentTypeId, ids) + .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder); + + // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout. + // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that. + + foreach (var row in Database.QueryPaged(PageSize, sql)) + { + yield return CreateMediaNodeKit(row); + } + } + + private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto) + { + ContentData d = null; + ContentData p = null; + + if (dto.Edited) + { + if (dto.EditData == null) + { + if (Debugger.IsAttached) + { + throw new InvalidOperationException("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + } + + _logger.LogWarning("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id); + } + else + { + var nested = DeserializeNestedData(dto.EditData); + + d = new ContentData + { + Name = dto.EditName, + Published = false, + TemplateId = dto.EditTemplateId, + VersionId = dto.VersionId, + VersionDate = dto.EditVersionDate, + WriterId = dto.EditWriterId, + Properties = nested.PropertyData, + CultureInfos = nested.CultureData, + UrlSegment = nested.UrlSegment + }; + } + } + + if (dto.Published) + { + if (dto.PubData == null) + { + if (Debugger.IsAttached) + { + throw new InvalidOperationException("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding."); + } + + _logger.LogWarning("Missing cmsContentNu published content for node {NodeId}, consider rebuilding.", dto.Id); + } + else + { + var nested = DeserializeNestedData(dto.PubData); + + p = new ContentData + { + Name = dto.PubName, + UrlSegment = nested.UrlSegment, + Published = true, + TemplateId = dto.PubTemplateId, + VersionId = dto.VersionId, + VersionDate = dto.PubVersionDate, + WriterId = dto.PubWriterId, + Properties = nested.PropertyData, + CultureInfos = nested.CultureData + }; + } + } + + var n = new ContentNode(dto.Id, dto.Uid, + dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); + + var s = new ContentNodeKit + { + Node = n, + ContentTypeId = dto.ContentTypeId, + DraftData = d, + PublishedData = p + }; + + return s; + } + + private static ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto) + { + if (dto.EditData == null) + throw new InvalidOperationException("No data for media " + dto.Id); + + var nested = DeserializeNestedData(dto.EditData); + + var p = new ContentData + { + Name = dto.EditName, + Published = true, + TemplateId = -1, + VersionId = dto.VersionId, + VersionDate = dto.EditVersionDate, + WriterId = dto.CreatorId, // what-else? + Properties = nested.PropertyData, + CultureInfos = nested.CultureData + }; + + var n = new ContentNode(dto.Id, dto.Uid, + dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId); + + var s = new ContentNodeKit + { + Node = n, + ContentTypeId = dto.ContentTypeId, + PublishedData = p + }; + + return s; + } + + private static ContentNestedData DeserializeNestedData(string data) + { + // by default JsonConvert will deserialize our numeric values as Int64 + // which is bad, because they were Int32 in the database - take care + + var settings = new JsonSerializerSettings + { + Converters = new List { new ForceInt32Converter() } + }; + + return JsonConvert.DeserializeObject(data, settings); + } + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs new file mode 100644 index 0000000000..5c7fdeb53b --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Umbraco.Core; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using Umbraco.Core.Scoping; +using Umbraco.Core.Services.Implement; +using Umbraco.Web.PublishedCache.NuCache; + +namespace Umbraco.Infrastructure.PublishedCache.Persistence +{ + public class NuCacheContentService : RepositoryService, INuCacheContentService + { + private readonly INuCacheContentRepository _repository; + + public NuCacheContentService(INuCacheContentRepository repository, IScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory) + : base(provider, loggerFactory, eventMessagesFactory) + { + _repository = repository; + } + + /// + public IEnumerable GetAllContentSources() + => _repository.GetAllContentSources(); + + /// + public IEnumerable GetAllMediaSources() + => _repository.GetAllMediaSources(); + + /// + public IEnumerable GetBranchContentSources(int id) + => _repository.GetBranchContentSources(id); + + /// + public IEnumerable GetBranchMediaSources(int id) + => _repository.GetBranchMediaSources(id); + + /// + public ContentNodeKit GetContentSource(int id) + => _repository.GetContentSource(id); + + /// + public ContentNodeKit GetMediaSource(int id) + => _repository.GetMediaSource(id); + + /// + public IEnumerable GetTypeContentSources(IEnumerable ids) + => _repository.GetTypeContentSources(ids); + + /// + public IEnumerable GetTypeMediaSources(IEnumerable ids) + => _repository.GetTypeContentSources(ids); + + /// + public void DeleteContentItem(IContentBase item) + => _repository.DeleteContentItem(item); + + /// + public void RefreshContent(IContent content) + => _repository.RefreshContent(content); + + /// + public void RefreshEntity(IContentBase content) + => _repository.RefreshEntity(content); + + /// + public void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null) + { + using (IScope scope = ScopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) + { + if (contentTypeIds != null) + { + scope.ReadLock(Constants.Locks.ContentTree); + } + + if (mediaTypeIds != null) + { + scope.ReadLock(Constants.Locks.MediaTree); + } + + if (memberTypeIds != null) + { + scope.ReadLock(Constants.Locks.MemberTree); + } + + _repository.Rebuild(groupSize, contentTypeIds, mediaTypeIds, memberTypeIds); + scope.Complete(); + } + } + + /// + public bool VerifyContentDbCache() + => _repository.VerifyContentDbCache(); + + /// + public bool VerifyMediaDbCache() + => _repository.VerifyMediaDbCache(); + + /// + public bool VerifyMemberDbCache() + => _repository.VerifyMemberDbCache(); + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 97e3df16a6..3011e3d655 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -4,9 +4,8 @@ using System.Globalization; using System.IO; using System.Linq; using CSharpTest.Net.Collections; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; @@ -17,15 +16,10 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Dtos; -using Umbraco.Core.Persistence.Repositories; -using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; -using Umbraco.Core.Services.Implement; -using Umbraco.Core.Strings; -using Umbraco.Net; +using Umbraco.Infrastructure.PublishedCache.Persistence; using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache.NuCache.DataSource; using Umbraco.Web.Routing; @@ -33,38 +27,30 @@ using File = System.IO.File; namespace Umbraco.Web.PublishedCache.NuCache { + internal class PublishedSnapshotService : PublishedSnapshotServiceBase { - private readonly PublishedSnapshotServiceOptions _options; - private readonly IMainDom _mainDom; - private readonly IUmbracoApplicationLifetime _lifeTime; - private readonly IRuntimeState _runtime; private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly IProfilingLogger _profilingLogger; private readonly IScopeProvider _scopeProvider; - private readonly IDataSource _dataSource; + private readonly INuCacheContentService _publishedContentService; private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IDocumentRepository _documentRepository; - private readonly IMediaRepository _mediaRepository; - private readonly IMemberRepository _memberRepository; private readonly GlobalSettings _globalSettings; private readonly IEntityXmlSerializer _entitySerializer; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; - private readonly UrlSegmentProviderCollection _urlSegmentProviders; private readonly IHostingEnvironment _hostingEnvironment; - private readonly IShortStringHelper _shortStringHelper; private readonly IIOHelper _ioHelper; private readonly NuCacheSettings _config; // volatile because we read it with no lock private volatile bool _isReady; - private ContentStore _contentStore; - private ContentStore _mediaStore; - private SnapDictionary _domainStore; + private readonly ContentStore _contentStore; + private readonly ContentStore _mediaStore; + private readonly SnapDictionary _domainStore; private readonly object _storesLock = new object(); private readonly object _elementsLock = new object(); @@ -73,19 +59,20 @@ namespace Umbraco.Web.PublishedCache.NuCache private bool _localContentDbExists; private bool _localMediaDbExists; + private long _contentGen; + private long _mediaGen; + private long _domainGen; + private IAppCache _elementsCache; + // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching // means faster execution, but uses memory - not sure if we want it // so making it configurable. public static readonly bool FullCacheWhenPreviewing = true; - #region Constructors - public PublishedSnapshotService( PublishedSnapshotServiceOptions options, IMainDom mainDom, - IUmbracoApplicationLifetime lifeTime, - IRuntimeState runtime, ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IPublishedSnapshotAccessor publishedSnapshotAccessor, @@ -93,40 +80,26 @@ namespace Umbraco.Web.PublishedCache.NuCache IProfilingLogger profilingLogger, ILoggerFactory loggerFactory, IScopeProvider scopeProvider, - IDocumentRepository documentRepository, - IMediaRepository mediaRepository, - IMemberRepository memberRepository, + INuCacheContentService publishedContentService, IDefaultCultureAccessor defaultCultureAccessor, - IDataSource dataSource, IOptions globalSettings, IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, - UrlSegmentProviderCollection urlSegmentProviders, IHostingEnvironment hostingEnvironment, - IShortStringHelper shortStringHelper, - IIOHelper ioHelper, + IIOHelper ioHelper, // TODO: Remove this, it is only needed for "EnsureEnvironment" which doesn't need to belong to this service IOptions config) : base(publishedSnapshotAccessor, variationContextAccessor) { - _options = options; - _mainDom = mainDom; - _lifeTime = lifeTime; - _runtime = runtime; _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; _profilingLogger = profilingLogger; - _dataSource = dataSource; _loggerFactory = loggerFactory; _logger = _loggerFactory.CreateLogger(); _scopeProvider = scopeProvider; - _documentRepository = documentRepository; - _mediaRepository = mediaRepository; - _memberRepository = memberRepository; + _publishedContentService = publishedContentService; _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings.Value; - _urlSegmentProviders = urlSegmentProviders; _hostingEnvironment = hostingEnvironment; - _shortStringHelper = shortStringHelper; _ioHelper = ioHelper; _config = config.Value; @@ -135,33 +108,15 @@ namespace Umbraco.Web.PublishedCache.NuCache _entitySerializer = entitySerializer; _publishedModelFactory = publishedModelFactory; - // we always want to handle repository events, configured or not - // assuming no repository event will trigger before the whole db is ready - // (ideally we'd have Upgrading.App vs Upgrading.Data application states...) - InitializeRepositoryEvents(); - - _lifeTime.ApplicationInit += OnApplicationInit; - } - - internal void OnApplicationInit(object sender, EventArgs e) - { - // however, the cache is NOT available until we are configured, because loading - // content (and content types) from database cannot be consistent (see notes in "Handle - // Notifications" region), so - // - notifications will be ignored - // - trying to obtain a published snapshot from the service will throw - if (_runtime.Level != RuntimeLevel.Run) - return; - // lock this entire call, we only want a single thread to be accessing the stores at once and within // the call below to mainDom.Register, a callback may occur on a threadpool thread to MainDomRelease // at the same time as we are trying to write to the stores. MainDomRelease also locks on _storesLock so // it will not be able to close the stores until we are done populating (if the store is empty) lock (_storesLock) { - if (!_options.IgnoreLocalDb) + if (!options.IgnoreLocalDb) { - _mainDom.Register(MainDomRegister, MainDomRelease); + mainDom.Register(MainDomRegister, MainDomRelease); // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to @@ -181,8 +136,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } _domainStore = new SnapDictionary(); - - LoadCachesOnStartup(); } } @@ -190,6 +143,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // NOTE: These aren't used within this object but are made available internally to improve the IdKey lookup performance // when nucache is enabled. + // TODO: Does this need to be here? internal int GetDocumentId(Guid udi) => GetId(_contentStore, udi); internal int GetMediaId(Guid udi) => GetId(_mediaStore, udi); @@ -239,11 +193,11 @@ namespace Umbraco.Web.PublishedCache.NuCache lock (_storesLock) { _logger.LogDebug("Releasing content store..."); - _contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned + _contentStore?.ReleaseLocalDb(); // null check because we could shut down before being assigned _localContentDb = null; _logger.LogDebug("Releasing media store..."); - _mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned + _mediaStore?.ReleaseLocalDb(); // null check because we could shut down before being assigned _localMediaDb = null; _logger.LogInformation("Released from MainDom"); @@ -253,100 +207,61 @@ namespace Umbraco.Web.PublishedCache.NuCache /// /// Populates the stores /// - /// This is called inside of a lock for _storesLock - private void LoadCachesOnStartup() + public override void LoadCachesOnStartup() { - var okContent = false; - var okMedia = false; - - try + lock (_storesLock) { - if (_localContentDbExists) + if (_isReady) { - okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); + throw new InvalidOperationException("The caches can only be loaded on startup one time"); + } + + var okContent = false; + var okMedia = false; + + try + { + if (_localContentDbExists) + { + okContent = LockAndLoadContent(() => LoadContentFromLocalDbLocked(true)); + if (!okContent) + { + _logger.LogWarning("Loading content from local db raised warnings, will reload from database."); + } + } + + if (_localMediaDbExists) + { + okMedia = LockAndLoadMedia(() => LoadMediaFromLocalDbLocked(true)); + if (!okMedia) + { + _logger.LogWarning("Loading media from local db raised warnings, will reload from database."); + } + } + if (!okContent) - _logger.LogWarning("Loading content from local db raised warnings, will reload from database."); - } + { + LockAndLoadContent(() => LoadContentFromDatabaseLocked(true)); + } - if (_localMediaDbExists) - { - okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); if (!okMedia) - _logger.LogWarning("Loading media from local db raised warnings, will reload from database."); + { + LockAndLoadMedia(() => LoadMediaFromDatabaseLocked(true)); + } + + LockAndLoadDomains(); + } + catch (Exception ex) + { + _logger.LogCritical(ex, "Panic, exception while loading cache data."); + throw; } - if (!okContent) - LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); - - if (!okMedia) - LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true)); - - LockAndLoadDomains(); + // finally, cache is ready! + _isReady = true; } - catch (Exception ex) - { - _logger.LogCritical(ex, "Panic, exception while loading cache data."); - throw; - } - - // finally, cache is ready! - _isReady = true; } - private void InitializeRepositoryEvents() - { - // TODO: The reason these events are in the repository is for legacy, the events should exist at the service - // level now since we can fire these events within the transaction... so move the events to service level - - // plug repository event handlers - // these trigger within the transaction to ensure consistency - // and are used to maintain the central, database-level XML cache - DocumentRepository.ScopeEntityRemove += OnContentRemovingEntity; - //ContentRepository.RemovedVersion += OnContentRemovedVersion; - DocumentRepository.ScopedEntityRefresh += OnContentRefreshedEntity; - MediaRepository.ScopeEntityRemove += OnMediaRemovingEntity; - //MediaRepository.RemovedVersion += OnMediaRemovedVersion; - MediaRepository.ScopedEntityRefresh += OnMediaRefreshedEntity; - MemberRepository.ScopeEntityRemove += OnMemberRemovingEntity; - //MemberRepository.RemovedVersion += OnMemberRemovedVersion; - MemberRepository.ScopedEntityRefresh += OnMemberRefreshedEntity; - - // plug - ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; - MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; - MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; - - LocalizationService.SavedLanguage += OnLanguageSaved; - } - - private void TearDownRepositoryEvents() - { - DocumentRepository.ScopeEntityRemove -= OnContentRemovingEntity; - //ContentRepository.RemovedVersion -= OnContentRemovedVersion; - DocumentRepository.ScopedEntityRefresh -= OnContentRefreshedEntity; - MediaRepository.ScopeEntityRemove -= OnMediaRemovingEntity; - //MediaRepository.RemovedVersion -= OnMediaRemovedVersion; - MediaRepository.ScopedEntityRefresh -= OnMediaRefreshedEntity; - MemberRepository.ScopeEntityRemove -= OnMemberRemovingEntity; - //MemberRepository.RemovedVersion -= OnMemberRemovedVersion; - MemberRepository.ScopedEntityRefresh -= OnMemberRefreshedEntity; - - ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; - MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; - MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; - - LocalizationService.SavedLanguage -= OnLanguageSaved; - } - - public override void Dispose() - { - TearDownRepositoryEvents(); - _lifeTime.ApplicationInit -= OnApplicationInit; - base.Dispose(); - } - - #endregion - #region Local files private string GetLocalFilesPath() @@ -354,7 +269,9 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = Path.Combine(_hostingEnvironment.LocalTempPath, "NuCache"); if (!Directory.Exists(path)) + { Directory.CreateDirectory(path); + } return path; } @@ -362,23 +279,31 @@ namespace Umbraco.Web.PublishedCache.NuCache private void DeleteLocalFilesForContent() { if (_isReady && _localContentDb != null) + { throw new InvalidOperationException("Cannot delete local files while the cache uses them."); + } var path = GetLocalFilesPath(); var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); if (File.Exists(localContentDbPath)) + { File.Delete(localContentDbPath); + } } private void DeleteLocalFilesForMedia() { if (_isReady && _localMediaDb != null) + { throw new InvalidOperationException("Cannot delete local files while the cache uses them."); + } var path = GetLocalFilesPath(); var localMediaDbPath = Path.Combine(path, "NuCache.Media.db"); if (File.Exists(localMediaDbPath)) + { File.Delete(localMediaDbPath); + } } #endregion @@ -400,11 +325,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // sudden panic... but in RepeatableRead can a content that I haven't already read, be removed // before I read it? NO! because the WHOLE content tree is read-locked using WithReadLocked. // don't panic. - - private bool LockAndLoadContent(Func action) + private bool LockAndLoadContent(Func action) { - - // first get a writer, then a scope // if there already is a scope, the writer will attach to it // otherwise, it will only exist here - cheap @@ -412,13 +334,13 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - var ok = action(scope); + var ok = action(); scope.Complete(); return ok; } } - private bool LoadContentFromDatabaseLocked(IScope scope, bool onStartup) + private bool LoadContentFromDatabaseLocked(bool onStartup) { // locks: // contentStore is wlocked (1 thread) @@ -437,7 +359,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _localContentDb?.Clear(); // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder - var kits = _dataSource.GetAllContentSources(scope); + var kits = _publishedContentService.GetAllContentSources(); return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits); } } @@ -457,45 +379,22 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // keep these around - might be useful - - //private void LoadContentBranch(IContent content) - //{ - // LoadContent(content); - - // foreach (var child in content.Children()) - // LoadContentBranch(child); - //} - - //private void LoadContent(IContent content) - //{ - // var contentService = _serviceContext.ContentService as ContentService; - // var newest = content; - // var published = newest.Published - // ? newest - // : (newest.HasPublishedVersion ? contentService.GetByVersion(newest.PublishedVersionGuid) : null); - - // var contentNode = CreateContentNode(newest, published); - // _contentStore.Set(contentNode); - //} - - private bool LockAndLoadMedia(Func action) + private bool LockAndLoadMedia(Func action) { // see note in LockAndLoadContent using (_mediaStore.GetScopedWriteLock(_scopeProvider)) using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - var ok = action(scope); + var ok = action(); scope.Complete(); return ok; } } - private bool LoadMediaFromDatabaseLocked(IScope scope, bool onStartup) + private bool LoadMediaFromDatabaseLocked(bool onStartup) { // locks & notes: see content - var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); _mediaStore.SetAllContentTypesLocked(mediaTypes); @@ -504,12 +403,11 @@ namespace Umbraco.Web.PublishedCache.NuCache { // beware! at that point the cache is inconsistent, // assuming we are going to SetAll content items! - _localMediaDb?.Clear(); _logger.LogDebug("Loading media from database..."); // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder - var kits = _dataSource.GetAllMediaSources(scope); + var kits = _publishedContentService.GetAllMediaSources(); return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits); } } @@ -527,7 +425,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return LoadEntitiesFromLocalDbLocked(onStartup, _localMediaDb, _mediaStore, "media"); } - } private bool LoadEntitiesFromLocalDbLocked(bool onStartup, BPlusTree localDb, ContentStore store, string entityType) @@ -562,90 +459,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits); } - // keep these around - might be useful - - //private void LoadMediaBranch(IMedia media) - //{ - // LoadMedia(media); - - // foreach (var child in media.Children()) - // LoadMediaBranch(child); - //} - - //private void LoadMedia(IMedia media) - //{ - // var mediaType = _contentTypeCache.Get(PublishedItemType.Media, media.ContentTypeId); - - // var mediaData = new ContentData - // { - // Name = media.Name, - // Published = true, - // Version = media.Version, - // VersionDate = media.UpdateDate, - // WriterId = media.CreatorId, // what else? - // TemplateId = -1, // have none - // Properties = GetPropertyValues(media) - // }; - - // var mediaNode = new ContentNode(media.Id, mediaType, - // media.Level, media.Path, media.SortOrder, - // media.ParentId, media.CreateDate, media.CreatorId, - // null, mediaData); - - // _mediaStore.Set(mediaNode); - //} - - //private Dictionary GetPropertyValues(IContentBase content) - //{ - // var propertyEditorResolver = PropertyEditorResolver.Current; // should inject - - // return content - // .Properties - // .Select(property => - // { - // var e = propertyEditorResolver.GetByAlias(property.PropertyType.PropertyEditorAlias); - // var v = e == null - // ? property.Value - // : e.ValueEditor.ConvertDbToString(property, property.PropertyType, _serviceContext.DataTypeService); - // return new KeyValuePair(property.Alias, v); - // }) - // .ToDictionary(x => x.Key, x => x.Value); - //} - - //private ContentData CreateContentData(IContent content) - //{ - // return new ContentData - // { - // Name = content.Name, - // Published = content.Published, - // Version = content.Version, - // VersionDate = content.UpdateDate, - // WriterId = content.WriterId, - // TemplateId = content.Template == null ? -1 : content.Template.Id, - // Properties = GetPropertyValues(content) - // }; - //} - - //private ContentNode CreateContentNode(IContent newest, IContent published) - //{ - // var contentType = _contentTypeCache.Get(PublishedItemType.Content, newest.ContentTypeId); - - // var draftData = newest.Published - // ? null - // : CreateContentData(newest); - - // var publishedData = newest.Published - // ? CreateContentData(newest) - // : (published == null ? null : CreateContentData(published)); - - // var contentNode = new ContentNode(newest.Id, contentType, - // newest.Level, newest.Path, newest.SortOrder, - // newest.ParentId, newest.CreateDate, newest.CreatorId, - // draftData, publishedData); - - // return contentNode; - //} - private void LockAndLoadDomains() { // see note in LockAndLoadContent @@ -713,9 +526,10 @@ namespace Umbraco.Web.PublishedCache.NuCache publishedChanged = publishedChanged2; } - if (draftChanged || publishedChanged) + { ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + } } // Calling this method means we have a lock on the contentStore (i.e. GetScopedWriteLock) @@ -739,7 +553,7 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - LoadContentFromDatabaseLocked(scope, false); + LoadContentFromDatabaseLocked(false); scope.Complete(); } draftChanged = publishedChanged = true; @@ -770,13 +584,13 @@ namespace Umbraco.Web.PublishedCache.NuCache { // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order - var kits = _dataSource.GetBranchContentSources(scope, capture.Id); + var kits = _publishedContentService.GetBranchContentSources(capture.Id); _contentStore.SetBranchLocked(capture.Id, kits); } else { // ?? should we do some RV check here? - var kit = _dataSource.GetContentSource(scope, capture.Id); + var kit = _publishedContentService.GetContentSource(capture.Id); if (kit.IsEmpty) { _contentStore.ClearLocked(capture.Id); @@ -813,7 +627,9 @@ namespace Umbraco.Web.PublishedCache.NuCache } if (anythingChanged) + { ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + } } private void NotifyLocked(IEnumerable payloads, out bool anythingChanged) @@ -832,9 +648,10 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - LoadMediaFromDatabaseLocked(scope, false); + LoadMediaFromDatabaseLocked(false); scope.Complete(); } + anythingChanged = true; continue; } @@ -842,7 +659,10 @@ namespace Umbraco.Web.PublishedCache.NuCache if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) { if (_mediaStore.ClearLocked(payload.Id)) + { anythingChanged = true; + } + continue; } @@ -853,7 +673,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } // TODO: should we do some RV checks here? (later) - var capture = payload; using (var scope = _scopeProvider.CreateScope()) { @@ -863,13 +682,13 @@ namespace Umbraco.Web.PublishedCache.NuCache { // ?? should we do some RV check here? // IMPORTANT GetbranchContentSources sorts kits by level and by sort order - var kits = _dataSource.GetBranchMediaSources(scope, capture.Id); + var kits = _publishedContentService.GetBranchMediaSources(capture.Id); _mediaStore.SetBranchLocked(capture.Id, kits); } else { // ?? should we do some RV check here? - var kit = _dataSource.GetMediaSource(scope, capture.Id); + var kit = _publishedContentService.GetMediaSource(capture.Id); if (kit.IsEmpty) { _mediaStore.ClearLocked(capture.Id); @@ -893,22 +712,26 @@ namespace Umbraco.Web.PublishedCache.NuCache { // no cache, nothing we can do if (_isReady == false) + { return; + } foreach (var payload in payloads) + { _logger.LogDebug("Notified {ChangeTypes} for {ItemType} {ItemId}", payload.ChangeTypes, payload.ItemType, payload.Id); + } Notify(_contentStore, payloads, RefreshContentTypesLocked); Notify(_mediaStore, payloads, RefreshMediaTypesLocked); if (_publishedModelFactory.IsLiveFactoryEnabled()) { - //In the case of Pure Live - we actually need to refresh all of the content and the media - //see https://github.com/umbraco/Umbraco-CMS/issues/5671 - //The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models - //into a new DLL for the application which includes both content types and media types. - //Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated - //to use the newest version of the class. + // In the case of Pure Live - we actually need to refresh all of the content and the media + // see https://github.com/umbraco/Umbraco-CMS/issues/5671 + // The underlying issue is that in Pure Live the ILivePublishedModelFactory will re-compile all of the classes/models + // into a new DLL for the application which includes both content types and media types. + // Since the models in the cache are based on these actual classes, all of the objects in the cache need to be updated + // to use the newest version of the class. // NOTE: Ideally this can be run on background threads here which would prevent blocking the UI // as is the case when saving a content type. Intially one would think that it won't be any different @@ -941,7 +764,10 @@ namespace Umbraco.Web.PublishedCache.NuCache private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, List, List, List> action) where T : IContentTypeComposition { - if (payloads.Length == 0) return; //nothing to do + if (payloads.Length == 0) + { + return; // nothing to do + } var nameOfT = typeof(T).Name; @@ -949,25 +775,37 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var payload in payloads) { - if (payload.ItemType != nameOfT) continue; + if (payload.ItemType != nameOfT) + { + continue; + } if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Remove)) + { AddToList(ref removedIds, payload.Id); + } else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshMain)) + { AddToList(ref refreshedIds, payload.Id); + } else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.RefreshOther)) + { AddToList(ref otherIds, payload.Id); + } else if (payload.ChangeTypes.HasType(ContentTypeChangeTypes.Create)) + { AddToList(ref newIds, payload.Id); + } } - if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) return; + if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) + { + return; + } using (store.GetScopedWriteLock(_scopeProvider)) { - // ReSharper disable AccessToModifiedClosure action(removedIds, refreshedIds, otherIds, newIds); - // ReSharper restore AccessToModifiedClosure } } @@ -975,14 +813,18 @@ namespace Umbraco.Web.PublishedCache.NuCache { // no cache, nothing we can do if (_isReady == false) + { return; + } var idsA = payloads.Select(x => x.Id).ToArray(); foreach (var payload in payloads) + { _logger.LogDebug("Notified {RemovedStatus} for data type {DataTypeId}", payload.Removed ? "Removed" : "Refreshed", payload.Id); + } using (_contentStore.GetScopedWriteLock(_scopeProvider)) using (_mediaStore.GetScopedWriteLock(_scopeProvider)) @@ -1015,7 +857,9 @@ namespace Umbraco.Web.PublishedCache.NuCache { // no cache, nothing we can do if (_isReady == false) + { return; + } // see note in LockAndLoadContent using (_domainStore.GetScopedWriteLock(_scopeProvider)) @@ -1048,7 +892,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - //Methods used to prevent allocations of lists + // Methods used to prevent allocations of lists private void AddToList(ref List list, int val) => GetOrCreateList(ref list).Add(val); private List GetOrCreateList(ref List list) => list ?? (list = new List()); @@ -1060,7 +904,9 @@ namespace Umbraco.Web.PublishedCache.NuCache { // XxxTypeService.GetAll(empty) returns everything! if (ids.Length == 0) + { return Array.Empty(); + } IEnumerable contentTypes; switch (itemType) @@ -1107,7 +953,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void RefreshContentTypesLocked(List removedIds, List refreshedIds, List otherIds, List newIds) { if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) + { return; + } // locks: // content (and content types) are read-locked while reading content @@ -1124,13 +972,19 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = refreshedIds.IsCollectionEmpty() ? Array.Empty() - : _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray(); + : _publishedContentService.GetTypeContentSources(refreshedIds).ToArray(); _contentStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) + { _contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray())); + } + if (!newIds.IsCollectionEmpty()) + { _contentStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Content, newIds.ToArray())); + } + scope.Complete(); } } @@ -1138,7 +992,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private void RefreshMediaTypesLocked(List removedIds, List refreshedIds, List otherIds, List newIds) { if (removedIds.IsCollectionEmpty() && refreshedIds.IsCollectionEmpty() && otherIds.IsCollectionEmpty() && newIds.IsCollectionEmpty()) + { return; + } // locks: // media (and content types) are read-locked while reading media @@ -1155,13 +1011,19 @@ namespace Umbraco.Web.PublishedCache.NuCache var kits = refreshedIds == null ? Array.Empty() - : _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray(); + : _publishedContentService.GetTypeMediaSources(refreshedIds).ToArray(); _mediaStore.UpdateContentTypesLocked(removedIds, typesA, kits); if (!otherIds.IsCollectionEmpty()) + { _mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray()); + } + if (!newIds.IsCollectionEmpty()) + { _mediaStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray()); + } + scope.Complete(); } } @@ -1170,14 +1032,13 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Create, Get Published Snapshot - private long _contentGen, _mediaGen, _domainGen; - private IAppCache _elementsCache; - public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { // no cache, no joy if (_isReady == false) + { throw new InvalidOperationException("The published snapshot service has not properly initialized."); + } var preview = previewToken.IsNullOrWhiteSpace() == false; return new PublishedSnapshot(this, preview); @@ -1272,6 +1133,7 @@ namespace Umbraco.Web.PublishedCache.NuCache #region Preview + // TODO: Delete this all public override string EnterPreview(IUser user, int contentId) { return "preview"; // anything @@ -1289,522 +1151,52 @@ namespace Umbraco.Web.PublishedCache.NuCache #endregion - #region Handle Repository Events For Database PreCache - - // note: if the service is not ready, ie _isReady is false, then we still handle repository events, - // because we can, we do not need a working published snapshot to do it - the only reason why it could cause an - // issue is if the database table is not ready, but that should be prevented by migrations. - - // we need them to be "repository" events ie to trigger from within the repository transaction, - // because they need to be consistent with the content that is being refreshed/removed - and that - // should be guaranteed by a DB transaction - - private void OnContentRemovingEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) - { - OnRemovedEntity(args.Scope.Database, args.Entity); - } - - private void OnMediaRemovingEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) - { - OnRemovedEntity(args.Scope.Database, args.Entity); - } - - private void OnMemberRemovingEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) - { - OnRemovedEntity(args.Scope.Database, args.Entity); - } - - private void OnRemovedEntity(IUmbracoDatabase db, IContentBase item) - { - db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id", new { id = item.Id }); - } - - private void OnContentRefreshedEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) - { - var db = args.Scope.Database; - var content = (Content)args.Entity; - - // always refresh the edited data - OnRepositoryRefreshed(db, content, false); - - // if unpublishing, remove published data from table - if (content.PublishedState == PublishedState.Unpublishing) - db.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id AND published=1", new { id = content.Id }); - - // if publishing, refresh the published data - else if (content.PublishedState == PublishedState.Publishing) - OnRepositoryRefreshed(db, content, true); - } - - private void OnMediaRefreshedEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) - { - var db = args.Scope.Database; - var media = args.Entity; - - // refresh the edited data - OnRepositoryRefreshed(db, media, false); - } - - private void OnMemberRefreshedEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) - { - var db = args.Scope.Database; - var member = args.Entity; - - // refresh the edited data - OnRepositoryRefreshed(db, member, false); - } - - private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published) - { - // use a custom SQL to update row version on each update - //db.InsertOrUpdate(dto); - - var dto = GetDto(content, published); - db.InsertOrUpdate(dto, - "SET data=@data, rv=rv+1 WHERE nodeId=@id AND published=@published", - new - { - data = dto.Data, - id = dto.NodeId, - published = dto.Published - }); - } - - private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs args) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; - var contentTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (contentTypeIds.Any()) - RebuildContentDbCache(contentTypeIds: contentTypeIds); - } - - private void OnMediaTypeRefreshedEntity(IMediaTypeService sender, ContentTypeChange.EventArgs args) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; - var mediaTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (mediaTypeIds.Any()) - RebuildMediaDbCache(contentTypeIds: mediaTypeIds); - } - - private void OnMemberTypeRefreshedEntity(IMemberTypeService sender, ContentTypeChange.EventArgs args) - { - const ContentTypeChangeTypes types // only for those that have been refreshed - = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; - var memberTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); - if (memberTypeIds.Any()) - RebuildMemberDbCache(contentTypeIds: memberTypeIds); - } - - /// - /// If a is ever saved with a different culture, we need to rebuild all of the content nucache table - /// - /// - /// - private void OnLanguageSaved(ILocalizationService sender, Core.Events.SaveEventArgs e) - { - //culture changed on an existing language - var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); - if (cultureChanged) - { - RebuildContentDbCache(); - } - } - - private ContentNuDto GetDto(IContentBase content, bool published) - { - // should inject these in ctor - // BUT for the time being we decide not to support ConvertDbToXml/String - //var propertyEditorResolver = PropertyEditorResolver.Current; - //var dataTypeService = ApplicationContext.Current.Services.DataTypeService; - - var propertyData = new Dictionary(); - foreach (var prop in content.Properties) - { - var pdatas = new List(); - foreach (var pvalue in prop.Values) - { - // sanitize - properties should be ok but ... never knows - if (!prop.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment)) - continue; - - // note: at service level, invariant is 'null', but here invariant becomes 'string.Empty' - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - if (value != null) - pdatas.Add(new PropertyData { Culture = pvalue.Culture ?? string.Empty, Segment = pvalue.Segment ?? string.Empty, Value = value }); - - //Core.Composing.Current.Logger.Debug($"{content.Id} {prop.Alias} [{pvalue.LanguageId},{pvalue.Segment}] {value} {(published?"pub":"edit")}"); - - //if (value != null) - //{ - // var e = propertyEditorResolver.GetByAlias(prop.PropertyType.PropertyEditorAlias); - - // // We are converting to string, even for database values which are integer or - // // DateTime, which is not optimum. Doing differently would require that we have a way to tell - // // whether the conversion to XML string changes something or not... which we don't, and we - // // don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway. - - // // Don't think about improving the situation here: this is a corner case and the real - // // thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String. - - // // Use ConvertDbToString to keep it simple, although everywhere we use ConvertDbToXml and - // // nothing ensures that the two methods are consistent. - - // if (e != null) - // value = e.ValueEditor.ConvertDbToString(prop, prop.PropertyType, dataTypeService); - //} - } - propertyData[prop.Alias] = pdatas.ToArray(); - } - - var cultureData = new Dictionary(); - - // sanitize - names should be ok but ... never knows - if (content.ContentType.VariesByCulture()) - { - var infos = content is IContent document - ? (published - ? document.PublishCultureInfos - : document.CultureInfos) - : content.CultureInfos; - - // ReSharper disable once UseDeconstruction - foreach (var cultureInfo in infos) - { - var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(cultureInfo.Culture); - cultureData[cultureInfo.Culture] = new CultureVariation - { - Name = cultureInfo.Name, - UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders, cultureInfo.Culture), - Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue, - IsDraft = cultureIsDraft - }; - } - } - - //the dictionary that will be serialized - var nestedData = new ContentNestedData - { - PropertyData = propertyData, - CultureData = cultureData, - UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders) - }; - - var dto = new ContentNuDto - { - NodeId = content.Id, - Published = published, - - // note that numeric values (which are Int32) are serialized without their - // type (eg "value":1234) and JsonConvert by default deserializes them as Int64 - - Data = JsonConvert.SerializeObject(nestedData) - }; - - //Core.Composing.Current.Logger.Debug(dto.Data); - - return dto; - } - - #endregion #region Rebuild Database PreCache - public override void Rebuild() - { - _logger.LogDebug("Rebuilding..."); - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) - { - scope.ReadLock(Constants.Locks.ContentTree); - scope.ReadLock(Constants.Locks.MediaTree); - scope.ReadLock(Constants.Locks.MemberTree); - RebuildContentDbCacheLocked(scope, 5000, null); - RebuildMediaDbCacheLocked(scope, 5000, null); - RebuildMemberDbCacheLocked(scope, 5000, null); - scope.Complete(); - } - } - - public void RebuildContentDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) - { - scope.ReadLock(Constants.Locks.ContentTree); - RebuildContentDbCacheLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes content tree lock - private void RebuildContentDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var contentObjectType = Constants.ObjectTypes.Document; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - db.Execute(@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = contentObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - db.Execute($@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = contentObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - // the tree is locked, counting and comparing to total is safe - var descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - var items = new List(); - var count = 0; - foreach (var c in descendants) - { - // always the edited version - items.Add(GetDto(c, false)); - - // and also the published version if it makes any sense - if (c.Published) - items.Add(GetDto(c, true)); - - count++; - } - - db.BulkInsertRecords(items); - processed += count; - } while (processed < total); - } - - public void RebuildMediaDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) - { - scope.ReadLock(Constants.Locks.MediaTree); - RebuildMediaDbCacheLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes media tree lock - public void RebuildMediaDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var mediaObjectType = Constants.ObjectTypes.Media; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - db.Execute(@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = mediaObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - db.Execute($@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = mediaObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - // the tree is locked, counting and comparing to total is safe - var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - var items = descendants.Select(m => GetDto(m, false)).ToList(); - db.BulkInsertRecords(items); - processed += items.Count; - } while (processed < total); - } - - public void RebuildMemberDbCache(int groupSize = 5000, IEnumerable contentTypeIds = null) - { - using (var scope = _scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) - { - scope.ReadLock(Constants.Locks.MemberTree); - RebuildMemberDbCacheLocked(scope, groupSize, contentTypeIds); - scope.Complete(); - } - } - - // assumes member tree lock - public void RebuildMemberDbCacheLocked(IScope scope, int groupSize, IEnumerable contentTypeIds) - { - var contentTypeIdsA = contentTypeIds?.ToArray(); - var memberObjectType = Constants.ObjectTypes.Member; - var db = scope.Database; - - // remove all - if anything fails the transaction will rollback - if (contentTypeIds == null || contentTypeIdsA.Length == 0) - { - // must support SQL-CE - db.Execute(@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType -)", - new { objType = memberObjectType }); - } - else - { - // assume number of ctypes won't blow IN(...) - // must support SQL-CE - db.Execute($@"DELETE FROM cmsContentNu -WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType=@objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) -)", - new { objType = memberObjectType, ctypes = contentTypeIdsA }); - } - - // insert back - if anything fails the transaction will rollback - var query = scope.SqlContext.Query(); - if (contentTypeIds != null && contentTypeIdsA.Length > 0) - query = query.WhereIn(x => x.ContentTypeId, contentTypeIdsA); // assume number of ctypes won't blow IN(...) - - long pageIndex = 0; - long processed = 0; - long total; - do - { - var descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path")); - var items = descendants.Select(m => GetDto(m, false)).ToArray(); - db.BulkInsertRecords(items); - processed += items.Length; - } while (processed < total); - } + public override void Rebuild( + int groupSize = 5000, + IReadOnlyCollection contentTypeIds = null, + IReadOnlyCollection mediaTypeIds = null, + IReadOnlyCollection memberTypeIds = null) + => _publishedContentService.Rebuild(groupSize, contentTypeIds, mediaTypeIds, memberTypeIds); public bool VerifyContentDbCache() { + // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - var ok = VerifyContentDbCacheLocked(scope); + var ok = _publishedContentService.VerifyContentDbCache(); scope.Complete(); return ok; } } - // assumes content tree lock - private bool VerifyContentDbCacheLocked(IScope scope) - { - // every document should have a corresponding row for edited properties - // and if published, may have a corresponding row for published properties - - var contentObjectType = Constants.ObjectTypes.Document; - var db = scope.Database; - - var count = db.ExecuteScalar($@"SELECT COUNT(*) -FROM umbracoNode -JOIN {Constants.DatabaseSchema.Tables.Document} ON umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId -LEFT JOIN cmsContentNu nuEdited ON (umbracoNode.id=nuEdited.nodeId AND nuEdited.published=0) -LEFT JOIN cmsContentNu nuPublished ON (umbracoNode.id=nuPublished.nodeId AND nuPublished.published=1) -WHERE umbracoNode.nodeObjectType=@objType -AND nuEdited.nodeId IS NULL OR ({Constants.DatabaseSchema.Tables.Document}.published=1 AND nuPublished.nodeId IS NULL);" - , new { objType = contentObjectType }); - - return count == 0; - } - public bool VerifyMediaDbCache() { + // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - var ok = VerifyMediaDbCacheLocked(scope); + var ok = _publishedContentService.VerifyMediaDbCache(); scope.Complete(); return ok; } } - // assumes media tree lock - public bool VerifyMediaDbCacheLocked(IScope scope) - { - // every media item should have a corresponding row for edited properties - - var mediaObjectType = Constants.ObjectTypes.Media; - var db = scope.Database; - - var count = db.ExecuteScalar(@"SELECT COUNT(*) -FROM umbracoNode -LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsContentNu.nodeId IS NULL -", new { objType = mediaObjectType }); - - return count == 0; - } - public bool VerifyMemberDbCache() { + // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MemberTree); - var ok = VerifyMemberDbCacheLocked(scope); + var ok = _publishedContentService.VerifyMemberDbCache(); scope.Complete(); return ok; } } - // assumes member tree lock - public bool VerifyMemberDbCacheLocked(IScope scope) - { - // every member item should have a corresponding row for edited properties - - var memberObjectType = Constants.ObjectTypes.Member; - var db = scope.Database; - - var count = db.ExecuteScalar(@"SELECT COUNT(*) -FROM umbracoNode -LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0) -WHERE umbracoNode.nodeObjectType=@objType -AND cmsContentNu.nodeId IS NULL -", new { objType = memberObjectType }); - - return count == 0; - } - #endregion #region Instrument diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs new file mode 100644 index 0000000000..20ce74ea70 --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -0,0 +1,187 @@ +using System; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Changes; +using Umbraco.Core.Services.Implement; +using Umbraco.Infrastructure.PublishedCache.Persistence; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + public class PublishedSnapshotServiceEventHandler : IDisposable + { + private readonly IRuntimeState _runtime; + private bool _disposedValue; + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly INuCacheContentService _publishedContentService; + + public PublishedSnapshotServiceEventHandler( + IRuntimeState runtime, + IPublishedSnapshotService publishedSnapshotService, + INuCacheContentService publishedContentService) + { + _runtime = runtime; + _publishedSnapshotService = publishedSnapshotService; + _publishedContentService = publishedContentService; + } + + public bool Start() + { + // however, the cache is NOT available until we are configured, because loading + // content (and content types) from database cannot be consistent (see notes in "Handle + // Notifications" region), so + // - notifications will be ignored + // - trying to obtain a published snapshot from the service will throw + if (_runtime.Level != RuntimeLevel.Run) + { + return false; + } + + // this initializes the caches. + // TODO: This is still temporal coupling (i.e. Initialize) + _publishedSnapshotService.LoadCachesOnStartup(); + + // we always want to handle repository events, configured or not + // assuming no repository event will trigger before the whole db is ready + // (ideally we'd have Upgrading.App vs Upgrading.Data application states...) + InitializeRepositoryEvents(); + + return true; + } + + private void InitializeRepositoryEvents() + { + // TODO: The reason these events are in the repository is for legacy, the events should exist at the service + // level now since we can fire these events within the transaction... so move the events to service level + + // plug repository event handlers + // these trigger within the transaction to ensure consistency + // and are used to maintain the central, database-level XML cache + DocumentRepository.ScopeEntityRemove += OnContentRemovingEntity; + DocumentRepository.ScopedEntityRefresh += DocumentRepository_ScopedEntityRefresh; + MediaRepository.ScopeEntityRemove += OnMediaRemovingEntity; + MediaRepository.ScopedEntityRefresh += MediaRepository_ScopedEntityRefresh; + MemberRepository.ScopeEntityRemove += OnMemberRemovingEntity; + MemberRepository.ScopedEntityRefresh += MemberRepository_ScopedEntityRefresh; + + // plug + ContentTypeService.ScopedRefreshedEntity += OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity += OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity += OnMemberTypeRefreshedEntity; + + // TODO: This should be a cache refresher call! + LocalizationService.SavedLanguage += OnLanguageSaved; + } + + private void TearDownRepositoryEvents() + { + DocumentRepository.ScopeEntityRemove -= OnContentRemovingEntity; + DocumentRepository.ScopedEntityRefresh -= DocumentRepository_ScopedEntityRefresh; + MediaRepository.ScopeEntityRemove -= OnMediaRemovingEntity; + MediaRepository.ScopedEntityRefresh -= MediaRepository_ScopedEntityRefresh; + MemberRepository.ScopeEntityRemove -= OnMemberRemovingEntity; + MemberRepository.ScopedEntityRefresh -= MemberRepository_ScopedEntityRefresh; + ContentTypeService.ScopedRefreshedEntity -= OnContentTypeRefreshedEntity; + MediaTypeService.ScopedRefreshedEntity -= OnMediaTypeRefreshedEntity; + MemberTypeService.ScopedRefreshedEntity -= OnMemberTypeRefreshedEntity; + LocalizationService.SavedLanguage -= OnLanguageSaved; // TODO: Shouldn't this be a cache refresher event? + } + + // note: if the service is not ready, ie _isReady is false, then we still handle repository events, + // because we can, we do not need a working published snapshot to do it - the only reason why it could cause an + // issue is if the database table is not ready, but that should be prevented by migrations. + + // we need them to be "repository" events ie to trigger from within the repository transaction, + // because they need to be consistent with the content that is being refreshed/removed - and that + // should be guaranteed by a DB transaction + private void OnContentRemovingEntity(DocumentRepository sender, DocumentRepository.ScopedEntityEventArgs args) + => _publishedContentService.DeleteContentItem(args.Entity); + + private void OnMediaRemovingEntity(MediaRepository sender, MediaRepository.ScopedEntityEventArgs args) + => _publishedContentService.DeleteContentItem(args.Entity); + + private void OnMemberRemovingEntity(MemberRepository sender, MemberRepository.ScopedEntityEventArgs args) + => _publishedContentService.DeleteContentItem(args.Entity); + + private void MemberRepository_ScopedEntityRefresh(MemberRepository sender, ContentRepositoryBase.ScopedEntityEventArgs e) + => _publishedContentService.RefreshEntity(e.Entity); + + private void MediaRepository_ScopedEntityRefresh(MediaRepository sender, ContentRepositoryBase.ScopedEntityEventArgs e) + => _publishedContentService.RefreshEntity(e.Entity); + + private void DocumentRepository_ScopedEntityRefresh(DocumentRepository sender, ContentRepositoryBase.ScopedEntityEventArgs e) + => _publishedContentService.RefreshContent(e.Entity); + + private void OnContentTypeRefreshedEntity(IContentTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var contentTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (contentTypeIds.Any()) + { + _publishedSnapshotService.Rebuild(contentTypeIds: contentTypeIds); + } + } + + private void OnMediaTypeRefreshedEntity(IMediaTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var mediaTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (mediaTypeIds.Any()) + { + _publishedSnapshotService.Rebuild(mediaTypeIds: mediaTypeIds); + } + } + + private void OnMemberTypeRefreshedEntity(IMemberTypeService sender, ContentTypeChange.EventArgs args) + { + const ContentTypeChangeTypes types // only for those that have been refreshed + = ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther; + var memberTypeIds = args.Changes.Where(x => x.ChangeTypes.HasTypesAny(types)).Select(x => x.Item.Id).ToArray(); + if (memberTypeIds.Any()) + { + _publishedSnapshotService.Rebuild(memberTypeIds: memberTypeIds); + } + } + + /// + /// If a is ever saved with a different culture, we need to rebuild all of the content nucache table + /// + private void OnLanguageSaved(ILocalizationService sender, Core.Events.SaveEventArgs e) + { + // TODO: This should be a cache refresher call! + + // culture changed on an existing language + var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); + if (cultureChanged) + { + // Rebuild all content types + _publishedSnapshotService.Rebuild(contentTypeIds: Array.Empty()); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + TearDownRepositoryEvents(); + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index b0ba97eedf..e0ef0434c9 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -114,7 +114,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor var languageRepository = new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), globalSettings); contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); - var entityRepository = new EntityRepository(scopeAccessor); + var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs index 3fb518661c..8fbfc765d7 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/EntityRepositoryTest.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Repositories.Implement; @@ -19,7 +20,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor { private EntityRepository CreateRepository(IScopeAccessor scopeAccessor) { - var entityRepository = new EntityRepository(scopeAccessor); + var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); return entityRepository; } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs index 7d3dcc7202..f504218cec 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Logging; using Moq; @@ -53,7 +53,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); - var entityRepository = new EntityRepository(scopeAccessor); + var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var mediaUrlGenerators = new MediaUrlGeneratorCollection(Enumerable.Empty()); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs index 26efdf325a..b73af3fdfc 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs @@ -19,7 +19,7 @@ using Umbraco.Tests.Testing; namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositories { [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Boot = true)] + [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RelationRepositoryTest : UmbracoIntegrationTest { private RelationType _relateContent; @@ -432,7 +432,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor { var accessor = (IScopeAccessor)ScopeProvider; var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of>()); - var entityRepository = new EntityRepository(accessor); + var entityRepository = new EntityRepository(accessor, AppCaches.Disabled); var relationRepository = new RelationRepository(accessor, Mock.Of>(), relationTypeRepository, entityRepository); relationTypeRepository.Save(_relateContent); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index 6deb579ad1..5cab23c4c9 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -262,7 +262,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Microsoft.Extensions.Options.Options.Create(globalSettings)); var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); - var entityRepository = new EntityRepository(scopeAccessor); + var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); var propertyEditors = new Lazy(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty()))); var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty()); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index b7ab3ceea1..4f4a852902 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -42,10 +42,9 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services [SetUp] public void SetupTestData() { - - //This is super nasty, but this lets us initialize the cache while it is empty. - var publishedSnapshotService = GetRequiredService() as PublishedSnapshotService; - publishedSnapshotService?.OnApplicationInit(null, EventArgs.Empty); + // This is super nasty, but this lets us initialize the cache while it is empty. + // var publishedSnapshotService = GetRequiredService() as PublishedSnapshotService; + // publishedSnapshotService?.OnApplicationInit(null, EventArgs.Empty); if (_langFr == null && _langEs == null) { diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index a17eb71ab0..8e5250499c 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -33,6 +33,7 @@ using Umbraco.Web.PublishedCache.NuCache.DataSource; using Current = Umbraco.Web.Composing.Current; using Umbraco.Core.Serialization; using Umbraco.Net; +using Umbraco.Infrastructure.PublishedCache.Persistence; namespace Umbraco.Tests.PublishedContent { @@ -148,11 +149,9 @@ namespace Umbraco.Tests.PublishedContent // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - var lifetime = new Mock(); - _snapshotService = new PublishedSnapshotService(options, + _snapshotService = new PublishedSnapshotService( + options, null, - lifetime.Object, - runtime, serviceContext, contentTypeFactory, _snapshotAccessor, @@ -160,23 +159,17 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), NullLoggerFactory.Instance, scopeProvider.Object, - Mock.Of(), - Mock.Of(), - Mock.Of(), - new TestDefaultCultureAccessor(), _source, + new TestDefaultCultureAccessor(), Options.Create(globalSettings), Mock.Of(), PublishedModelFactory, - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider(TestHelper.ShortStringHelper) }), hostingEnvironment, - Mock.Of(), TestHelper.IOHelper, Options.Create(nuCacheSettings)); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); - lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); Mock.Get(factory).Setup(x => x.GetService(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index d689215081..bc87967780 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data; using System.Linq; @@ -189,11 +189,9 @@ namespace Umbraco.Tests.PublishedContent // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - var lifetime = new Mock(); - _snapshotService = new PublishedSnapshotService(options, + _snapshotService = new PublishedSnapshotService( + options, null, - lifetime.Object, - runtime, serviceContext, contentTypeFactory, new TestPublishedSnapshotAccessor(), @@ -201,22 +199,15 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), NullLoggerFactory.Instance, scopeProvider, - Mock.Of(), - Mock.Of(), - Mock.Of(), - new TestDefaultCultureAccessor(), dataSource, + new TestDefaultCultureAccessor(), Microsoft.Extensions.Options.Options.Create(globalSettings), Mock.Of(), publishedModelFactory, - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider(TestHelper.ShortStringHelper) }), TestHelper.GetHostingEnvironment(), - Mock.Of(), TestHelper.IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); - lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); - // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 1a8e485634..cd733abad2 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -17,6 +17,7 @@ using Umbraco.Core.Services; 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; @@ -71,7 +72,7 @@ namespace Umbraco.Tests.Scoping protected override IPublishedSnapshotService CreatePublishedSnapshotService(GlobalSettings globalSettings = null) { var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; - var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor); + var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Current.UmbracoContextAccessor); var runtimeStateMock = new Mock(); runtimeStateMock.Setup(x => x.Level).Returns(() => RuntimeLevel.Run); @@ -85,27 +86,23 @@ namespace Umbraco.Tests.Scoping var nuCacheSettings = new NuCacheSettings(); var lifetime = new Mock(); + var repository = new NuCacheContentRepository(ScopeProvider, AppCaches.Disabled, Mock.Of>(), memberRepository, documentRepository, mediaRepository, Mock.Of(), new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider(ShortStringHelper) })); var snapshotService = new PublishedSnapshotService( options, null, - lifetime.Object, - runtimeStateMock.Object, ServiceContext, contentTypeFactory, publishedSnapshotAccessor, Mock.Of(), - ProfilingLogger, + base.ProfilingLogger, NullLoggerFactory.Instance, ScopeProvider, - documentRepository, mediaRepository, memberRepository, + new NuCacheContentService(repository, ScopeProvider, NullLoggerFactory.Instance, Mock.Of()), DefaultCultureAccessor, - new DatabaseDataSource(Mock.Of>()), Microsoft.Extensions.Options.Options.Create(globalSettings ?? new GlobalSettings()), Factory.GetRequiredService(), new NoopPublishedModelFactory(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider(ShortStringHelper) }), hostingEnvironment, - Mock.Of(), IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); diff --git a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs index fc8a48581a..d8a413fab9 100644 --- a/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs +++ b/src/Umbraco.Tests/Testing/Objects/TestDataSource.cs @@ -1,16 +1,17 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Scoping; +using Umbraco.Infrastructure.PublishedCache.Persistence; using Umbraco.Web; using Umbraco.Web.PublishedCache.NuCache; -using Umbraco.Web.PublishedCache.NuCache.DataSource; namespace Umbraco.Tests.Testing.Objects { - internal class TestDataSource : IDataSource + internal class TestDataSource : INuCacheContentService { private IPublishedModelFactory PublishedModelFactory { get; } = new NoopPublishedModelFactory(); @@ -19,27 +20,23 @@ namespace Umbraco.Tests.Testing.Objects : this((IEnumerable) kits) { } - public TestDataSource(IEnumerable kits) - { - Kits = kits.ToDictionary(x => x.Node.Id, x => x); - } + public TestDataSource(IEnumerable kits) => Kits = kits.ToDictionary(x => x.Node.Id, x => x); public Dictionary Kits { get; } // note: it is important to clone the returned kits, as the inner // ContentNode is directly reused and modified by the snapshot service + public ContentNodeKit GetContentSource(int id) + => Kits.TryGetValue(id, out ContentNodeKit kit) ? kit.Clone(PublishedModelFactory) : default; - public ContentNodeKit GetContentSource(IScope scope, int id) - => Kits.TryGetValue(id, out var kit) ? kit.Clone(PublishedModelFactory) : default; - - public IEnumerable GetAllContentSources(IScope scope) + public IEnumerable GetAllContentSources() => Kits.Values .OrderBy(x => x.Node.Level) .ThenBy(x => x.Node.ParentContentId) .ThenBy(x => x.Node.SortOrder) .Select(x => x.Clone(PublishedModelFactory)); - public IEnumerable GetBranchContentSources(IScope scope, int id) + public IEnumerable GetBranchContentSources(int id) => Kits.Values .Where(x => x.Node.Path.EndsWith("," + id) || x.Node.Path.Contains("," + id + ",")) .OrderBy(x => x.Node.Level) @@ -47,7 +44,7 @@ namespace Umbraco.Tests.Testing.Objects .ThenBy(x => x.Node.SortOrder) .Select(x => x.Clone(PublishedModelFactory)); - public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids) + public IEnumerable GetTypeContentSources(IEnumerable ids) => Kits.Values .Where(x => ids.Contains(x.ContentTypeId)) .OrderBy(x => x.Node.Level) @@ -55,24 +52,19 @@ namespace Umbraco.Tests.Testing.Objects .ThenBy(x => x.Node.SortOrder) .Select(x => x.Clone(PublishedModelFactory)); - public ContentNodeKit GetMediaSource(IScope scope, int id) - { - return default; - } + public ContentNodeKit GetMediaSource(int id) => default; - public IEnumerable GetAllMediaSources(IScope scope) - { - return Enumerable.Empty(); - } + public IEnumerable GetAllMediaSources() => Enumerable.Empty(); - public IEnumerable GetBranchMediaSources(IScope scope, int id) - { - return Enumerable.Empty(); - } + public IEnumerable GetBranchMediaSources(int id) => Enumerable.Empty(); - public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids) - { - return Enumerable.Empty(); - } + public IEnumerable GetTypeMediaSources(IEnumerable ids) => Enumerable.Empty(); + public void DeleteContentItem(IContentBase item) => throw new NotImplementedException(); + public void RefreshContent(IContent content) => throw new NotImplementedException(); + public void RefreshEntity(IContentBase content) => throw new NotImplementedException(); + public bool VerifyContentDbCache() => throw new NotImplementedException(); + public bool VerifyMediaDbCache() => throw new NotImplementedException(); + public bool VerifyMemberDbCache() => throw new NotImplementedException(); + public void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) => throw new NotImplementedException(); } } diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 99a2b2aa3f..7290aa9b0e 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -11,6 +11,7 @@ using Umbraco.Core; using Umbraco.Core.Hosting; using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Web.Common.Middleware; +using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.Extensions { @@ -36,6 +37,7 @@ namespace Umbraco.Extensions // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed // before endpoint routing middleware. app.UseUmbracoRouting(); + app.UseUmbracoContentCache(); app.UseStatusCodePages(); @@ -176,6 +178,16 @@ namespace Umbraco.Extensions return app; } + /// + /// Enables the Umbraco content cache + /// + public static IApplicationBuilder UseUmbracoContentCache(this IApplicationBuilder app) + { + PublishedSnapshotServiceEventHandler publishedContentEvents = app.ApplicationServices.GetRequiredService(); + publishedContentEvents.Start(); + return app; + } + /// /// Ensures the runtime is shutdown when the application is shutting down /// diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 6b5d305a64..56f093ed2b 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -55,7 +55,7 @@ namespace Umbraco.Web.Common.Middleware return; } - _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext + _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext, TODO: Why? UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); try diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index b09fde0a6a..fdc488c65b 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index a6582f03ae..328b7cc1d4 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -190,7 +190,7 @@ namespace Umbraco.Web.Website.Routing { ControllerActionDescriptor descriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items .Cast() - .First(x => + .FirstOrDefault(x => x.ControllerName.Equals(controllerName)); return descriptor?.ControllerTypeInfo; From 63ab8ec52c773c0d5c22b67b5c30500df0b2f0c0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 10 Dec 2020 18:09:32 +1100 Subject: [PATCH 006/127] Lots of notes, removes data tokens, --- src/Umbraco.Core/Constants-Web.cs | 11 +- .../IPublishedSnapshotService.cs | 7 + src/Umbraco.Core/Routing/IPublishedRequest.cs | 2 +- src/Umbraco.Infrastructure/Scoping/Scope.cs | 32 ++-- .../Scoping/ScopeProvider.cs | 12 +- .../Umbraco.Core/Components/ComponentTests.cs | 3 +- .../ModelBinders/ContentModelBinderTests.cs | 27 ++- .../ModelBinders/RenderModelBinderTests.cs | 23 ++- ...RenderIndexActionSelectorAttributeTests.cs | 169 ------------------ .../Controllers/SurfaceControllerTests.cs | 11 +- .../XmlPublishedSnapshotService.cs | 6 +- .../Scoping/ScopeEventDispatcherTests.cs | 20 +-- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 4 +- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 31 +--- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 9 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../Web/Mvc/SurfaceControllerTests.cs | 4 +- .../Web/WebExtensionMethodTests.cs | 139 -------------- .../AspNetCore/UmbracoViewPage.cs | 7 +- .../Controllers/RenderController.cs | 37 ++-- .../Macros/MacroRenderer.cs | 35 ++-- .../Macros/PartialViewMacroEngine.cs | 119 ++++++------ .../ModelBinders/ContentModelBinder.cs | 33 ++-- .../Routing/UmbracoRouteValues.cs | 68 +++++++ .../ActionResults/UmbracoPageResult.cs | 4 +- .../RenderIndexActionSelectorAttribute.cs | 60 ------- .../Controllers/SurfaceController.cs | 32 ++-- .../Controllers/UmbracoRenderingDefaults.cs | 1 + .../Routing/RouteDefinition.cs | 29 --- .../Routing/UmbracoRouteValueTransformer.cs | 123 ++++++------- .../ViewEngines/PluginViewEngine.cs | 26 +-- .../ViewEngines/RenderViewEngine.cs | 59 +++--- .../Mvc/AreaRegistrationExtensions.cs | 6 +- .../Mvc/ControllerContextExtensions.cs | 38 ---- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 25 +-- src/Umbraco.Web/Mvc/SurfaceController.cs | 8 +- src/Umbraco.Web/Mvc/UmbracoPageResult.cs | 6 +- .../Mvc/UmbracoViewPageOfTModel.cs | 42 ++--- .../Mvc/UmbracoVirtualNodeRouteHandler.cs | 14 +- .../Runtime/WebInitialComponent.cs | 11 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 41 files changed, 464 insertions(+), 831 deletions(-) delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs delete mode 100644 src/Umbraco.Tests/Web/WebExtensionMethodTests.cs rename src/{Umbraco.Web.Website => Umbraco.Web.Common}/Controllers/RenderController.cs (63%) create mode 100644 src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs delete mode 100644 src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs delete mode 100644 src/Umbraco.Web.Website/Routing/RouteDefinition.cs delete mode 100644 src/Umbraco.Web/Mvc/ControllerContextExtensions.cs diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index 5d059d8a23..e29d793909 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core +namespace Umbraco.Core { public static partial class Constants { @@ -7,10 +7,11 @@ /// public static class Web { - public const string UmbracoContextDataToken = "umbraco-context"; - public const string UmbracoDataToken = "umbraco"; - public const string PublishedDocumentRequestDataToken = "umbraco-doc-request"; - public const string CustomRouteDataToken = "umbraco-custom-route"; + // TODO: Need to review these... + //public const string UmbracoContextDataToken = "umbraco-context"; + //public const string UmbracoDataToken = "umbraco"; + //public const string PublishedDocumentRequestDataToken = "umbraco-doc-request"; + //public const string CustomRouteDataToken = "umbraco-custom-route"; public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def"; /// diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 73ce858b52..cc526ffe6e 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -23,6 +23,13 @@ namespace Umbraco.Web.PublishedCache * */ + /// + /// Loads the caches on startup - called once during startup + /// TODO: Temporary, this is temporal coupling, we cannot use IUmbracoApplicationLifetime.ApplicationInit (which we want to delete) + /// handler because that is executed with netcore's IHostApplicationLifetime.ApplicationStarted mechanism which fires async + /// which we don't want since this will not have initialized before our endpoints execute. So for now this is explicitly + /// called on UseUmbracoContentCaching on startup. + /// void LoadCachesOnStartup(); /// diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index f357108a4e..51fc9ccf64 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -11,7 +11,7 @@ namespace Umbraco.Web.Routing /// /// Gets the UmbracoContext. /// - IUmbracoContext UmbracoContext { get; } + IUmbracoContext UmbracoContext { get; } // TODO: This should be injected and removed from here /// /// Gets or sets the cleaned up Uri used for routing. diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs index 65e8e343f7..84945c78d4 100644 --- a/src/Umbraco.Infrastructure/Scoping/Scope.cs +++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Microsoft.Extensions.Logging; using Umbraco.Core.Cache; @@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; private readonly ILogger _logger; - private readonly ITypeFinder _typeFinder; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; @@ -38,10 +37,15 @@ namespace Umbraco.Core.Scoping private IEventDispatcher _eventDispatcher; // initializes a new scope - private Scope(ScopeProvider scopeProvider, + private Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IScopeContext scopeContext, bool detachable, + ILogger logger, + FileSystems fileSystems, + Scope parent, + IScopeContext scopeContext, + bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, @@ -53,7 +57,6 @@ namespace Umbraco.Core.Scoping _coreDebugSettings = coreDebugSettings; _mediaFileSystem = mediaFileSystem; _logger = logger; - _typeFinder = typeFinder; Context = scopeContext; @@ -117,31 +120,38 @@ namespace Umbraco.Core.Scoping } // initializes a new scope - public Scope(ScopeProvider scopeProvider, + public Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, IScopeContext scopeContext, + ILogger logger, + FileSystems fileSystems, + bool detachable, + IScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent - public Scope(ScopeProvider scopeProvider, + public Scope( + ScopeProvider scopeProvider, CoreDebugSettings coreDebugSettings, IMediaFileSystem mediaFileSystem, - ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, + ILogger logger, + FileSystems fileSystems, + Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs index 52c096b224..151c4cfb3c 100644 --- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Data; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -23,13 +23,12 @@ namespace Umbraco.Core.Scoping { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly ITypeFinder _typeFinder; private readonly IRequestCache _requestCache; private readonly FileSystems _fileSystems; private readonly CoreDebugSettings _coreDebugSettings; private readonly IMediaFileSystem _mediaFileSystem; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, ITypeFinder typeFinder, IRequestCache requestCache) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; @@ -37,7 +36,6 @@ namespace Umbraco.Core.Scoping _mediaFileSystem = mediaFileSystem; _logger = logger; _loggerFactory = loggerFactory; - _typeFinder = typeFinder; _requestCache = requestCache; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -256,7 +254,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -312,13 +310,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 44aacab944..3bc29c9a9d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -39,14 +39,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components var mock = new Mock(); var loggerFactory = NullLoggerFactory.Instance; var logger = loggerFactory.CreateLogger("GenericLogger"); - var typeFinder = TestHelper.GetTypeFinder(); var globalSettings = new GlobalSettings(); var connectionStrings = new ConnectionStrings(); var f = new UmbracoDatabaseFactory(loggerFactory.CreateLogger(), loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); var coreDebug = new CoreDebugSettings(); var mediaFileSystem = Mock.Of(); - var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, typeFinder, NoAppCache.Instance); + var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(loggerFactory.CreateLogger); 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 b414e49e95..ba5910da29 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -9,7 +9,9 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; +using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { @@ -20,7 +22,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_UmbracoDataToken_Not_In_Route_Data() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), withUmbracoDataToken: false); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, withUmbracoDataToken: false); var binder = new ContentModelBinder(); // Act @@ -34,7 +37,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_Source_Not_Of_Expected_Type() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new NonContentModel()); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new NonContentModel()); var binder = new ContentModelBinder(); // Act @@ -48,8 +52,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void BindModel_Returns_If_Same_Type() { // Arrange - var content = new ContentModel(CreatePublishedContent()); - var bindingContext = CreateBindingContext(typeof(ContentModel), source: content); + IPublishedContent pc = CreatePublishedContent(); + var content = new ContentModel(pc); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: content); var binder = new ContentModelBinder(); // Act @@ -63,7 +68,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: CreatePublishedContent()); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: pc); var binder = new ContentModelBinder(); // Act @@ -77,7 +83,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model_Of_T() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(new ContentType2(CreatePublishedContent()))); + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new ContentModel(new ContentType2(pc))); var binder = new ContentModelBinder(); // Act @@ -87,12 +94,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders Assert.True(bindingContext.Result.IsModelSet); } - private ModelBindingContext CreateBindingContext(Type modelType, bool withUmbracoDataToken = true, object source = null) + private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) { var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) - routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + { + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs index 501c10551d..660a9b7bd1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -9,7 +9,9 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; +using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { @@ -106,8 +108,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void No_DataToken_Returns_Null() { - var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(ContentModel), false, content); + IPublishedContent pc = Mock.Of(); + var content = new MyContent(pc); + var bindingContext = CreateBindingContext(typeof(ContentModel), pc, false, content); _contentModelBinder.BindModelAsync(bindingContext); @@ -117,7 +120,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void Invalid_DataToken_Model_Type_Returns_Null() { - var bindingContext = CreateBindingContext(typeof(IPublishedContent), source: "Hello"); + IPublishedContent pc = Mock.Of(); + var bindingContext = CreateBindingContext(typeof(IPublishedContent), pc, source: "Hello"); _contentModelBinder.BindModelAsync(bindingContext); Assert.IsNull(bindingContext.Result.Model); } @@ -125,20 +129,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation() { - var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(MyContent), source: content); + IPublishedContent pc = Mock.Of(); + var content = new MyContent(pc); + var bindingContext = CreateBindingContext(typeof(MyContent), pc, source: content); _contentModelBinder.BindModelAsync(bindingContext); Assert.AreEqual(content, bindingContext.Result.Model); } - private ModelBindingContext CreateBindingContext(Type modelType, bool withUmbracoDataToken = true, object source = null) + private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) { var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) - routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + { + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs deleted file mode 100644 index bf5c422bd8..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -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.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using Umbraco.Web.Models; -using Umbraco.Web.Mvc; -using Umbraco.Web.Website.Controllers; - -namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers -{ - [TestFixture] - public class RenderIndexActionSelectorAttributeTests - { - [Test] - public void IsValidForRequest__ensure_caching_works() - { - var sut = new RenderIndexActionSelectorAttribute(); - - var actionDescriptor = - GetRenderMvcControllerIndexMethodFromCurrentType(typeof(MatchesDefaultIndexController)).First(); - var actionDescriptorCollectionProviderMock = new Mock(); - actionDescriptorCollectionProviderMock.Setup(x => x.ActionDescriptors) - .Returns(new ActionDescriptorCollection(Array.Empty(), 1)); - - var routeContext = CreateRouteContext(actionDescriptorCollectionProviderMock.Object); - - // Call the method multiple times - for (var i = 0; i < 1; i++) - { - sut.IsValidForRequest(routeContext, actionDescriptor); - } - - //Ensure the underlying ActionDescriptors is only called once. - actionDescriptorCollectionProviderMock.Verify(x=>x.ActionDescriptors, Times.Once); - } - - [Test] - [TestCase(typeof(MatchesDefaultIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesOverriddenIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesCustomIndexController), - "Index", new[] { typeof(ContentModel), typeof(int) }, typeof(IActionResult), ExpectedResult = false)] - [TestCase(typeof(MatchesAsyncIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(Task), ExpectedResult = false)] - public bool IsValidForRequest__must_return_the_expected_result(Type controllerType, string actionName, Type[] parameterTypes, Type returnType) - { - //Fake all IActionDescriptor's that will be returned by IActionDescriptorCollectionProvider - var actionDescriptors = GetRenderMvcControllerIndexMethodFromCurrentType(controllerType); - - // Find the one that match the current request - var actualActionDescriptor = actionDescriptors.Single(x => x.ActionName == actionName - && x.ControllerTypeInfo.Name == controllerType.Name - && x.MethodInfo.ReturnType == returnType - && x.MethodInfo.GetParameters().Select(m => m.ParameterType).SequenceEqual(parameterTypes)); - - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext - var sut = new RenderIndexActionSelectorAttribute(); - - var routeContext = CreateRouteContext(new TestActionDescriptorCollectionProvider(actionDescriptors)); - - //Act - var result = sut.IsValidForRequest(routeContext, actualActionDescriptor); - return result; - } - - private ControllerActionDescriptor[] GetRenderMvcControllerIndexMethodFromCurrentType(Type controllerType) - { - var actions = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(m => !m.IsSpecialName - && m.GetCustomAttribute() is null - && m.Module.Name.Contains("Umbraco")); - - var actionDescriptors = actions - .Select(x => new ControllerActionDescriptor() - { - ControllerTypeInfo = controllerType.GetTypeInfo(), - ActionName = x.Name, - MethodInfo = x - }).ToArray(); - - return actionDescriptors; - } - - private static RouteContext CreateRouteContext(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) - { - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext - var httpContext = new DefaultHttpContext(); - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(actionDescriptorCollectionProvider); - httpContext.RequestServices = - new DefaultServiceProviderFactory(new ServiceProviderOptions()) - .CreateServiceProvider(serviceCollection); - - // Put the fake httpcontext on the route context. - var routeContext = new RouteContext(httpContext); - return routeContext; - } - - private class TestActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider - { - public TestActionDescriptorCollectionProvider(IReadOnlyList items) - { - ActionDescriptors = new ActionDescriptorCollection(items, 1); - } - - public ActionDescriptorCollection ActionDescriptors { get; } - } - - private class MatchesDefaultIndexController : RenderController - { - public MatchesDefaultIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesOverriddenIndexController : RenderController - { - public override IActionResult Index(ContentModel model) - { - return base.Index(model); - } - - public MatchesOverriddenIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesCustomIndexController : RenderController - { - public IActionResult Index(ContentModel model, int page) - { - return base.Index(model); - } - - public MatchesCustomIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - - private class MatchesAsyncIndexController : RenderController - { - public new async Task Index(ContentModel model) - { - return await Task.FromResult(base.Index(model)); - } - - public MatchesAsyncIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) - { - } - } - } -} 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 d1ffc2044e..5db2d435c9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Services; using Umbraco.Tests.Common; using Umbraco.Tests.Testing; using Umbraco.Web; +using Umbraco.Web.Common.Routing; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web.Security; @@ -164,16 +165,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers var content = Mock.Of(publishedContent => publishedContent.Id == 12345); - var publishedRequestMock = new Mock(); - publishedRequestMock.Setup(x => x.PublishedContent).Returns(content); - - var routeDefinition = new RouteDefinition - { - PublishedRequest = publishedRequestMock.Object - }; + var routeDefinition = new UmbracoRouteValues(content); var routeData = new RouteData(); - routeData.DataTokens.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); + routeData.Values.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of(), Mock.Of()); ctrl.ControllerContext = new ControllerContext() diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 2bc89e0842..9c9e2d1da2 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Core; @@ -161,7 +161,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache var domainCache = new DomainCache(_domainService, _defaultCultureAccessor); return new PublishedSnapshot( - new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache,_variationContextAccessor, previewToken), + new PublishedContentCache(_xmlStore, domainCache, _requestCache, _globalSettings, _contentTypeCache, _routesCache, _variationContextAccessor, previewToken), new PublishedMediaCache(_xmlStore, _mediaService, _userService, _requestCache, _contentTypeCache, _entitySerializer, _umbracoContextAccessor, _variationContextAccessor), new PublishedMemberCache(_xmlStore, _requestCache, _memberService, _contentTypeCache, _userService, _variationContextAccessor), domainCache); @@ -278,5 +278,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache { return "Test status"; } + + public override void LoadCachesOnStartup() { } } } diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 4e1540a525..e663996d60 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; @@ -35,11 +35,11 @@ namespace Umbraco.Tests.Scoping DoThing2 = null; DoThing3 = null; - var services = TestHelper.GetRegister(); + var services = TestHelper.GetRegister(); var composition = new UmbracoBuilder(services, Mock.Of(), TestHelper.GetMockedTypeLoader()); - _testObjects = new TestObjects(services); + _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())); @@ -47,7 +47,7 @@ namespace Umbraco.Tests.Scoping Current.Factory = composition.CreateServiceProvider(); } - + [TestCase(false, true, true)] [TestCase(false, true, false)] [TestCase(false, false, true)] @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Scoping { //content1 will be filtered from the args - scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[]{ content1 , content3})); + scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(new[] { content1, content3 })); scope.Events.Dispatch(DoDeleteForContent, this, new DeleteEventArgs(content1), "DoDeleteForContent"); scope.Events.Dispatch(DoSaveForContent, this, new SaveEventArgs(content2)); //this entire event will be filtered @@ -156,7 +156,7 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(content3.Id, ((SaveEventArgs)events[0].Args).SavedEntities.First().Id); Assert.AreEqual(typeof(DeleteEventArgs), events[1].Args.GetType()); - Assert.AreEqual(content1.Id, ((DeleteEventArgs) events[1].Args).DeletedEntities.First().Id); + Assert.AreEqual(content1.Id, ((DeleteEventArgs)events[1].Args).DeletedEntities.First().Id); Assert.AreEqual(typeof(SaveEventArgs), events[2].Args.GetType()); Assert.AreEqual(content2.Id, ((SaveEventArgs)events[2].Args).SavedEntities.First().Id); @@ -177,8 +177,8 @@ namespace Umbraco.Tests.Scoping var scopeProvider = _testObjects.GetScopeProvider(NullLoggerFactory.Instance); using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher())) { - scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new [] { content }), "Unpublished"); - scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new [] { content }), "Deleted"); + scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs(new[] { content }), "Unpublished"); + scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs(new[] { content }), "Deleted"); // see U4-10764 var events = scope.Events.GetEvents(EventDefinitionFilter.All).ToArray(); @@ -258,9 +258,9 @@ namespace Umbraco.Tests.Scoping // events have been queued var events = scope.Events.GetEvents(EventDefinitionFilter.FirstIn).ToArray(); Assert.AreEqual(1, events.Length); - Assert.AreEqual(content1, ((SaveEventArgs) events[0].Args).SavedEntities.First()); + Assert.AreEqual(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First()); Assert.IsTrue(object.ReferenceEquals(content1, ((SaveEventArgs)events[0].Args).SavedEntities.First())); - Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs) events[0].Args).SavedEntities.First().UpdateDate); + Assert.AreEqual(content1.UpdateDate, ((SaveEventArgs)events[0].Args).SavedEntities.First().UpdateDate); } } diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 6e27bdd07c..fc3b0d6dba 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -27,7 +27,7 @@ namespace Umbraco.Tests.TestHelpers protected ISqlContext SqlContext { get; private set; } - internal TestObjects TestObjects = new TestObjects(null); + internal TestObjects TestObjects = new TestObjects(); protected Sql Sql() { diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 91b82caccb..3b17861fb3 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -1,7 +1,5 @@ -using System; +using System; using System.Configuration; -using System.IO; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; @@ -9,20 +7,14 @@ using Moq; using NPoco; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Events; -using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Scoping; using Umbraco.Persistance.SqlCe; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.TestHelpers.Stubs; using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Tests.TestHelpers @@ -32,11 +24,9 @@ namespace Umbraco.Tests.TestHelpers /// internal partial class TestObjects { - private readonly IServiceCollection _register; - public TestObjects(IServiceCollection register) + public TestObjects() { - _register = register; } /// @@ -69,19 +59,7 @@ namespace Umbraco.Tests.TestHelpers return new UmbracoDatabase(connection, sqlContext, logger, TestHelper.BulkSqlInsertProvider); } - private Lazy GetLazyService(IServiceProvider container, Func ctor) - where T : class - { - return new Lazy(() => container?.GetService() ?? ctor(container)); - } - - private T GetRepo(IServiceProvider container) - where T : class, IRepository - { - return container?.GetService() ?? Mock.Of(); - } - - public IScopeProvider GetScopeProvider(ILoggerFactory loggerFactory, ITypeFinder typeFinder = null, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) + public IScopeProvider GetScopeProvider(ILoggerFactory loggerFactory, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) { var globalSettings = new GlobalSettings(); var connectionString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString; @@ -103,11 +81,10 @@ namespace Umbraco.Tests.TestHelpers TestHelper.DbProviderFactoryCreator); } - typeFinder ??= new TypeFinder(loggerFactory.CreateLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); fileSystems ??= new FileSystems(Current.Factory, loggerFactory.CreateLogger(), loggerFactory, TestHelper.IOHelper, Options.Create(globalSettings), TestHelper.GetHostingEnvironment()); var coreDebug = TestHelper.CoreDebugSettings; var mediaFileSystem = Mock.Of(); - return new ScopeProvider(databaseFactory, fileSystems, Microsoft.Extensions.Options.Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, typeFinder, NoAppCache.Instance); + return new ScopeProvider(databaseFactory, fileSystems, Options.Create(coreDebugSettings), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, NoAppCache.Instance); } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 533966f5a9..a3d48e3592 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -1,4 +1,4 @@ -using Examine; +using Examine; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -235,10 +235,7 @@ namespace Umbraco.Tests.Testing services.AddUnique(membershipHelper); - - - - TestObjects = new TestObjects(services); + TestObjects = new TestObjects(); Compose(); Current.Factory = Factory = Builder.CreateServiceProvider(); Initialize(); @@ -494,7 +491,7 @@ namespace Umbraco.Tests.Testing Builder.WithCollectionBuilder(); // empty Builder.Services.AddUnique(factory - => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService(), factory.GetService())); + => TestObjects.GetScopeProvider(_loggerFactory, factory.GetService(), factory.GetService())); Builder.Services.AddUnique(factory => (IScopeAccessor)factory.GetRequiredService()); Builder.ComposeServices(); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 74eed99143..2f9ab25d2a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -275,7 +275,6 @@ - diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index a2bcb4928a..6afc75e931 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; @@ -161,7 +161,7 @@ namespace Umbraco.Tests.Web.Mvc }; var routeData = new RouteData(); - routeData.DataTokens.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); + routeData.Values.Add(Core.Constants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of()); ctrl.ControllerContext = new ControllerContext(Mock.Of(), routeData, ctrl); diff --git a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs b/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs deleted file mode 100644 index 4e52617e6c..0000000000 --- a/src/Umbraco.Tests/Web/WebExtensionMethodTests.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.IO; -using System.Web; -using System.Web.Mvc; -using System.Web.Routing; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Security; -using Umbraco.Tests.Common; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Web; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Tests.Web -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class WebExtensionMethodTests : UmbracoTestBase - { - [Test] - public void RouteDataExtensions_GetUmbracoContext() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - var r1 = new RouteData(); - r1.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); - - Assert.IsTrue(r1.DataTokens.ContainsKey(Core.Constants.Web.UmbracoContextDataToken)); - Assert.AreSame(umbCtx, r1.DataTokens[Core.Constants.Web.UmbracoContextDataToken]); - } - - [Test] - public void ControllerContextExtensions_GetUmbracoContext_From_RouteValues() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - var r1 = new RouteData(); - r1.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); - var ctx1 = CreateViewContext(new ControllerContext(Mock.Of(), r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - var ctx2 = CreateViewContext(new ControllerContext(Mock.Of(), r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - var ctx3 = CreateViewContext(new ControllerContext(Mock.Of(), r3, new MyController())); - - var result = ctx3.GetUmbracoContext(); - - Assert.IsNotNull(result); - Assert.AreSame(umbCtx, result); - } - - [Test] - public void ControllerContextExtensions_GetUmbracoContext_From_Current() - { - var httpContextAccessor = TestHelper.GetHttpContextAccessor(); - - var umbCtx = new UmbracoContext( - httpContextAccessor, - Mock.Of(), - Mock.Of(), - TestObjects.GetGlobalSettings(), - HostingEnvironment, - new TestVariationContextAccessor(), - UriUtility, - new AspNetCookieManager(httpContextAccessor)); - - var httpContext = Mock.Of(); - - var r1 = new RouteData(); - var ctx1 = CreateViewContext(new ControllerContext(httpContext, r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - var ctx2 = CreateViewContext(new ControllerContext(httpContext, r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - var ctx3 = CreateViewContext(new ControllerContext(httpContext, r3, new MyController())); - - Current.UmbracoContextAccessor = new TestUmbracoContextAccessor(); - Current.UmbracoContextAccessor.UmbracoContext = umbCtx; - - var result = ctx3.GetUmbracoContext(); - - Assert.IsNotNull(result); - Assert.AreSame(umbCtx, result); - } - - [Test] - public void ControllerContextExtensions_GetDataTokenInViewContextHierarchy() - { - var r1 = new RouteData(); - r1.DataTokens.Add("hello", "world"); - r1.DataTokens.Add("r", "1"); - var ctx1 = CreateViewContext(new ControllerContext(Mock.Of(), r1, new MyController())); - var r2 = new RouteData(); - r2.DataTokens.Add("ParentActionViewContext", ctx1); - r2.DataTokens.Add("r", "2"); - var ctx2 = CreateViewContext(new ControllerContext(Mock.Of(), r2, new MyController())); - var r3 = new RouteData(); - r3.DataTokens.Add("ParentActionViewContext", ctx2); - r3.DataTokens.Add("r", "3"); - var ctx3 = CreateViewContext(new ControllerContext(Mock.Of(), r3, new MyController())); - - var result = ctx3.GetDataTokenInViewContextHierarchy("hello"); - - Assert.IsNotNull(result as string); - Assert.AreEqual((string) result, "world"); - } - - private static ViewContext CreateViewContext(ControllerContext ctx) - { - return new ViewContext(ctx, Mock.Of(), new ViewDataDictionary(), new TempDataDictionary(), new StringWriter()); - } - - private class MyController : Controller - { } - } -} diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index 4b8f730e45..a97b67a900 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; @@ -19,6 +19,7 @@ using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.AspNetCore { + // TODO: Should be in Views namespace? public abstract class UmbracoViewPage : UmbracoViewPage { @@ -92,6 +93,8 @@ namespace Umbraco.Web.Common.AspNetCore base.WriteLiteral(value); } + // TODO: This trick doesn't work anymore, this method used to be an override. + // Now the model is bound in a different place // maps model protected async Task SetViewDataAsync(ViewDataDictionary viewData) { @@ -111,8 +114,6 @@ namespace Umbraco.Web.Common.AspNetCore ViewData = (ViewDataDictionary) viewData; } - - // viewData is the ViewDataDictionary (maybe ) that we have // modelType is the type of the model that we need to bind to // diff --git a/src/Umbraco.Web.Website/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs similarity index 63% rename from src/Umbraco.Web.Website/Controllers/RenderController.cs rename to src/Umbraco.Web.Common/Controllers/RenderController.cs index 071560d860..ec73c061e2 100644 --- a/src/Umbraco.Web.Website/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -2,13 +2,14 @@ using System; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Logging; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; using Umbraco.Web.Routing; -namespace Umbraco.Web.Website.Controllers +namespace Umbraco.Web.Common.Controllers { /// @@ -17,9 +18,9 @@ namespace Umbraco.Web.Website.Controllers [TypeFilter(typeof(ModelBindingExceptionFilter))] public class RenderController : UmbracoController, IRenderController { - private IPublishedRequest _publishedRequest; private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; + private UmbracoRouteValues _routeValues; /// /// Initializes a new instance of the class. @@ -33,27 +34,27 @@ namespace Umbraco.Web.Website.Controllers /// /// Gets the current content item. /// - protected IPublishedContent CurrentPage => PublishedRequest.PublishedContent; + protected IPublishedContent CurrentPage => UmbracoRouteValues.PublishedContent; /// - /// Gets the current published content request. + /// Gets the /// - protected internal virtual IPublishedRequest PublishedRequest + protected UmbracoRouteValues UmbracoRouteValues { get { - if (_publishedRequest != null) + if (_routeValues != null) { - return _publishedRequest; + return _routeValues; } - if (RouteData.DataTokens.ContainsKey(Core.Constants.Web.PublishedDocumentRequestDataToken) == false) + if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) { - throw new InvalidOperationException("DataTokens must contain an 'umbraco-doc-request' key with a PublishedRequest object"); + throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); } - _publishedRequest = (IPublishedRequest)RouteData.DataTokens[Core.Constants.Web.PublishedDocumentRequestDataToken]; - return _publishedRequest; + _routeValues = (UmbracoRouteValues)def; + return _routeValues; } } @@ -79,22 +80,20 @@ namespace Umbraco.Web.Website.Controllers /// The type of the model. /// The model. /// The action result. - /// If the template found in the route values doesn't physically exist, then an empty ContentResult will be returned. + /// If the template found in the route values doesn't physically exist and exception is thrown protected IActionResult CurrentTemplate(T model) { - var template = ControllerContext.RouteData.Values["action"].ToString(); - if (EnsurePhsyicalViewExists(template) == false) + if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) { - throw new InvalidOperationException("No physical template file was found for template " + template); + throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName); } - return View(template, model); + return View(UmbracoRouteValues.TemplateName, model); } /// /// The default action to render the front-end view. /// - [RenderIndexActionSelector] - public virtual IActionResult Index(ContentModel model) => CurrentTemplate(model); + public virtual IActionResult Index() => CurrentTemplate(new ContentModel(CurrentPage)); } } diff --git a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs index c79c5229fc..9a9e46eefc 100644 --- a/src/Umbraco.Web.Common/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web.Common/Macros/MacroRenderer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -38,7 +38,7 @@ namespace Umbraco.Web.Macros private readonly IHttpContextAccessor _httpContextAccessor; public MacroRenderer( - IProfilingLogger profilingLogger , + IProfilingLogger profilingLogger, ILogger logger, IUmbracoContextAccessor umbracoContextAccessor, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, @@ -53,7 +53,7 @@ namespace Umbraco.Web.Macros IRequestAccessor requestAccessor, IHttpContextAccessor httpContextAccessor) { - _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger )); + _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); _logger = logger; _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -111,12 +111,14 @@ namespace Umbraco.Web.Macros private MacroContent GetMacroContentFromCache(MacroModel model) { // only if cache is enabled - if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return null; + if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) + return null; var cache = _appCaches.RuntimeCache; var macroContent = cache.GetCacheItem(CacheKeys.MacroContentCacheKey + model.CacheIdentifier); - if (macroContent == null) return null; + if (macroContent == null) + return null; _logger.LogDebug("Macro content loaded from cache '{MacroCacheId}'", model.CacheIdentifier); @@ -145,16 +147,19 @@ namespace Umbraco.Web.Macros private void AddMacroContentToCache(MacroModel model, MacroContent macroContent) { // only if cache is enabled - if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) return; + if (_umbracoContextAccessor.UmbracoContext.InPreviewMode || model.CacheDuration <= 0) + return; // just make sure... - if (macroContent == null) return; + if (macroContent == null) + return; // do not cache if it should cache by member and there's not member if (model.CacheByMember) { var key = _memberUserKeyProvider.GetMemberProviderUserKey(); - if (key is null) return; + if (key is null) + return; } // remember when we cache the content @@ -184,10 +189,12 @@ namespace Umbraco.Web.Macros private FileInfo GetMacroFile(MacroModel model) { var filename = GetMacroFileName(model); - if (filename == null) return null; + if (filename == null) + return null; var mapped = _hostingEnvironment.MapPathContentRoot(filename); - if (mapped == null) return null; + if (mapped == null) + return null; var file = new FileInfo(mapped); return file.Exists ? file : null; @@ -223,7 +230,8 @@ namespace Umbraco.Web.Macros private MacroContent Render(MacroModel macro, IPublishedContent content) { - if (content == null) throw new ArgumentNullException(nameof(content)); + if (content == null) + throw new ArgumentNullException(nameof(content)); var macroInfo = $"Render Macro: {macro.Name}, cache: {macro.CacheDuration}"; using (_profilingLogger.DebugDuration(macroInfo, "Rendered Macro.")) @@ -328,7 +336,8 @@ namespace Umbraco.Web.Macros /// should not be cached. In that case the attempt may also contain an exception. private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { - if (model == null) throw new ArgumentNullException(nameof(model)); + if (model == null) + throw new ArgumentNullException(nameof(model)); // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -356,7 +365,7 @@ namespace Umbraco.Web.Macros /// The text output of the macro execution. private MacroContent ExecutePartialView(MacroModel macro, IPublishedContent content) { - var engine = new PartialViewMacroEngine(_umbracoContextAccessor, _httpContextAccessor, _hostingEnvironment); + var engine = new PartialViewMacroEngine(_httpContextAccessor, _hostingEnvironment); return engine.Execute(macro, content); } diff --git a/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs index db1658e962..790e437148 100644 --- a/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web.Common/Macros/PartialViewMacroEngine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text.Encodings.Web; @@ -27,56 +27,71 @@ namespace Umbraco.Web.Common.Macros { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHostingEnvironment _hostingEnvironment; - private readonly Func _getUmbracoContext; + //private readonly Func _getUmbracoContext; public PartialViewMacroEngine( - IUmbracoContextAccessor umbracoContextAccessor, + //IUmbracoContextAccessor umbracoContextAccessor, IHttpContextAccessor httpContextAccessor, IHostingEnvironment hostingEnvironment) { _httpContextAccessor = httpContextAccessor; _hostingEnvironment = hostingEnvironment; - _getUmbracoContext = () => - { - var context = umbracoContextAccessor.UmbracoContext; - if (context == null) - throw new InvalidOperationException( - $"The {GetType()} cannot execute with a null UmbracoContext.Current reference."); - return context; - }; + //_getUmbracoContext = () => + //{ + // var context = umbracoContextAccessor.UmbracoContext; + // if (context == null) + // { + // throw new InvalidOperationException( + // $"The {GetType()} cannot execute with a null UmbracoContext.Current reference."); + // } + + // return context; + //}; } - public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage) - { - var temp = GetVirtualPathFromPhysicalPath(tempFileName); - try - { - CompileAndInstantiate(temp); - } - catch (Exception exception) - { - errorMessage = exception.Message; - return false; - } + //public bool Validate(string code, string tempFileName, IPublishedContent currentPage, out string errorMessage) + //{ + // var temp = GetVirtualPathFromPhysicalPath(tempFileName); + // try + // { + // CompileAndInstantiate(temp); + // } + // catch (Exception exception) + // { + // errorMessage = exception.Message; + // return false; + // } - errorMessage = string.Empty; - return true; - } + // errorMessage = string.Empty; + // return true; + //} public MacroContent Execute(MacroModel macro, IPublishedContent content) { - if (macro == null) throw new ArgumentNullException(nameof(macro)); - if (content == null) throw new ArgumentNullException(nameof(content)); + if (macro == null) + { + throw new ArgumentNullException(nameof(macro)); + } + + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + if (string.IsNullOrWhiteSpace(macro.MacroSource)) + { throw new ArgumentException("The MacroSource property of the macro object cannot be null or empty"); + } var httpContext = _httpContextAccessor.GetRequiredHttpContext(); - var umbCtx = _getUmbracoContext(); + //var umbCtx = _getUmbracoContext(); var routeVals = new RouteData(); routeVals.Values.Add("controller", "PartialViewMacro"); routeVals.Values.Add("action", "Index"); - routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); //required for UmbracoViewPage + + //TODO: Was required for UmbracoViewPage need to figure out if we still need that, i really don't think this is necessary + //routeVals.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, umbCtx); var modelMetadataProvider = httpContext.RequestServices.GetRequiredService(); var tempDataProvider = httpContext.RequestServices.GetRequiredService(); @@ -109,6 +124,7 @@ namespace Umbraco.Web.Common.Macros return new MacroContent { Text = output }; } + private class FakeView : IView { /// @@ -120,29 +136,30 @@ namespace Umbraco.Web.Common.Macros /// public string Path { get; } = "View"; } - private string GetVirtualPathFromPhysicalPath(string physicalPath) - { - var rootpath = _hostingEnvironment.MapPathContentRoot("~/"); - physicalPath = physicalPath.Replace(rootpath, ""); - physicalPath = physicalPath.Replace("\\", "/"); - return "~/" + physicalPath; - } - private static PartialViewMacroPage CompileAndInstantiate(string virtualPath) - { - // //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages - // //Security in medium trust is strict around here, so we can only pass a virtual file path - // //ASP.NET Compilation Engine caches returned types - // //Changed From BuildManager As Other Properties Are Attached Like Context Path/ - // var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath); - // var webPage = webPageBase as PartialViewMacroPage; - // if (webPage == null) - // throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName); - // return webPage; + //private string GetVirtualPathFromPhysicalPath(string physicalPath) + //{ + // var rootpath = _hostingEnvironment.MapPathContentRoot("~/"); + // physicalPath = physicalPath.Replace(rootpath, ""); + // physicalPath = physicalPath.Replace("\\", "/"); + // return "~/" + physicalPath; + //} - //TODO? How to check this - return null; - } + //private static PartialViewMacroPage CompileAndInstantiate(string virtualPath) + //{ + // // //Compile Razor - We Will Leave This To ASP.NET Compilation Engine & ASP.NET WebPages + // // //Security in medium trust is strict around here, so we can only pass a virtual file path + // // //ASP.NET Compilation Engine caches returned types + // // //Changed From BuildManager As Other Properties Are Attached Like Context Path/ + // // var webPageBase = WebPageBase.CreateInstanceFromVirtualPath(virtualPath); + // // var webPage = webPageBase as PartialViewMacroPage; + // // if (webPage == null) + // // throw new InvalidCastException("All Partial View Macro views must inherit from " + typeof(PartialViewMacroPage).FullName); + // // return webPage; + + // //TODO? How to check this + // return null; + //} } } diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs index 113f411c6f..d8178033c9 100644 --- a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; namespace Umbraco.Web.Common.ModelBinders @@ -15,26 +16,15 @@ namespace Umbraco.Web.Common.ModelBinders { public Task BindModelAsync(ModelBindingContext bindingContext) { - if (bindingContext.ActionContext.RouteData.DataTokens.TryGetValue(Core.Constants.Web.UmbracoDataToken, out var source) == false) + // Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality + // only IPublishedContent will ever exist in the request. + if (!bindingContext.ActionContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var source) + || !(source is UmbracoRouteValues umbracoRouteValues)) { return Task.CompletedTask; } - // This model binder deals with IContentModel and IPublishedContent by extracting the model from the route's - // datatokens. This data token is set in 2 places: RenderRouteHandler, UmbracoVirtualNodeRouteHandler - // and both always set the model to an instance of `ContentModel`. - - // No need for type checks to ensure we have the appropriate binder, as in .NET Core this is handled in the provider, - // in this case ContentModelBinderProvider. - - // Being defensive though.... if for any reason the model is not either IContentModel or IPublishedContent, - // then we return since those are the only types this binder is dealing with. - if (source is IContentModel == false && source is IPublishedContent == false) - { - return Task.CompletedTask; - } - - BindModelAsync(bindingContext, source, bindingContext.ModelType); + BindModelAsync(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType); return Task.CompletedTask; } @@ -56,7 +46,7 @@ namespace Umbraco.Web.Common.ModelBinders // If types already match, return var sourceType = source.GetType(); - if (sourceType. Inherits(modelType)) // includes == + if (sourceType.Inherits(modelType)) // includes == { bindingContext.Result = ModelBindingResult.Success(source); return Task.CompletedTask; @@ -71,7 +61,8 @@ namespace Umbraco.Web.Common.ModelBinders { // else check if we can convert it to a content var attempt1 = source.TryConvertTo(); - if (attempt1.Success) sourceContent = attempt1.Result; + if (attempt1.Success) + sourceContent = attempt1.Result; } // If we have a content @@ -129,11 +120,13 @@ namespace Umbraco.Web.Common.ModelBinders // prepare message msg.Append("Cannot bind source"); - if (sourceContent) msg.Append(" content"); + if (sourceContent) + msg.Append(" content"); msg.Append(" type "); msg.Append(sourceType.FullName); msg.Append(" to model"); - if (modelContent) msg.Append(" content"); + if (modelContent) + msg.Append(" content"); msg.Append(" type "); msg.Append(modelType.FullName); msg.Append("."); diff --git a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs new file mode 100644 index 0000000000..2ab047a757 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs @@ -0,0 +1,68 @@ +using System; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Extensions; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Routing +{ + /// + /// Represents the data required to route to a specific controller/action during an Umbraco request + /// + public class UmbracoRouteValues + { + /// + /// The default action name + /// + public const string DefaultActionName = nameof(RenderController.Index); + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRouteValues( + IPublishedContent publishedContent, + string controllerName = null, + Type controllerType = null, + string actionName = DefaultActionName, + string templateName = null, + bool hasHijackedRoute = false) + { + ControllerName = controllerName ?? ControllerExtensions.GetControllerName(); + ControllerType = controllerType ?? typeof(RenderController); + PublishedContent = publishedContent; + HasHijackedRoute = hasHijackedRoute; + ActionName = actionName; + TemplateName = templateName; + } + + /// + /// Gets the controller name + /// + public string ControllerName { get; } + + /// + /// Gets the action name + /// + public string ActionName { get; } + + /// + /// Gets the template name + /// + public string TemplateName { get; } + + /// + /// Gets the Controller type found for routing to + /// + public Type ControllerType { get; } + + /// + /// Gets the + /// + public IPublishedContent PublishedContent { get; } + + /// + /// Gets a value indicating whether the current request has a hijacked route/user controller routed for it + /// + public bool HasHijackedRoute { get; } + } +} diff --git a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs index 62942541e9..abf269e062 100644 --- a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -99,7 +99,7 @@ namespace Umbraco.Web.Website.ActionResults /// private static void ValidateRouteData(RouteData routeData) { - if (routeData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken) == false) + if (routeData.Values.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken) == false) { throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); diff --git a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs b/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs deleted file mode 100644 index 0027132c23..0000000000 --- a/src/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttribute.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; - -namespace Umbraco.Web.Website.Controllers -{ - /// - /// A custom ActionMethodSelector which will ensure that the RenderMvcController.Index(ContentModel model) action will be executed - /// if the - /// - internal class RenderIndexActionSelectorAttribute : ActionMethodSelectorAttribute - { - private static readonly ConcurrentDictionary> _controllerActionsCache = new ConcurrentDictionary>(); - - /// - /// Determines whether the action method selection is valid for the specified controller context. - /// - /// - /// true if the action method selection is valid for the specified controller context; otherwise, false. - /// - /// The route context. - /// Information about the action method. - public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action) - { - if (action is ControllerActionDescriptor controllerAction) - { - var currType = controllerAction.ControllerTypeInfo.UnderlyingSystemType; - var baseType = controllerAction.ControllerTypeInfo.BaseType; - - //It's the same type, so this must be the Index action to use - if (currType == baseType) - return true; - - var actions = _controllerActionsCache.GetOrAdd(currType, type => - { - var actionDescriptors = routeContext.HttpContext.RequestServices - .GetRequiredService().ActionDescriptors.Items - .Where(x => x is ControllerActionDescriptor).Cast() - .Where(x => x.ControllerTypeInfo == controllerAction.ControllerTypeInfo); - - return actionDescriptors; - }); - - //If there are more than one Index action for this controller, then - // this base class one should not be matched - return actions.Count(x => x.ActionName == "Index") <= 1; - } - - return false; - - } - } -} diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs index 1d3e4c5626..390da69453 100644 --- a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs +++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Routing; using Umbraco.Web.Routing; using Umbraco.Web.Website.ActionResults; -using Umbraco.Web.Website.Routing; namespace Umbraco.Web.Website.Controllers { @@ -39,18 +39,18 @@ namespace Umbraco.Web.Website.Controllers { var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts(); if (routeDefAttempt.Success == false) + { throw routeDefAttempt.Exception; + } var routeDef = routeDefAttempt.Result; - return routeDef.PublishedRequest.PublishedContent; + return routeDef.PublishedContent; } } /// /// Redirects to the Umbraco page with the given id /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey) { return new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor); @@ -59,9 +59,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given id and passes provided querystring /// - /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey, QueryString queryString) { return new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -70,8 +67,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given published content /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent) { return new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor); @@ -80,9 +75,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the Umbraco page with the given published content and passes provided querystring /// - /// - /// - /// protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, QueryString queryString) { return new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -91,7 +83,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco page /// - /// protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage() { return new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor); @@ -100,8 +91,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco page and passes provided querystring /// - /// - /// protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(QueryString queryString) { return new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor); @@ -110,7 +99,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Redirects to the currently rendered Umbraco URL /// - /// /// /// This is useful if you need to redirect /// to the current page but the current page is actually a rewritten URL normally done with something like @@ -124,7 +112,6 @@ namespace Umbraco.Web.Website.Controllers /// /// Returns the currently rendered Umbraco page /// - /// protected UmbracoPageResult CurrentUmbracoPage() { return new UmbracoPageResult(ProfilingLogger); @@ -133,18 +120,19 @@ namespace Umbraco.Web.Website.Controllers /// /// we need to recursively find the route definition based on the parent view context /// - /// - private Attempt TryGetRouteDefinitionFromAncestorViewContexts() + private Attempt TryGetRouteDefinitionFromAncestorViewContexts() { var currentContext = ControllerContext; while (!(currentContext is null)) { var currentRouteData = currentContext.RouteData; - if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) - return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + if (currentRouteData.Values.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) + { + return Attempt.Succeed((UmbracoRouteValues)currentRouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + } } - return Attempt.Fail( + return Attempt.Fail( new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request")); } } diff --git a/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs index 669e1835d4..65c27a3269 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbracoRenderingDefaults.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Web.Common.Controllers; namespace Umbraco.Web.Website.Controllers { diff --git a/src/Umbraco.Web.Website/Routing/RouteDefinition.cs b/src/Umbraco.Web.Website/Routing/RouteDefinition.cs deleted file mode 100644 index 47206bd0c3..0000000000 --- a/src/Umbraco.Web.Website/Routing/RouteDefinition.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Umbraco.Web.Routing; - -namespace Umbraco.Web.Website.Routing -{ - /// - /// Represents the data required to route to a specific controller/action during an Umbraco request - /// - public class RouteDefinition - { - public string ControllerName { get; set; } - public string ActionName { get; set; } - - /// - /// The Controller type found for routing to - /// - public Type ControllerType { get; set; } - - /// - /// Everything related to the current content request including the requested content - /// - public IPublishedRequest PublishedRequest { get; set; } - - /// - /// Gets/sets whether the current request has a hijacked route/user controller routed for it - /// - public bool HasHijackedRoute { get; set; } - } -} diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 328b7cc1d4..2c1debc3ee 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -10,10 +12,12 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Composing; +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; @@ -23,6 +27,12 @@ namespace Umbraco.Web.Website.Routing /// /// The route value transformer for Umbraco front-end routes /// + /// + /// NOTE: In aspnet 5 DynamicRouteValueTransformer has been improved, see https://github.com/dotnet/aspnetcore/issues/21471 + /// It seems as though with the "State" parameter we could more easily assign the IPublishedRequest or IPublishedContent + /// or UmbracoContext more easily that way. In the meantime we will rely on assigning the IPublishedRequest to the + /// route values along with the IPublishedContent to the umbraco context + /// public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer { private readonly ILogger _logger; @@ -59,20 +69,13 @@ namespace Umbraco.Web.Website.Routing throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); } - bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext); + bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); if (!routed) { // TODO: Deal with it not being routable, perhaps this should be an enum result? } - IPublishedRequest request = _umbracoContextAccessor.UmbracoContext.PublishedRequest; // This cannot be null here - - SetupRouteDataForRequest( - new ContentModel(request.PublishedContent), - request, - values); - - RouteDefinition routeDef = GetUmbracoRouteDefinition(httpContext, values, request); + UmbracoRouteValues routeDef = GetUmbracoRouteDefinition(httpContext, values, publishedRequest); values["controller"] = routeDef.ControllerName; if (string.IsNullOrWhiteSpace(routeDef.ActionName) == false) { @@ -83,29 +86,9 @@ namespace Umbraco.Web.Website.Routing } /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views + /// Returns a object based on the current content request /// - private void SetupRouteDataForRequest(ContentModel contentModel, IPublishedRequest frequest, RouteValueDictionary values) - { - // put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - - // required for the ContentModelBinder and view engine. - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.UmbracoDataToken, contentModel); - - // required for RenderMvcController - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.PublishedDocumentRequestDataToken, frequest); - - // required for UmbracoViewPage - // TODO: Are we sure, seems strange to need this in netcore - values.TryAdd(Constants.Web.UmbracoContextDataToken, _umbracoContextAccessor.UmbracoContext); - } - - /// - /// Returns a object based on the current content request - /// - private RouteDefinition GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) + private UmbracoRouteValues GetUmbracoRouteDefinition(HttpContext httpContext, RouteValueDictionary values, IPublishedRequest request) { if (httpContext is null) { @@ -125,17 +108,8 @@ namespace Umbraco.Web.Website.Routing Type defaultControllerType = _renderingDefaults.DefaultControllerType; 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(), - ActionName = "Index", - HasHijackedRoute = false - }; + 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 @@ -144,34 +118,47 @@ 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. - var templateName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); - def.ActionName = templateName; + customActionName = request.TemplateAlias.Split('.')[0].ToSafeAlias(_shortStringHelper); } - // check if there's a custom controller assigned, base on the document type alias. - Type controllerType = FindControllerType(request.PublishedContent.ContentType.Alias); + // creates the default route definition which maps to the 'UmbracoController' controller + var def = new UmbracoRouteValues( + request.PublishedContent, + defaultControllerName, + defaultControllerType, + templateName: customActionName); - // check if that controller exists - if (controllerType != null) + IReadOnlyList candidates = FindControllerCandidates(customControllerName, customActionName, def.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(controllerType) - && TypeHelper.IsTypeAssignableFrom(controllerType)) + if (TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo) + && TypeHelper.IsTypeAssignableFrom(controllerDescriptor.ControllerTypeInfo)) { - // set the controller and name to the custom one - def.ControllerType = controllerType; - def.ControllerName = ControllerExtensions.GetControllerName(controllerType); - if (def.ControllerName != defaultControllerName) - { - def.HasHijackedRoute = true; - } + // now check if the custom action matches + var customActionExists = customActionName != null && customControllerCandidates.Any(x => x.ActionName.InvariantEquals(customActionName)); + + def = new UmbracoRouteValues( + request.PublishedContent, + controllerDescriptor.ControllerName, + controllerDescriptor.ControllerTypeInfo, + customActionExists ? customActionName : def.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, - controllerType.FullName, + controllerDescriptor.ControllerTypeInfo.FullName, typeof(IRenderController).FullName, typeof(ControllerBase).FullName); @@ -186,17 +173,21 @@ namespace Umbraco.Web.Website.Routing return def; } - private Type FindControllerType(string controllerName) + /// + /// Return a list of controller candidates that match the custom controller and action names + /// + private IReadOnlyList FindControllerCandidates(string customControllerName, string customActionName, string defaultActionName) { - ControllerActionDescriptor descriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items + var descriptors = _actionDescriptorCollectionProvider.ActionDescriptors.Items .Cast() - .FirstOrDefault(x => - x.ControllerName.Equals(controllerName)); + .Where(x => x.ControllerName.InvariantEquals(customControllerName) + && (x.ActionName.InvariantEquals(defaultActionName) || (customActionName != null && x.ActionName.InvariantEquals(customActionName)))) + .ToList(); - return descriptor?.ControllerTypeInfo; + return descriptors; } - private bool RouteRequest(IUmbracoContext umbracoContext) + private bool RouteRequest(IUmbracoContext umbracoContext, out IPublishedRequest publishedRequest) { // TODO: I suspect one day this will be async @@ -209,7 +200,9 @@ 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); - umbracoContext.PublishedRequest = request; // TODO: This is ugly + + // 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; diff --git a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs index ac16be417a..e0b16a351e 100644 --- a/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/PluginViewEngine.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Logging; @@ -6,6 +6,10 @@ 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 /// @@ -21,28 +25,28 @@ namespace Umbraco.Web.Website.ViewEngines { } - private static IOptions OverrideViewLocations() + private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() { - return Options.Create(new RazorViewEngineOptions() - { - AreaViewLocationFormats = + // 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 + // 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 + // 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. + // 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 = + ViewLocationFormats = { string.Concat(Core.Constants.SystemDirectories.AppPlugins, "/{2}/Views/{1}/{0}.cshtml"), } - }); - } + }); } } diff --git a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs index ea727a07f1..8c53255928 100644 --- a/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs +++ b/src/Umbraco.Web.Website/ViewEngines/RenderViewEngine.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -16,6 +16,10 @@ 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. @@ -23,6 +27,9 @@ namespace Umbraco.Web.Website.ViewEngines public class RenderViewEngine : RazorViewEngine, IRenderViewEngine { + /// + /// Initializes a new instance of the class. + /// public RenderViewEngine( IRazorPageFactoryProvider pageFactory, IRazorPageActivator pageActivator, @@ -33,27 +40,24 @@ namespace Umbraco.Web.Website.ViewEngines { } - private static IOptions OverrideViewLocations() + private static IOptions OverrideViewLocations() => Options.Create(new RazorViewEngineOptions() { - return 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 = + // 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 = { - "/Partials/{0}.cshtml", - "/MacroPartials/{0}.cshtml", - "/{0}.cshtml" + "/Views/Partials/{0}.cshtml", + "/Views/MacroPartials/{0}.cshtml", + "/Views/{0}.cshtml" }, - AreaViewLocationFormats = + AreaViewLocationFormats = { - "/Partials/{0}.cshtml", - "/MacroPartials/{0}.cshtml", - "/{0}.cshtml" + "/Views/Partials/{0}.cshtml", + "/Views/MacroPartials/{0}.cshtml", + "/Views/{0}.cshtml" } - }); - } + }); public new ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) { @@ -72,16 +76,17 @@ namespace Umbraco.Web.Website.ViewEngines /// private static bool ShouldFindView(ActionContext context, bool isMainPage) { - //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; + 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; } diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index 1c2be3f713..c59c701d42 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Web.Http; using System.Web.Mvc; @@ -127,7 +127,9 @@ namespace Umbraco.Web.Mvc //match this area controllerPluginRoute.DataTokens.Add("area", area.AreaName); - controllerPluginRoute.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, umbracoTokenValue); //ensure the umbraco token is set + + // TODO: No longer needed, remove + //controllerPluginRoute.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, umbracoTokenValue); //ensure the umbraco token is set return controllerPluginRoute; } diff --git a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs b/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs deleted file mode 100644 index 4baaaac4fc..0000000000 --- a/src/Umbraco.Web/Mvc/ControllerContextExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Web.Mvc; -using Umbraco.Web.Composing; - -namespace Umbraco.Web.Mvc -{ - public static class ControllerContextExtensions - { - /// - /// Gets the Umbraco context from a controller context hierarchy, if any, else the 'current' Umbraco context. - /// - /// The controller context. - /// The Umbraco context. - public static IUmbracoContext GetUmbracoContext(this ControllerContext controllerContext) - { - var o = controllerContext.GetDataTokenInViewContextHierarchy(Core.Constants.Web.UmbracoContextDataToken); - return o != null ? o as IUmbracoContext : Current.UmbracoContext; - } - - /// - /// Recursively gets a data token from a controller context hierarchy. - /// - /// The controller context. - /// The name of the data token. - /// The data token, or null. - internal static object GetDataTokenInViewContextHierarchy(this ControllerContext controllerContext, string dataTokenName) - { - var context = controllerContext; - while (context != null) - { - object token; - if (context.RouteData.DataTokens.TryGetValue(dataTokenName, out token)) - return token; - context = context.ParentActionViewContext; - } - return null; - } - } -} diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 19e1b79c89..3ca0931585 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -56,8 +56,6 @@ namespace Umbraco.Web.Mvc /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to process the response, /// this also stores the render model into the data tokens for the current RouteData. /// - /// - /// public IHttpHandler GetHttpHandler(RequestContext requestContext) { if (UmbracoContext == null) @@ -70,37 +68,18 @@ namespace Umbraco.Web.Mvc throw new NullReferenceException("There is no current PublishedRequest, it must be initialized before the RenderRouteHandler executes"); } - SetupRouteDataForRequest( - new ContentModel(request.PublishedContent), - requestContext, - request); - return GetHandlerForRoute(requestContext, request); } #endregion - /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views - /// - /// - /// - /// - internal void SetupRouteDataForRequest(ContentModel contentModel, RequestContext requestContext, IPublishedRequest frequest) - { - //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, contentModel); //required for the ContentModelBinder and view engine - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.PublishedDocumentRequestDataToken, frequest); //required for RenderMvcController - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.UmbracoContextDataToken, UmbracoContext); //required for UmbracoViewPage - } - private void UpdateRouteDataForRequest(ContentModel contentModel, RequestContext requestContext) { if (contentModel == null) throw new ArgumentNullException(nameof(contentModel)); if (requestContext == null) throw new ArgumentNullException(nameof(requestContext)); - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; + // requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoDataToken] = contentModel; // the rest should not change -- it's only the published content that has changed } @@ -293,7 +272,7 @@ namespace Umbraco.Web.Mvc } //store the route definition - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return def; } diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index cd344ea261..fa67248e7d 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core; using System.Collections.Specialized; using Umbraco.Core.Cache; @@ -204,8 +204,10 @@ namespace Umbraco.Web.Mvc while (currentContext != null) { var currentRouteData = currentContext.RouteData; - if (currentRouteData.DataTokens.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) - return Attempt.Succeed((RouteDefinition)currentRouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + if (currentRouteData.Values.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken)) + { + return Attempt.Succeed((RouteDefinition)currentRouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken]); + } currentContext = currentContext.IsChildAction ? currentContext.ParentActionViewContext diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 30c990a981..580924b909 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Web.Mvc; using System.Web.Routing; @@ -26,7 +26,7 @@ namespace Umbraco.Web.Mvc ValidateRouteData(context.RouteData); - var routeDef = (RouteDefinition)context.RouteData.DataTokens[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; + var routeDef = (RouteDefinition)context.RouteData.Values[Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken]; var factory = ControllerBuilder.Current.GetControllerFactory(); context.RouteData.Values["action"] = routeDef.ActionName; @@ -72,7 +72,7 @@ namespace Umbraco.Web.Mvc /// private static void ValidateRouteData(RouteData routeData) { - if (routeData.DataTokens.ContainsKey(Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken) == false) + if (routeData.Values.ContainsKey(Umbraco.Core.Constants.Web.UmbracoRouteDefinitionDataToken) == false) { throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); diff --git a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs index 18e1fb8a1a..43dc341655 100644 --- a/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs +++ b/src/Umbraco.Web/Mvc/UmbracoViewPageOfTModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Web; using System.Web.Mvc; @@ -20,9 +20,7 @@ using Current = Umbraco.Web.Composing.Current; namespace Umbraco.Web.Mvc { - /// - /// Represents the properties and methods that are needed in order to render an Umbraco view. - /// + // TODO: This has been ported to netcore, just needs testing public abstract class UmbracoViewPage : WebViewPage { private readonly GlobalSettings _globalSettings; @@ -50,11 +48,9 @@ namespace Umbraco.Web.Mvc // like the Services & ApplicationCache properties, and have a setter for those special weird // cases. - /// - /// Gets the Umbraco context. - /// - public IUmbracoContext UmbracoContext => _umbracoContext - ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); + // TODO: Can be injected to the view in netcore, else injected to the base model + // public IUmbracoContext UmbracoContext => _umbracoContext + // ?? (_umbracoContext = ViewContext.GetUmbracoContext() ?? Current.UmbracoContext); /// /// Gets the public content request. @@ -63,21 +59,27 @@ namespace Umbraco.Web.Mvc { get { - const string token = Core.Constants.Web.PublishedDocumentRequestDataToken; + // TODO: we only have one data token for a route now: Constants.Web.UmbracoRouteDefinitionDataToken - // we should always try to return the object from the data tokens just in case its a custom object and not - // the one from UmbracoContext. Fallback to UmbracoContext if necessary. + throw new NotImplementedException("Probably needs to be ported to netcore"); - // try view context - if (ViewContext.RouteData.DataTokens.ContainsKey(token)) - return (IPublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(token); + //// we should always try to return the object from the data tokens just in case its a custom object and not + //// the one from UmbracoContext. Fallback to UmbracoContext if necessary. - // child action, try parent view context - if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(token)) - return (IPublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(token); + //// try view context + //if (ViewContext.RouteData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)) + //{ + // return (IPublishedRequest) ViewContext.RouteData.DataTokens.GetRequiredObject(Constants.Web.UmbracoRouteDefinitionDataToken); + //} - // fallback to UmbracoContext - return UmbracoContext.PublishedRequest; + //// child action, try parent view context + //if (ViewContext.IsChildAction && ViewContext.ParentActionViewContext.RouteData.DataTokens.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken)) + //{ + // return (IPublishedRequest) ViewContext.ParentActionViewContext.RouteData.DataTokens.GetRequiredObject(Constants.Web.UmbracoRouteDefinitionDataToken); + //} + + //// fallback to UmbracoContext + //return UmbracoContext.PublishedRequest; } } diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index c00eb24cca..dc922d9fd2 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; @@ -62,12 +62,12 @@ namespace Umbraco.Web.Mvc var renderModel = new ContentModel(umbracoContext.PublishedRequest.PublishedContent); // 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); + //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); - // this is used just for a flag that this is an umbraco custom route - requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); + //// this is used just for a flag that this is an umbraco custom route + //requestContext.RouteData.DataTokens.Add(Core.Constants.Web.CustomRouteDataToken, true); // Here we need to detect if a SurfaceController has posted var formInfo = RenderRouteHandler.GetFormInfo(requestContext); @@ -81,7 +81,7 @@ namespace Umbraco.Web.Mvc }; // set the special data token to the current route definition - requestContext.RouteData.DataTokens[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; + requestContext.RouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken] = def; return RenderRouteHandler.HandlePostedValues(requestContext, formInfo); } diff --git a/src/Umbraco.Web/Runtime/WebInitialComponent.cs b/src/Umbraco.Web/Runtime/WebInitialComponent.cs index 4fb1754946..c11d648b37 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComponent.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComponent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; @@ -171,7 +171,9 @@ namespace Umbraco.Web.Runtime new[] { meta.ControllerNamespace }); if (route.DataTokens == null) // web api routes don't set the data tokens object route.DataTokens = new RouteValueDictionary(); - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set + + // TODO: Pretty sure this is not necessary, we'll see + //route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "api"); //ensure the umbraco token is set } private static void RouteLocalSurfaceController(Type controller, string umbracoPath) @@ -183,7 +185,10 @@ namespace Umbraco.Web.Runtime url, // URL to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, new[] { meta.ControllerNamespace }); // look in this namespace to create the controller - route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + + // TODO: Pretty sure this is not necessary, we'll see + //route.DataTokens.Add(Core.Constants.Web.UmbracoDataToken, "surface"); // ensure the umbraco token is set + route.DataTokens.Add("UseNamespaceFallback", false); // don't look anywhere else except this namespace! // make it use our custom/special SurfaceMvcHandler route.RouteHandler = new SurfaceRouteHandler(); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8867971657..758839314b 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -184,7 +184,6 @@ - From b5e3bc9e0d1406cf9af89713155e369bcadfa15e Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 11 Dec 2020 14:55:19 +1100 Subject: [PATCH 007/127] Fix the UmbracoViewPage and view model binding, combine the tests cases, remove IPublishedContentType2, front end is rendering --- src/Umbraco.Core/Models/IContentModel.cs | 22 ++- .../PublishedContent/IPublishedContentType.cs | 12 +- .../PublishedContent/PublishedContentType.cs | 4 +- .../PublishedContentTypeExtensions.cs | 24 --- .../BlockListPropertyValueConverter.cs | 8 +- .../PublishedContentTypeCache.cs | 8 +- .../ContentStore.cs | 5 +- .../BlockListPropertyValueConverterTests.cs | 10 +- .../ModelBinders/ContentModelBinderTests.cs | 179 ++++++++++++++--- .../ModelBinders/RenderModelBinderTests.cs | 182 ------------------ .../Views/UmbracoViewPageTests.cs | 62 +++--- .../AspNetCore/UmbracoViewPage.cs | 105 +++++++--- .../Extensions/RazorPageExtensions.cs | 19 +- .../ModelBinders/ContentModelBinder.cs | 54 ++++-- 14 files changed, 334 insertions(+), 360 deletions(-) delete mode 100644 src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs diff --git a/src/Umbraco.Core/Models/IContentModel.cs b/src/Umbraco.Core/Models/IContentModel.cs index d0d4f175d7..692547aa3e 100644 --- a/src/Umbraco.Core/Models/IContentModel.cs +++ b/src/Umbraco.Core/Models/IContentModel.cs @@ -1,10 +1,30 @@ -using Umbraco.Core.Models; +using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { + /// + /// The basic view model returned for front-end Umbraco controllers + /// + /// + /// + /// exists in order to unify all view models in Umbraco, whether it's a normal template view or a partial view macro, or + /// a user's custom model that they have created when doing route hijacking or custom routes. + /// + /// + /// By default all front-end template views inherit from UmbracoViewPage which has a model of but the model returned + /// from the controllers is which in normal circumstances would not work. This works with UmbracoViewPage because it + /// performs model binding between IContentModel and IPublishedContent. This offers a lot of flexibility when rendering views. In some cases if you + /// are route hijacking and returning a custom implementation of and your view is strongly typed to this model, you can still + /// render partial views created in the back office that have the default model of IPublishedContent without having to worry about explicitly passing + /// that model to the view. + /// + /// public interface IContentModel { + /// + /// Gets the + /// IPublishedContent Content { get; } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs index cfc789324a..f9330176aa 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace Umbraco.Core.Models.PublishedContent @@ -8,21 +8,13 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances implementing the interface should be /// immutable, ie if the content type changes, then a new instance needs to be created. - public interface IPublishedContentType2 : IPublishedContentType + public interface IPublishedContentType { /// /// Gets the unique key for the content type. /// Guid Key { get; } - } - /// - /// Represents an type. - /// - /// Instances implementing the interface should be - /// immutable, ie if the content type changes, then a new instance needs to be created. - public interface IPublishedContentType - { /// /// Gets the content type identifier. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 14c26442eb..daf75f5c50 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Instances of the class are immutable, ie /// if the content type changes, then a new class needs to be created. - public class PublishedContentType : IPublishedContentType2 + public class PublishedContentType : IPublishedContentType { private readonly IPublishedPropertyType[] _propertyTypes; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs deleted file mode 100644 index feab33c1d6..0000000000 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Umbraco.Core.Models.PublishedContent -{ - public static class PublishedContentTypeExtensions - { - /// - /// Get the GUID key from an - /// - /// - /// - /// - public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key) - { - if (publishedContentType is IPublishedContentType2 contentTypeWithKey) - { - key = contentTypeWithKey.Key; - return true; - } - key = Guid.Empty; - return false; - } - } -} diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs index f46c118174..f35f9b9469 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -103,8 +103,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters if (settingGuidUdi != null) settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData); - if (!contentData.ContentType.TryGetKey(out var contentTypeKey)) - throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2)); + var contentTypeKey = contentData.ContentType.Key; if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig)) continue; @@ -113,8 +112,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters // we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted. if (settingsData != null) { - if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey)) - throw new InvalidOperationException("The settings element type was not of type " + typeof(IPublishedContentType2)); + var settingsElementTypeKey = settingsData.ContentType.Key; if (!blockConfig.SettingsElementTypeKey.HasValue || settingsElementTypeKey != blockConfig.SettingsElementTypeKey) settingsData = null; diff --git a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs index 4c1482c82c..ae99243a2c 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -190,8 +190,7 @@ namespace Umbraco.Web.PublishedCache try { _lock.EnterWriteLock(); - if (type.TryGetKey(out var key)) - _keyToIdMap[key] = type.Id; + _keyToIdMap[type.Key] = type.Id; return _typesByAlias[aliasKey] = _typesById[type.Id] = type; } finally @@ -227,8 +226,7 @@ namespace Umbraco.Web.PublishedCache try { _lock.EnterWriteLock(); - if (type.TryGetKey(out var key)) - _keyToIdMap[key] = type.Id; + _keyToIdMap[type.Key] = type.Id; return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type; } finally diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index d41ca344dc..e79c195b46 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -1167,8 +1167,7 @@ namespace Umbraco.Web.PublishedCache.NuCache SetValueLocked(_contentTypesById, type.Id, type); SetValueLocked(_contentTypesByAlias, type.Alias, type); // ensure the key/id map is accurate - if (type.TryGetKey(out var key)) - _contentTypeKeyToIdMap[key] = type.Id; + _contentTypeKeyToIdMap[type.Key] = type.Id; } // set a node (just the node, not the tree) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index eb77ad2e1c..dc6d059a0a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -1,4 +1,4 @@ -using Moq; +using Moq; using NUnit.Framework; using System; using System.Collections.Generic; @@ -33,19 +33,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors /// private IPublishedSnapshotAccessor GetPublishedSnapshotAccessor() { - var test1ContentType = Mock.Of(x => + var test1ContentType = Mock.Of(x => x.IsElement == true && x.Key == ContentKey1 && x.Alias == ContentAlias1); - var test2ContentType = Mock.Of(x => + var test2ContentType = Mock.Of(x => x.IsElement == true && x.Key == ContentKey2 && x.Alias == ContentAlias2); - var test3ContentType = Mock.Of(x => + var test3ContentType = Mock.Of(x => x.IsElement == true && x.Key == SettingKey1 && x.Alias == SettingAlias1); - var test4ContentType = Mock.Of(x => + var test4ContentType = Mock.Of(x => x.IsElement == true && x.Key == SettingKey2 && x.Alias == SettingAlias2); 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 ba5910da29..caac2f9207 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -11,57 +12,95 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; -using Umbraco.Web.Website.Routing; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { [TestFixture] public class ContentModelBinderTests { + private ContentModelBinder _contentModelBinder; + + [SetUp] + public void SetUp() => _contentModelBinder = new ContentModelBinder(); + [Test] - public void Does_Not_Bind_Model_When_UmbracoDataToken_Not_In_Route_Data() + [TestCase(typeof(IPublishedContent), false)] + [TestCase(typeof(ContentModel), false)] + [TestCase(typeof(ContentType1), false)] + [TestCase(typeof(ContentModel), false)] + [TestCase(typeof(NonContentModel), true)] + [TestCase(typeof(MyCustomContentModel), true)] + [TestCase(typeof(IContentModel), true)] + public void Returns_Binder_For_IPublishedContent_And_IRenderModel(Type testType, bool expectNull) + { + var binderProvider = new ContentModelBinderProvider(); + var contextMock = new Mock(); + contextMock.Setup(x => x.Metadata).Returns(new EmptyModelMetadataProvider().GetMetadataForType(testType)); + + IModelBinder found = binderProvider.GetBinder(contextMock.Object); + if (expectNull) + { + Assert.IsNull(found); + } + else + { + Assert.IsNotNull(found); + } + } + + [Test] + public async Task Does_Not_Bind_Model_When_UmbracoToken_Not_In_Route_Values() { // Arrange IPublishedContent pc = CreatePublishedContent(); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, withUmbracoDataToken: false); - var binder = new ContentModelBinder(); + var bindingContext = CreateBindingContextForUmbracoRequest(typeof(ContentModel), pc); + bindingContext.ActionContext.RouteData.Values.Remove(Constants.Web.UmbracoRouteDefinitionDataToken); // Act - binder.BindModelAsync(bindingContext); + await _contentModelBinder.BindModelAsync(bindingContext); // Assert Assert.False(bindingContext.Result.IsModelSet); } [Test] - public void Does_Not_Bind_Model_When_Source_Not_Of_Expected_Type() + public async Task Does_Not_Bind_Model_When_UmbracoToken_Has_Incorrect_Model() { // Arrange IPublishedContent pc = CreatePublishedContent(); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new NonContentModel()); - var binder = new ContentModelBinder(); + var bindingContext = CreateBindingContextForUmbracoRequest(typeof(ContentModel), pc); + bindingContext.ActionContext.RouteData.Values[Constants.Web.UmbracoRouteDefinitionDataToken] = new NonContentModel(); // Act - binder.BindModelAsync(bindingContext); + await _contentModelBinder.BindModelAsync(bindingContext); // Assert Assert.False(bindingContext.Result.IsModelSet); } [Test] - public void BindModel_Returns_If_Same_Type() + public async Task Bind_Model_When_UmbracoToken_Is_In_Route_Values() { // Arrange IPublishedContent pc = CreatePublishedContent(); - var content = new ContentModel(pc); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: content); - var binder = new ContentModelBinder(); + var bindingContext = CreateBindingContextForUmbracoRequest(typeof(ContentModel), pc); // Act - binder.BindModelAsync(bindingContext); + await _contentModelBinder.BindModelAsync(bindingContext); // Assert - Assert.AreSame(content, bindingContext.Result.Model); + Assert.True(bindingContext.Result.IsModelSet); + } + + [Test] + public void Throws_When_Source_Not_Of_Expected_Type() + { + // Arrange + IPublishedContent pc = CreatePublishedContent(); + var bindingContext = new DefaultModelBindingContext(); + + // Act/Assert + Assert.Throws(() => _contentModelBinder.BindModel(bindingContext, new NonContentModel(), typeof(ContentModel))); } [Test] @@ -69,11 +108,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { // Arrange IPublishedContent pc = CreatePublishedContent(); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: pc); - var binder = new ContentModelBinder(); + var bindingContext = new DefaultModelBindingContext(); // Act - binder.BindModelAsync(bindingContext); + _contentModelBinder.BindModel(bindingContext, pc, typeof(ContentModel)); // Assert Assert.True(bindingContext.Result.IsModelSet); @@ -84,24 +122,95 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { // Arrange IPublishedContent pc = CreatePublishedContent(); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, source: new ContentModel(new ContentType2(pc))); - var binder = new ContentModelBinder(); + var bindingContext = new DefaultModelBindingContext(); // Act - binder.BindModelAsync(bindingContext); + _contentModelBinder.BindModel(bindingContext, new ContentModel(new ContentType2(pc)), typeof(ContentModel)); // Assert Assert.True(bindingContext.Result.IsModelSet); } - private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) + [Test] + public void BindModel_Null_Source_Returns_Null() + { + var bindingContext = new DefaultModelBindingContext(); + _contentModelBinder.BindModel(bindingContext, null, typeof(ContentType1)); + Assert.IsNull(bindingContext.Result.Model); + } + + [Test] + public void BindModel_Returns_If_Same_Type() + { + var content = new ContentType1(Mock.Of()); + var bindingContext = new DefaultModelBindingContext(); + + _contentModelBinder.BindModel(bindingContext, content, typeof(ContentType1)); + + Assert.AreSame(content, bindingContext.Result.Model); + } + + [Test] + public void BindModel_RenderModel_To_IPublishedContent() + { + var content = new ContentType1(Mock.Of()); + var renderModel = new ContentModel(content); + + var bindingContext = new DefaultModelBindingContext(); + _contentModelBinder.BindModel(bindingContext, renderModel, typeof(IPublishedContent)); + + Assert.AreSame(content, bindingContext.Result.Model); + } + + [Test] + public void BindModel_IPublishedContent_To_RenderModel() + { + var content = new ContentType1(Mock.Of()); + var bindingContext = new DefaultModelBindingContext(); + + _contentModelBinder.BindModel(bindingContext, content, typeof(ContentModel)); + var bound = (IContentModel)bindingContext.Result.Model; + + Assert.AreSame(content, bound.Content); + } + + [Test] + public void BindModel_IPublishedContent_To_Generic_RenderModel() + { + var content = new ContentType1(Mock.Of()); + var bindingContext = new DefaultModelBindingContext(); + + _contentModelBinder.BindModel(bindingContext, content, typeof(ContentModel)); + var bound = (IContentModel)bindingContext.Result.Model; + + Assert.AreSame(content, bound.Content); + } + + [Test] + public void Null_Model_Binds_To_Null() + { + IPublishedContent pc = Mock.Of(); + var bindingContext = new DefaultModelBindingContext(); + _contentModelBinder.BindModel(bindingContext, null, typeof(ContentModel)); + Assert.IsNull(bindingContext.Result.Model); + } + + [Test] + public void Invalid_Model_Type_Throws_Exception() + { + IPublishedContent pc = Mock.Of(); + var bindingContext = new DefaultModelBindingContext(); + Assert.Throws(() => _contentModelBinder.BindModel(bindingContext, "Hello", typeof(IPublishedContent))); + } + + /// + /// Creates a binding context with the route values populated to similute an Umbraco dynamically routed request + /// + private ModelBindingContext CreateBindingContextForUmbracoRequest(Type modelType, IPublishedContent publishedContent) { var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); - if (withUmbracoDataToken) - { - routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); - } + routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); @@ -120,19 +229,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { } - private IPublishedContent CreatePublishedContent() - { - return new ContentType2(new Mock().Object); - } + private IPublishedContent CreatePublishedContent() => new ContentType2(new Mock().Object); public class ContentType1 : PublishedContentWrapped { - public ContentType1(IPublishedContent content) : base(content) { } + public ContentType1(IPublishedContent content) + : base(content) { } } public class ContentType2 : ContentType1 { - public ContentType2(IPublishedContent content) : base(content) { } + public ContentType2(IPublishedContent content) + : base(content) { } + } + + public class MyCustomContentModel : ContentModel + { + public MyCustomContentModel(IPublishedContent content) + : base(content) + { } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs deleted file mode 100644 index 660a9b7bd1..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Routing; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Common.ModelBinders; -using Umbraco.Web.Common.Routing; -using Umbraco.Web.Models; -using Umbraco.Web.Website.Routing; - -namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders -{ - [TestFixture] - public class RenderModelBinderTests - { - private ContentModelBinder _contentModelBinder; - [SetUp] - public void SetUp() - { - _contentModelBinder = new ContentModelBinder(); - } - - [Test] - [TestCase(typeof(IPublishedContent), false)] - [TestCase(typeof(ContentModel), false)] - [TestCase(typeof(MyContent), false)] - [TestCase(typeof(ContentModel), false)] - [TestCase(typeof(MyOtherContent), true)] - [TestCase(typeof(MyCustomContentModel), true)] - [TestCase(typeof(IContentModel), true)] - public void Returns_Binder_For_IPublishedContent_And_IRenderModel(Type testType, bool expectNull) - { - var binderProvider = new ContentModelBinderProvider(); - var contextMock = new Mock(); - contextMock.Setup(x => x.Metadata).Returns(new EmptyModelMetadataProvider().GetMetadataForType(testType)); - - var found = binderProvider.GetBinder(contextMock.Object); - if (expectNull) - { - Assert.IsNull(found); - } - else - { - Assert.IsNotNull(found); - } - } - - [Test] - public void BindModel_Null_Source_Returns_Null() - { - var bindingContext = new DefaultModelBindingContext(); - _contentModelBinder.BindModelAsync(bindingContext, null, typeof(MyContent)); - Assert.IsNull(bindingContext.Result.Model); - } - - [Test] - public void BindModel_Returns_If_Same_Type() - { - var content = new MyContent(Mock.Of()); - var bindingContext = new DefaultModelBindingContext(); - - _contentModelBinder.BindModelAsync(bindingContext, content, typeof(MyContent)); - - Assert.AreSame(content, bindingContext.Result.Model); - } - - [Test] - public void BindModel_RenderModel_To_IPublishedContent() - { - var content = new MyContent(Mock.Of()); - var renderModel = new ContentModel(content); - - var bindingContext = new DefaultModelBindingContext(); - _contentModelBinder.BindModelAsync(bindingContext, renderModel, typeof(IPublishedContent)); - - Assert.AreSame(content, bindingContext.Result.Model); - } - - [Test] - public void BindModel_IPublishedContent_To_RenderModel() - { - var content = new MyContent(Mock.Of()); - var bindingContext = new DefaultModelBindingContext(); - - _contentModelBinder.BindModelAsync(bindingContext, content, typeof(ContentModel)); - var bound = (IContentModel) bindingContext.Result.Model; - - Assert.AreSame(content, bound.Content); - } - - [Test] - public void BindModel_IPublishedContent_To_Generic_RenderModel() - { - var content = new MyContent(Mock.Of()); - var bindingContext = new DefaultModelBindingContext(); - - _contentModelBinder.BindModelAsync(bindingContext, content, typeof(ContentModel)); - var bound = (IContentModel) bindingContext.Result.Model; - - Assert.AreSame(content, bound.Content); - } - - [Test] - public void No_DataToken_Returns_Null() - { - IPublishedContent pc = Mock.Of(); - var content = new MyContent(pc); - var bindingContext = CreateBindingContext(typeof(ContentModel), pc, false, content); - - _contentModelBinder.BindModelAsync(bindingContext); - - Assert.IsNull(bindingContext.Result.Model); - } - - [Test] - public void Invalid_DataToken_Model_Type_Returns_Null() - { - IPublishedContent pc = Mock.Of(); - var bindingContext = CreateBindingContext(typeof(IPublishedContent), pc, source: "Hello"); - _contentModelBinder.BindModelAsync(bindingContext); - Assert.IsNull(bindingContext.Result.Model); - } - - [Test] - public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation() - { - IPublishedContent pc = Mock.Of(); - var content = new MyContent(pc); - var bindingContext = CreateBindingContext(typeof(MyContent), pc, source: content); - - _contentModelBinder.BindModelAsync(bindingContext); - - Assert.AreEqual(content, bindingContext.Result.Model); - } - - private ModelBindingContext CreateBindingContext(Type modelType, IPublishedContent publishedContent, bool withUmbracoDataToken = true, object source = null) - { - var httpContext = new DefaultHttpContext(); - var routeData = new RouteData(); - if (withUmbracoDataToken) - { - routeData.Values.Add(Constants.Web.UmbracoRouteDefinitionDataToken, new UmbracoRouteValues(publishedContent)); - } - - var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); - var metadataProvider = new EmptyModelMetadataProvider(); - var routeValueDictionary = new RouteValueDictionary(); - var valueProvider = new RouteValueProvider(BindingSource.Path, routeValueDictionary); - return new DefaultModelBindingContext - { - ActionContext = actionContext, - ModelMetadata = metadataProvider.GetMetadataForType(modelType), - ModelName = modelType.Name, - ValueProvider = valueProvider, - }; - } - - public class MyCustomContentModel : ContentModel - { - public MyCustomContentModel(IPublishedContent content) - : base(content) - { } - } - - public class MyOtherContent - { - - } - - public class MyContent : PublishedContentWrapped - { - public MyContent(IPublishedContent content) : base(content) - { - } - } - } -} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs index 3b52d0701e..03065d4bcb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using NUnit.Framework; using Umbraco.Core.Models.PublishedContent; @@ -13,7 +14,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views [TestFixture] public class UmbracoViewPageTests { - #region RenderModel To ... [Test] public void RenderModel_To_RenderModel() { @@ -58,7 +58,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new ContentType2TestPage(); var viewData = GetViewDataDictionary(model); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } [Test] @@ -96,12 +96,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new RenderModelOfContentType2TestPage(); var viewData = GetViewDataDictionary(model); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } - #endregion - - #region RenderModelOf To ... [Test] public void RenderModelOf_ContentType1_To_RenderModel() @@ -117,20 +114,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views } [Test] - public async Task RenderModelOf_ContentType1_To_ContentType1() + public void RenderModelOf_ContentType1_To_ContentType1() { var content = new ContentType1(null); var model = new ContentModel(content); var view = new ContentType1TestPage(); var viewData = GetViewDataDictionary>(model); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf(view.Model); } [Test] - public async Task RenderModelOf_ContentType2_To_ContentType1() + public void RenderModelOf_ContentType2_To_ContentType1() { var content = new ContentType2(null); var model = new ContentModel(content); @@ -140,13 +137,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views Model = model }; - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf(view.Model); } [Test] - public async Task RenderModelOf_ContentType1_To_ContentType2() + public void RenderModelOf_ContentType1_To_ContentType2() { var content = new ContentType1(null); @@ -154,7 +151,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new ContentType2TestPage(); var viewData = GetViewDataDictionary(model); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } [Test] @@ -172,14 +169,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views } [Test] - public async Task RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() + public void RenderModelOf_ContentType2_To_RenderModelOf_ContentType1() { var content = new ContentType2(null); var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); var viewData = GetViewDataDictionary>(model); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); @@ -193,48 +190,44 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new RenderModelOfContentType2TestPage(); var viewData = GetViewDataDictionary(model); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } - #endregion - - #region ContentType To ... - [Test] - public async Task ContentType1_To_RenderModel() + public void ContentType1_To_RenderModel() { var content = new ContentType1(null); var view = new RenderModelTestPage(); var viewData = GetViewDataDictionary(content); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf(view.Model); } [Test] - public async Task ContentType1_To_RenderModelOf_ContentType1() + public void ContentType1_To_RenderModelOf_ContentType1() { var content = new ContentType1(null); var view = new RenderModelOfContentType1TestPage(); var viewData = GetViewDataDictionary(content); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); } [Test] - public async Task ContentType2_To_RenderModelOf_ContentType1() + public void ContentType2_To_RenderModelOf_ContentType1() { // Same as above but with ContentModel var content = new ContentType2(null); var view = new RenderModelOfContentType1TestPage(); var viewData = GetViewDataDictionary(content); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf>(view.Model); Assert.IsInstanceOf(view.Model.Content); @@ -247,17 +240,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new RenderModelOfContentType2TestPage(); var viewData = GetViewDataDictionary(content); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } [Test] - public async Task ContentType1_To_ContentType1() + public void ContentType1_To_ContentType1() { var content = new ContentType1(null); var view = new ContentType1TestPage(); var viewData = GetViewDataDictionary(content); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf(view.Model); } @@ -269,23 +262,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var view = new ContentType2TestPage(); var viewData = GetViewDataDictionary(content); - Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); + Assert.Throws(() => view.SetViewData(viewData)); } [Test] - public async Task ContentType2_To_ContentType1() + public void ContentType2_To_ContentType1() { var content = new ContentType2(null); var view = new ContentType1TestPage(); var viewData = GetViewDataDictionary(content); - await view.SetViewDataAsyncX(viewData); + view.SetViewData(viewData); Assert.IsInstanceOf(view.Model); } - #endregion - #region Test helpers methods private ViewDataDictionary GetViewDataDictionary(object model) @@ -324,10 +315,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views throw new NotImplementedException(); } - public async Task SetViewDataAsyncX(ViewDataDictionary viewData) - { - await SetViewDataAsync(viewData); - } + public void SetViewData(ViewDataDictionary viewData) => ViewData = (ViewDataDictionary)BindViewData(viewData); } public class RenderModelTestPage : TestPage diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs index a97b67a900..3afc8978b6 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs @@ -5,6 +5,7 @@ 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.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -16,6 +17,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Extensions; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Models; namespace Umbraco.Web.Common.AspNetCore { @@ -28,23 +30,44 @@ namespace Umbraco.Web.Common.AspNetCore public abstract class UmbracoViewPage : RazorPage { - private IUmbracoContext _umbracoContext; + private IUmbracoContextAccessor UmbracoContextAccessor => Context.RequestServices.GetRequiredService(); + private GlobalSettings GlobalSettings => Context.RequestServices.GetRequiredService>().Value; + private ContentSettings ContentSettings => Context.RequestServices.GetRequiredService>().Value; + private IProfilerHtml ProfilerHtml => Context.RequestServices.GetRequiredService(); + private IIOHelper IOHelper => Context.RequestServices.GetRequiredService(); + private ContentModelBinder ContentModelBinder => new ContentModelBinder(); + /// + /// Gets the + /// protected IUmbracoContext UmbracoContext => _umbracoContext ??= UmbracoContextAccessor.UmbracoContext; + /// + public override ViewContext ViewContext + { + get => base.ViewContext; + set + { + // Here we do the magic model swap + ViewContext ctx = value; + ctx.ViewData = BindViewData(ctx.ViewData); + base.ViewContext = ctx; + } + } + /// public override void Write(object value) { if (value is IHtmlEncodedString htmlEncodedString) { - base.WriteLiteral(htmlEncodedString.ToHtmlString()); + WriteLiteral(htmlEncodedString.ToHtmlString()); } else { @@ -52,10 +75,12 @@ namespace Umbraco.Web.Common.AspNetCore } } + /// public override void WriteLiteral(object value) { // filter / add preview banner - if (Context.Response.ContentType.InvariantEquals("text/html")) // ASP.NET default value + // ASP.NET default value is text/html + if (Context.Response.ContentType.InvariantEquals("text/html")) { if (UmbracoContext.IsDebug || UmbracoContext.InPreviewMode) { @@ -70,7 +95,8 @@ namespace Umbraco.Web.Common.AspNetCore { // creating previewBadge markup markupToInject = - string.Format(ContentSettings.PreviewBadge, + string.Format( + ContentSettings.PreviewBadge, IOHelper.ResolveUrl(GlobalSettings.UmbracoPath), Context.Request.GetEncodedUrl(), UmbracoContext.PublishedRequest.PublishedContent.Id); @@ -84,7 +110,7 @@ namespace Umbraco.Web.Common.AspNetCore var sb = new StringBuilder(text); sb.Insert(pos, markupToInject); - base.WriteLiteral(sb.ToString()); + WriteLiteral(sb.ToString()); return; } } @@ -93,70 +119,93 @@ namespace Umbraco.Web.Common.AspNetCore base.WriteLiteral(value); } - // TODO: This trick doesn't work anymore, this method used to be an override. - // Now the model is bound in a different place - // maps model - protected async Task SetViewDataAsync(ViewDataDictionary viewData) + /// + /// Dynamically binds the incoming to the required + /// + /// + /// This is used in order to provide the ability for an Umbraco view to either have a model of type + /// or . This will use the to bind the models + /// to the correct output type. + /// + protected ViewDataDictionary BindViewData(ViewDataDictionary viewData) { + // check if it's already the correct type and continue if it is + if (viewData is ViewDataDictionary vdd) + { + return vdd; + } + + // Here we hand the default case where we know the incoming model is ContentModel and the + // outgoing model is IPublishedContent. This is a fast conversion that doesn't require doing the full + // model binding, allocating classes, etc... + if (viewData.ModelMetadata.ModelType == typeof(ContentModel) + && typeof(TModel) == typeof(IPublishedContent)) + { + var contentModel = (ContentModel)viewData.Model; + viewData.Model = contentModel.Content; + return viewData; + } + // capture the model before we tinker with the viewData var viewDataModel = viewData.Model; // map the view data (may change its type, may set model to null) - viewData = MapViewDataDictionary(viewData, typeof (TModel)); + viewData = MapViewDataDictionary(viewData, typeof(TModel)); // bind the model var bindingContext = new DefaultModelBindingContext(); - await ContentModelBinder.BindModelAsync(bindingContext, viewDataModel, typeof (TModel)); + ContentModelBinder.BindModel(bindingContext, viewDataModel, typeof(TModel)); viewData.Model = bindingContext.Result.Model; - // set the view data - ViewData = (ViewDataDictionary) viewData; + // return the new view data + return (ViewDataDictionary)viewData; } // viewData is the ViewDataDictionary (maybe ) that we have // modelType is the type of the model that we need to bind to - // // figure out whether viewData can accept modelType else replace it - // private static ViewDataDictionary MapViewDataDictionary(ViewDataDictionary viewData, Type modelType) { - var viewDataType = viewData.GetType(); - + Type viewDataType = viewData.GetType(); if (viewDataType.IsGenericType) { // ensure it is the proper generic type - var def = viewDataType.GetGenericTypeDefinition(); + Type def = viewDataType.GetGenericTypeDefinition(); if (def != typeof(ViewDataDictionary<>)) + { throw new Exception("Could not map viewData of type \"" + viewDataType.FullName + "\"."); + } // get the viewData model type and compare with the actual view model type: // viewData is ViewDataDictionary and we will want to assign an // object of type modelType to the Model property of type viewDataModelType, we // need to check whether that is possible - var viewDataModelType = viewDataType.GenericTypeArguments[0]; + Type viewDataModelType = viewDataType.GenericTypeArguments[0]; if (viewDataModelType.IsAssignableFrom(modelType)) + { return viewData; + } } // if not possible or it is not generic then we need to create a new ViewDataDictionary - var nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); + Type nViewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); var tViewData = new ViewDataDictionary(viewData) { Model = null }; // temp view data to copy values var nViewData = (ViewDataDictionary)Activator.CreateInstance(nViewDataType, tViewData); return nViewData; } - public HtmlString RenderSection(string name, HtmlString defaultContents) - { - return RazorPageExtensions.RenderSection(this, name, defaultContents); - } + /// + /// Renders a section with default content if the section isn't defined + /// + public HtmlString RenderSection(string name, HtmlString defaultContents) => RazorPageExtensions.RenderSection(this, name, defaultContents); - public HtmlString RenderSection(string name, string defaultContents) - { - return RazorPageExtensions.RenderSection(this, name, defaultContents); - } + /// + /// Renders a section with default content if the section isn't defined + /// + public HtmlString RenderSection(string name, string defaultContents) => RazorPageExtensions.RenderSection(this, name, defaultContents); } } diff --git a/src/Umbraco.Web.Common/Extensions/RazorPageExtensions.cs b/src/Umbraco.Web.Common/Extensions/RazorPageExtensions.cs index 884e2bbdbc..d6c3fb5715 100644 --- a/src/Umbraco.Web.Common/Extensions/RazorPageExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/RazorPageExtensions.cs @@ -1,19 +1,24 @@ -using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Razor; namespace Umbraco.Extensions { + /// + /// Extension methods for + /// public static class RazorPageExtensions { + /// + /// Renders a section with default content if the section isn't defined + /// public static HtmlString RenderSection(this RazorPage webPage, string name, HtmlString defaultContents) - { - return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents; - } + => webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : defaultContents; + /// + /// Renders a section with default content if the section isn't defined + /// public static HtmlString RenderSection(this RazorPage webPage, string name, string defaultContents) - { - return webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : new HtmlString(defaultContents); - } + => webPage.IsSectionDefined(name) ? webPage.RenderSection(name) : new HtmlString(defaultContents); } } diff --git a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs index d8178033c9..d747a4ff86 100644 --- a/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs +++ b/src/Umbraco.Web.Common/ModelBinders/ContentModelBinder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -10,21 +10,24 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Common.ModelBinders { /// - /// Maps view models, supporting mapping to and from any IPublishedContent or IContentModel. + /// Maps view models, supporting mapping to and from any or . /// public class ContentModelBinder : IModelBinder { + /// public Task BindModelAsync(ModelBindingContext bindingContext) { // Although this model binder is built to work both ways between IPublishedContent and IContentModel in reality - // only IPublishedContent will ever exist in the request. + // only IPublishedContent will ever exist in the request so when this model binder is used as an IModelBinder + // in the aspnet pipeline it will really only support converting from IPublishedContent which is contained + // in the UmbracoRouteValues --> IContentModel if (!bindingContext.ActionContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var source) || !(source is UmbracoRouteValues umbracoRouteValues)) { return Task.CompletedTask; } - BindModelAsync(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType); + BindModel(bindingContext, umbracoRouteValues.PublishedContent, bindingContext.ModelType); return Task.CompletedTask; } @@ -35,34 +38,42 @@ namespace Umbraco.Web.Common.ModelBinders // { ContentModel, ContentModel, IPublishedContent } // to // { ContentModel, ContentModel, IPublishedContent } - // - public Task BindModelAsync(ModelBindingContext bindingContext, object source, Type modelType) + + /// + /// Attempts to bind the model + /// + public void BindModel(ModelBindingContext bindingContext, object source, Type modelType) { // Null model, return if (source == null) { - return Task.CompletedTask; + return; } // If types already match, return - var sourceType = source.GetType(); - if (sourceType.Inherits(modelType)) // includes == + Type sourceType = source.GetType(); + if (sourceType.Inherits(modelType)) { bindingContext.Result = ModelBindingResult.Success(source); - return Task.CompletedTask; + return; } // Try to grab the content var sourceContent = source as IPublishedContent; // check if what we have is an IPublishedContent if (sourceContent == null && sourceType.Implements()) + { // else check if it's an IContentModel, and get the content sourceContent = ((IContentModel)source).Content; + } + if (sourceContent == null) { // else check if we can convert it to a content - var attempt1 = source.TryConvertTo(); + Attempt attempt1 = source.TryConvertTo(); if (attempt1.Success) + { sourceContent = attempt1.Result; + } } // If we have a content @@ -77,41 +88,41 @@ namespace Umbraco.Web.Common.ModelBinders } bindingContext.Result = ModelBindingResult.Success(sourceContent); - return Task.CompletedTask; + return; } // If model is ContentModel, create and return if (modelType == typeof(ContentModel)) { bindingContext.Result = ModelBindingResult.Success(new ContentModel(sourceContent)); - return Task.CompletedTask; + return; } // If model is ContentModel, check content type, then create and return if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(ContentModel<>)) { - var targetContentType = modelType.GetGenericArguments()[0]; + Type targetContentType = modelType.GetGenericArguments()[0]; if (sourceContent.GetType().Inherits(targetContentType) == false) { ThrowModelBindingException(true, true, sourceContent.GetType(), targetContentType); } bindingContext.Result = ModelBindingResult.Success(Activator.CreateInstance(modelType, sourceContent)); - return Task.CompletedTask; + return; } } // Last chance : try to convert - var attempt2 = source.TryConvertTo(modelType); + Attempt attempt2 = source.TryConvertTo(modelType); if (attempt2.Success) { bindingContext.Result = ModelBindingResult.Success(attempt2.Result); - return Task.CompletedTask; + return; } // Fail ThrowModelBindingException(false, false, sourceType, modelType); - return Task.CompletedTask; + return; } private void ThrowModelBindingException(bool sourceContent, bool modelContent, Type sourceType, Type modelType) @@ -121,12 +132,18 @@ namespace Umbraco.Web.Common.ModelBinders // prepare message msg.Append("Cannot bind source"); if (sourceContent) + { msg.Append(" content"); + } + msg.Append(" type "); msg.Append(sourceType.FullName); msg.Append(" to model"); if (modelContent) + { msg.Append(" content"); + } + msg.Append(" type "); msg.Append(modelType.FullName); msg.Append("."); @@ -134,7 +151,6 @@ namespace Umbraco.Web.Common.ModelBinders // raise event, to give model factories a chance at reporting // the error with more details, and optionally request that // the application restarts. - var args = new ModelBindingArgs(sourceType, modelType, msg); ModelBindingException?.Invoke(this, args); From 776df77dfe22a95e2f3c628405b551555d583ede Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 11 Dec 2020 15:29:27 +1100 Subject: [PATCH 008/127] Notes and cleanup --- src/Umbraco.Core/Constants-Web.cs | 5 ----- src/Umbraco.Core/Web/IUmbracoContext.cs | 3 ++- .../Repositories/Implement/ContentRepositoryBase.cs | 9 ++++----- .../Implement/ContentTypeServiceBaseOfTItemTService.cs | 2 +- .../Services/EntityServiceTests.cs | 4 ---- .../Extensions/ApplicationBuilderExtensions.cs | 2 ++ .../Routing/UmbracoRouteValueTransformer.cs | 1 + 7 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index e29d793909..8199d9fbd0 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -7,11 +7,6 @@ namespace Umbraco.Core /// public static class Web { - // TODO: Need to review these... - //public const string UmbracoContextDataToken = "umbraco-context"; - //public const string UmbracoDataToken = "umbraco"; - //public const string PublishedDocumentRequestDataToken = "umbraco-doc-request"; - //public const string CustomRouteDataToken = "umbraco-custom-route"; public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def"; /// diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index 681dedbfd2..7fa02e3b73 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -49,11 +49,12 @@ namespace Umbraco.Web /// /// Boolean value indicating whether the current request is a front-end umbraco request /// - bool IsFrontEndUmbracoRequest { get; } + bool IsFrontEndUmbracoRequest { get; } // TODO: This could easily be an ext method and mocking just means setting the published request to null /// /// 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 IPublishedRequest PublishedRequest { get; set; } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 7ce363e446..a84b34b75a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -772,11 +772,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement * level now since we can fire these events within the transaction... * The reason these events 'need' to fire in the transaction is to ensure data consistency with Nucache (currently * the only thing that uses them). For example, if the transaction succeeds and NuCache listened to ContentService.Saved - * and then NuCache failed at persisting data after the trans completed, then NuCache would be out of sync. This way - * the entire trans is rolled back if NuCache files. That said, I'm unsure this is really required because there - * are other systems that rely on the "ed" (i.e. Saved) events like Examine which would be inconsistent if it failed - * too. I'm just not sure this is totally necessary especially. - * So these events can be moved to the service level. However, see the notes below, it seems the only event we + * and then NuCache failed at persisting data after the trans completed, then NuCache would be out of sync. This way + * the entire trans is rolled back if NuCache files. This is part of the discussion about removing the static events, + * possibly there's 3 levels of eventing, "ing", "scoped" (in trans) and "ed" (after trans). + * These particular events can be moved to the service level. However, see the notes below, it seems the only event we * really need is the ScopedEntityRefresh. The only tricky part with moving that to the service level is that the * handlers of that event will need to deal with the data a little differently because it seems that the * "Published" flag on the content item matters and this event is raised before that flag is switched. Weird. diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs index 1bdd00f576..be541486ff 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Services.Implement /// The purpose of this event being raised within the transaction is so that listeners can perform database /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong /// the entire transaction can be rolled back. This is used by Nucache. - /// TODO: See remarks in ContentRepositoryBase about these types of events. Not sure we need/want them. + /// TODO: See remarks in ContentRepositoryBase about these types of events. /// public static event TypedEventHandler.EventArgs> ScopedRefreshedEntity; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index 4f4a852902..6d5cf19b50 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -42,10 +42,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services [SetUp] public void SetupTestData() { - // This is super nasty, but this lets us initialize the cache while it is empty. - // var publishedSnapshotService = GetRequiredService() as PublishedSnapshotService; - // publishedSnapshotService?.OnApplicationInit(null, EventArgs.Empty); - if (_langFr == null && _langEs == null) { var globalSettings = new GlobalSettings(); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 7290aa9b0e..6fdd5c9be7 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -183,6 +183,8 @@ namespace Umbraco.Extensions /// public static IApplicationBuilder UseUmbracoContentCache(this IApplicationBuilder app) { + // TODO: This should install middleware to initialize instead of eagerly doing the initialize here + PublishedSnapshotServiceEventHandler publishedContentEvents = app.ApplicationServices.GetRequiredService(); publishedContentEvents.Start(); return app; diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index 2c1debc3ee..be7c9f7409 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.Website.Routing /// It seems as though with the "State" parameter we could more easily assign the IPublishedRequest or IPublishedContent /// or UmbracoContext more easily that way. In the meantime we will rely on assigning the IPublishedRequest to the /// route values along with the IPublishedContent to the umbraco context + /// have created a GH discussion here https://github.com/dotnet/aspnetcore/discussions/28562 we'll see if anyone responds /// public class UmbracoRouteValueTransformer : DynamicRouteValueTransformer { From fc16669a91017efb5c991d8528395e1aa88981ed Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Dec 2020 16:55:45 +1100 Subject: [PATCH 009/127] Moves cache initialization the the request middleware --- .../ApplicationBuilderExtensions.cs | 13 ------- .../Middleware/UmbracoRequestMiddleware.cs | 39 +++++++++++++++---- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 6fdd5c9be7..655867ebeb 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -37,7 +37,6 @@ namespace Umbraco.Extensions // We need to add this before UseRouting so that the UmbracoContext and other middlewares are executed // before endpoint routing middleware. app.UseUmbracoRouting(); - app.UseUmbracoContentCache(); app.UseStatusCodePages(); @@ -178,18 +177,6 @@ namespace Umbraco.Extensions return app; } - /// - /// Enables the Umbraco content cache - /// - public static IApplicationBuilder UseUmbracoContentCache(this IApplicationBuilder app) - { - // TODO: This should install middleware to initialize instead of eagerly doing the initialize here - - PublishedSnapshotServiceEventHandler publishedContentEvents = app.ApplicationServices.GetRequiredService(); - publishedContentEvents.Start(); - return app; - } - /// /// Ensures the runtime is shutdown when the application is shutting down /// diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 56f093ed2b..7845962928 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -8,6 +9,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Web.Common.Lifetime; +using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.Web.Common.Middleware { @@ -16,7 +18,12 @@ namespace Umbraco.Web.Common.Middleware /// Manages Umbraco request objects and their lifetime /// /// + /// + /// This is responsible for initializing the content cache + /// + /// /// This is responsible for creating and assigning an + /// /// public class UmbracoRequestMiddleware : IMiddleware { @@ -25,6 +32,10 @@ namespace Umbraco.Web.Common.Middleware private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly IRequestCache _requestCache; private readonly IBackOfficeSecurityFactory _backofficeSecurityFactory; + private readonly PublishedSnapshotServiceEventHandler _publishedSnapshotServiceEventHandler; + private static bool s_cacheInitialized = false; + private static bool s_cacheInitializedFlag = false; + private static object s_cacheInitializedLock = new object(); /// /// Initializes a new instance of the class. @@ -34,13 +45,15 @@ namespace Umbraco.Web.Common.Middleware IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager, IUmbracoContextFactory umbracoContextFactory, IRequestCache requestCache, - IBackOfficeSecurityFactory backofficeSecurityFactory) + IBackOfficeSecurityFactory backofficeSecurityFactory, + PublishedSnapshotServiceEventHandler publishedSnapshotServiceEventHandler) { _logger = logger; _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager; _umbracoContextFactory = umbracoContextFactory; _requestCache = requestCache; _backofficeSecurityFactory = backofficeSecurityFactory; + _publishedSnapshotServiceEventHandler = publishedSnapshotServiceEventHandler; } /// @@ -55,6 +68,8 @@ namespace Umbraco.Web.Common.Middleware return; } + EnsureContentCacheInitialized(); + _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext, TODO: Why? UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); @@ -109,16 +124,15 @@ 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) { // do not process if client-side request if (requestUri.IsClientSideRequest()) + { return; + } - //get a list of keys to dispose + // get a list of keys to dispose var keys = new HashSet(); foreach (var i in requestCache) { @@ -127,7 +141,7 @@ namespace Umbraco.Web.Common.Middleware keys.Add(i.Key); } } - //dispose each item and key that was found as disposable. + // dispose each item and key that was found as disposable. foreach (var k in keys) { try @@ -149,6 +163,17 @@ namespace Umbraco.Web.Common.Middleware } } - + /// + /// Initializes the content cache one time + /// + private void EnsureContentCacheInitialized() => LazyInitializer.EnsureInitialized( + ref s_cacheInitialized, + ref s_cacheInitializedFlag, + ref s_cacheInitializedLock, + () => + { + _publishedSnapshotServiceEventHandler.Start(); + return true; + }); } } From 827bf80d1d86bb7d60585f147442211451305009 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Dec 2020 17:04:02 +1100 Subject: [PATCH 010/127] Removes EnterPreview, RefreshPreview, ExitPreview --- .../IPublishedSnapshotService.cs | 50 ------------------- .../PublishedSnapshotServiceBase.cs | 9 ---- .../PublishedSnapshotService.cs | 21 -------- .../XmlPublishedSnapshotService.cs | 26 ---------- .../Controllers/PreviewController.cs | 14 ++---- 5 files changed, 4 insertions(+), 116 deletions(-) diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index cc526ffe6e..a953c7677e 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -77,56 +77,6 @@ namespace Umbraco.Web.PublishedCache #endregion - #region Preview - - /* Later on we can imagine that EnterPreview would handle a "level" that would be either - * the content only, or the content's branch, or the whole tree + it could be possible - * to register filters against the factory to filter out which nodes should be preview - * vs non preview. - * - * EnterPreview() returns the previewToken. It is up to callers to store that token - * wherever they want, most probably in a cookie. - * - */ - - /// - /// Enters preview for specified user and content. - /// - /// The user. - /// The content identifier. - /// A preview token. - /// - /// Tells the caches that they should prepare any data that they would be keeping - /// in order to provide preview to a given user. In the Xml cache this means creating the Xml - /// file, though other caches may do things differently. - /// Does not handle the preview token storage (cookie, etc) that must be handled separately. - /// - string EnterPreview(IUser user, int contentId); // TODO: Remove this, it is not needed and is legacy from the XML cache - - /// - /// Refreshes preview for a specified content. - /// - /// The preview token. - /// The content identifier. - /// Tells the caches that they should update any data that they would be keeping - /// in order to provide preview to a given user. In the Xml cache this means updating the Xml - /// file, though other caches may do things differently. - void RefreshPreview(string previewToken, int contentId); // TODO: Remove this, it is not needed and is legacy from the XML cache - - /// - /// Exits preview for a specified preview token. - /// - /// The preview token. - /// - /// Tells the caches that they can dispose of any data that they would be keeping - /// in order to provide preview to a given user. In the Xml cache this means deleting the Xml file, - /// though other caches may do things differently. - /// Does not handle the preview token storage (cookie, etc) that must be handled separately. - /// - void ExitPreview(string previewToken); // TODO: Remove this, it is not needed and is legacy from the XML cache - - #endregion - #region Changes /* An IPublishedCachesService implementation can rely on transaction-level events to update diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs index 6a8324cc27..d334e69775 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs @@ -36,15 +36,6 @@ namespace Umbraco.Web.PublishedCache /// public abstract bool EnsureEnvironment(out IEnumerable errors); - /// - public abstract string EnterPreview(IUser user, int contentId); - - /// - public abstract void RefreshPreview(string previewToken, int contentId); - - /// - public abstract void ExitPreview(string previewToken); - /// public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 3011e3d655..cb5fed176e 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -1131,27 +1131,6 @@ namespace Umbraco.Web.PublishedCache.NuCache #endregion - #region Preview - - // TODO: Delete this all - public override string EnterPreview(IUser user, int contentId) - { - return "preview"; // anything - } - - public override void RefreshPreview(string previewToken, int contentId) - { - // nothing - } - - public override void ExitPreview(string previewToken) - { - // nothing - } - - #endregion - - #region Rebuild Database PreCache public override void Rebuild( diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 9c9e2d1da2..134f3b1938 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -169,32 +169,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #endregion - #region Preview - - public override string EnterPreview(IUser user, int contentId) - { - var previewContent = new PreviewContent(_xmlStore, user.Id); - previewContent.CreatePreviewSet(contentId, true); // preview branch below that content - return previewContent.Token; - //previewContent.ActivatePreviewCookie(); - } - - public override void RefreshPreview(string previewToken, int contentId) - { - if (previewToken.IsNullOrWhiteSpace()) return; - var previewContent = new PreviewContent(_xmlStore, previewToken); - previewContent.CreatePreviewSet(contentId, true); // preview branch below that content - } - - public override void ExitPreview(string previewToken) - { - if (previewToken.IsNullOrWhiteSpace()) return; - var previewContent = new PreviewContent(_xmlStore, previewToken); - previewContent.ClearPreviewSet(); - } - - #endregion - #region Xml specific /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 3dd4191c2e..0405012898 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Options; @@ -108,7 +108,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// The endpoint that is loaded within the preview iframe /// - /// [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public ActionResult Frame(int id, string culture) { @@ -119,22 +118,17 @@ namespace Umbraco.Web.BackOffice.Controllers return RedirectPermanent($"../../{id}.aspx{query}"); } + public ActionResult EnterPreview(int id) { var user = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; - - var previewToken = _publishedSnapshotService.EnterPreview(user, id); - - _cookieManager.SetCookieValue(Constants.Web.PreviewCookieName, previewToken); + _cookieManager.SetCookieValue(Constants.Web.PreviewCookieName, "preview"); return null; } + public ActionResult End(string redir = null) { - var previewToken = _cookieManager.GetPreviewCookieValue(); - - _publishedSnapshotService.ExitPreview(previewToken); - _cookieManager.ExpireCookie(Constants.Web.PreviewCookieName); // Expire Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. From 4bb1535786bf827edd30c1ec0899c539e4b515a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 14 Dec 2020 17:43:00 +1100 Subject: [PATCH 011/127] allows the back office to route --- src/Umbraco.Core/UriExtensions.cs | 16 ++++-------- .../Extensions/HttpRequestExtensions.cs | 23 ++++++----------- ...racoWebsiteApplicationBuilderExtensions.cs | 4 +-- .../Routing/UmbracoRouteValueTransformer.cs | 25 +++++++++++++++++-- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 0452373d55..ea846f7f7a 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; @@ -100,16 +100,13 @@ namespace Umbraco.Core return false; } - //if its anything else we can assume it's back office + // 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); @@ -117,18 +114,14 @@ namespace Umbraco.Core .TrimStart(authority) .TrimStart("/"); - //check if this is in the umbraco back office + // 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 /// - /// - /// - /// - /// - internal static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + public static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/")) @@ -138,6 +131,7 @@ namespace Umbraco.Core { return true; } + return false; } diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index 69fae56d32..fe61941e5c 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net; using System.Text; @@ -18,27 +18,20 @@ namespace Umbraco.Extensions /// /// Check if a preview cookie exist /// - /// - /// public static bool HasPreviewCookie(this HttpRequest request) - { - return request.Cookies.TryGetValue(Constants.Web.PreviewCookieName, out var cookieVal) && !cookieVal.IsNullOrWhiteSpace(); - } + => request.Cookies.TryGetValue(Constants.Web.PreviewCookieName, out var cookieVal) && !cookieVal.IsNullOrWhiteSpace(); public static bool IsBackOfficeRequest(this HttpRequest request, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - return new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment); - } + => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment); public static bool IsClientSideRequest(this HttpRequest request) - { - return new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest(); - } + => 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); public static string ClientCulture(this HttpRequest request) - { - return request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; - } + => request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; /// /// Determines if a request is local. diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs index 32d84088c1..438ad154ed 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs @@ -38,10 +38,10 @@ namespace Umbraco.Extensions { app.UseEndpoints(endpoints => { - endpoints.MapDynamicControllerRoute("/{**slug}"); - NoContentRoutes noContentRoutes = app.ApplicationServices.GetRequiredService(); noContentRoutes.CreateRoutes(endpoints); + + endpoints.MapDynamicControllerRoute("/{**slug}"); }); return app; diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index be7c9f7409..c41f34acc6 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -4,14 +4,18 @@ 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; 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.Models.PublishedContent; using Umbraco.Core.Strings; using Umbraco.Extensions; @@ -42,6 +46,8 @@ namespace Umbraco.Web.Website.Routing private readonly IShortStringHelper _shortStringHelper; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private readonly IPublishedRouter _publishedRouter; + private readonly GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; /// /// Initializes a new instance of the class. @@ -52,7 +58,9 @@ namespace Umbraco.Web.Website.Routing IUmbracoRenderingDefaults renderingDefaults, IShortStringHelper shortStringHelper, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, - IPublishedRouter publishedRouter) + IPublishedRouter publishedRouter, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment) { _logger = logger; _umbracoContextAccessor = umbracoContextAccessor; @@ -60,19 +68,32 @@ namespace Umbraco.Web.Website.Routing _shortStringHelper = shortStringHelper; _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; _publishedRouter = publishedRouter; + _globalSettings = globalSettings.Value; + _hostingEnvironment = hostingEnvironment; } /// public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) { + // will be null for any client side requests like JS, etc... if (_umbracoContextAccessor.UmbracoContext == null) { - throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); + return values; + // throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); + } + + // 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; } bool routed = RouteRequest(_umbracoContextAccessor.UmbracoContext, out IPublishedRequest publishedRequest); if (!routed) { + return values; // TODO: Deal with it not being routable, perhaps this should be an enum result? } 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 012/127] 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 013/127] 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 014/127] 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 015/127] 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 47e98bfdc67729a0c5866699c0bfe35e3a060c6c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Dec 2020 16:31:23 +1100 Subject: [PATCH 016/127] Fixes up issue with UsePlugins and where it's executed --- .../BackOfficeApplicationBuilderExtensions.cs | 33 +------------------ .../ApplicationBuilderExtensions.cs | 28 ++++++++++++++++ .../UmbracoPluginPhysicalFileProvider.cs | 7 ++-- 3 files changed, 32 insertions(+), 36 deletions(-) rename src/{Umbraco.Web.BackOffice => Umbraco.Web.Common}/Plugins/UmbracoPluginPhysicalFileProvider.cs (92%) diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs index bee2854a7f..71cb14eb78 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs @@ -1,14 +1,7 @@ using System; -using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using SixLabors.ImageSharp.Web.DependencyInjection; -using Umbraco.Core; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Middleware; -using Umbraco.Web.BackOffice.Plugins; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Security; @@ -19,7 +12,6 @@ namespace Umbraco.Extensions /// public static class BackOfficeApplicationBuilderExtensions { - app.UseUmbracoPlugins(); public static IApplicationBuilder UseUmbracoBackOffice(this IApplicationBuilder app) { // NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization @@ -50,30 +42,6 @@ namespace Umbraco.Extensions return app; } - public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app) - { - var hostingEnvironment = app.ApplicationServices.GetRequiredService(); - var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); - - var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); - - // Ensure the plugin folder exists - Directory.CreateDirectory(pluginFolder); - - var fileProvider = new UmbracoPluginPhysicalFileProvider( - pluginFolder, - umbracoPluginSettings); - - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = fileProvider, - RequestPath = Constants.SystemDirectories.AppPlugins - }); - - return app; - } - - public static IApplicationBuilder UseUmbracoPreview(this IApplicationBuilder app) { // TODO: I'm unsure this middleware will execute before the endpoint, we'll have to see @@ -87,6 +55,7 @@ namespace Umbraco.Extensions return app; } + private static IApplicationBuilder UseBackOfficeUserManagerAuditing(this IApplicationBuilder app) { var auditer = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 655867ebeb..4eb26ce789 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -1,16 +1,20 @@ using System; +using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Serilog.Context; using SixLabors.ImageSharp.Web.DependencyInjection; using Smidge; using Smidge.Nuglify; using StackExchange.Profiling; using Umbraco.Core; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Web.Common.Middleware; +using Umbraco.Web.Common.Plugins; using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.Extensions @@ -44,6 +48,7 @@ namespace Umbraco.Extensions // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoBackOffice too? app.UseImageSharp(); app.UseStaticFiles(); + app.UseUmbracoPlugins(); // UseRouting adds endpoint routing middleware, this means that middlewares registered after this one // will execute after endpoint routing. The ordering of everything is quite important here, see @@ -177,6 +182,29 @@ namespace Umbraco.Extensions return app; } + public static IApplicationBuilder UseUmbracoPlugins(this IApplicationBuilder app) + { + var hostingEnvironment = app.ApplicationServices.GetRequiredService(); + var umbracoPluginSettings = app.ApplicationServices.GetRequiredService>(); + + var pluginFolder = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); + + // Ensure the plugin folder exists + Directory.CreateDirectory(pluginFolder); + + var fileProvider = new UmbracoPluginPhysicalFileProvider( + pluginFolder, + umbracoPluginSettings); + + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = fileProvider, + RequestPath = Constants.SystemDirectories.AppPlugins + }); + + return app; + } + /// /// Ensures the runtime is shutdown when the application is shutting down /// diff --git a/src/Umbraco.Web.BackOffice/Plugins/UmbracoPluginPhysicalFileProvider.cs b/src/Umbraco.Web.Common/Plugins/UmbracoPluginPhysicalFileProvider.cs similarity index 92% rename from src/Umbraco.Web.BackOffice/Plugins/UmbracoPluginPhysicalFileProvider.cs rename to src/Umbraco.Web.Common/Plugins/UmbracoPluginPhysicalFileProvider.cs index 42300e3b71..d62e203cce 100644 --- a/src/Umbraco.Web.BackOffice/Plugins/UmbracoPluginPhysicalFileProvider.cs +++ b/src/Umbraco.Web.Common/Plugins/UmbracoPluginPhysicalFileProvider.cs @@ -1,14 +1,13 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.IO; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.Options; -using Umbraco.Core; using Umbraco.Core.Configuration.Models; -namespace Umbraco.Web.BackOffice.Plugins +namespace Umbraco.Web.Common.Plugins { /// /// Looks up files using the on-disk file system and check file extensions are on a allow list @@ -41,7 +40,7 @@ namespace Umbraco.Web.BackOffice.Plugins public new IFileInfo GetFileInfo(string subpath) { var extension = Path.GetExtension(subpath); - var subPathInclAppPluginsFolder = Path.Combine(Constants.SystemDirectories.AppPlugins, subpath); + var subPathInclAppPluginsFolder = Path.Combine(Core.Constants.SystemDirectories.AppPlugins, subpath); if (!_options.Value.BrowsableFileExtensions.Contains(extension)) { return new NotFoundFileInfo(subPathInclAppPluginsFolder); From 868c9d02df2905ea3e38ac325ee3ae48d542bd0d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Dec 2020 16:39:06 +1100 Subject: [PATCH 017/127] Fixes routing when installer needs to run --- .../Routing/UmbracoRouteValueTransformer.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index c41f34acc6..731c0320d6 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -48,6 +48,7 @@ namespace Umbraco.Web.Website.Routing private readonly IPublishedRouter _publishedRouter; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; + private readonly IRuntimeState _runtime; /// /// Initializes a new instance of the class. @@ -60,7 +61,8 @@ namespace Umbraco.Web.Website.Routing IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IPublishedRouter publishedRouter, IOptions globalSettings, - IHostingEnvironment hostingEnvironment) + IHostingEnvironment hostingEnvironment, + IRuntimeState runtime) { _logger = logger; _umbracoContextAccessor = umbracoContextAccessor; @@ -70,16 +72,22 @@ namespace Umbraco.Web.Website.Routing _publishedRouter = publishedRouter; _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; + _runtime = runtime; } /// public override async ValueTask TransformAsync(HttpContext httpContext, RouteValueDictionary values) { + // If we aren't running, then we have nothing to route + if (_runtime.Level != RuntimeLevel.Run) + { + return values; + } + // will be null for any client side requests like JS, etc... if (_umbracoContextAccessor.UmbracoContext == null) { return values; - // throw new InvalidOperationException($"There is no current UmbracoContext, it must be initialized before the {nameof(UmbracoRouteValueTransformer)} executes, ensure that {nameof(UmbracoRequestMiddleware)} is registered prior to 'UseRouting'"); } // Check for back office request From cc1404747b2437dd3c20a2b4f9ddf166bf2b432d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Dec 2020 16:27:28 +1100 Subject: [PATCH 018/127] Changes PublishedSnapshotService to lazily load it's caches on demand when they are required instead of relying on an external initializer to load them. --- .../IPublishedSnapshotService.cs | 25 +-- .../PublishedSnapshotServiceBase.cs | 9 +- ...UmbracoContextPublishedSnapshotAccessor.cs | 7 +- .../Persistence/INuCacheContentService.cs | 2 +- .../Property.cs | 3 +- .../PublishedContent.cs | 4 +- .../PublishedSnapshotService.cs | 210 ++++++++---------- .../PublishedSnapshotServiceEventHandler.cs | 4 - .../Controllers/ContentControllerTests.cs | 25 +-- .../UmbracoTestServerTestBase.cs | 12 +- .../XmlPublishedSnapshotService.cs | 2 +- 11 files changed, 131 insertions(+), 172 deletions(-) diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index a953c7677e..af8f72ce6d 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -24,13 +24,9 @@ namespace Umbraco.Web.PublishedCache */ /// - /// Loads the caches on startup - called once during startup - /// TODO: Temporary, this is temporal coupling, we cannot use IUmbracoApplicationLifetime.ApplicationInit (which we want to delete) - /// handler because that is executed with netcore's IHostApplicationLifetime.ApplicationStarted mechanism which fires async - /// which we don't want since this will not have initialized before our endpoints execute. So for now this is explicitly - /// called on UseUmbracoContentCaching on startup. + /// Gets the published snapshot accessor. /// - void LoadCachesOnStartup(); + IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } /// /// Creates a published snapshot. @@ -42,11 +38,6 @@ namespace Umbraco.Web.PublishedCache /// which is not specified and depends on the actual published snapshot service implementation. IPublishedSnapshot CreatePublishedSnapshot(string previewToken); - /// - /// Gets the published snapshot accessor. - /// - IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } - /// /// Ensures that the published snapshot has the proper environment to run. /// @@ -54,17 +45,15 @@ namespace Umbraco.Web.PublishedCache /// A value indicating whether the published snapshot has the proper environment to run. bool EnsureEnvironment(out IEnumerable errors); - #region Rebuild - /// - /// Rebuilds internal caches (but does not reload). + /// Rebuilds internal database caches (but does not reload). /// /// The operation batch size to process the items /// If not null will process content for the matching content types, if empty will process all content /// If not null will process content for the matching media types, if empty will process all media /// If not null will process content for the matching members types, if empty will process all members /// - /// Forces the snapshot service to rebuild its internal caches. For instance, some caches + /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches /// may rely on a database table to store pre-serialized version of documents. /// This does *not* reload the caches. Caches need to be reloaded, for instance via /// RefreshAllPublishedSnapshot method. @@ -75,10 +64,6 @@ namespace Umbraco.Web.PublishedCache IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null); - #endregion - - #region Changes - /* An IPublishedCachesService implementation can rely on transaction-level events to update * its internal, database-level data, as these events are purely internal. However, it cannot * rely on cache refreshers CacheUpdated events to update itself, as these events are external @@ -123,8 +108,6 @@ namespace Umbraco.Web.PublishedCache /// The changes. void Notify(DomainCacheRefresher.JsonPayload[] payloads); - #endregion - // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus? string GetStatus(); diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs index d334e69775..f33eb61e8f 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs @@ -6,6 +6,7 @@ using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache { + // TODO: This base class probably shouldn't exist public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService { /// @@ -51,15 +52,12 @@ namespace Umbraco.Web.PublishedCache /// public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads); - // TODO: Why is this virtual? - /// - public virtual void Rebuild( + public abstract void Rebuild( int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, - IReadOnlyCollection memberTypeIds = null) - { } + IReadOnlyCollection memberTypeIds = null); /// public virtual void Dispose() @@ -76,6 +74,5 @@ namespace Umbraco.Web.PublishedCache { } - public abstract void LoadCachesOnStartup(); } } diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs index 7e0ebc6f13..874da1f3aa 100644 --- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -1,6 +1,11 @@ -using System; +using System; namespace Umbraco.Web.PublishedCache { + // TODO: This is a mess. This is a circular reference: + // IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor + // Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange + // The underlying reason for this mess is because IPublishedContent is both a service and a model. + // Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor { private readonly IUmbracoContextAccessor _umbracoContextAccessor; diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs index 0ac3939742..4a3f5b2b5d 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs @@ -75,7 +75,7 @@ namespace Umbraco.Infrastructure.PublishedCache.Persistence void RefreshEntity(IContentBase content); /// - /// Rebuilds the caches for content, media and/or members based on the content type ids specified + /// Rebuilds the database caches for content, media and/or members based on the content type ids specified /// /// The operation batch size to process the items /// If not null will process content for the matching content types, if empty will process all content diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index 86023bb302..1b70c6504c 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Xml.Serialization; using Umbraco.Core; @@ -158,6 +158,7 @@ namespace Umbraco.Web.PublishedCache.NuCache default: throw new InvalidOperationException("Invalid cache level."); } + return cacheValues; } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedContent.cs b/src/Umbraco.PublishedCache.NuCache/PublishedContent.cs index 6fe65a4ff5..9cdc0db4fa 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedContent.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedContent.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -43,7 +43,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // add one property per property type - this is required, for the indexing to work // if contentData supplies pdatas, use them, else use null contentData.Properties.TryGetValue(propertyType.Alias, out var pdatas); // else will be null - properties[i++] =new Property(propertyType, this, pdatas, _publishedSnapshotAccessor); + properties[i++] = new Property(propertyType, this, pdatas, _publishedSnapshotAccessor); } PropertiesArray = properties; } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index cb5fed176e..6225e68ec6 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Threading; using CSharpTest.Net.Collections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -46,7 +47,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly NuCacheSettings _config; // volatile because we read it with no lock - private volatile bool _isReady; + private bool _isReady; + private bool _isReadSet; + private object _isReadyLock; private readonly ContentStore _contentStore; private readonly ContentStore _mediaStore; @@ -139,20 +142,20 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - #region Id <-> Key methods - // NOTE: These aren't used within this object but are made available internally to improve the IdKey lookup performance // when nucache is enabled. - // TODO: Does this need to be here? - + // TODO: Does this need to be here? internal int GetDocumentId(Guid udi) => GetId(_contentStore, udi); - internal int GetMediaId(Guid udi) => GetId(_mediaStore, udi); - internal Guid GetDocumentUid(int id) => GetUid(_contentStore, id); - internal Guid GetMediaUid(int id) => GetUid(_mediaStore, id); - private int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? 0; - private Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? Guid.Empty; - #endregion + internal int GetMediaId(Guid udi) => GetId(_mediaStore, udi); + + internal Guid GetDocumentUid(int id) => GetUid(_contentStore, id); + + internal Guid GetMediaUid(int id) => GetUid(_mediaStore, id); + + private int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? 0; + + private Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? Guid.Empty; /// /// Install phase of @@ -204,66 +207,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - /// - /// Populates the stores - /// - public override void LoadCachesOnStartup() - { - lock (_storesLock) - { - if (_isReady) - { - throw new InvalidOperationException("The caches can only be loaded on startup one time"); - } - - var okContent = false; - var okMedia = false; - - try - { - if (_localContentDbExists) - { - okContent = LockAndLoadContent(() => LoadContentFromLocalDbLocked(true)); - if (!okContent) - { - _logger.LogWarning("Loading content from local db raised warnings, will reload from database."); - } - } - - if (_localMediaDbExists) - { - okMedia = LockAndLoadMedia(() => LoadMediaFromLocalDbLocked(true)); - if (!okMedia) - { - _logger.LogWarning("Loading media from local db raised warnings, will reload from database."); - } - } - - if (!okContent) - { - LockAndLoadContent(() => LoadContentFromDatabaseLocked(true)); - } - - if (!okMedia) - { - LockAndLoadMedia(() => LoadMediaFromDatabaseLocked(true)); - } - - LockAndLoadDomains(); - } - catch (Exception ex) - { - _logger.LogCritical(ex, "Panic, exception while loading cache data."); - throw; - } - - // finally, cache is ready! - _isReady = true; - } - } - - #region Local files - private string GetLocalFilesPath() { var path = Path.Combine(_hostingEnvironment.LocalTempPath, "NuCache"); @@ -278,7 +221,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private void DeleteLocalFilesForContent() { - if (_isReady && _localContentDb != null) + if (Volatile.Read(ref _isReady) && _localContentDb != null) { throw new InvalidOperationException("Cannot delete local files while the cache uses them."); } @@ -293,7 +236,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private void DeleteLocalFilesForMedia() { - if (_isReady && _localMediaDb != null) + if (Volatile.Read(ref _isReady) && _localMediaDb != null) { throw new InvalidOperationException("Cannot delete local files while the cache uses them."); } @@ -306,10 +249,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - #endregion - - #region Environment - public override bool EnsureEnvironment(out IEnumerable errors) { // must have app_data and be able to write files into it @@ -318,9 +257,62 @@ namespace Umbraco.Web.PublishedCache.NuCache return ok; } - #endregion + /// + /// Populates the stores + /// + private void EnsureCaches() => LazyInitializer.EnsureInitialized( + ref _isReady, + ref _isReadSet, + ref _isReadyLock, + () => + { + // even though we are ready locked here we want to ensure that the stores lock is also locked + lock (_storesLock) + { + var okContent = false; + var okMedia = false; - #region Populate Stores + try + { + if (_localContentDbExists) + { + okContent = LockAndLoadContent(() => LoadContentFromLocalDbLocked(true)); + if (!okContent) + { + _logger.LogWarning("Loading content from local db raised warnings, will reload from database."); + } + } + + if (_localMediaDbExists) + { + okMedia = LockAndLoadMedia(() => LoadMediaFromLocalDbLocked(true)); + if (!okMedia) + { + _logger.LogWarning("Loading media from local db raised warnings, will reload from database."); + } + } + + if (!okContent) + { + LockAndLoadContent(() => LoadContentFromDatabaseLocked(true)); + } + + if (!okMedia) + { + LockAndLoadMedia(() => LoadMediaFromDatabaseLocked(true)); + } + + LockAndLoadDomains(); + } + catch (Exception ex) + { + _logger.LogCritical(ex, "Panic, exception while loading cache data."); + throw; + } + + return true; + } + }); // sudden panic... but in RepeatableRead can a content that I haven't already read, be removed // before I read it? NO! because the WHOLE content tree is read-locked using WithReadLocked. @@ -482,10 +474,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - #endregion - - #region Handle Notifications - // note: if the service is not ready, ie _isReady is false, then notifications are ignored // SetUmbracoVersionStep issues a DistributedCache.Instance.RefreshAll...() call which should cause @@ -512,7 +500,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) { // no cache, trash everything - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { DeleteLocalFilesForContent(); draftChanged = publishedChanged = true; @@ -613,7 +601,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { // no cache, trash everything - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { DeleteLocalFilesForMedia(); anythingChanged = true; @@ -711,7 +699,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { return; } @@ -812,7 +800,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { return; } @@ -856,7 +844,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { return; } @@ -894,12 +882,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // Methods used to prevent allocations of lists private void AddToList(ref List list, int val) => GetOrCreateList(ref list).Add(val); + private List GetOrCreateList(ref List list) => list ?? (list = new List()); - #endregion - - #region Content Types - private IReadOnlyCollection CreateContentTypes(PublishedItemType itemType, int[] ids) { // XxxTypeService.GetAll(empty) returns everything! @@ -1028,14 +1013,12 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - #endregion - - #region Create, Get Published Snapshot - public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { + EnsureCaches(); + // no cache, no joy - if (_isReady == false) + if (Volatile.Read(ref _isReady) == false) { throw new InvalidOperationException("The published snapshot service has not properly initialized."); } @@ -1049,6 +1032,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // even though the underlying elements may not change (store snapshots) public PublishedSnapshot.PublishedSnapshotElements GetElements(bool previewDefault) { + EnsureCaches(); + // note: using ObjectCacheAppCache for elements and snapshot caches // is not recommended because it creates an inner MemoryCache which is a heavy // thing - better use a dictionary-based cache which "just" creates a concurrent @@ -1129,10 +1114,7 @@ namespace Umbraco.Web.PublishedCache.NuCache }; } - #endregion - - #region Rebuild Database PreCache - + /// public override void Rebuild( int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, @@ -1176,12 +1158,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - #endregion - - #region Instrument - public override string GetStatus() { + EnsureCaches(); + var dbCacheIsOk = VerifyContentDbCache() && VerifyMediaDbCache() && VerifyMemberDbCache(); @@ -1203,20 +1183,26 @@ namespace Umbraco.Web.PublishedCache.NuCache " and " + ms + " snapshot" + (ms > 1 ? "s" : "") + "."; } + // TODO: This should be async since it's calling into async public override void Collect() { + EnsureCaches(); + var contentCollect = _contentStore.CollectAsync(); var mediaCollect = _mediaStore.CollectAsync(); System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect); } - #endregion + internal ContentStore GetContentStore() + { + EnsureCaches(); + return _contentStore; + } - #region Internals/Testing - - internal ContentStore GetContentStore() => _contentStore; - internal ContentStore GetMediaStore() => _mediaStore; - - #endregion + internal ContentStore GetMediaStore() + { + EnsureCaches(); + return _mediaStore; + } } } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 20ce74ea70..31bc9b3d63 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -40,10 +40,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return false; } - // this initializes the caches. - // TODO: This is still temporal coupling (i.e. Initialize) - _publishedSnapshotService.LoadCachesOnStartup(); - // we always want to handle repository events, configured or not // assuming no repository event will trigger before the whole db is ready // (ideally we'd have Upgrading.App vs Upgrading.Data application states...) diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index 731079da7c..dbac7c9f76 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -22,13 +22,12 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers /// /// Returns 404 if the content wasn't found based on the ID specified /// - /// [Test] public async Task PostSave_Validate_Existing_Content() { var localizationService = GetRequiredService(); - //Add another language + // Add another language localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") .WithIsDefault(false) @@ -76,10 +75,10 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers // Assert Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); -// Assert.AreEqual(")]}',\n{\"Message\":\"content was not found\"}", response.Item1.Content.ReadAsStringAsync().Result); -// -// //var obj = JsonConvert.DeserializeObject>(response.Item2); -// //Assert.AreEqual(0, obj.TotalItems); + // Assert.AreEqual(")]}',\n{\"Message\":\"content was not found\"}", response.Item1.Content.ReadAsStringAsync().Result); + // + // //var obj = JsonConvert.DeserializeObject>(response.Item2); + // //Assert.AreEqual(0, obj.TotalItems); } [Test] @@ -88,7 +87,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers var localizationService = GetRequiredService(); - //Add another language + // Add another language localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") .WithIsDefault(false) @@ -96,7 +95,6 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers var url = PrepareUrl(x => x.PostSave(null)); - var contentTypeService = GetRequiredService(); var contentType = new ContentTypeBuilder() @@ -128,7 +126,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers .Build(); // HERE we force the test to fail - model.Variants = model.Variants.Select(x=> + model.Variants = model.Variants.Select(x => { x.Save = false; return x; @@ -141,7 +139,6 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers }); // Assert - var body = await response.Content.ReadAsStringAsync(); Assert.Multiple(() => @@ -155,13 +152,12 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers /// /// Returns 404 if any of the posted properties dont actually exist /// - /// [Test] public async Task PostSave_Validate_Properties_Exist() { var localizationService = GetRequiredService(); - //Add another language + // Add another language localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") .WithIsDefault(false) @@ -215,12 +211,11 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers }); // Assert - var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); - Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); } [Test] diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 93769eaaed..d0bc38ea0b 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -1,4 +1,4 @@ - + using System; using System.Linq.Expressions; using System.Net.Http; @@ -116,20 +116,20 @@ namespace Umbraco.Tests.Integration.TestServerTest } protected HttpClient Client { get; private set; } + protected LinkGenerator LinkGenerator { get; private set; } + protected WebApplicationFactory Factory { get; private set; } [TearDown] public override void TearDown() { base.TearDown(); - base.TerminateCoreRuntime(); + TerminateCoreRuntime(); Factory.Dispose(); } - #region IStartup - public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); @@ -160,9 +160,5 @@ namespace Umbraco.Tests.Integration.TestServerTest { app.UseUmbraco(); } - - #endregion - - } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 134f3b1938..5e05e31708 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -253,6 +253,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache return "Test status"; } - public override void LoadCachesOnStartup() { } + public override void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) { } } } From 3f0f5c14495eb7dd6194b15f5309e7d0cddc20ee Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 18 Dec 2020 12:23:44 +0100 Subject: [PATCH 019/127] 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 75796e3eae279773759ea21c24fee912906e9869 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Dec 2020 15:58:47 +1100 Subject: [PATCH 020/127] Fixing tests, removing old files, adds notes --- .../Controllers/ContentControllerTests.cs | 2 +- .../UmbracoTestServerTestBase.cs | 33 ++++--- .../ControllerTesting/TestRunner.cs | 95 ------------------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - .../Controllers/ContentController.cs | 28 +++--- .../Controllers/ContentControllerBase.cs | 35 +++++-- .../Filters/JsonDateTimeFormatAttribute.cs | 3 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 - .../WebApi/AngularJsonMediaTypeFormatter.cs | 57 ----------- .../AngularJsonOnlyConfigurationAttribute.cs | 24 ----- 10 files changed, 68 insertions(+), 212 deletions(-) delete mode 100644 src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs delete mode 100644 src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs delete mode 100644 src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs index dbac7c9f76..f9d5d9f7da 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/ContentControllerTests.cs @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Integration.TestServerTest.Controllers Assert.Multiple(() => { Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode); - Assert.AreEqual(")]}',\n{\"Message\":\"No variants flagged for saving\"}", body); + Assert.AreEqual(AngularJsonMediaTypeFormatter.XsrfPrefix + "{\"Message\":\"No variants flagged for saving\"}", body); }); } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index d0bc38ea0b..1f9283b9f3 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -2,7 +2,6 @@ using System; using System.Linq.Expressions; using System.Net.Http; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -11,20 +10,18 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; -using Umbraco.Core.DependencyInjection; -using Umbraco.Web.Common.Controllers; -using Microsoft.Extensions.Hosting; -using Umbraco.Core.Cache; -using Umbraco.Core.Persistence; -using Umbraco.Core.Runtime; using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Website.Controllers; namespace Umbraco.Tests.Integration.TestServerTest { @@ -133,8 +130,14 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); - var typeLoader = services.AddTypeLoader(GetType().Assembly, TestHelper.GetWebHostEnvironment(), TestHelper.GetHostingEnvironment(), - TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler); + var typeLoader = services.AddTypeLoader( + GetType().Assembly, + TestHelper.GetWebHostEnvironment(), + TestHelper.GetHostingEnvironment(), + TestHelper.ConsoleLoggerFactory, + AppCaches.NoCache, + Configuration, + TestHelper.Profiler); var builder = new UmbracoBuilder(services, Configuration, typeLoader); @@ -147,10 +150,16 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) .AddPreviewSupport() - //.WithMiniProfiler() // we don't want this running in tests .AddMvcAndRazor(mvcBuilding: mvcBuilder => { + // Adds Umbraco.Web.BackOffice mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly); + + // Adds Umbraco.Web.Common + mvcBuilder.AddApplicationPart(typeof(RenderController).Assembly); + + // Adds Umbraco.Web.Website + mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() .Build(); @@ -159,6 +168,8 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void Configure(IApplicationBuilder app) { app.UseUmbraco(); + app.UseUmbracoBackOffice(); + app.UseUmbracoWebsite(); } } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs deleted file mode 100644 index 34b649d3bb..0000000000 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestRunner.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using System.Web.Http; -using Microsoft.Owin.Testing; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Web; -using Umbraco.Web.WebApi; - -namespace Umbraco.Tests.TestHelpers.ControllerTesting -{ - public class TestRunner - { - private readonly Func _controllerFactory; - - public TestRunner(Func controllerFactory) - { - _controllerFactory = controllerFactory; - } - - public async Task> Execute(string controllerName, string actionName, HttpMethod method, - HttpContent content = null, - MediaTypeWithQualityHeaderValue mediaTypeHeader = null, - bool assertOkResponse = true, object routeDefaults = null, string url = null) - { - if (mediaTypeHeader == null) - { - mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json"); - } - if (routeDefaults == null) - { - routeDefaults = new { controller = controllerName, action = actionName, id = RouteParameter.Optional }; - } - - var startup = new TestStartup( - configuration => - { - configuration.Routes.MapHttpRoute("Default", - routeTemplate: "{controller}/{action}/{id}", - defaults: routeDefaults); - }, - _controllerFactory); - - using (var server = TestServer.Create(builder => startup.Configuration(builder))) - { - var request = new HttpRequestMessage - { - RequestUri = new Uri("https://testserver/" + (url ?? "")), - Method = method - }; - - if (content != null) - request.Content = content; - - request.Headers.Accept.Add(mediaTypeHeader); - - Console.WriteLine(request); - var response = await server.HttpClient.SendAsync(request); - Console.WriteLine(response); - - if (response.IsSuccessStatusCode == false) - { - WriteResponseError(response); - } - - var json = (await ((StreamContent)response.Content).ReadAsStringAsync()).TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); - if (!json.IsNullOrWhiteSpace()) - { - var deserialized = JsonConvert.DeserializeObject(json); - Console.Write(JsonConvert.SerializeObject(deserialized, Formatting.Indented)); - } - - if (assertOkResponse) - { - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - } - - return Tuple.Create(response, json); - } - } - - private static void WriteResponseError(HttpResponseMessage response) - { - var result = response.Content.ReadAsStringAsync().Result; - Console.Out.WriteLine("Http operation unsuccessfull"); - Console.Out.WriteLine($"Status: '{response.StatusCode}'"); - Console.Out.WriteLine($"Reason: '{response.ReasonPhrase}'"); - Console.Out.WriteLine(result); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 2a7ada4c15..2d5fb4fa1b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -174,7 +174,6 @@ - diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index a6f6bc8fb8..74856f4d1b 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -636,19 +636,20 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Saves content /// - /// [FileUploadCleanupFilter] [ContentSaveValidation] public async Task PostSaveBlueprint([ModelBinder(typeof(BlueprintItemBinder))] ContentItemSave contentItem) { - var contentItemDisplay = await PostSaveInternal(contentItem, + var contentItemDisplay = await PostSaveInternal( + contentItem, content => { EnsureUniqueName(content.Name, content, "Name"); _contentService.SaveBlueprint(contentItem.PersistedContent, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); - //we need to reuse the underlying logic so return the result that it wants - return OperationResult.Succeed(new EventMessages()); + + // we need to reuse the underlying logic so return the result that it wants + return OperationResult.Succeed(new EventMessages()); }, content => { @@ -663,7 +664,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Saves content /// - /// [FileUploadCleanupFilter] [ContentSaveValidation] [OutgoingEditorModelEvent] @@ -679,9 +679,9 @@ namespace Umbraco.Web.BackOffice.Controllers 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 - //uploaded files to being *only* the actual file name (as it should be). + // 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 + // uploaded files to being *only* the actual file name (as it should be). if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any()) { foreach (var file in contentItem.UploadedFiles) @@ -690,7 +690,7 @@ namespace Umbraco.Web.BackOffice.Controllers } } - //If we've reached here it means: + // If we've reached here it means: // * Our model has been bound // * and validated // * any file attachments have been saved to their temporary location for us to use @@ -700,20 +700,20 @@ namespace Umbraco.Web.BackOffice.Controllers var passesCriticalValidationRules = ValidateCriticalData(contentItem, out var variantCount); - //we will continue to save if model state is invalid, however we cannot save if critical data is missing. + // we will continue to save if model state is invalid, however we cannot save if critical data is missing. if (!ModelState.IsValid) { - //check for critical data validation issues, we can't continue saving if this data is invalid + // check for critical data validation issues, we can't continue saving if this data is invalid if (!passesCriticalValidationRules) { - //ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! + // ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue! // 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); } - //if there's only one variant and the model state is not valid we cannot publish so change it to save + // if there's only one variant and the model state is not valid we cannot publish so change it to save if (variantCount == 1) { switch (contentItem.Action) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs index aef6abdd5e..50012c7921 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentControllerBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Net; using System.Net.Http; @@ -29,14 +29,12 @@ namespace Umbraco.Web.BackOffice.Controllers [JsonDateTimeFormat] public abstract class ContentControllerBase : BackOfficeNotificationsController { - protected ICultureDictionary CultureDictionary { get; } - protected ILoggerFactory LoggerFactory { get; } - protected IShortStringHelper ShortStringHelper { get; } - protected IEventMessagesFactory EventMessages { get; } - protected ILocalizedTextService LocalizedTextService { get; } private readonly ILogger _logger; private readonly IJsonSerializer _serializer; + /// + /// Initializes a new instance of the class. + /// protected ContentControllerBase( ICultureDictionary cultureDictionary, ILoggerFactory loggerFactory, @@ -54,6 +52,31 @@ namespace Umbraco.Web.BackOffice.Controllers _serializer = serializer; } + /// + /// Gets the + /// + protected ICultureDictionary CultureDictionary { get; } + + /// + /// Gets the + /// + protected ILoggerFactory LoggerFactory { get; } + + /// + /// Gets the + /// + protected IShortStringHelper ShortStringHelper { get; } + + /// + /// Gets the + /// + protected IEventMessagesFactory EventMessages { get; } + + /// + /// Gets the + /// + protected ILocalizedTextService LocalizedTextService { get; } + protected NotFoundObjectResult HandleContentNotFound(object id, bool throwException = true) { ModelState.AddModelError("id", $"content with id: {id} was not found"); diff --git a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs index 9c9496b282..031aeb1f4c 100644 --- a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -34,6 +34,7 @@ namespace Umbraco.Web.Common.Filters _arrayPool = arrayPool; _options = options; } + public void OnResultExecuted(ResultExecutedContext context) { } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b84a003813..62060169d0 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -192,8 +192,6 @@ - - diff --git a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs deleted file mode 100644 index 0e7cf6453a..0000000000 --- a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Formatting; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Umbraco.Core.Logging; - -namespace Umbraco.Web.WebApi -{ - /// - /// This will format the JSON output for use with AngularJs's approach to JSON Vulnerability attacks - /// - /// - /// See: http://docs.angularjs.org/api/ng.$http (Security considerations) - /// - public class AngularJsonMediaTypeFormatter : JsonMediaTypeFormatter - { - - public const string XsrfPrefix = ")]}',\n"; - - /// - /// This will prepend the special chars to the stream output that angular will strip - /// - /// - /// - /// - /// - /// - /// - public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) - { - if (type == null) throw new ArgumentNullException("type"); - if (writeStream == null) throw new ArgumentNullException("writeStream"); - - var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers); - - using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding, - //we are only writing a few chars so we don't need to allocate a large buffer - 128, - //this is important! We don't want to close the stream, the base class is in charge of stream management, we just want to write to it. - leaveOpen:true)) - { - //write the special encoding for angular json to the start - // (see: http://docs.angularjs.org/api/ng.$http) - streamWriter.Write(XsrfPrefix); - streamWriter.Flush(); - await base.WriteToStreamAsync(type, value, writeStream, content, transportContext); - } - } - - } -} diff --git a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs b/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs deleted file mode 100644 index 6a6e63f335..0000000000 --- a/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http.Formatting; -using System.Web.Http.Controllers; - -namespace Umbraco.Web.WebApi -{ - /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. - /// - public class AngularJsonOnlyConfigurationAttribute : Attribute, IControllerConfiguration - { - public virtual void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor) - { - //remove all json/xml formatters then add our custom one - var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList(); - foreach (var r in toRemove) - { - controllerSettings.Formatters.Remove(r); - } - controllerSettings.Formatters.Add(new AngularJsonMediaTypeFormatter()); - } - } -} From 03f22e93626eee865562b50aa63e0c94caf43253 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Dec 2020 16:44:50 +1100 Subject: [PATCH 021/127] Fixing tests after merge --- .../Runtime/CoreRuntime.cs | 2 - .../PublishedSnapshotServiceEventHandler.cs | 10 +++++ .../Testing/UmbracoIntegrationTest.cs | 31 +++++++------- .../ContentTypeServiceVariantsTests.cs | 41 +++++++++++-------- .../ApplicationBuilderExtensions.cs | 3 +- 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 55fa3457ed..35b7443338 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -43,8 +43,6 @@ namespace Umbraco.Infrastructure.Runtime _databaseFactory = databaseFactory; _eventAggregator = eventAggregator; _hostingEnvironment = hostingEnvironment; - - _logger = _loggerFactory.CreateLogger(); } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 31bc9b3d63..084ed569ca 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -11,6 +11,9 @@ using Umbraco.Infrastructure.PublishedCache.Persistence; namespace Umbraco.Web.PublishedCache.NuCache { + /// + /// Subscribes to Umbraco events to ensure nucache remains consistent with the source data + /// public class PublishedSnapshotServiceEventHandler : IDisposable { private readonly IRuntimeState _runtime; @@ -18,6 +21,9 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly INuCacheContentService _publishedContentService; + /// + /// Initializes a new instance of the class. + /// public PublishedSnapshotServiceEventHandler( IRuntimeState runtime, IPublishedSnapshotService publishedSnapshotService, @@ -28,6 +34,10 @@ namespace Umbraco.Web.PublishedCache.NuCache _publishedContentService = publishedContentService; } + /// + /// Binds to the Umbraco events + /// + /// Returns true if binding occurred public bool Start() { // however, the cache is NOT available until we are configured, because loading diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index d3abd76d7b..7aea7f37a5 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -50,7 +50,10 @@ namespace Umbraco.Tests.Integration.Testing public void OnTestTearDown(Action tearDown) { if (_testTeardown == null) + { _testTeardown = new List(); + } + _testTeardown.Add(tearDown); } @@ -60,7 +63,9 @@ namespace Umbraco.Tests.Integration.Testing public void FixtureTearDown() { foreach (var a in _fixtureTeardown) + { a(); + } } [TearDown] @@ -69,7 +74,9 @@ namespace Umbraco.Tests.Integration.Testing if (_testTeardown != null) { foreach (var a in _testTeardown) + { a(); + } } _testTeardown = null; @@ -107,7 +114,7 @@ namespace Umbraco.Tests.Integration.Testing Configure(app); } - protected void BeforeHostStart(IHost host) + protected virtual void BeforeHostStart(IHost host) { Services = host.Services; UseTestDatabase(Services); @@ -149,7 +156,6 @@ namespace Umbraco.Tests.Integration.Testing /// /// Create the Generic Host and execute startup ConfigureServices/Configure calls /// - /// public virtual IHostBuilder CreateHostBuilder() { var hostBuilder = Host.CreateDefaultBuilder() @@ -183,8 +189,6 @@ namespace Umbraco.Tests.Integration.Testing #endregion - #region IStartup - public virtual void ConfigureServices(IServiceCollection services) { services.AddSingleton(TestHelper.DbProviderFactoryCreator); @@ -245,8 +249,6 @@ namespace Umbraco.Tests.Integration.Testing app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast } - #endregion - #region LocalDb private static readonly object _dbLocker = new object(); @@ -387,8 +389,6 @@ namespace Umbraco.Tests.Integration.Testing #endregion - #region Common services - protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions(); protected virtual T GetRequiredService() => Services.GetRequiredService(); @@ -420,13 +420,16 @@ namespace Umbraco.Tests.Integration.Testing /// Returns the /// protected ILoggerFactory LoggerFactory => Services.GetRequiredService(); - protected AppCaches AppCaches => Services.GetRequiredService(); - protected IIOHelper IOHelper => Services.GetRequiredService(); - protected IShortStringHelper ShortStringHelper => Services.GetRequiredService(); - protected GlobalSettings GlobalSettings => Services.GetRequiredService>().Value; - protected IMapperCollection Mappers => Services.GetRequiredService(); - #endregion + protected AppCaches AppCaches => Services.GetRequiredService(); + + protected IIOHelper IOHelper => Services.GetRequiredService(); + + protected IShortStringHelper ShortStringHelper => Services.GetRequiredService(); + + protected GlobalSettings GlobalSettings => Services.GetRequiredService>().Value; + + protected IMapperCollection Mappers => Services.GetRequiredService(); #region Builders diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index e9ec0cbae0..1fbfb00a1b 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; @@ -25,11 +27,33 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services public class ContentTypeServiceVariantsTests : UmbracoIntegrationTest { private ISqlContext SqlContext => GetRequiredService(); + private IContentService ContentService => GetRequiredService(); + private IContentTypeService ContentTypeService => GetRequiredService(); + private IRedirectUrlService RedirectUrlService => GetRequiredService(); + private ILocalizationService LocalizationService => GetRequiredService(); + protected override void BeforeHostStart(IHost host) + { + base.BeforeHostStart(host); + + // Ensure that the events are bound on each test + PublishedSnapshotServiceEventHandler eventBinder = host.Services.GetRequiredService(); + eventBinder.Start(); + } + + public override void TearDown() + { + // Ensure this is dipsosed + // TODO: How come MSDI doesn't automatically dispose all of these at the end of each test? + // How can we automatically dispose of all IDisposables at the end of the tests as if it were an aspnet app? + Services.GetRequiredService().Dispose(); + base.TearDown(); + } + private void AssertJsonStartsWith(int id, string expected) { var json = GetJson(id).Replace('"', '\''); @@ -512,9 +536,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { // one simple content type, variant, with both variant and invariant properties // can change it to invariant and back - - //hack to ensure events are initialized - (GetRequiredService() as PublishedSnapshotService)?.OnApplicationInit(null, null); CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture); @@ -603,10 +624,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services // one simple content type, invariant // can change it to variant and back // can then switch one property to variant - - //hack to ensure events are initialized - (GetRequiredService() as PublishedSnapshotService)?.OnApplicationInit(null, null); - var globalSettings = new GlobalSettings(); var languageEn = new Language(globalSettings, "en") { IsDefault = true }; @@ -696,9 +713,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - - //hack to ensure events are initialized - (GetRequiredService() as PublishedSnapshotService)?.OnApplicationInit(null, null); CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture); @@ -893,7 +907,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { // one simple content type, variant, with both variant and invariant properties // can change an invariant property to variant and back - CreateFrenchAndEnglishLangs(); var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment); @@ -974,9 +987,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services // one composed content type, variant, with both variant and invariant properties // can change the composing content type to invariant and back // can change the composed content type to invariant and back - - //hack to ensure events are initialized - (GetRequiredService() as PublishedSnapshotService)?.OnApplicationInit(null, null); CreateFrenchAndEnglishLangs(); var composing = CreateContentType(ContentVariation.Culture, "composing"); @@ -1071,9 +1081,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services // one composed content type, invariant // can change the composing content type to invariant and back // can change the variant composed content type to invariant and back - - //hack to ensure events are initialized - (GetRequiredService() as PublishedSnapshotService)?.OnApplicationInit(null, null); CreateFrenchAndEnglishLangs(); var composing = CreateContentType(ContentVariation.Culture, "composing"); diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index a118fa746c..0f2d1ffcdd 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -10,6 +10,7 @@ using Smidge.Nuglify; using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.Plugins; @@ -79,7 +80,7 @@ namespace Umbraco.Extensions } /// - /// Start Umbraco + /// Enables core Umbraco functionality /// public static IApplicationBuilder UseUmbracoCore(this IApplicationBuilder app) { From e8f5aa8ebc1ce3c91adfc57ffed707d4b25677c2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Dec 2020 17:04:29 +1100 Subject: [PATCH 022/127] removes PublishedSnapshotServiceBase, shrinks interface --- .../IPublishedSnapshotService.cs | 6 -- .../PublishedSnapshotServiceBase.cs | 78 ------------------- .../PublishedSnapshotService.cs | 59 ++++++++------ .../XmlPublishedSnapshotService.cs | 54 ++++++++----- 4 files changed, 68 insertions(+), 129 deletions(-) delete mode 100644 src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index af8f72ce6d..840b64c541 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Umbraco.Core.Models.Membership; using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache @@ -23,11 +22,6 @@ namespace Umbraco.Web.PublishedCache * */ - /// - /// Gets the published snapshot accessor. - /// - IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } - /// /// Creates a published snapshot. /// diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs deleted file mode 100644 index f33eb61e8f..0000000000 --- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using Umbraco.Core.Models.Membership; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Cache; - -namespace Umbraco.Web.PublishedCache -{ - // TODO: This base class probably shouldn't exist - public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService - { - /// - /// Initializes a new instance of the class. - /// - protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor) - { - PublishedSnapshotAccessor = publishedSnapshotAccessor; - VariationContextAccessor = variationContextAccessor; - } - - /// - public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; } - - /// - /// Gets the - /// - public IVariationContextAccessor VariationContextAccessor { get; } - - // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the - // responsibility of the caller to manage what the 'current' facade is - - /// - public abstract IPublishedSnapshot CreatePublishedSnapshot(string previewToken); - - protected IPublishedSnapshot CurrentPublishedSnapshot => PublishedSnapshotAccessor.PublishedSnapshot; - - /// - public abstract bool EnsureEnvironment(out IEnumerable errors); - - /// - public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); - - /// - public abstract void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); - - /// - public abstract void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); - - /// - public abstract void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); - - /// - public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads); - - /// - public abstract void Rebuild( - int groupSize = 5000, - IReadOnlyCollection contentTypeIds = null, - IReadOnlyCollection mediaTypeIds = null, - IReadOnlyCollection memberTypeIds = null); - - /// - public virtual void Dispose() - { } - - /// - public abstract string GetStatus(); - - /// - public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html"; - - /// - public virtual void Collect() - { - } - - } -} diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 6225e68ec6..e695517553 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Hosting; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; using Umbraco.Core.Scoping; @@ -29,10 +28,12 @@ using File = System.IO.File; namespace Umbraco.Web.PublishedCache.NuCache { - internal class PublishedSnapshotService : PublishedSnapshotServiceBase + internal class PublishedSnapshotService : IPublishedSnapshotService { private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IVariationContextAccessor _variationContextAccessor; private readonly IProfilingLogger _profilingLogger; private readonly IScopeProvider _scopeProvider; private readonly INuCacheContentService _publishedContentService; @@ -91,10 +92,11 @@ namespace Umbraco.Web.PublishedCache.NuCache IHostingEnvironment hostingEnvironment, IIOHelper ioHelper, // TODO: Remove this, it is only needed for "EnsureEnvironment" which doesn't need to belong to this service IOptions config) - : base(publishedSnapshotAccessor, variationContextAccessor) { _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _variationContextAccessor = variationContextAccessor; _profilingLogger = profilingLogger; _loggerFactory = loggerFactory; _logger = _loggerFactory.CreateLogger(); @@ -126,22 +128,24 @@ namespace Umbraco.Web.PublishedCache.NuCache // figure out whether it can read the databases or it should populate them from sql _logger.LogInformation("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); - _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localContentDb); + _contentStore = new ContentStore(_publishedSnapshotAccessor, _variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localContentDb); _logger.LogInformation("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); - _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localMediaDb); + _mediaStore = new ContentStore(_publishedSnapshotAccessor, _variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localMediaDb); } else { _logger.LogInformation("Creating the content store (local db ignored)"); - _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); + _contentStore = new ContentStore(_publishedSnapshotAccessor, _variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); _logger.LogInformation("Creating the media store (local db ignored)"); - _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); + _mediaStore = new ContentStore(_publishedSnapshotAccessor, _variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); } _domainStore = new SnapDictionary(); } } + protected PublishedSnapshot CurrentPublishedSnapshot => (PublishedSnapshot)_publishedSnapshotAccessor.PublishedSnapshot; + // NOTE: These aren't used within this object but are made available internally to improve the IdKey lookup performance // when nucache is enabled. // TODO: Does this need to be here? @@ -249,7 +253,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public override bool EnsureEnvironment(out IEnumerable errors) + public bool EnsureEnvironment(out IEnumerable errors) { // must have app_data and be able to write files into it var ok = _ioHelper.TryCreateDirectory(GetLocalFilesPath()); @@ -497,7 +501,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // pure live model factory, if any, locked and refreshed - see ContentTypeCacheRefresher and // DataTypeCacheRefresher - public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) { // no cache, trash everything if (Volatile.Read(ref _isReady) == false) @@ -516,7 +520,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (draftChanged || publishedChanged) { - ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + CurrentPublishedSnapshot.Resync(); } } @@ -598,7 +602,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) + public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { // no cache, trash everything if (Volatile.Read(ref _isReady) == false) @@ -616,7 +620,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (anythingChanged) { - ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + CurrentPublishedSnapshot.Resync(); } } @@ -696,7 +700,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do if (Volatile.Read(ref _isReady) == false) @@ -746,7 +750,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + CurrentPublishedSnapshot.Resync(); } private void Notify(ContentStore store, ContentTypeCacheRefresher.JsonPayload[] payloads, Action, List, List, List> action) @@ -797,7 +801,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do if (Volatile.Read(ref _isReady) == false) @@ -838,10 +842,10 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - ((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync(); + CurrentPublishedSnapshot.Resync(); } - public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) + public void Notify(DomainCacheRefresher.JsonPayload[] payloads) { // no cache, nothing we can do if (Volatile.Read(ref _isReady) == false) @@ -1013,7 +1017,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken) + public IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { EnsureCaches(); @@ -1105,9 +1109,9 @@ namespace Umbraco.Web.PublishedCache.NuCache return new PublishedSnapshot.PublishedSnapshotElements { - ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, Options.Create(_globalSettings), VariationContextAccessor), - MediaCache = new MediaCache(previewDefault, mediaSnap, VariationContextAccessor), - MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, PublishedSnapshotAccessor, VariationContextAccessor, _entitySerializer, _publishedModelFactory), + ContentCache = new ContentCache(previewDefault, contentSnap, snapshotCache, elementsCache, domainCache, Options.Create(_globalSettings), _variationContextAccessor), + MediaCache = new MediaCache(previewDefault, mediaSnap, _variationContextAccessor), + MemberCache = new MemberCache(previewDefault, snapshotCache, _serviceContext.MemberService, memberTypeCache, _publishedSnapshotAccessor, _variationContextAccessor, _entitySerializer, _publishedModelFactory), DomainCache = domainCache, SnapshotCache = snapshotCache, ElementsCache = elementsCache @@ -1115,7 +1119,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - public override void Rebuild( + public void Rebuild( int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, @@ -1158,7 +1162,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public override string GetStatus() + public string GetStatus() { EnsureCaches(); @@ -1184,7 +1188,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // TODO: This should be async since it's calling into async - public override void Collect() + public void Collect() { EnsureCaches(); @@ -1204,5 +1208,12 @@ namespace Umbraco.Web.PublishedCache.NuCache EnsureCaches(); return _mediaStore; } + + /// + public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html"; + + /// + public void Dispose() + { } } } diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 5e05e31708..6b8b13cc99 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache /// /// Implements a published snapshot service. /// - internal class XmlPublishedSnapshotService : PublishedSnapshotServiceBase + internal class XmlPublishedSnapshotService : IPublishedSnapshotService { private readonly XmlStore _xmlStore; private readonly RoutesCache _routesCache; @@ -48,13 +48,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Constructors // used in WebBootManager + tests - public XmlPublishedSnapshotService(ServiceContext serviceContext, + public XmlPublishedSnapshotService( + ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IScopeProvider scopeProvider, IAppCache requestCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, - IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + IDocumentRepository documentRepository, + IMediaRepository mediaRepository, + IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, ILoggerFactory loggerFactory, GlobalSettings globalSettings, @@ -63,9 +67,9 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IShortStringHelper shortStringHelper, ISiteDomainHelper siteDomainHelper, IEntityXmlSerializer entitySerializer, - MainDom mainDom, - bool testing = false, bool enableRepositoryEvents = true) + bool testing = false, + bool enableRepositoryEvents = true) : this(serviceContext, publishedContentTypeFactory, scopeProvider, requestCache, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor, documentRepository, mediaRepository, memberRepository, @@ -76,13 +80,17 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache } // used in some tests - internal XmlPublishedSnapshotService(ServiceContext serviceContext, + internal XmlPublishedSnapshotService( + ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, IScopeProvider scopeProvider, IAppCache requestCache, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor, - IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + IDocumentRepository documentRepository, + IMediaRepository mediaRepository, + IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, ILoggerFactory loggerFactory, GlobalSettings globalSettings, @@ -93,8 +101,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache IEntityXmlSerializer entitySerializer, PublishedContentTypeCache contentTypeCache, MainDom mainDom, - bool testing, bool enableRepositoryEvents) - : base(publishedSnapshotAccessor, variationContextAccessor) + bool testing, + bool enableRepositoryEvents) { _routesCache = new RoutesCache(); _publishedContentTypeFactory = publishedContentTypeFactory; @@ -120,7 +128,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache _hostingLifetime = hostingLifetime; } - public override void Dispose() + public void Dispose() { _xmlStore.Dispose(); } @@ -129,7 +137,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Environment - public override bool EnsureEnvironment(out IEnumerable errors) + public bool EnsureEnvironment(out IEnumerable errors) { // Test creating/saving/deleting a file in the same location as the content xml file // NOTE: We cannot modify the xml file directly because a background thread is responsible for @@ -151,7 +159,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Caches - public override IPublishedSnapshot CreatePublishedSnapshot(string previewToken) + public IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { // use _requestCache to store recursive properties lookup, etc. both in content // and media cache. Life span should be the current request. Or, ideally @@ -169,6 +177,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #endregion + public string StatusUrl => throw new System.NotImplementedException(); + #region Xml specific /// @@ -215,12 +225,12 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #region Change management - public override void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) { _xmlStore.Notify(payloads, out draftChanged, out publishedChanged); } - public override void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) + public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) { foreach (var payload in payloads) PublishedMediaCache.ClearCache(payload.Id); @@ -228,31 +238,33 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache anythingChanged = true; } - public override void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) { _xmlStore.Notify(payloads); if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) _routesCache.Clear(); } - public override void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) { _publishedContentTypeFactory.NotifyDataTypeChanges(payloads.Select(x => x.Id).ToArray()); _xmlStore.Notify(payloads); } - public override void Notify(DomainCacheRefresher.JsonPayload[] payloads) + public void Notify(DomainCacheRefresher.JsonPayload[] payloads) { _routesCache.Clear(); } #endregion - public override string GetStatus() + public string GetStatus() { return "Test status"; } - public override void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) { } + public void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) { } + + public void Collect() { } } } From c761fa0506272a8321e32fd5fd927b2b4e2b3063 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 21 Dec 2020 17:41:12 +1100 Subject: [PATCH 023/127] New IPublishedSnapshotStatus, reduces IPublishedSnapshotService --- .../IPublishedSnapshotService.cs | 12 ++- .../IPublishedSnapshotStatus.cs | 18 ++++ .../ContentStore.cs | 11 +++ .../NuCacheComposer.cs | 4 + .../Persistence/NuCacheContentService.cs | 18 +++- .../PublishedSnapshotService.cs | 83 ++----------------- .../PublishedSnapshotStatus.cs | 51 ++++++++++++ .../XmlPublishedSnapshotService.cs | 10 +-- .../PublishedSnapshotCacheStatusController.cs | 42 ++++++---- .../Controllers/PublishedStatusController.cs | 14 ++-- 10 files changed, 146 insertions(+), 117 deletions(-) create mode 100644 src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs create mode 100644 src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 840b64c541..ce61dc4b8e 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Umbraco.Web.Cache; namespace Umbraco.Web.PublishedCache @@ -102,12 +103,9 @@ namespace Umbraco.Web.PublishedCache /// The changes. void Notify(DomainCacheRefresher.JsonPayload[] payloads); - // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus? - string GetStatus(); - - // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus? - string StatusUrl { get; } - - void Collect(); + /// + /// Cleans up unused snapshots + /// + Task CollectAsync(); } } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs new file mode 100644 index 0000000000..0f88bd4085 --- /dev/null +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs @@ -0,0 +1,18 @@ +namespace Umbraco.Web.PublishedCache +{ + /// + /// Returns the currents status for nucache + /// + public interface IPublishedSnapshotStatus + { + /// + /// Gets the status report as a string + /// + string GetStatus(); + + /// + /// Gets the URL used to retreive the status + /// + string StatusUrl { get; } + } +} diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index e79c195b46..bb03693adf 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -1352,7 +1352,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // reading _floorGen is safe if _collectTask is null if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta) + { CollectAsyncLocked(); + } return snapshot; } @@ -1374,8 +1376,17 @@ namespace Umbraco.Web.PublishedCache.NuCache private Task CollectAsyncLocked() { + // NOTE: What in the heck is going on here? Why is any of this running in async contexts? + // SD: From what I can tell this was designed to be a set and forget background task to do the + // collecting which is why it's called from non-async methods within this class. This is + // slightly dangerous because it's not taking into account app shutdown. + // TODO: There should be a different method or class responsible for executing the cleanup on a + // background (set and forget) thread. + if (_collectTask != null) + { return _collectTask; + } // ReSharper disable InconsistentlySynchronizedField var task = _collectTask = Task.Run((Action)Collect); diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs index 5e618d361c..c1c80cf43c 100644 --- a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs +++ b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs @@ -27,6 +27,10 @@ namespace Umbraco.Web.PublishedCache.NuCache builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions()); builder.SetPublishedSnapshotService(); + // Add as itself + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + // replace this service since we want to improve the content/media // mapping lookups if we are using nucache. // TODO: Gotta wonder how much this does actually improve perf? It's a lot of weird code to make this happen so hope it's worth it diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs index 5c7fdeb53b..00c3671217 100644 --- a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs +++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentService.cs @@ -94,14 +94,26 @@ namespace Umbraco.Infrastructure.PublishedCache.Persistence /// public bool VerifyContentDbCache() - => _repository.VerifyContentDbCache(); + { + using IScope scope = ScopeProvider.CreateScope(autoComplete: true); + scope.ReadLock(Constants.Locks.ContentTree); + return _repository.VerifyContentDbCache(); + } /// public bool VerifyMediaDbCache() - => _repository.VerifyMediaDbCache(); + { + using IScope scope = ScopeProvider.CreateScope(autoComplete: true); + scope.ReadLock(Constants.Locks.MediaTree); + return _repository.VerifyMediaDbCache(); + } /// public bool VerifyMemberDbCache() - => _repository.VerifyMemberDbCache(); + { + using IScope scope = ScopeProvider.CreateScope(autoComplete: true); + scope.ReadLock(Constants.Locks.MemberTree); + return _repository.VerifyMemberDbCache(); + } } } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index e695517553..1ddb1cef63 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using CSharpTest.Net.Collections; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -47,7 +48,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IIOHelper _ioHelper; private readonly NuCacheSettings _config; - // volatile because we read it with no lock private bool _isReady; private bool _isReadSet; private object _isReadyLock; @@ -264,7 +264,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// /// Populates the stores /// - private void EnsureCaches() => LazyInitializer.EnsureInitialized( + internal void EnsureCaches() => LazyInitializer.EnsureInitialized( ref _isReady, ref _isReadSet, ref _isReadyLock, @@ -914,7 +914,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } // some may be missing - not checking here - return contentTypes.Select(x => _publishedContentTypeFactory.CreateContentType(x)).ToList(); } @@ -950,7 +949,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // content (and content types) are read-locked while reading content // contentStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTypes); @@ -989,7 +987,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // media (and content types) are read-locked while reading media // mediaStore is wlocked (so readable, only no new views) // and it can be wlocked by 1 thread only at a time - using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTypes); @@ -1047,7 +1044,6 @@ namespace Umbraco.Web.PublishedCache.NuCache // nothing like that... // for elements cache, DictionaryAppCache is a No-No, use something better. // ie FastDictionaryAppCache (thread safe and all) - ContentStore.Snapshot contentSnap, mediaSnap; SnapDictionary.Snapshot domainSnap; IAppCache elementsCache; @@ -1059,7 +1055,7 @@ namespace Umbraco.Web.PublishedCache.NuCache lock (_elementsLock) { - var scopeContext = _scopeProvider.Context; + IScopeContext scopeContext = _scopeProvider.Context; if (scopeContext == null) { @@ -1089,7 +1085,6 @@ namespace Umbraco.Web.PublishedCache.NuCache }, int.MaxValue); } - // create a new snapshot cache if snapshots are different gens if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null) { @@ -1126,75 +1121,12 @@ namespace Umbraco.Web.PublishedCache.NuCache IReadOnlyCollection memberTypeIds = null) => _publishedContentService.Rebuild(groupSize, contentTypeIds, mediaTypeIds, memberTypeIds); - public bool VerifyContentDbCache() - { - // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.ContentTree); - var ok = _publishedContentService.VerifyContentDbCache(); - scope.Complete(); - return ok; - } - } - - public bool VerifyMediaDbCache() - { - // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MediaTree); - var ok = _publishedContentService.VerifyMediaDbCache(); - scope.Complete(); - return ok; - } - } - - public bool VerifyMemberDbCache() - { - // TODO: Shouldn't this entire logic just exist in the call to _publishedContentService? - using (var scope = _scopeProvider.CreateScope()) - { - scope.ReadLock(Constants.Locks.MemberTree); - var ok = _publishedContentService.VerifyMemberDbCache(); - scope.Complete(); - return ok; - } - } - - public string GetStatus() + public async Task CollectAsync() { EnsureCaches(); - var dbCacheIsOk = VerifyContentDbCache() - && VerifyMediaDbCache() - && VerifyMemberDbCache(); - - var cg = _contentStore.GenCount; - var mg = _mediaStore.GenCount; - var cs = _contentStore.SnapCount; - var ms = _mediaStore.SnapCount; - var ce = _contentStore.Count; - var me = _mediaStore.Count; - - return - " Database cache is " + (dbCacheIsOk ? "ok" : "NOT ok (rebuild?)") + "." + - " ContentStore contains " + ce + " item" + (ce > 1 ? "s" : "") + - " and has " + cg + " generation" + (cg > 1 ? "s" : "") + - " and " + cs + " snapshot" + (cs > 1 ? "s" : "") + "." + - " MediaStore contains " + me + " item" + (ce > 1 ? "s" : "") + - " and has " + mg + " generation" + (mg > 1 ? "s" : "") + - " and " + ms + " snapshot" + (ms > 1 ? "s" : "") + "."; - } - - // TODO: This should be async since it's calling into async - public void Collect() - { - EnsureCaches(); - - var contentCollect = _contentStore.CollectAsync(); - var mediaCollect = _mediaStore.CollectAsync(); - System.Threading.Tasks.Task.WaitAll(contentCollect, mediaCollect); + await _contentStore.CollectAsync(); + await _mediaStore.CollectAsync(); } internal ContentStore GetContentStore() @@ -1209,9 +1141,6 @@ namespace Umbraco.Web.PublishedCache.NuCache return _mediaStore; } - /// - public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html"; - /// public void Dispose() { } diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs new file mode 100644 index 0000000000..c8975844ef --- /dev/null +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs @@ -0,0 +1,51 @@ +using Umbraco.Infrastructure.PublishedCache.Persistence; + +namespace Umbraco.Web.PublishedCache.NuCache +{ + /// + /// Generates a status report for + /// + internal class PublishedSnapshotStatus : IPublishedSnapshotStatus + { + private readonly PublishedSnapshotService _service; + private readonly INuCacheContentService _publishedContentService; + + public PublishedSnapshotStatus(PublishedSnapshotService service, INuCacheContentService publishedContentService) + { + _service = service; + _publishedContentService = publishedContentService; + } + + /// + public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html"; + + /// + public string GetStatus() + { + _service.EnsureCaches(); + + var dbCacheIsOk = _publishedContentService.VerifyContentDbCache() + && _publishedContentService.VerifyMediaDbCache() + && _publishedContentService.VerifyMemberDbCache(); + + ContentStore contentStore = _service.GetContentStore(); + ContentStore mediaStore = _service.GetMediaStore(); + + var cg = contentStore.GenCount; + var mg = mediaStore.GenCount; + var cs = contentStore.SnapCount; + var ms = mediaStore.SnapCount; + var ce = contentStore.Count; + var me = mediaStore.Count; + + return + " Database cache is " + (dbCacheIsOk ? "ok" : "NOT ok (rebuild?)") + "." + + " ContentStore contains " + ce + " item" + (ce > 1 ? "s" : "") + + " and has " + cg + " generation" + (cg > 1 ? "s" : "") + + " and " + cs + " snapshot" + (cs > 1 ? "s" : "") + "." + + " MediaStore contains " + me + " item" + (ce > 1 ? "s" : "") + + " and has " + mg + " generation" + (mg > 1 ? "s" : "") + + " and " + ms + " snapshot" + (ms > 1 ? "s" : "") + "."; + } + } +} diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 6b8b13cc99..0111c9f088 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; @@ -177,8 +178,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #endregion - public string StatusUrl => throw new System.NotImplementedException(); - #region Xml specific /// @@ -258,13 +257,8 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #endregion - public string GetStatus() - { - return "Test status"; - } - public void Rebuild(int groupSize = 5000, IReadOnlyCollection contentTypeIds = null, IReadOnlyCollection mediaTypeIds = null, IReadOnlyCollection memberTypeIds = null) { } - public void Collect() { } + public Task CollectAsync() => Task.CompletedTask; } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs index c9c420f254..90db13227f 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedSnapshotCacheStatusController.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Reflection.Metadata; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Umbraco.Core; using Umbraco.Web.Cache; @@ -8,43 +9,54 @@ using Umbraco.Web.PublishedCache; namespace Umbraco.Web.BackOffice.Controllers { - [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] + [PluginController(Constants.Web.Mvc.BackOfficeApiArea)] public class PublishedSnapshotCacheStatusController : UmbracoAuthorizedApiController { private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IPublishedSnapshotStatus _publishedSnapshotStatus; private readonly DistributedCache _distributedCache; - public PublishedSnapshotCacheStatusController(IPublishedSnapshotService publishedSnapshotService, DistributedCache distributedCache) + /// + /// Initializes a new instance of the class. + /// + public PublishedSnapshotCacheStatusController( + IPublishedSnapshotService publishedSnapshotService, + IPublishedSnapshotStatus publishedSnapshotStatus, + DistributedCache distributedCache) { _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); + _publishedSnapshotStatus = publishedSnapshotStatus; _distributedCache = distributedCache; } + /// + /// Rebuilds the Database cache + /// [HttpPost] public string RebuildDbCache() { _publishedSnapshotService.Rebuild(); - return _publishedSnapshotService.GetStatus(); + return _publishedSnapshotStatus.GetStatus(); } + /// + /// Gets a status report + /// [HttpGet] - public string GetStatus() - { - return _publishedSnapshotService.GetStatus(); - } + public string GetStatus() => _publishedSnapshotStatus.GetStatus(); + /// + /// Cleans up unused snapshots + /// [HttpGet] - public string Collect() + public async Task Collect() { GC.Collect(); - _publishedSnapshotService.Collect(); - return _publishedSnapshotService.GetStatus(); + await _publishedSnapshotService.CollectAsync(); + return _publishedSnapshotStatus.GetStatus(); } [HttpPost] - public void ReloadCache() - { - _distributedCache.RefreshAllPublishedSnapshot(); - } + public void ReloadCache() => _distributedCache.RefreshAllPublishedSnapshot(); } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs b/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs index f63c2d5e6a..5c41d54cb8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PublishedStatusController.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.AspNetCore.Mvc; using Umbraco.Web.PublishedCache; @@ -6,22 +6,22 @@ namespace Umbraco.Web.BackOffice.Controllers { public class PublishedStatusController : UmbracoAuthorizedApiController { - private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly IPublishedSnapshotStatus _publishedSnapshotStatus; - public PublishedStatusController(IPublishedSnapshotService publishedSnapshotService) + public PublishedStatusController(IPublishedSnapshotStatus publishedSnapshotStatus) { - _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); + _publishedSnapshotStatus = publishedSnapshotStatus ?? throw new ArgumentNullException(nameof(publishedSnapshotStatus)); } [HttpGet] public string GetPublishedStatusUrl() { - if (!string.IsNullOrWhiteSpace(_publishedSnapshotService.StatusUrl)) + if (!string.IsNullOrWhiteSpace(_publishedSnapshotStatus.StatusUrl)) { - return _publishedSnapshotService.StatusUrl; + return _publishedSnapshotStatus.StatusUrl; } - throw new NotSupportedException("Not supported: " + _publishedSnapshotService.GetType().FullName); + throw new NotSupportedException("Not supported: " + _publishedSnapshotStatus.GetType().FullName); } } } From 198586a82b7385d03c7fa902e5d114b9ace4ea84 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 21 Dec 2020 09:02:05 +0000 Subject: [PATCH 024/127] Dispose host on test teardown. --- .../Testing/UmbracoIntegrationTest.cs | 1 + .../Services/ContentTypeServiceVariantsTests.cs | 9 --------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 7aea7f37a5..414ad6ec43 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -86,6 +86,7 @@ namespace Umbraco.Tests.Integration.Testing // Ensure CoreRuntime stopped (now it's a HostedService) IHost host = Services.GetRequiredService(); host.StopAsync().GetAwaiter().GetResult(); + host.Dispose(); } [TearDown] diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 1fbfb00a1b..995e4d60ae 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -45,15 +45,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services eventBinder.Start(); } - public override void TearDown() - { - // Ensure this is dipsosed - // TODO: How come MSDI doesn't automatically dispose all of these at the end of each test? - // How can we automatically dispose of all IDisposables at the end of the tests as if it were an aspnet app? - Services.GetRequiredService().Dispose(); - base.TearDown(); - } - private void AssertJsonStartsWith(int id, string expected) { var json = GetJson(id).Replace('"', '\''); From 822aa2897b026225c7f42df8edb4eaecc4db59a9 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Mon, 21 Dec 2020 09:38:06 +0000 Subject: [PATCH 025/127] Remove NotificationHandler lifetime hack --- .../UmbracoBuilder.Events.cs | 18 ----- src/Umbraco.Core/Manifest/ManifestWatcher.cs | 15 +++-- ...uginsManifestWatcherNotificationHandler.cs | 45 +++++++++++++ .../Runtime/CoreInitialServices.cs | 3 +- .../Runtime/ManifestWatcher.cs | 65 ------------------- 5 files changed, 55 insertions(+), 91 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs delete mode 100644 src/Umbraco.Infrastructure/Runtime/ManifestWatcher.cs diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index a4ae2874e3..a21ae74976 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -1,7 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.Events; @@ -27,22 +26,5 @@ namespace Umbraco.Core.DependencyInjection builder.Services.AddTransient(typeof(INotificationHandler), typeof(TNotificationHandler)); return builder; } - - /// - /// Registers a notification handler against the Umbraco service collection. - /// - /// The type of notification. - /// The type of notificiation handler. - /// The Umbraco builder. - /// Factory method - /// The . - public static IUmbracoBuilder AddNotificationHandler(this IUmbracoBuilder builder, Func factory) - where TNotificationHandler : class, INotificationHandler - where TNotification : INotification - { - // Register the handler as transient. This ensures that anything can be injected into it. - builder.Services.AddTransient(typeof(INotificationHandler), factory); - return builder; - } } } diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs index 6bd893d298..16e961652f 100644 --- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs +++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs @@ -1,14 +1,13 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; -using Umbraco.Core.Hosting; using Umbraco.Net; namespace Umbraco.Core.Manifest { - public class ManifestWatcher : DisposableObjectSlim + public class ManifestWatcher : IDisposable { private static readonly object Locker = new object(); private static volatile bool _isRestarting; @@ -48,7 +47,10 @@ namespace Umbraco.Core.Manifest private void FswChanged(object sender, FileSystemEventArgs e) { - if (e.Name.InvariantContains("package.manifest") == false) return; + if (!e.Name.InvariantContains("package.manifest")) + { + return; + } // ensure the app is not restarted multiple times for multiple // savings during the same app domain execution - restart once @@ -59,14 +61,15 @@ namespace Umbraco.Core.Manifest _isRestarting = true; _logger.LogInformation("Manifest has changed, app pool is restarting ({Path})", e.FullPath); _umbracoApplicationLifetime.Restart(); - Dispose(); // uh? if the app restarts then this should be disposed anyways? } } - protected override void DisposeResources() + public void Dispose() { foreach (var fw in _fws) + { fw.Dispose(); + } } } } diff --git a/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs new file mode 100644 index 0000000000..b67f41136a --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs @@ -0,0 +1,45 @@ +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 +{ + /// + /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes. + /// + public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler + { + private readonly ManifestWatcher _manifestWatcher; + private readonly IHostingEnvironment _hostingEnvironment; + + public AppPluginsManifestWatcherNotificationHandler(ManifestWatcher manifestWatcher, IHostingEnvironment hostingEnvironment) + { + _manifestWatcher = manifestWatcher ?? throw new ArgumentNullException(nameof(manifestWatcher)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) + { + if (!_hostingEnvironment.IsDebugMode) + { + return Task.CompletedTask; + } + + var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); + + if (!Directory.Exists(appPlugins)) + { + return Task.CompletedTask; + } + + _manifestWatcher.Start(Directory.GetDirectories(appPlugins)); + + return Task.CompletedTask; + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs index 0fa3250522..32f5e6bf36 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs @@ -71,8 +71,7 @@ namespace Umbraco.Infrastructure.Runtime builder.AddNotificationHandler(); builder.Services.AddSingleton(); - builder.AddNotificationHandler(factory => factory.GetRequiredService()); - builder.AddNotificationHandler(factory => factory.GetRequiredService()); + builder.AddNotificationHandler(); // composers builder diff --git a/src/Umbraco.Infrastructure/Runtime/ManifestWatcher.cs b/src/Umbraco.Infrastructure/Runtime/ManifestWatcher.cs deleted file mode 100644 index 358dfb2ed5..0000000000 --- a/src/Umbraco.Infrastructure/Runtime/ManifestWatcher.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Umbraco.Core; -using Umbraco.Core.Events; -using Umbraco.Core.Hosting; -using Umbraco.Net; - -namespace Umbraco.Infrastructure.Runtime -{ - public sealed class ManifestWatcher : - INotificationHandler, - INotificationHandler - { - private readonly IHostingEnvironment _hosting; - private readonly ILoggerFactory _loggerFactory; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; - - // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for - // package.manifest chances and restarts the application on any change - private Core.Manifest.ManifestWatcher _mw; - - public ManifestWatcher(IHostingEnvironment hosting, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime) - { - _hosting = hosting; - _loggerFactory = loggerFactory; - _hostingEnvironment = hostingEnvironment; - _umbracoApplicationLifetime = umbracoApplicationLifetime; - } - - public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken) - { - if (_hosting.IsDebugMode == false) - { - return Task.CompletedTask; - } - - var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins); - if (Directory.Exists(appPlugins) == false) - { - return Task.CompletedTask; - } - - _mw = new Core.Manifest.ManifestWatcher(_loggerFactory.CreateLogger(), _umbracoApplicationLifetime); - _mw.Start(Directory.GetDirectories(appPlugins)); - - return Task.CompletedTask; - } - - public Task HandleAsync(UmbracoApplicationStopping notification, CancellationToken cancellationToken) - { - if (_mw == null) - { - return Task.CompletedTask; - } - - _mw.Dispose(); - _mw = null; - - return Task.CompletedTask; - } - } -} From 3e948475a690756f698748c4986b72017caa840b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 10:30:16 +1100 Subject: [PATCH 026/127] Remove unneeded NPocoRepositoryBase --- .../Implement/AuditEntryRepository.cs | 4 +-- .../Repositories/Implement/AuditRepository.cs | 4 +-- .../Implement/ConsentRepository.cs | 4 +-- .../Implement/ContentRepositoryBase.cs | 4 +-- .../Implement/ContentTypeRepositoryBase.cs | 2 +- .../Implement/DataTypeRepository.cs | 4 +-- .../Implement/DictionaryRepository.cs | 4 +-- .../Implement/DocumentRepository.cs | 2 +- .../Implement/DomainRepository.cs | 2 +- .../Implement/EntityContainerRepository.cs | 2 +- .../Implement/ExternalLoginRepository.cs | 2 +- .../Implement/KeyValueRepository.cs | 4 +-- .../Implement/LanguageRepository.cs | 4 +-- .../Repositories/Implement/MacroRepository.cs | 2 +- .../Repositories/Implement/MediaRepository.cs | 2 +- .../Implement/MemberGroupRepository.cs | 2 +- .../Implement/NPocoRepositoryBase.cs | 26 ------------------- .../Implement/PermissionRepository.cs | 2 +- .../Implement/PublicAccessRepository.cs | 2 +- .../Implement/RedirectUrlRepository.cs | 2 +- .../Implement/RelationRepository.cs | 4 +-- .../Implement/RelationTypeRepository.cs | 4 +-- .../Implement/ServerRegistrationRepository.cs | 2 +- .../Implement/SimpleGetRepository.cs | 2 +- .../Repositories/Implement/TagRepository.cs | 2 +- .../Implement/TemplateRepository.cs | 4 +-- .../Implement/UserGroupRepository.cs | 6 ++--- .../Repositories/Implement/UserRepository.cs | 4 +-- 28 files changed, 41 insertions(+), 67 deletions(-) delete mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index 4031047970..59387fcb9f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the NPoco implementation of . /// - internal class AuditEntryRepository : NPocoRepositoryBase, IAuditEntryRepository + internal class AuditEntryRepository : EntityRepositoryBase, IAuditEntryRepository { /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs index a42019e59f..8ad370672e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NPoco; @@ -12,7 +12,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class AuditRepository : NPocoRepositoryBase, IAuditRepository + internal class AuditRepository : EntityRepositoryBase, IAuditRepository { public AuditRepository(IScopeAccessor scopeAccessor, ILogger logger) : base(scopeAccessor, AppCaches.NoCache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs index 47ebddf698..cff06a2126 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.Extensions.Logging; using NPoco; @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the NPoco implementation of . /// - internal class ConsentRepository : NPocoRepositoryBase, IConsentRepository + internal class ConsentRepository : EntityRepositoryBase, IConsentRepository { /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index a84b34b75a..7b90efd4ae 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public static bool ThrowOnWarning = false; } - public abstract class ContentRepositoryBase : NPocoRepositoryBase, IContentRepository + public abstract class ContentRepositoryBase : EntityRepositoryBase, IContentRepository where TEntity : class, IContentBase where TRepository : class, IRepository { @@ -51,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected ContentRepositoryBase( IScopeAccessor scopeAccessor, AppCaches cache, - ILogger> logger, + ILogger> logger, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 26596410bf..6554782d24 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Exposes shared functionality /// - internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository + internal abstract class ContentTypeRepositoryBase : EntityRepositoryBase, IReadRepository where TEntity : class, IContentTypeComposition { private readonly IShortStringHelper _shortStringHelper; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs index 482a333631..1f614e7647 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents a repository for doing CRUD operations for /// - internal class DataTypeRepository : NPocoRepositoryBase, IDataTypeRepository + internal class DataTypeRepository : EntityRepositoryBase, IDataTypeRepository { private readonly Lazy _editors; private readonly IConfigurationEditorJsonSerializer _serializer; @@ -81,7 +81,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs index 0c58d26a2a..abab07a7bb 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents a repository for doing CRUD operations for /// - internal class DictionaryRepository : NPocoRepositoryBase, IDictionaryRepository + internal class DictionaryRepository : EntityRepositoryBase, IDictionaryRepository { private readonly ILoggerFactory _loggerFactory; @@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 6ed884eb0c..f3b9ca58d6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -952,7 +952,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // reading repository purely for looking up by GUID // TODO: ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! - private class ContentByGuidReadRepository : NPocoRepositoryBase + private class ContentByGuidReadRepository : EntityRepositoryBase { private readonly DocumentRepository _outerRepo; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs index f0315f747c..e9e62d76c9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { // TODO: We need to get a readonly ISO code for the domain assigned - internal class DomainRepository : NPocoRepositoryBase, IDomainRepository + internal class DomainRepository : EntityRepositoryBase, IDomainRepository { public DomainRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index 36213b089f..26159c4fdf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// An internal repository for managing entity containers such as doc type, media type, data type containers. /// - internal class EntityContainerRepository : NPocoRepositoryBase, IEntityContainerRepository + internal class EntityContainerRepository : EntityRepositoryBase, IEntityContainerRepository { private readonly Guid _containerObjectType; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index c3ed111ffb..29cbdf04e5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { // TODO: We should update this to support both users and members. It means we would remove referential integrity from users // and the user/member key would be a GUID (we also need to add a GUID to users) - internal class ExternalLoginRepository : NPocoRepositoryBase, IExternalLoginRepository + internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginRepository { public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index eb55b476c7..ba3754486c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class KeyValueRepository : NPocoRepositoryBase, IKeyValueRepository + internal class KeyValueRepository : EntityRepositoryBase, IKeyValueRepository { public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger) : base(scopeAccessor, AppCaches.NoCache, logger) @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Guid NodeObjectTypeId => throw new NotSupportedException(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index fd791fe01f..bd72a3faf5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents a repository for doing CRUD operations for /// - internal class LanguageRepository : NPocoRepositoryBase, ILanguageRepository + internal class LanguageRepository : EntityRepositoryBase, ILanguageRepository { private readonly GlobalSettings _globalSettings; private readonly Dictionary _codeIdMap = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs index 61dad47378..678f826fb4 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs @@ -14,7 +14,7 @@ using Umbraco.Core.Strings; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class MacroRepository : NPocoRepositoryBase, IMacroRepository + internal class MacroRepository : EntityRepositoryBase, IMacroRepository { private readonly IShortStringHelper _shortStringHelper; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index 0ebea656b1..7e3425707a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -412,7 +412,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them /// - private class MediaByGuidReadRepository : NPocoRepositoryBase + private class MediaByGuidReadRepository : EntityRepositoryBase { private readonly MediaRepository _outerRepo; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index 482a0e627f..6916203e93 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -14,7 +14,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class MemberGroupRepository : NPocoRepositoryBase, IMemberGroupRepository + internal class MemberGroupRepository : EntityRepositoryBase, IMemberGroupRepository { public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs deleted file mode 100644 index ff820da577..0000000000 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; -using NPoco; -using Umbraco.Core.Cache; -using Umbraco.Core.Models.Entities; -using Umbraco.Core.Persistence.Querying; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Scoping; - -namespace Umbraco.Core.Persistence.Repositories.Implement -{ - /// - /// Represent an abstract Repository for NPoco based repositories - /// - public abstract class NPocoRepositoryBase : EntityRepositoryBase - where TEntity : class, IEntity - { - /// - /// Initializes a new instance of the class. - /// - protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) - : base(scopeAccessor, cache, logger) - { } - } -} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index 279a7075ea..161de8c58e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// This repo implements the base class so that permissions can be queued to be persisted /// like the normal repository pattern but the standard repository Get commands don't apply and will throw /// - internal class PermissionRepository : NPocoRepositoryBase + internal class PermissionRepository : EntityRepositoryBase where TEntity : class, IEntity { public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs index 6d2f95bb4d..5730272dd9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class PublicAccessRepository : NPocoRepositoryBase, IPublicAccessRepository + internal class PublicAccessRepository : EntityRepositoryBase, IPublicAccessRepository { public PublicAccessRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index 9e72846b58..246adf7415 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -12,7 +12,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class RedirectUrlRepository : NPocoRepositoryBase, IRedirectUrlRepository + internal class RedirectUrlRepository : EntityRepositoryBase, IRedirectUrlRepository { public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index 21b4ce5911..4299d50f15 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents a repository for doing CRUD operations for /// - internal class RelationRepository : NPocoRepositoryBase, IRelationRepository + internal class RelationRepository : EntityRepositoryBase, IRelationRepository { private readonly IRelationTypeRepository _relationTypeRepository; private readonly IEntityRepository _entityRepository; @@ -88,7 +88,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs index 398dd225ba..953999eaf2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents a repository for doing CRUD operations for /// - internal class RelationTypeRepository : NPocoRepositoryBase, IRelationTypeRepository + internal class RelationTypeRepository : EntityRepositoryBase, IRelationTypeRepository { public RelationTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) @@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index f215a8997b..556f837245 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -13,7 +13,7 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository + internal class ServerRegistrationRepository : EntityRepositoryBase, IServerRegistrationRepository { public ServerRegistrationRepository(IScopeAccessor scopeAccessor, ILogger logger) : base(scopeAccessor, AppCaches.NoCache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs index bbe751d2c6..9ddb5c5b60 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache /// - internal abstract class SimpleGetRepository : NPocoRepositoryBase + internal abstract class SimpleGetRepository : EntityRepositoryBase where TEntity : class, IEntity where TDto: class { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index dcd9464ae0..94c2f4289a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -15,7 +15,7 @@ using static Umbraco.Core.Persistence.SqlExtensionsStatics; namespace Umbraco.Core.Persistence.Repositories.Implement { - internal class TagRepository : NPocoRepositoryBase, ITagRepository + internal class TagRepository : EntityRepositoryBase, ITagRepository { public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) : base(scopeAccessor, cache, logger) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index b36474d688..d391bb9e4d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the Template Repository /// - internal class TemplateRepository : NPocoRepositoryBase, ITemplateRepository + internal class TemplateRepository : EntityRepositoryBase, ITemplateRepository { private readonly IIOHelper _ioHelper; private readonly IShortStringHelper _shortStringHelper; @@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index 30b9b29416..4786548e57 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the UserGroupRepository for doing CRUD operations for /// - public class UserGroupRepository : NPocoRepositoryBase, IUserGroupRepository + public class UserGroupRepository : EntityRepositoryBase, IUserGroupRepository { private readonly IShortStringHelper _shortStringHelper; private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository; @@ -216,7 +216,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected Sql GetBaseQuery(QueryType type) { @@ -358,7 +358,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// used to persist a user group with associated users at once /// - private class UserGroupWithUsersRepository : NPocoRepositoryBase + private class UserGroupWithUsersRepository : EntityRepositoryBase { private readonly UserGroupRepository _userGroupRepo; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 51000dbe70..1557dcc1d1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement /// /// Represents the UserRepository for doing CRUD operations for /// - internal class UserRepository : NPocoRepositoryBase, IUserRepository + internal class UserRepository : EntityRepositoryBase, IUserRepository { private readonly IMapperCollection _mapperCollection; private readonly GlobalSettings _globalSettings; @@ -376,7 +376,7 @@ ORDER BY colName"; #endregion - #region Overrides of NPocoRepositoryBase + #region Overrides of EntityRepositoryBase protected override Sql GetBaseQuery(bool isCount) { From b1045e081b8101b9981fa2a56c3ac26d007c8c0b Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 10:52:25 +1100 Subject: [PATCH 027/127] Makes ModelBindingExceptionAttribute --- .../Implement/EntityRepositoryBase.cs | 3 +- .../Controllers/RenderController.cs | 12 +-- .../Filters/ModelBindingExceptionAttribute.cs | 88 +++++++++++++++++++ .../Filters/ModelBindingExceptionFilter.cs | 77 ---------------- 4 files changed, 96 insertions(+), 84 deletions(-) create mode 100644 src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs delete mode 100644 src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index d7f6e63c55..8f9c5102ab 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -156,7 +156,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected abstract void PersistUpdatedItem(TEntity item); - protected abstract Sql GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere + // TODO: obsolete, use QueryType instead everywhere like GetBaseQuery(QueryType queryType); + protected abstract Sql GetBaseQuery(bool isCount); protected abstract string GetBaseWhereClause(); diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index ec73c061e2..099dfd59cd 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -15,12 +15,12 @@ namespace Umbraco.Web.Common.Controllers /// /// Represents the default front-end rendering controller. /// - [TypeFilter(typeof(ModelBindingExceptionFilter))] + [ModelBindingException] public class RenderController : UmbracoController, IRenderController { private readonly ILogger _logger; private readonly ICompositeViewEngine _compositeViewEngine; - private UmbracoRouteValues _routeValues; + private UmbracoRouteValues _umbracoRouteValues; /// /// Initializes a new instance of the class. @@ -43,9 +43,9 @@ namespace Umbraco.Web.Common.Controllers { get { - if (_routeValues != null) + if (_umbracoRouteValues != null) { - return _routeValues; + return _umbracoRouteValues; } if (!ControllerContext.RouteData.Values.TryGetValue(Core.Constants.Web.UmbracoRouteDefinitionDataToken, out var def)) @@ -53,8 +53,8 @@ namespace Umbraco.Web.Common.Controllers throw new InvalidOperationException($"No route value found with key {Core.Constants.Web.UmbracoRouteDefinitionDataToken}"); } - _routeValues = (UmbracoRouteValues)def; - return _routeValues; + _umbracoRouteValues = (UmbracoRouteValues)def; + return _umbracoRouteValues; } } diff --git a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs new file mode 100644 index 0000000000..6e9d6d29a7 --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionAttribute.cs @@ -0,0 +1,88 @@ +using System; +using System.Net; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using Umbraco.Core; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.ModelBinders; + +namespace Umbraco.Web.Common.Filters +{ + /// + /// An exception filter checking if we get a or with the same model. + /// In which case it returns a redirect to the same page after 1 sec if not in debug mode. + /// + /// + /// This is only enabled when running PureLive + /// + public sealed class ModelBindingExceptionAttribute : TypeFilterAttribute + { + /// + /// Initializes a new instance of the class. + /// + public ModelBindingExceptionAttribute() + : base(typeof(ModelBindingExceptionFilter)) + { + } + + private class ModelBindingExceptionFilter : IExceptionFilter + { + private static readonly Regex s_getPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled); + + private readonly ExceptionFilterSettings _exceptionFilterSettings; + private readonly IPublishedModelFactory _publishedModelFactory; + + public ModelBindingExceptionFilter(IOptions exceptionFilterSettings, IPublishedModelFactory publishedModelFactory) + { + _exceptionFilterSettings = exceptionFilterSettings.Value; + _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + } + + public void OnException(ExceptionContext filterContext) + { + var disabled = _exceptionFilterSettings?.Disabled ?? false; + if (_publishedModelFactory.IsLiveFactory() + && !disabled + && !filterContext.ExceptionHandled + && (filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException) + && IsMessageAboutTheSameModelType(filterContext.Exception.Message)) + { + filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1"); + filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.GetEncodedUrl(), false); + + filterContext.ExceptionHandled = true; + } + } + + /// + /// Returns true if the message is about two models with the same name. + /// + /// + /// Message could be something like: + /// + /// InvalidCastException: + /// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'. + /// + /// + /// ModelBindingException: + /// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now. + /// + /// + private bool IsMessageAboutTheSameModelType(string exceptionMessage) + { + MatchCollection matches = s_getPublishedModelsTypesRegex.Matches(exceptionMessage); + + if (matches.Count >= 2) + { + return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture); + } + + return false; + } + } + } +} diff --git a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs b/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs deleted file mode 100644 index 6cec04e0b6..0000000000 --- a/src/Umbraco.Web.Common/Filters/ModelBindingExceptionFilter.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Net; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Options; -using Umbraco.Core; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Common.ModelBinders; - -namespace Umbraco.Web.Common.Filters -{ - /// - /// An exception filter checking if we get a or with the same model. - /// In which case it returns a redirect to the same page after 1 sec if not in debug mode. - /// - /// - /// This is only enabled when running PureLive - /// - public class ModelBindingExceptionFilter : ActionFilterAttribute, IExceptionFilter - { - private static readonly Regex _getPublishedModelsTypesRegex = new Regex("Umbraco.Web.PublishedModels.(\\w+)", RegexOptions.Compiled); - - private readonly ExceptionFilterSettings _exceptionFilterSettings; - private readonly IPublishedModelFactory _publishedModelFactory; - - public ModelBindingExceptionFilter(IOptions exceptionFilterSettings, IPublishedModelFactory publishedModelFactory) - { - _exceptionFilterSettings = exceptionFilterSettings.Value; - _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); - } - - public void OnException(ExceptionContext filterContext) - { - var disabled = _exceptionFilterSettings?.Disabled ?? false; - if (_publishedModelFactory.IsLiveFactory() - && !disabled - && !filterContext.ExceptionHandled - && ((filterContext.Exception is ModelBindingException || filterContext.Exception is InvalidCastException) - && IsMessageAboutTheSameModelType(filterContext.Exception.Message))) - { - filterContext.HttpContext.Response.Headers.Add(HttpResponseHeader.RetryAfter.ToString(), "1"); - filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.GetEncodedUrl(), false); - - filterContext.ExceptionHandled = true; - } - } - - /// - /// Returns true if the message is about two models with the same name. - /// - /// - /// Message could be something like: - /// - /// InvalidCastException: - /// [A]Umbraco.Web.PublishedModels.Home cannot be cast to [B]Umbraco.Web.PublishedModels.Home. Type A originates from 'App_Web_all.generated.cs.8f9494c4.rtdigm_z, Version=0.0.0.3, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rtdigm_z.dll'. Type B originates from 'App_Web_all.generated.cs.8f9494c4.rbyqlplu, Version=0.0.0.5, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Users\User\AppData\Local\Temp\Temporary ASP.NET Files\root\c5c63f4d\c168d9d4\App_Web_all.generated.cs.8f9494c4.rbyqlplu.dll'. - /// - /// - /// ModelBindingException: - /// Cannot bind source content type Umbraco.Web.PublishedModels.Home to model type Umbraco.Web.PublishedModels.Home. Both view and content models are PureLive, with different versions. The application is in an unstable state and is going to be restarted. The application is restarting now. - /// - /// - private bool IsMessageAboutTheSameModelType(string exceptionMessage) - { - var matches = _getPublishedModelsTypesRegex.Matches(exceptionMessage); - - if (matches.Count >= 2) - { - return string.Equals(matches[0].Value, matches[1].Value, StringComparison.InvariantCulture); - } - - return false; - } - } -} From 74d253a88f282d968902ff563c0fa736c36bf66e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 11:22:29 +1100 Subject: [PATCH 028/127] Removes IPublishedSnapshotService.EnsureEnvironment --- .../FolderAndFilePermissionsCheck.cs | 40 +++++++------------ .../Install/IFilePermissionHelper.cs | 17 +++++++- .../IPublishedSnapshotService.cs | 7 ---- .../Install/FilePermissionHelper.cs | 35 ++++++---------- .../PublishedSnapshotService.cs | 11 ----- .../XmlPublishedSnapshotService.cs | 30 -------------- .../PublishedContent/NuCacheChildrenTests.cs | 1 - .../PublishedContent/NuCacheTests.cs | 1 - .../Scoping/ScopedNuCacheTests.cs | 1 - 9 files changed, 43 insertions(+), 100 deletions(-) diff --git a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs index a22748094a..28e1de3996 100644 --- a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -32,22 +32,16 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions /// /// Get the status for this health check /// - /// - public override IEnumerable GetStatus() - { - //return the statuses - return new[] { CheckFolderPermissions(), CheckFilePermissions() }; - } + // TODO: This should really just run the IFilePermissionHelper.RunFilePermissionTestSuite and then we'd have a + // IFilePermissions interface resolved as a collection within the IFilePermissionHelper that runs checks against all + // IFilePermissions registered. Then there's no hard coding things done here and the checks here will be consistent + // with the checks run in IFilePermissionHelper.RunFilePermissionTestSuite which occurs on install too. + public override IEnumerable GetStatus() => new[] { CheckFolderPermissions(), CheckFilePermissions() }; /// /// Executes the action and returns it's status /// - /// - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); - } + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); private HealthCheckStatus CheckFolderPermissions() { @@ -67,8 +61,8 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions { Constants.SystemDirectories.MvcViews, PermissionCheckRequirement.Optional } }; - //These are special paths to check that will restart an app domain if a file is written to them, - //so these need to be tested differently + // These are special paths to check that will restart an app domain if a file is written to them, + // so these need to be tested differently var pathsToCheckWithRestarts = new Dictionary { { Constants.SystemDirectories.Bin, PermissionCheckRequirement.Optional } @@ -80,7 +74,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions var optionalPathCheckResult = _filePermissionHelper.EnsureDirectories( GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths); - //now check the special folders + // now check the special folders var requiredPathCheckResult2 = _filePermissionHelper.EnsureDirectories( GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart: true); var optionalPathCheckResult2 = _filePermissionHelper.EnsureDirectories( @@ -89,7 +83,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2; optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2; - //combine the paths + // combine the paths requiredFailedPaths = requiredFailedPaths.Concat(requiredFailedPaths2).ToList(); optionalFailedPaths = requiredFailedPaths.Concat(optionalFailedPaths2).ToList(); @@ -106,23 +100,19 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions }; // Run checks for required and optional paths for modify permission - IEnumerable requiredFailedPaths; - IEnumerable optionalFailedPaths; - var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths); - var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths); + var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out IEnumerable requiredFailedPaths); + var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out IEnumerable optionalFailedPaths); return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File); } - private string[] GetPathsToCheck(Dictionary pathsToCheck, - PermissionCheckRequirement requirement) - { - return pathsToCheck + private string[] GetPathsToCheck( + Dictionary pathsToCheck, + PermissionCheckRequirement requirement) => pathsToCheck .Where(x => x.Value == requirement) .Select(x => _hostingEnvironment.MapPathContentRoot(x.Key)) .OrderBy(x => x) .ToArray(); - } private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable requiredFailedPaths, bool optionalPathCheckResult, IEnumerable optionalFailedPaths, PermissionCheckFor checkingFor) { diff --git a/src/Umbraco.Core/Install/IFilePermissionHelper.cs b/src/Umbraco.Core/Install/IFilePermissionHelper.cs index b60839cb00..ab521d214e 100644 --- a/src/Umbraco.Core/Install/IFilePermissionHelper.cs +++ b/src/Umbraco.Core/Install/IFilePermissionHelper.cs @@ -1,11 +1,26 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Umbraco.Core.Install { public interface IFilePermissionHelper { bool RunFilePermissionTestSuite(out Dictionary> report); + + /// + /// This will test the directories for write access + /// + /// The directories to check + /// The resulting errors, if any + /// + /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause + /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as + /// reliable but we cannot write a file since it will cause an app domain restart. + /// + /// Returns true if test succeeds + // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false); + + // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus bool EnsureFiles(string[] files, out IEnumerable errors); } } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index ce61dc4b8e..7307ba97f9 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -33,13 +33,6 @@ namespace Umbraco.Web.PublishedCache /// which is not specified and depends on the actual published snapshot service implementation. IPublishedSnapshot CreatePublishedSnapshot(string previewToken); - /// - /// Ensures that the published snapshot has the proper environment to run. - /// - /// The errors, if any. - /// A value indicating whether the published snapshot has the proper environment to run. - bool EnsureEnvironment(out IEnumerable errors); - /// /// Rebuilds internal database caches (but does not reload). /// diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs index a12634f01d..00f7c80fe4 100644 --- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs +++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.IO; @@ -20,7 +20,7 @@ namespace Umbraco.Web.Install private readonly string[] _packagesPermissionsDirs; // ensure Umbraco can write to these files (the directories must exist) - private readonly string[] _permissionFiles = { }; + private readonly string[] _permissionFiles = Array.Empty(); private readonly GlobalSettings _globalSettings; private readonly IIOHelper _ioHelper; private readonly IHostingEnvironment _hostingEnvironment; @@ -49,26 +49,13 @@ namespace Umbraco.Web.Install if (EnsureFiles(_permissionFiles, out errors) == false) report["File writing failed"] = errors.ToList(); - if (TestPublishedSnapshotService(out errors) == false) - report["Published snapshot environment check failed"] = errors.ToList(); - if (EnsureCanCreateSubDirectory(_globalSettings.UmbracoMediaPath, out errors) == false) report["Media folder creation failed"] = errors.ToList(); return report.Count == 0; } - /// - /// This will test the directories for write access - /// - /// - /// - /// - /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause - /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as - /// reliable but we cannot write a file since it will cause an app domain restart. - /// - /// + /// public bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false) { List temp = null; @@ -96,9 +83,16 @@ namespace Umbraco.Web.Install foreach (var file in files) { var canWrite = TryWriteFile(file); - if (canWrite) continue; + if (canWrite) + { + continue; + } + + if (temp == null) + { + temp = new List(); + } - if (temp == null) temp = new List(); temp.Add(file); success = false; } @@ -130,11 +124,6 @@ namespace Umbraco.Web.Install return success; } - public bool TestPublishedSnapshotService(out IEnumerable errors) - { - return _publishedSnapshotService.EnsureEnvironment(out errors); - } - // tries to create a sub-directory // if successful, the sub-directory is deleted // creates the directory if needed - does not delete it diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 1ddb1cef63..49283de276 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -45,7 +45,6 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly IHostingEnvironment _hostingEnvironment; - private readonly IIOHelper _ioHelper; private readonly NuCacheSettings _config; private bool _isReady; @@ -90,7 +89,6 @@ namespace Umbraco.Web.PublishedCache.NuCache IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, IHostingEnvironment hostingEnvironment, - IIOHelper ioHelper, // TODO: Remove this, it is only needed for "EnsureEnvironment" which doesn't need to belong to this service IOptions config) { _serviceContext = serviceContext; @@ -105,7 +103,6 @@ namespace Umbraco.Web.PublishedCache.NuCache _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings.Value; _hostingEnvironment = hostingEnvironment; - _ioHelper = ioHelper; _config = config.Value; // we need an Xml serializer here so that the member cache can support XPath, @@ -253,14 +250,6 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - public bool EnsureEnvironment(out IEnumerable errors) - { - // must have app_data and be able to write files into it - var ok = _ioHelper.TryCreateDirectory(GetLocalFilesPath()); - errors = ok ? Enumerable.Empty() : new[] { "NuCache local files." }; - return ok; - } - /// /// Populates the stores /// diff --git a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs index 0111c9f088..1b65d6c70e 100644 --- a/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs +++ b/src/Umbraco.Tests/LegacyXmlPublishedCache/XmlPublishedSnapshotService.cs @@ -2,14 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Runtime; @@ -136,30 +132,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache #endregion - #region Environment - - public bool EnsureEnvironment(out IEnumerable errors) - { - // Test creating/saving/deleting a file in the same location as the content xml file - // NOTE: We cannot modify the xml file directly because a background thread is responsible for - // that and we might get lock issues. - try - { - XmlStore.EnsureFilePermission(); - errors = Enumerable.Empty(); - return true; - } - catch - { - errors = new[] { SystemFiles.GetContentCacheXml(_hostingEnvironment) }; - return false; - } - } - - #endregion - - #region Caches - public IPublishedSnapshot CreatePublishedSnapshot(string previewToken) { // use _requestCache to store recursive properties lookup, etc. both in content @@ -176,8 +148,6 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache domainCache); } - #endregion - #region Xml specific /// diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 2297f5cf5b..cc3325377e 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -162,7 +162,6 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), PublishedModelFactory, hostingEnvironment, - TestHelper.IOHelper, Options.Create(nuCacheSettings)); // invariant is the current default diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index e11ccc9612..b56c0047ed 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -202,7 +202,6 @@ namespace Umbraco.Tests.PublishedContent Mock.Of(), publishedModelFactory, TestHelper.GetHostingEnvironment(), - TestHelper.IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); // invariant is the current default diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index cd733abad2..76a947f63a 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -103,7 +103,6 @@ namespace Umbraco.Tests.Scoping Factory.GetRequiredService(), new NoopPublishedModelFactory(), hostingEnvironment, - IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); From f5e9441e9f457532ecdbc39e42941d9a272d9077 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 12:16:37 +1100 Subject: [PATCH 029/127] renames umb builder ext class to be more explicit, no more AddAllBackOfficeComponents, just AddBackOffice, removes NuCacheComposer --- .../NuCacheUmbracoBuilderExtensions.cs} | 33 ++++++++------ src/Umbraco.Tests.Integration/RuntimeTests.cs | 1 + .../UmbracoBuilderExtensions.cs | 1 + .../UmbracoTestServerTestBase.cs | 3 +- .../Testing/UmbracoIntegrationTest.cs | 3 +- ... => BackOfficeUmbracoBuilderExtensions.cs} | 45 ++++++++++++++----- .../UmbracoBuilderExtensions.cs | 32 +++++++------ ....cs => WebsiteUmbracoBuilderExtensions.cs} | 7 ++- 8 files changed, 82 insertions(+), 43 deletions(-) rename src/Umbraco.PublishedCache.NuCache/{NuCacheComposer.cs => Extensions/NuCacheUmbracoBuilderExtensions.cs} (61%) rename src/Umbraco.Web.BackOffice/Extensions/{UmbracoBuilderExtensions.cs => BackOfficeUmbracoBuilderExtensions.cs} (70%) rename src/Umbraco.Web.Common/{Builder => Extensions}/UmbracoBuilderExtensions.cs (93%) rename src/Umbraco.Web.Website/Extensions/{UmbracoBuilderExtensions.cs => WebsiteUmbracoBuilderExtensions.cs} (87%) diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs b/src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs similarity index 61% rename from src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs rename to src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs index c1c80cf43c..ee0b0e2630 100644 --- a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs +++ b/src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs @@ -1,35 +1,39 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Infrastructure.PublishedCache; using Umbraco.Infrastructure.PublishedCache.Persistence; +using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.Web.PublishedCache.NuCache +namespace Umbraco.Infrastructure.PublishedCache.Extensions { - // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there, - // see comment in ModelsBuilderComposer which requires this weird IPublishedCacheComposer - public class NuCacheComposer : IComposer, IPublishedCacheComposer + /// + /// Extension methods for for the Umbraco's NuCache + /// + public static class NuCacheUmbracoBuilderExtensions { - /// - public void Compose(IUmbracoBuilder builder) + /// + /// Adds Umbraco NuCache dependencies + /// + public static IUmbracoBuilder AddNuCache(this IUmbracoBuilder builder) { // register the NuCache database data source - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); // register the NuCache published snapshot service // must register default options, required in the service ctor - builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions()); + builder.Services.TryAddTransient(factory => new PublishedSnapshotServiceOptions()); builder.SetPublishedSnapshotService(); // Add as itself - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); // replace this service since we want to improve the content/media // mapping lookups if we are using nucache. @@ -49,6 +53,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // add the NuCache health check (hidden from type finder) // TODO: no NuCache health check yet // composition.HealthChecks().Add(); + return builder; } } } diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index f74a95af4c..a5239bf05e 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Tests.Integration { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs index 0fcf47978a..cfab1e29c5 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Runtime; using Umbraco.Tests.Integration.Implementations; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Tests.Integration.TestServerTest { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index cc3df709af..d2b7f1664d 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -20,6 +20,7 @@ using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Website.Controllers; namespace Umbraco.Tests.Integration.TestServerTest @@ -135,7 +136,7 @@ namespace Umbraco.Tests.Integration.TestServerTest .AddTestCore(TestHelper) // This is the important one! .AddWebComponents() .AddRuntimeMinifier() - .AddBackOffice() + .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies(TestAuthHandler.TestAuthenticationScheme) .AddPreviewSupport() diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 414ad6ec43..823071ad11 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -30,6 +30,7 @@ using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.Testing; using Umbraco.Web; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Tests.Integration.Testing { @@ -223,7 +224,7 @@ namespace Umbraco.Tests.Integration.Testing builder.AddWebComponents(); builder.AddRuntimeMinifier(); - builder.AddBackOffice(); + builder.AddBackOfficeAuthentication(); builder.AddBackOfficeIdentity(); services.AddMvc(); diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs similarity index 70% rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs index 336855dd1b..16afd976db 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs @@ -1,22 +1,26 @@ using System; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; -using Umbraco.Web.BackOffice.Filters; +using Umbraco.Infrastructure.PublishedCache.Extensions; using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.Extensions; namespace Umbraco.Extensions { - public static class UmbracoBuilderExtensions + /// + /// Extension methods for for the Umbraco back office + /// + public static class BackOfficeUmbracoBuilderExtensions { - public static IUmbracoBuilder AddAllBackOfficeComponents(this IUmbracoBuilder builder) - { - return builder + /// + /// Adds all required components to run the Umbraco back office + /// + public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder) => builder .AddConfiguration() .AddUmbracoCore() .AddWebComponents() .AddRuntimeMinifier() - .AddBackOffice() + .AddBackOfficeAuthentication() .AddBackOfficeIdentity() .AddBackOfficeAuthorizationPolicies() .AddMiniProfiler() @@ -24,22 +28,29 @@ namespace Umbraco.Extensions .AddWebServer() .AddPreviewSupport() .AddHostedServices() - .AddHttpClients(); - } + .AddHttpClients() + .AddNuCache(); - public static IUmbracoBuilder AddBackOffice(this IUmbracoBuilder builder) + /// + /// Adds Umbraco back office authentication requirements + /// + public static IUmbracoBuilder AddBackOfficeAuthentication(this IUmbracoBuilder builder) { builder.Services.AddAntiforgery(); builder.Services - .AddAuthentication() // This just creates a builder, nothing more - // Add our custom schemes which are cookie handlers + + // This just creates a builder, nothing more + .AddAuthentication() + + // Add our custom schemes which are cookie handlers .AddCookie(Core.Constants.Security.BackOfficeAuthenticationType) .AddCookie(Core.Constants.Security.BackOfficeExternalAuthenticationType, o => { o.Cookie.Name = Core.Constants.Security.BackOfficeExternalAuthenticationType; o.ExpireTimeSpan = TimeSpan.FromMinutes(5); }) + // Although we don't natively support this, we add it anyways so that if end-users implement the required logic // they don't have to worry about manually adding this scheme or modifying the sign in manager .AddCookie(Core.Constants.Security.BackOfficeTwoFactorAuthenticationType, o => @@ -52,6 +63,9 @@ namespace Umbraco.Extensions return builder; } + /// + /// Adds Identity support for Umbraco back office + /// public static IUmbracoBuilder AddBackOfficeIdentity(this IUmbracoBuilder builder) { builder.Services.AddUmbracoBackOfficeIdentity(); @@ -59,9 +73,13 @@ namespace Umbraco.Extensions return builder; } + /// + /// Adds Umbraco back office authorization policies + /// public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder, string backOfficeAuthenticationScheme = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType) { builder.Services.AddBackOfficeAuthorizationPolicies(backOfficeAuthenticationScheme); + // TODO: See other TODOs in things like UmbracoApiControllerBase ... AFAIK all of this is only used for the back office // so IMO these controllers and the features auth policies should just be moved to the back office project and then this // ext method can be removed. @@ -70,6 +88,9 @@ namespace Umbraco.Extensions return builder; } + /// + /// Adds Umbraco preview support + /// public static IUmbracoBuilder AddPreviewSupport(this IUmbracoBuilder builder) { builder.Services.AddSignalR(); diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs similarity index 93% rename from src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs rename to src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs index 445025a80c..80185f30a3 100644 --- a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs @@ -18,11 +18,13 @@ 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.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; @@ -34,15 +36,20 @@ using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Infrastructure.Runtime; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Common.Profiler; using Umbraco.Web.Telemetry; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; -namespace Umbraco.Core.DependencyInjection +namespace Umbraco.Web.Common.Extensions { // TODO: We could add parameters to configure each of these for flexibility + + /// + /// Extension methods for for the common Umbraco functionality + /// public static class UmbracoBuilderExtensions { public static IUmbracoBuilder AddUmbraco( @@ -62,7 +69,7 @@ namespace Umbraco.Core.DependencyInjection IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config); - var loggingDir = tempHostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles); + var loggingDir = tempHostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.LogFiles); var loggingConfig = new LoggingConfiguration(loggingDir); services.AddLogger(tempHostingEnvironment, loggingConfig, config); @@ -72,10 +79,10 @@ namespace Umbraco.Core.DependencyInjection var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); var appCaches = AppCaches.Create(requestCache); - services.AddUnique(appCaches); + services.AddUnique(appCaches); IProfiler profiler = GetWebProfiler(config); - services.AddUnique(profiler); + services.AddUnique(profiler); ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler); @@ -102,7 +109,7 @@ namespace Umbraco.Core.DependencyInjection factory.GetServices() )); - builder.Services.AddUnique(factory => + builder.Services.AddUnique(factory => { var globalSettings = factory.GetRequiredService>().Value; var connectionStrings = factory.GetRequiredService>().Value; @@ -135,20 +142,20 @@ namespace Umbraco.Core.DependencyInjection } ); - builder.Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); - builder.Services.AddUnique(factory => factory.GetRequiredService().RequestCache); + 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(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.Services.AddHostedService(factory => factory.GetRequiredService()); builder.AddCoreInitialServices(); builder.AddComposers(); @@ -201,7 +208,6 @@ namespace Umbraco.Core.DependencyInjection return builder; } - public static IUmbracoBuilder AddHostedServices(this IUmbracoBuilder builder) { builder.Services.AddHostedService(); @@ -235,8 +241,8 @@ namespace Umbraco.Core.DependencyInjection { // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important // this will directly affect developers who need to call that themselves. - //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. - //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. + // We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. + // But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. var mvcBuilder = builder.Services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new ContentModelBinderProvider()); diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs similarity index 87% rename from src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs rename to src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs index cbfa0c659e..204f97ae30 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; +using Umbraco.Infrastructure.PublishedCache.Extensions; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; using Umbraco.Web.Website.ViewEngines; @@ -11,12 +12,12 @@ namespace Umbraco.Extensions /// /// extensions for umbraco front-end website /// - public static class UmbracoBuilderExtensions + public static class WebsiteUmbracoBuilderExtensions { /// /// Add services for the umbraco front-end website /// - public static IUmbracoBuilder AddUmbracoWebsite(this IUmbracoBuilder builder) + public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) { // Set the render & plugin view engines (Super complicated, but this allows us to use the IServiceCollection // to inject dependencies into the viewEngines) @@ -34,6 +35,8 @@ namespace Umbraco.Extensions builder.Services.AddScoped(); builder.Services.AddSingleton(); + builder.AddNuCache(); + return builder; } From 57020b47789829986b4f55ceb54e9d41bc45979f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 12:33:00 +1100 Subject: [PATCH 030/127] adds notes, removes IPublishedCacheComposer --- .../Composing/IPublishedCacheComposer .cs | 5 ----- .../Runtime/CoreRuntime.cs | 3 +++ .../Compose/DisabledModelsBuilderComponent.cs | 6 +++++- .../Compose/ModelsBuilderComposer.cs | 2 +- .../PublishedSnapshotServiceEventHandler.cs | 2 +- .../Services/ContentTypeServiceVariantsTests.cs | 2 +- .../Controllers/ContentTypeController.cs | 17 ----------------- .../Filters/JsonDateTimeFormatAttribute.cs | 16 ++++++++-------- .../Middleware/UmbracoRequestMiddleware.cs | 2 +- 9 files changed, 20 insertions(+), 35 deletions(-) delete mode 100644 src/Umbraco.Core/Composing/IPublishedCacheComposer .cs diff --git a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs b/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs deleted file mode 100644 index d88eb44ea3..0000000000 --- a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Umbraco.Core.Composing -{ - public interface IPublishedCacheComposer : ICoreComposer - { } -} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 35b7443338..a05c1a7f98 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -23,6 +23,9 @@ namespace Umbraco.Infrastructure.Runtime private readonly IEventAggregator _eventAggregator; private readonly IHostingEnvironment _hostingEnvironment; + /// + /// Initializes a new instance of the class. + /// public CoreRuntime( ILoggerFactory loggerFactory, IRuntimeState state, diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs index 2031a23af5..f7fab098b0 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs @@ -1,9 +1,13 @@ -using Umbraco.Core.Composing; +using Umbraco.Core.Composing; using Umbraco.ModelsBuilder.Embedded.BackOffice; using Umbraco.Web.Features; namespace Umbraco.ModelsBuilder.Embedded.Compose { + // TODO: This needs to die, see TODO in ModelsBuilderComposer. This is also no longer used in this netcore + // codebase. Potentially this could be changed to ext methods if necessary that could be used by end users who will + // install the community MB package to disable any built in MB stuff. + /// /// Special component used for when MB is disabled with the legacy MB is detected /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs index 344e1b025a..ca597a607b 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs @@ -12,7 +12,7 @@ using Umbraco.Core.DependencyInjection; namespace Umbraco.ModelsBuilder.Embedded.Compose { // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there - [ComposeBefore(typeof(IPublishedCacheComposer))] + // This needs to execute before the AddNuCache call public sealed class ModelsBuilderComposer : ICoreComposer { public void Compose(IUmbracoBuilder builder) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 084ed569ca..2c37356191 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -38,7 +38,7 @@ namespace Umbraco.Web.PublishedCache.NuCache /// Binds to the Umbraco events /// /// Returns true if binding occurred - public bool Start() + public bool Initialize() { // however, the cache is NOT available until we are configured, because loading // content (and content types) from database cannot be consistent (see notes in "Handle diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 995e4d60ae..5e50f570ab 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -42,7 +42,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services // Ensure that the events are bound on each test PublishedSnapshotServiceEventHandler eventBinder = host.Services.GetRequiredService(); - eventBinder.Start(); + eventBinder.Initialize(); } private void AssertJsonStartsWith(int id, string expected) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs index 05e4db5daa..50be27a108 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentTypeController.cs @@ -114,8 +114,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given id /// - /// - /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public DocumentTypeDisplay GetById(int id) @@ -133,8 +131,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given guid /// - /// - /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public DocumentTypeDisplay GetById(Guid id) @@ -152,8 +148,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets the document type a given udi /// - /// - /// [DetermineAmbiguousActionByPassingParameters] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public DocumentTypeDisplay GetById(Udi id) @@ -175,8 +169,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Deletes a document type with a given ID /// - /// - /// [HttpDelete] [HttpPost] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] @@ -195,7 +187,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets all user defined properties. /// - /// [Authorize(Policy = AuthorizationPolicies.TreeAccessAnyContentOrTypes)] public IEnumerable GetAllPropertyTypeAliases() { @@ -205,7 +196,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Gets all the standard fields. /// - /// [Authorize(Policy = AuthorizationPolicies.TreeAccessAnyContentOrTypes)] public IEnumerable GetAllStandardFields() { @@ -217,8 +207,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// Returns the available compositions for this content type /// This has been wrapped in a dto instead of simple parameters to support having multiple parameters in post request body /// - /// - /// [HttpPost] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public IActionResult GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) @@ -235,7 +223,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Returns true if any content types have culture variation enabled /// - /// [HttpGet] [Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] public bool AllowsCultureVariation() @@ -248,8 +235,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// Returns where a particular composition has been used /// This has been wrapped in a dto instead of simple parameters to support having multiple parameters in post request body /// - /// - /// [HttpPost] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] public IActionResult GetWhereCompositionIsUsedInContentTypes(GetAvailableCompositionsFilter filter) @@ -287,8 +272,6 @@ namespace Umbraco.Web.BackOffice.Controllers /// /// Deletes a document type container with a given ID /// - /// - /// [HttpDelete] [HttpPost] [Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)] diff --git a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs index 031aeb1f4c..edf8489f12 100644 --- a/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/JsonDateTimeFormatAttribute.cs @@ -13,24 +13,24 @@ namespace Umbraco.Web.Common.Filters /// /// Applying this attribute to any controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. /// - public class JsonDateTimeFormatAttribute : TypeFilterAttribute + public sealed class JsonDateTimeFormatAttribute : TypeFilterAttribute { - public JsonDateTimeFormatAttribute() : base(typeof(JsonDateTimeFormatFilter)) - { - Order = 2; //must be higher than AngularJsonOnlyConfigurationAttribute.Order - } + /// + /// Initializes a new instance of the class. + /// + public JsonDateTimeFormatAttribute() + : base(typeof(JsonDateTimeFormatFilter)) => + Order = 2; // must be higher than AngularJsonOnlyConfigurationAttribute.Order private class JsonDateTimeFormatFilter : IResultFilter { private readonly string _format = "yyyy-MM-dd HH:mm:ss"; - private readonly IOptions _mvcNewtonsoftJsonOptions; private readonly ArrayPool _arrayPool; private readonly IOptions _options; - public JsonDateTimeFormatFilter(IOptions mvcNewtonsoftJsonOptions, ArrayPool arrayPool, IOptions options) + public JsonDateTimeFormatFilter(ArrayPool arrayPool, IOptions options) { - _mvcNewtonsoftJsonOptions = mvcNewtonsoftJsonOptions; _arrayPool = arrayPool; _options = options; } diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 7845962928..35cd2250cb 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -172,7 +172,7 @@ namespace Umbraco.Web.Common.Middleware ref s_cacheInitializedLock, () => { - _publishedSnapshotServiceEventHandler.Start(); + _publishedSnapshotServiceEventHandler.Initialize(); return true; }); } From c074a4d7d4b3ad52841b9e3d6235bb12b04f124d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 13:32:12 +1100 Subject: [PATCH 031/127] IsFrontEndUmbracoRequest as ext method instead, makes IUmbracoContext IDisposable since it has a Dispose method --- src/Umbraco.Core/UmbracoContextAccessorExtensions.cs | 1 + src/Umbraco.Core/UmbracoContextExtensions.cs | 12 ++++++++++++ src/Umbraco.Core/Web/IUmbracoContext.cs | 8 +------- .../Middleware/UmbracoRequestMiddleware.cs | 6 ++++-- .../UmbracoContext/UmbracoContext.cs | 5 ----- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 +- src/Umbraco.Web/UmbracoContext.cs | 8 ++------ 7 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 src/Umbraco.Core/UmbracoContextExtensions.cs diff --git a/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs index 74e2dd7380..a8521762c6 100644 --- a/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs +++ b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs @@ -3,6 +3,7 @@ using Umbraco.Web; namespace Umbraco.Core { + public static class UmbracoContextAccessorExtensions { public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor) diff --git a/src/Umbraco.Core/UmbracoContextExtensions.cs b/src/Umbraco.Core/UmbracoContextExtensions.cs new file mode 100644 index 0000000000..06ae2aa497 --- /dev/null +++ b/src/Umbraco.Core/UmbracoContextExtensions.cs @@ -0,0 +1,12 @@ +using Umbraco.Web; + +namespace Umbraco.Core +{ + public static class UmbracoContextExtensions + { + /// + /// Boolean value indicating whether the current request is a front-end umbraco request + /// + public static bool IsFrontEndUmbracoRequest(this IUmbracoContext umbracoContext) => umbracoContext.PublishedRequest != null; + } +} diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index 7fa02e3b73..c034997d7e 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -6,7 +6,7 @@ using Umbraco.Web.Routing; namespace Umbraco.Web { - public interface IUmbracoContext + public interface IUmbracoContext : IDisposable { /// /// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this @@ -46,11 +46,6 @@ namespace Umbraco.Web /// IDomainCache Domains { get; } - /// - /// Boolean value indicating whether the current request is a front-end umbraco request - /// - bool IsFrontEndUmbracoRequest { get; } // TODO: This could easily be an ext method and mocking just means setting the published request to null - /// /// Gets/sets the PublishedRequest object /// @@ -74,6 +69,5 @@ namespace Umbraco.Web bool InPreviewMode { get; } IDisposable ForcedPreview(bool preview); - void Dispose(); } } diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 35cd2250cb..0474f2445c 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -73,9 +73,11 @@ namespace Umbraco.Web.Common.Middleware _backofficeSecurityFactory.EnsureBackOfficeSecurity(); // Needs to be before UmbracoContext, TODO: Why? UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + bool isFrontEndRequest = umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest(); + try { - if (umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest) + if (isFrontEndRequest) { LogHttpRequest.TryGetCurrentHttpRequestId(out Guid httpRequestId, _requestCache); _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); @@ -104,7 +106,7 @@ namespace Umbraco.Web.Common.Middleware } finally { - if (umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest) + 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); diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index 97bb9ac7c4..2d22bc5a90 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -110,11 +110,6 @@ namespace Umbraco.Web /// public IDomainCache Domains => PublishedSnapshot.Domains; - /// - /// Boolean value indicating whether the current request is a front-end umbraco request - /// - public bool IsFrontEndUmbracoRequest => PublishedRequest != null; - /// /// Gets/sets the PublishedRequest object /// diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 7528755865..cac49f9421 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Runtime if (state.Level == RuntimeLevel.Run) { var umbCtx = factory.GetRequiredService(); - return new UmbracoHelper(umbCtx.IsFrontEndUmbracoRequest ? umbCtx.PublishedRequest?.PublishedContent : null, factory.GetRequiredService(), + return new UmbracoHelper(umbCtx.IsFrontEndUmbracoRequest() ? umbCtx.PublishedRequest?.PublishedContent : null, factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService()); } diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 5fdfd8c255..8707bea26b 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -29,7 +29,8 @@ namespace Umbraco.Web // internal for unit tests // otherwise it's used by EnsureContext above // warn: does *not* manage setting any IUmbracoContextAccessor - internal UmbracoContext(IHttpContextAccessor httpContextAccessor, + internal UmbracoContext( + IHttpContextAccessor httpContextAccessor, IPublishedSnapshotService publishedSnapshotService, IBackOfficeSecurity backofficeSecurity, GlobalSettings globalSettings, @@ -123,11 +124,6 @@ namespace Umbraco.Web /// public IDomainCache Domains => PublishedSnapshot.Domains; - /// - /// Boolean value indicating whether the current request is a front-end umbraco request - /// - public bool IsFrontEndUmbracoRequest => PublishedRequest != null; - /// /// Gets/sets the PublishedRequest object /// From c88ba7afa580122ece287ff349796e694b01286f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 22 Dec 2020 13:49:26 +1100 Subject: [PATCH 032/127] notes, removes todo --- .../PublishedSnapshotServiceEventHandler.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs index 2c37356191..a8f3f9338b 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotServiceEventHandler.cs @@ -155,17 +155,15 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - /// If a is ever saved with a different culture, we need to rebuild all of the content nucache table + /// If a is ever saved with a different culture, we need to rebuild all of the content nucache database table /// private void OnLanguageSaved(ILocalizationService sender, Core.Events.SaveEventArgs e) { - // TODO: This should be a cache refresher call! - // culture changed on an existing language var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode))); if (cultureChanged) { - // Rebuild all content types + // Rebuild all content for all content types _publishedSnapshotService.Rebuild(contentTypeIds: Array.Empty()); } } From 3be9610c72832bd4078417737e4cb79991ac72c5 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 20 Dec 2020 08:36:11 +0100 Subject: [PATCH 033/127] Cleaned-up code in Umbraco.Web.UnitTests to match linting rules. --- .../Configuration/NCronTabParser.cs | 3 +- .../AutoFixture/AutoMoqDataAttribute.cs | 34 +- .../AutoFixture/InlineAutoMoqDataAttribute.cs | 22 + .../TestHelpers/BaseUsingSqlSyntax.cs | 21 +- .../TestHelpers/CompositionExtensions.cs | 6 +- .../Objects/TestUmbracoContextFactory.cs | 39 +- .../TestHelpers/TestHelper.cs | 167 +++-- .../Models/ConnectionStringsTests.cs | 5 +- .../Umbraco.Core/AttemptTests.cs | 11 +- .../BackOfficeClaimsPrincipalFactoryTests.cs | 31 +- .../BackOffice/IdentityExtensionsTests.cs | 15 +- .../BackOffice/NopLookupNormalizerTests.cs | 4 + .../UmbracoBackOfficeIdentityTests.cs | 44 +- .../Umbraco.Core/Cache/AppCacheTests.cs | 37 +- .../Cache/DeepCloneAppCacheTests.cs | 19 +- .../Cache/DefaultCachePolicyTests.cs | 35 +- .../Cache/DictionaryAppCacheTests.cs | 5 +- .../DistributedCache/DistributedCacheTests.cs | 91 +-- .../Cache/FullDataSetCachePolicyTests.cs | 63 +- .../Cache/HttpRequestAppCacheTests.cs | 7 +- .../Umbraco.Core/Cache/ObjectAppCacheTests.cs | 20 +- .../Umbraco.Core/Cache/RefresherTests.cs | 27 +- .../Cache/RuntimeAppCacheTests.cs | 7 +- .../Cache/SingleItemsOnlyCachePolicyTests.cs | 19 +- .../ClaimsIdentityExtensionsTests.cs | 9 +- .../Collections/DeepCloneableListTests.cs | 77 +- .../Collections/OrderedHashSetTests.cs | 61 +- .../Umbraco.Core/Components/ComponentTests.cs | 315 ++++---- .../Composing/CollectionBuildersTests.cs | 244 +++---- .../Composing/ComposingTestBase.cs | 3 + .../Composing/CompositionTests.cs | 6 +- .../Composing/LazyCollectionBuilderTests.cs | 96 +-- .../Composing/PackageActionCollectionTests.cs | 67 +- .../Umbraco.Core/Composing/TypeFinderTests.cs | 30 +- .../Umbraco.Core/Composing/TypeHelperTests.cs | 105 +-- .../Composing/TypeLoaderExtensions.cs | 5 +- .../Umbraco.Core/Composing/TypeLoaderTests.cs | 291 ++++---- .../Models}/GlobalSettingsTests.cs | 8 +- .../GlobalSettingsValidatorTests.cs | 2 +- .../HealthChecksSettingsValidatorTests.cs | 2 +- .../NCronTabParserTests.cs | 7 +- .../CoreThings/CallContextTests.cs | 48 +- .../CoreThings/ObjectExtensionsTests.cs | 84 +-- .../CoreThings/TryConvertToTests.cs | 10 +- .../Umbraco.Core/CoreThings/UdiTests.cs | 95 +-- .../Umbraco.Core/CoreXml/FrameworkXmlTests.cs | 74 +- .../CoreXml/NavigableNavigatorTests.cs | 270 +++---- .../CoreXml/RenamedRootNavigatorTests.cs | 17 +- .../Umbraco.Core/DelegateExtensionsTests.cs | 31 +- .../Umbraco.Core/EnumExtensionsTests.cs | 32 +- .../Umbraco.Core/EnumerableExtensionsTests.cs | 40 +- .../ClaimsPrincipalExtensionsTests.cs | 25 +- .../Extensions/UriExtensionsTests.cs | 7 +- .../Umbraco.Core/GuidUtilsTests.cs | 5 +- .../Umbraco.Core/HashCodeCombinerTests.cs | 36 +- .../Umbraco.Core/HashGeneratorTests.cs | 168 +++-- .../Umbraco.Core/HexEncoderTests.cs | 12 +- .../IO/AbstractFileSystemTests.cs | 49 +- .../IO/PhysicalFileSystemTests.cs | 26 +- .../Manifest/ManifestContentAppTests.cs | 60 +- .../Manifest/ManifestParserTests.cs | 85 ++- .../Umbraco.Core/Models/Collections/Item.cs | 74 +- .../Models/Collections/OrderItem.cs | 26 +- .../Collections/PropertyCollectionTests.cs | 5 +- .../Models/Collections/SimpleOrder.cs | 40 +- .../Models/ContentExtensionsTests.cs | 61 +- .../Models/ContentScheduleTests.cs | 27 +- .../Umbraco.Core/Models/ContentTests.cs | 328 +++++---- .../Umbraco.Core/Models/ContentTypeTests.cs | 77 +- .../Umbraco.Core/Models/CultureImpactTests.cs | 28 +- .../Models/DeepCloneHelperTests.cs | 26 +- .../Models/DictionaryItemTests.cs | 20 +- .../Models/DictionaryTranslationTests.cs | 34 +- .../Models/DocumentEntityTests.cs | 13 +- .../Umbraco.Core/Models/LanguageTests.cs | 23 +- .../Umbraco.Core/Models/MacroTests.cs | 24 +- .../Umbraco.Core/Models/MemberGroupTests.cs | 29 +- .../Umbraco.Core/Models/MemberTests.cs | 31 +- .../Umbraco.Core/Models/PropertyGroupTests.cs | 30 +- .../Umbraco.Core/Models/PropertyTests.cs | 24 +- .../Umbraco.Core/Models/PropertyTypeTests.cs | 28 +- .../Umbraco.Core/Models/RelationTests.cs | 29 +- .../Umbraco.Core/Models/RelationTypeTests.cs | 23 +- .../Umbraco.Core/Models/StylesheetTests.cs | 32 +- .../Umbraco.Core/Models/TemplateTests.cs | 28 +- .../Models/UserExtensionsTests.cs | 23 +- .../Umbraco.Core/Models/UserTests.cs | 29 +- .../Umbraco.Core/Models/VariationTests.cs | 186 +++-- .../Packaging/PackageExtractionTests.cs | 14 +- .../BlockEditorComponentTests.cs | 91 ++- .../BlockListPropertyValueConverterTests.cs | 193 +++-- .../PropertyEditors/ColorListValidatorTest.cs | 57 +- .../PropertyEditors/ConvertersTests.cs | 67 +- ...ataValueReferenceFactoryCollectionTests.cs | 61 +- .../EnsureUniqueValuesValidatorTest.cs | 89 ++- .../MultiValuePropertyEditorTests.cs | 42 +- .../NestedContentPropertyComponentTests.cs | 57 +- .../PropertyEditorValueConverterTests.cs | 36 +- .../PropertyEditorValueEditorTests.cs | 61 +- .../Umbraco.Core/Published/ConvertersTests.cs | 46 +- .../Umbraco.Core/Published/ModelTypeTests.cs | 26 +- .../Published/NestedContentTests.cs | 64 +- .../Published/PropertyCacheLevelTests.cs | 63 +- .../Umbraco.Core/ReflectionTests.cs | 15 +- .../Umbraco.Core/ReflectionUtilitiesTests.cs | 336 +++++---- .../Routing/SiteDomainHelperTests.cs | 223 +++--- .../Umbraco.Core/Routing/UriUtilityTests.cs | 17 +- .../Umbraco.Core/Routing/WebPathTests.cs | 18 +- .../Scoping/EventNameExtractorTests.cs | 20 +- .../Security/ContentPermissionsTests.cs | 240 +++--- .../Security/LegacyPasswordSecurityTests.cs | 31 +- .../Security/MediaPermissionsTests.cs | 113 +-- .../ContentTypeServiceExtensionsTests.cs | 132 ++-- .../ShortStringHelper/CmsHelperCasingTests.cs | 14 +- .../DefaultShortStringHelperTests.cs | 39 +- ...faultShortStringHelperTestsWithoutSetup.cs | 29 +- .../MockShortStringHelper.cs | 67 +- .../StringExtensionsTests.cs | 17 +- .../StringValidationTests.cs | 11 +- .../StylesheetHelperTests.cs | 55 +- .../Templates/HtmlImageSourceParserTests.cs | 48 +- .../Templates/HtmlLocalLinkParserTests.cs | 27 +- .../Umbraco.Core/Templates/ViewHelperTests.cs | 26 +- .../Umbraco.Core/VersionExtensionTests.cs | 7 +- .../Umbraco.Core/Xml/XmlHelperTests.cs | 76 +- .../Umbraco.Core/XmlExtensionsTests.cs | 11 +- .../BackOfficeLookupNormalizerTests.cs | 4 + .../UserEditorAuthorizationHelperTests.cs | 210 +++--- .../UmbracoContentValueSetValidatorTests.cs | 98 ++- .../HealthChecks/HealthCheckResultsTests.cs | 36 +- .../Logging/LogviewerTests.cs | 143 ++-- .../Macros/MacroParserTests.cs | 1 - .../Manifest/ManifestContentAppTests.cs | 1 - .../Manifest/ManifestParserTests.cs | 1 - .../Mapping/MappingTests.cs | 58 +- .../Media/ImageSharpImageUrlGeneratorTests.cs | 61 +- .../Migrations/AlterMigrationTests.cs | 22 +- .../Migrations/MigrationPlanTests.cs | 23 +- .../Migrations/MigrationTests.cs | 8 +- .../Migrations/PostMigrationTests.cs | 5 +- .../Stubs/AlterUserTableMigrationStub.cs | 8 +- .../Stubs/DropForeignKeyMigrationStub.cs | 8 +- .../Migrations/Stubs/Dummy.cs | 5 +- .../Migrations/Stubs/FiveZeroMigration.cs | 11 +- .../Migrations/Stubs/FourElevenMigration.cs | 9 +- .../Migrations/Stubs/SixZeroMigration1.cs | 9 +- .../Migrations/Stubs/SixZeroMigration2.cs | 9 +- .../Models/DataTypeTests.cs | 28 +- .../Models/PathValidationTests.cs | 54 +- .../Persistence/BulkDataReaderTests.cs | 683 ++++++++---------- .../Persistence/Mappers/ContentMapperTest.cs | 5 +- .../Mappers/ContentTypeMapperTest.cs | 8 +- .../Persistence/Mappers/DataTypeMapperTest.cs | 8 +- .../Mappers/DictionaryMapperTest.cs | 7 +- .../DictionaryTranslationMapperTest.cs | 8 +- .../Persistence/Mappers/LanguageMapperTest.cs | 7 +- .../Persistence/Mappers/MediaMapperTest.cs | 5 +- .../Mappers/PropertyGroupMapperTest.cs | 5 +- .../Mappers/PropertyTypeMapperTest.cs | 5 +- .../Persistence/Mappers/RelationMapperTest.cs | 5 +- .../Mappers/RelationTypeMapperTest.cs | 7 +- .../NPocoTests/NPocoSqlExtensionsTests.cs | 42 +- .../NPocoTests/NPocoSqlTemplateTests.cs | 33 +- .../Persistence/NPocoTests/NPocoSqlTests.cs | 68 +- .../ContentTypeRepositorySqlClausesTest.cs | 5 +- ...aTypeDefinitionRepositorySqlClausesTest.cs | 9 +- .../Persistence/Querying/ExpressionTests.cs | 38 +- .../Querying/MediaRepositorySqlClausesTest.cs | 9 +- .../MediaTypeRepositorySqlClausesTest.cs | 11 +- .../Persistence/Querying/QueryBuilderTests.cs | 37 +- .../Serialization/JsonNetSerializerTests.cs | 8 +- .../Services/AmbiguousEventTests.cs | 39 +- .../Services/LocalizedTextServiceTests.cs | 106 +-- .../PropertyValidationServiceTests.cs | 91 ++- .../BuilderTests.cs | 102 ++- .../StringExtensions.cs | 10 +- .../UmbracoApplicationTests.cs | 35 +- .../SnapDictionaryTests.cs | 179 +++-- .../Builders/AllowedContentTypeDetail.cs | 5 +- .../Builders/ContentTypeBuilderTests.cs | 14 +- .../Builders/DataTypeBuilderTests.cs | 10 +- .../DocumentEntitySlimBuilderTests.cs | 12 +- .../Builders/LanguageBuilderTests.cs | 8 +- .../Builders/MacroBuilderTests.cs | 8 +- .../Builders/MediaTypeBuilderTests.cs | 14 +- .../Builders/MemberBuilderTests.cs | 19 +- .../Builders/MemberGroupBuilderTests.cs | 12 +- .../Builders/MemberTypeBuilderTests.cs | 13 +- .../Builders/PropertyBuilderTests.cs | 12 +- .../Builders/PropertyGroupBuilderTests.cs | 12 +- .../Builders/PropertyTypeBuilderTests.cs | 11 +- .../Builders/PropertyTypeDetail.cs | 5 +- .../Builders/RelationBuilderTests.cs | 12 +- .../Builders/RelationTypeBuilderTests.cs | 14 +- .../Builders/StylesheetBuilderTests.cs | 7 +- .../Builders/TemplateBuilderTests.cs | 11 +- .../Builders/TemplateDetail.cs | 5 +- .../Builders/UserBuilderTests.cs | 18 +- .../Builders/UserGroupBuilderTests.cs | 8 +- .../Builders/XmlDocumentBuilderTests.cs | 8 +- ...erUnitTests.cs => UsersControllerTests.cs} | 9 +- .../Extensions/ModelStateExtensionsTests.cs | 35 +- .../AppendUserModifiedHeaderAttributeTests.cs | 21 +- .../Filters/ContentModelValidatorTests.cs | 11 +- ...terAllowedOutgoingContentAttributeTests.cs | 75 +- .../OnlyLocalRequestsAttributeTests.cs | 17 +- .../Filters/ValidationFilterAttributeTests.cs | 10 +- .../Security/BackOfficeAntiforgeryTests.cs | 13 +- .../Security/BackOfficeCookieManagerTests.cs | 16 +- .../ContentModelSerializationTests.cs | 12 +- .../JsInitializationTests.cs | 9 +- .../ServerVariablesParserTests.cs | 19 +- .../HtmlHelperExtensionMethodsTests.cs | 16 +- .../Umbraco.Web.Common/FileNameTests.cs | 18 +- ...lidateUmbracoFormRouteStringFilterTests.cs | 6 +- ...noreRequiredAttributesResolverUnitTests.cs | 19 +- .../Umbraco.Web.Common/ImageCropperTest.cs | 192 +++-- .../Macros/MacroParserTests.cs | 245 +++---- .../Umbraco.Web.Common/Macros/MacroTests.cs | 26 +- .../ModelBinders/ContentModelBinderTests.cs | 32 +- .../HttpQueryStringModelBinderTests.cs | 14 +- .../ModelBinders/RenderModelBinderTests.cs | 32 +- .../Mvc/HtmlStringUtilitiesTests.cs | 20 +- .../Routing/BackOfficeAreaRoutesTests.cs | 21 +- .../EndpointRouteBuilderExtensionsTests.cs | 54 +- .../Routing/InstallAreaRoutesTests.cs | 28 +- .../Routing/PreviewRoutesTests.cs | 26 +- .../Routing/TestRouteBuilder.cs | 14 +- .../Security/EncryptionHelperTests.cs | 26 +- .../Views/UmbracoViewPageTests.cs | 102 ++- .../AspNetCoreHostingEnvironmentTests.cs | 17 +- ...RenderIndexActionSelectorAttributeTests.cs | 111 +-- .../RenderNoContentControllerTests.cs | 8 +- .../Controllers/SurfaceControllerTests.cs | 86 +-- .../Security/UmbracoWebsiteSecurityTests.cs | 5 +- ...ewEngineWrapperMvcViewOptionsSetupTests.cs | 11 +- 236 files changed, 5804 insertions(+), 5268 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs rename src/Umbraco.Tests.UnitTests/Umbraco.Core/{Configurations => Configuration/Models}/GlobalSettingsTests.cs (88%) rename src/Umbraco.Tests.UnitTests/Umbraco.Core/{Configurations => Configuration}/NCronTabParserTests.cs (89%) delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Macros/MacroParserTests.cs delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestContentAppTests.cs delete mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestParserTests.cs rename src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/{UsersControllerUnitTests.cs => UsersControllerTests.cs} (86%) diff --git a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs index 5642c882e6..ca25563730 100644 --- a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs +++ b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs @@ -1,9 +1,8 @@ -using System; +using System; using NCrontab; namespace Umbraco.Core.Configuration { - public class NCronTabParser : ICronTabParser { public bool IsValidCronTab(string cronTab) diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs index 365dca780c..bed11ca866 100644 --- a/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/AutoMoqDataAttribute.cs @@ -1,6 +1,7 @@ -using System; -using System.Linq; -using System.Reflection; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using AutoFixture; using AutoFixture.AutoMoq; using AutoFixture.Kernel; @@ -14,7 +15,6 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Security; -using Umbraco.Tests.Common.Builders; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; using Umbraco.Web.Common.Install; @@ -23,20 +23,6 @@ using Umbraco.Web.WebApi; namespace Umbraco.Tests.UnitTests.AutoFixture { - - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple = true)] - public class InlineAutoMoqDataAttribute : InlineAutoDataAttribute - { - /// - /// Uses AutoFixture to automatically mock (using Moq) the injected types. E.g when injecting interfaces. - /// AutoFixture is used to generate concrete types. If the concrete type required some types injected, the - /// [Frozen] can be used to ensure the same variable is injected and available as parameter for the test - /// - public InlineAutoMoqDataAttribute(params object[] arguments) : base(() => AutoMoqDataAttribute.AutoMockCustomizations.Default, arguments) - { - } - } - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)] public class AutoMoqDataAttribute : AutoDataAttribute { @@ -45,7 +31,8 @@ namespace Umbraco.Tests.UnitTests.AutoFixture /// AutoFixture is used to generate concrete types. If the concrete type required some types injected, the /// [Frozen] can be used to ensure the same variable is injected and available as parameter for the test /// - public AutoMoqDataAttribute() : base(() => AutoMockCustomizations.Default) + public AutoMoqDataAttribute() + : base(() => AutoMockCustomizations.Default) { } @@ -58,8 +45,8 @@ namespace Umbraco.Tests.UnitTests.AutoFixture public void Customize(IFixture fixture) { fixture.Customize( - u => u.FromFactory( - (a,b,c) => BackOfficeIdentityUser.CreateNew(new GlobalSettings(),a,b,c))); + u => u.FromFactory( + (a, b, c) => BackOfficeIdentityUser.CreateNew(new GlobalSettings(), a, b, c))); fixture .Customize(new ConstructorCustomization(typeof(UsersController), new GreedyConstructorQuery())) .Customize(new ConstructorCustomization(typeof(InstallController), new GreedyConstructorQuery())) @@ -68,6 +55,7 @@ namespace Umbraco.Tests.UnitTests.AutoFixture .Customize(new ConstructorCustomization(typeof(BackOfficeUserManager), new GreedyConstructorQuery())); fixture.Customize(new AutoMoqCustomization()); + // When requesting an IUserStore ensure we actually uses a IUserLockoutStore fixture.Customize>(cc => cc.FromFactory(() => Mock.Of>())); @@ -93,13 +81,11 @@ namespace Umbraco.Tests.UnitTests.AutoFixture Mock.Of(x => x.Level == RuntimeLevel.Run)))); var connectionStrings = new ConnectionStrings(); - fixture.Customize(x => x.FromFactory(() => connectionStrings )); - + fixture.Customize(x => x.FromFactory(() => connectionStrings)); var httpContextAccessor = new HttpContextAccessor { HttpContext = new DefaultHttpContext() }; fixture.Customize(x => x.FromFactory(() => httpContextAccessor.HttpContext)); fixture.Customize(x => x.FromFactory(() => httpContextAccessor)); - } } } diff --git a/src/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs b/src/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs new file mode 100644 index 0000000000..0c182e6116 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/AutoFixture/InlineAutoMoqDataAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using AutoFixture.NUnit3; + +namespace Umbraco.Tests.UnitTests.AutoFixture +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple = true)] + public class InlineAutoMoqDataAttribute : InlineAutoDataAttribute + { + /// + /// Uses AutoFixture to automatically mock (using Moq) the injected types. E.g when injecting interfaces. + /// AutoFixture is used to generate concrete types. If the concrete type required some types injected, the + /// [Frozen] can be used to ensure the same variable is injected and available as parameter for the test + /// + public InlineAutoMoqDataAttribute(params object[] arguments) + : base(() => AutoMoqDataAttribute.AutoMockCustomizations.Default, arguments) + { + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs index 52bc1880a3..76f96ead05 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/BaseUsingSqlSyntax.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -5,14 +8,12 @@ using Moq; using NPoco; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Logging; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.UnitTests.TestHelpers; -using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.TestHelpers { @@ -23,26 +24,22 @@ namespace Umbraco.Tests.TestHelpers protected ISqlContext SqlContext { get; private set; } - protected Sql Sql() - { - return NPoco.Sql.BuilderFor(SqlContext); - } + protected Sql Sql() => NPoco.Sql.BuilderFor(SqlContext); [SetUp] public virtual void Setup() { - var container = TestHelper.GetServiceCollection(); - var typeLoader = TestHelper.GetMockedTypeLoader(); + IServiceCollection container = TestHelper.GetServiceCollection(); + TypeLoader typeLoader = TestHelper.GetMockedTypeLoader(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); - composition.WithCollectionBuilder() .AddCoreMappers(); - composition.Services.AddUnique(_ => SqlContext); + composition.Services.AddUnique(_ => SqlContext); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); var pocoMappers = new NPoco.MapperCollection { new PocoMapper() }; var pocoDataFactory = new FluentPocoDataFactory((type, iPocoDataFactory) => new PocoDataBuilder(type, pocoMappers).Init()); var sqlSyntax = new SqlServerSyntaxProvider(); diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/CompositionExtensions.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/CompositionExtensions.cs index 5481bfcd76..806878fcc3 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/CompositionExtensions.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/CompositionExtensions.cs @@ -1,7 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; namespace Umbraco.Tests.UnitTests.TestHelpers { diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs index 7889d49192..91d05ba0df 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs @@ -1,16 +1,17 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Moq; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Security; -using Umbraco.Core.Services; using Umbraco.Tests.Common; using Umbraco.Web; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Security; namespace Umbraco.Tests.UnitTests.TestHelpers.Objects { @@ -19,15 +20,31 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects /// public class TestUmbracoContextFactory { - public static IUmbracoContextFactory Create(GlobalSettings globalSettings = null, + public static IUmbracoContextFactory Create( + GlobalSettings globalSettings = null, IUmbracoContextAccessor umbracoContextAccessor = null, IHttpContextAccessor httpContextAccessor = null, IPublishedUrlProvider publishedUrlProvider = null) { - if (globalSettings == null) globalSettings = new GlobalSettings(); - if (umbracoContextAccessor == null) umbracoContextAccessor = new TestUmbracoContextAccessor(); - if (httpContextAccessor == null) httpContextAccessor = Mock.Of(); - if (publishedUrlProvider == null) publishedUrlProvider = Mock.Of(); + if (globalSettings == null) + { + globalSettings = new GlobalSettings(); + } + + if (umbracoContextAccessor == null) + { + umbracoContextAccessor = new TestUmbracoContextAccessor(); + } + + if (httpContextAccessor == null) + { + httpContextAccessor = Mock.Of(); + } + + if (publishedUrlProvider == null) + { + publishedUrlProvider = Mock.Of(); + } var contentCache = new Mock(); var mediaCache = new Mock(); @@ -37,11 +54,10 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects var snapshotService = new Mock(); snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny())).Returns(snapshot.Object); - var hostingEnvironment = Mock.Of(); + IHostingEnvironment hostingEnvironment = Mock.Of(); var backofficeSecurityAccessorMock = new Mock(); backofficeSecurityAccessorMock.Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); - - + var umbracoContextFactory = new UmbracoContextFactory( umbracoContextAccessor, snapshotService.Object, @@ -52,8 +68,7 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects new UriUtility(hostingEnvironment), new AspNetCoreCookieManager(httpContextAccessor), Mock.Of(), - backofficeSecurityAccessorMock.Object - ); + backofficeSecurityAccessorMock.Object); return umbracoContextFactory; } diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs index e7d6eec391..0b0f3120d0 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs @@ -1,20 +1,23 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; -using System.Threading; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Models; using Umbraco.Core.Diagnostics; using Umbraco.Core.Hosting; using Umbraco.Core.IO; @@ -22,21 +25,19 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Net; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; using Umbraco.Core.Strings; +using Umbraco.Infrastructure.Persistence.Mappers; +using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Web; +using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Routing; using File = System.IO.File; -using Microsoft.Extensions.Options; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Web.Common.AspNetCore; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; -using Umbraco.Infrastructure.Persistence.Mappers; namespace Umbraco.Tests.TestHelpers { @@ -45,14 +46,13 @@ namespace Umbraco.Tests.TestHelpers /// public static class TestHelper { - private static readonly TestHelperInternal _testHelperInternal = new TestHelperInternal(); - private static IEmailSender _emailSender; + private static readonly TestHelperInternal s_testHelperInternal = new TestHelperInternal(); private class TestHelperInternal : TestHelperBase { - public TestHelperInternal() : base(typeof(TestHelperInternal).Assembly) + public TestHelperInternal() + : base(typeof(TestHelperInternal).Assembly) { - } public override IDbProviderFactoryCreator DbProviderFactoryCreator { get; } = Mock.Of(); @@ -68,9 +68,9 @@ namespace Umbraco.Tests.TestHelpers { var testPath = TestContext.CurrentContext.TestDirectory.Split("bin")[0]; return new AspNetCoreHostingEnvironment( - Mock.Of>(x=>x.CurrentValue == new HostingSettings()), + Mock.Of>(x => x.CurrentValue == new HostingSettings()), Mock.Of( - x=> + x => x.WebRootPath == "/" && x.ContentRootPath == testPath)); } @@ -82,13 +82,13 @@ namespace Umbraco.Tests.TestHelpers => Mock.Of(); } - public static ITypeFinder GetTypeFinder() => _testHelperInternal.GetTypeFinder(); + public static ITypeFinder GetTypeFinder() => s_testHelperInternal.GetTypeFinder(); - public static TypeLoader GetMockedTypeLoader() => _testHelperInternal.GetMockedTypeLoader(); + public static TypeLoader GetMockedTypeLoader() => s_testHelperInternal.GetMockedTypeLoader(); public static Lazy GetMockSqlContext() { - var sqlContext = Mock.Of(); + ISqlContext sqlContext = Mock.Of(); var syntax = new SqlServerSyntaxProvider(); Mock.Get(sqlContext).Setup(x => x.SqlSyntax).Returns(syntax); return new Lazy(() => sqlContext); @@ -97,50 +97,48 @@ namespace Umbraco.Tests.TestHelpers public static MapperConfigurationStore CreateMaps() => new MapperConfigurationStore(); - //public static Configs GetConfigs() => _testHelperInternal.GetConfigs(); + //// public static Configs GetConfigs() => _testHelperInternal.GetConfigs(); - public static IBackOfficeInfo GetBackOfficeInfo() => _testHelperInternal.GetBackOfficeInfo(); + public static IBackOfficeInfo GetBackOfficeInfo() => s_testHelperInternal.GetBackOfficeInfo(); - // public static IConfigsFactory GetConfigsFactory() => _testHelperInternal.GetConfigsFactory(); + //// public static IConfigsFactory GetConfigsFactory() => _testHelperInternal.GetConfigsFactory(); /// /// Gets the working directory of the test project. /// /// The assembly directory. - public static string WorkingDirectory => _testHelperInternal.WorkingDirectory; + public static string WorkingDirectory => s_testHelperInternal.WorkingDirectory; - public static IShortStringHelper ShortStringHelper => _testHelperInternal.ShortStringHelper; - public static IJsonSerializer JsonSerializer => _testHelperInternal.JsonSerializer; - public static IVariationContextAccessor VariationContextAccessor => _testHelperInternal.VariationContextAccessor; - public static IDbProviderFactoryCreator DbProviderFactoryCreator => _testHelperInternal.DbProviderFactoryCreator; - public static IBulkSqlInsertProvider BulkSqlInsertProvider => _testHelperInternal.BulkSqlInsertProvider; - public static IMarchal Marchal => _testHelperInternal.Marchal; - public static CoreDebugSettings CoreDebugSettings => _testHelperInternal.CoreDebugSettings; + public static IShortStringHelper ShortStringHelper => s_testHelperInternal.ShortStringHelper; + public static IJsonSerializer JsonSerializer => s_testHelperInternal.JsonSerializer; - public static IIOHelper IOHelper => _testHelperInternal.IOHelper; - public static IMainDom MainDom => _testHelperInternal.MainDom; - public static UriUtility UriUtility => _testHelperInternal.UriUtility; + public static IVariationContextAccessor VariationContextAccessor => s_testHelperInternal.VariationContextAccessor; + + public static IDbProviderFactoryCreator DbProviderFactoryCreator => s_testHelperInternal.DbProviderFactoryCreator; + + public static IBulkSqlInsertProvider BulkSqlInsertProvider => s_testHelperInternal.BulkSqlInsertProvider; + + public static IMarchal Marchal => s_testHelperInternal.Marchal; + + public static CoreDebugSettings CoreDebugSettings => s_testHelperInternal.CoreDebugSettings; + + public static IIOHelper IOHelper => s_testHelperInternal.IOHelper; + + public static IMainDom MainDom => s_testHelperInternal.MainDom; + + public static UriUtility UriUtility => s_testHelperInternal.UriUtility; public static IEmailSender EmailSender { get; } = new EmailSender(Options.Create(new GlobalSettings())); - /// /// Some test files are copied to the /bin (/bin/debug) on build, this is a utility to return their physical path based on a virtual path name /// - /// - /// - public static string MapPathForTestFiles(string relativePath) => _testHelperInternal.MapPathForTestFiles(relativePath); + public static string MapPathForTestFiles(string relativePath) => s_testHelperInternal.MapPathForTestFiles(relativePath); - public static void InitializeContentDirectories() - { - CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins }); - } + public static void InitializeContentDirectories() => CreateDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath, Constants.SystemDirectories.AppPlugins }); - public static void CleanContentDirectories() - { - CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath }); - } + public static void CleanContentDirectories() => CleanDirectories(new[] { Constants.SystemDirectories.MvcViews, new GlobalSettings().UmbracoMediaPath }); public static void CreateDirectories(string[] directories) { @@ -148,7 +146,9 @@ namespace Umbraco.Tests.TestHelpers { var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); if (directoryInfo.Exists == false) + { Directory.CreateDirectory(IOHelper.MapPath(directory)); + } } } @@ -156,15 +156,20 @@ namespace Umbraco.Tests.TestHelpers { var preserves = new Dictionary { - { Constants.SystemDirectories.MvcViews, new[] {"dummy.txt"} } + { Constants.SystemDirectories.MvcViews, new[] { "dummy.txt" } } }; + foreach (var directory in directories) { var directoryInfo = new DirectoryInfo(IOHelper.MapPath(directory)); var preserve = preserves.ContainsKey(directory) ? preserves[directory] : null; if (directoryInfo.Exists) - foreach (var x in directoryInfo.GetFiles().Where(x => preserve == null || preserve.Contains(x.Name) == false)) - x.Delete(); + { + foreach (FileInfo fileInfo in directoryInfo.GetFiles().Where(x => preserve == null || preserve.Contains(x.Name) == false)) + { + fileInfo.Delete(); + } + } } } @@ -174,7 +179,9 @@ namespace Umbraco.Tests.TestHelpers var umbracoSettingsFile = Path.Combine(currDir.Parent.Parent.FullName, "config", "umbracoSettings.config"); if (File.Exists(umbracoSettingsFile)) + { File.Delete(umbracoSettingsFile); + } } // TODO: Move to Assertions or AssertHelper @@ -183,17 +190,21 @@ namespace Umbraco.Tests.TestHelpers { const int dateDeltaMilliseconds = 500; // .5s - var properties = expected.GetType().GetProperties(); - foreach (var property in properties) + PropertyInfo[] properties = expected.GetType().GetProperties(); + foreach (PropertyInfo property in properties) { // ignore properties that are attributed with EditorBrowsableState.Never - var att = property.GetCustomAttribute(false); + EditorBrowsableAttribute att = property.GetCustomAttribute(false); if (att != null && att.State == EditorBrowsableState.Never) + { continue; + } // ignore explicitely ignored properties if (ignoreProperties != null && ignoreProperties.Contains(property.Name)) + { continue; + } var actualValue = property.GetValue(actual, null); var expectedValue = property.GetValue(expected, null); @@ -204,31 +215,36 @@ namespace Umbraco.Tests.TestHelpers private static void AssertAreEqual(PropertyInfo property, object expected, object actual, Func sorter = null, int dateDeltaMilliseconds = 0) { - if (!(expected is string) && expected is IEnumerable) + if (!(expected is string) && expected is IEnumerable enumerable) { // sort property collection by alias, not by property ids // on members, built-in properties don't have ids (always zero) if (expected is PropertyCollection) - sorter = e => ((PropertyCollection) e).OrderBy(x => x.Alias); + { + sorter = e => ((PropertyCollection)e).OrderBy(x => x.Alias); + } // compare lists - AssertListsAreEqual(property, (IEnumerable) actual, (IEnumerable) expected, sorter, dateDeltaMilliseconds); + AssertListsAreEqual(property, (IEnumerable)actual, enumerable, sorter, dateDeltaMilliseconds); } else if (expected is DateTime expectedDateTime) { // compare date & time with delta - var actualDateTime = (DateTime) actual; + var actualDateTime = (DateTime)actual; var delta = (actualDateTime - expectedDateTime).TotalMilliseconds; Assert.IsTrue(Math.Abs(delta) <= dateDeltaMilliseconds, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual); } else if (expected is Property expectedProperty) { // compare values - var actualProperty = (Property) actual; - var expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); - var actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); + var actualProperty = (Property)actual; + IPropertyValue[] expectedPropertyValues = expectedProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); + IPropertyValue[] actualPropertyValues = actualProperty.Values.OrderBy(x => x.Culture).ThenBy(x => x.Segment).ToArray(); if (expectedPropertyValues.Length != actualPropertyValues.Length) + { Assert.Fail($"{property.DeclaringType.Name}.{property.Name}: Expected {expectedPropertyValues.Length} but got {actualPropertyValues.Length}."); + } + for (var i = 0; i < expectedPropertyValues.Length; i++) { Assert.AreEqual(expectedPropertyValues[i].EditedValue, actualPropertyValues[i].EditedValue, $"{property.DeclaringType.Name}.{property.Name}: Expected draft value \"{expectedPropertyValues[i].EditedValue}\" but got \"{actualPropertyValues[i].EditedValue}\"."); @@ -238,22 +254,27 @@ namespace Umbraco.Tests.TestHelpers else if (expected is IDataEditor expectedEditor) { Assert.IsInstanceOf(actual); - var actualEditor = (IDataEditor) actual; + var actualEditor = (IDataEditor)actual; Assert.AreEqual(expectedEditor.Alias, actualEditor.Alias); + // what else shall we test? } else { // directly compare values - Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, - expected?.ToString() ?? "", actual?.ToString() ?? ""); + Assert.AreEqual( + expected, + actual, + "Property {0}.{1} does not match. Expected: {2} but was: {3}", + property.DeclaringType.Name, + property.Name, + expected?.ToString() ?? "", + actual?.ToString() ?? ""); } } private static void AssertListsAreEqual(PropertyInfo property, IEnumerable expected, IEnumerable actual, Func sorter = null, int dateDeltaMilliseconds = 0) { - - if (sorter == null) { // this is pretty hackerific but saves us some code to write @@ -261,7 +282,7 @@ namespace Umbraco.Tests.TestHelpers { // semi-generic way of ensuring any collection of IEntity are sorted by Ids for comparison var entities = enumerable.OfType().ToList(); - return entities.Count > 0 ? (IEnumerable) entities.OrderBy(x => x.Id) : entities; + return entities.Count > 0 ? (IEnumerable)entities.OrderBy(x => x.Id) : entities; }; } @@ -269,28 +290,30 @@ namespace Umbraco.Tests.TestHelpers var actualListEx = sorter(actual).Cast().ToList(); if (actualListEx.Count != expectedListEx.Count) + { Assert.Fail("Collection {0}.{1} does not match. Expected IEnumerable containing {2} elements but was IEnumerable containing {3} elements", property.PropertyType.Name, property.Name, expectedListEx.Count, actualListEx.Count); + } for (var i = 0; i < actualListEx.Count; i++) + { AssertAreEqual(property, expectedListEx[i], actualListEx[i], sorter, dateDeltaMilliseconds); + } } - - - public static IUmbracoVersion GetUmbracoVersion() => _testHelperInternal.GetUmbracoVersion(); + public static IUmbracoVersion GetUmbracoVersion() => s_testHelperInternal.GetUmbracoVersion(); public static IServiceCollection GetServiceCollection() => new ServiceCollection().AddLazySupport(); - public static IHostingEnvironment GetHostingEnvironment() => _testHelperInternal.GetHostingEnvironment(); + public static IHostingEnvironment GetHostingEnvironment() => s_testHelperInternal.GetHostingEnvironment(); - public static ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv) => _testHelperInternal.GetLoggingConfiguration(hostingEnv); + public static ILoggingConfiguration GetLoggingConfiguration(IHostingEnvironment hostingEnv) => s_testHelperInternal.GetLoggingConfiguration(hostingEnv); - public static IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _testHelperInternal.GetHostingEnvironmentLifetime(); + public static IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => s_testHelperInternal.GetHostingEnvironmentLifetime(); - public static IIpResolver GetIpResolver() => _testHelperInternal.GetIpResolver(); + public static IIpResolver GetIpResolver() => s_testHelperInternal.GetIpResolver(); - public static IRequestCache GetRequestCache() => _testHelperInternal.GetRequestCache(); + public static IRequestCache GetRequestCache() => s_testHelperInternal.GetRequestCache(); - public static IPublishedUrlProvider GetPublishedUrlProvider() => _testHelperInternal.GetPublishedUrlProvider(); + public static IPublishedUrlProvider GetPublishedUrlProvider() => s_testHelperInternal.GetPublishedUrlProvider(); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Configuration/Models/ConnectionStringsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Configuration/Models/ConnectionStringsTests.cs index 544765ee9c..7d3099eb58 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Configuration/Models/ConnectionStringsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Configuration/Models/ConnectionStringsTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs index 9a16c5b10f..be1e3cc0d8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/AttemptTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; namespace Umbraco.Tests.UnitTests.Umbraco.Core @@ -9,10 +12,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void AttemptIf() { - // just making sure that it is ok to use TryParse as a condition - - int value; - var attempt = Attempt.If(int.TryParse("1234", out value), value); + // Just making sure that it is ok to use TryParse as a condition. + var attempt = Attempt.If(int.TryParse("1234", out int value), value); Assert.IsTrue(attempt.Success); Assert.AreEqual(1234, attempt.Result); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs index 64bdca6437..e681fc6841 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/BackOfficeClaimsPrincipalFactoryTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Security.Claims; @@ -47,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice [Test] public void CreateAsync_When_User_Is_Null_Expect_ArgumentNullException() { - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); Assert.ThrowsAsync(async () => await sut.CreateAsync(null)); } @@ -55,9 +58,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice [Test] public async Task CreateAsync_Should_Create_Principal_With_Umbraco_Identity() { - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); - var claimsPrincipal = await sut.CreateAsync(_testUser); + ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); var umbracoBackOfficeIdentity = claimsPrincipal.Identity as UmbracoBackOfficeIdentity; Assert.IsNotNull(umbracoBackOfficeIdentity); @@ -67,9 +70,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice [TestCase(ClaimTypes.Name, TestUserName)] public async Task CreateAsync_Should_Include_Claim(string expectedClaimType, object expectedClaimValue) { - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); - var claimsPrincipal = await sut.CreateAsync(_testUser); + ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue.ToString())); Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue.ToString())); @@ -84,9 +87,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice _mockUserManager.Setup(x => x.SupportsUserSecurityStamp).Returns(true); _mockUserManager.Setup(x => x.GetSecurityStampAsync(_testUser)).ReturnsAsync(_testUser.SecurityStamp); - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); - var claimsPrincipal = await sut.CreateAsync(_testUser); + ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue)); @@ -100,11 +103,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice _testUser.Roles.Add(new IdentityUserRole { RoleId = expectedClaimValue }); _mockUserManager.Setup(x => x.SupportsUserRole).Returns(true); - _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] {expectedClaimValue}); + _mockUserManager.Setup(x => x.GetRolesAsync(_testUser)).ReturnsAsync(new[] { expectedClaimValue }); - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); - var claimsPrincipal = await sut.CreateAsync(_testUser); + ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue)); } @@ -115,14 +118,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string expectedClaimType = "custom"; const string expectedClaimValue = "val"; - _testUser.Claims.Add(new IdentityUserClaim { ClaimType = expectedClaimType, ClaimValue = expectedClaimValue}); + _testUser.Claims.Add(new IdentityUserClaim { ClaimType = expectedClaimType, ClaimValue = expectedClaimValue }); _mockUserManager.Setup(x => x.SupportsUserClaim).Returns(true); _mockUserManager.Setup(x => x.GetClaimsAsync(_testUser)).ReturnsAsync( - new List {new Claim(expectedClaimType, expectedClaimValue)}); + new List { new Claim(expectedClaimType, expectedClaimValue) }); - var sut = CreateSut(); + BackOfficeClaimsPrincipalFactory sut = CreateSut(); - var claimsPrincipal = await sut.CreateAsync(_testUser); + ClaimsPrincipal claimsPrincipal = await sut.CreateAsync(_testUser); Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue)); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/IdentityExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/IdentityExtensionsTests.cs index 0c8b469f02..3b2d0391e2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/IdentityExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/IdentityExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Identity; using NUnit.Framework; @@ -20,9 +23,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice public void ToErrorMessage_When_Single_Error_Expect_Error_Description() { const string expectedError = "invalid something"; - var errors = new List {new IdentityError {Code = "1", Description = expectedError}}; + var errors = new List { new IdentityError { Code = "1", Description = expectedError } }; - var errorMessage = errors.ToErrorMessage(); + string errorMessage = errors.ToErrorMessage(); Assert.AreEqual(expectedError, errorMessage); } @@ -34,11 +37,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice const string error2 = "invalid something else"; var errors = new List { - new IdentityError {Code = "1", Description = error1}, - new IdentityError {Code = "2", Description = error2} + new IdentityError { Code = "1", Description = error1 }, + new IdentityError { Code = "2", Description = error2 } }; - var errorMessage = errors.ToErrorMessage(); + string errorMessage = errors.ToErrorMessage(); Assert.AreEqual($"{error1}, {error2}", errorMessage); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs index 02ff01ff3b..f4ea348892 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/NopLookupNormalizerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using NUnit.Framework; using Umbraco.Core.Security; @@ -29,6 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice Assert.AreEqual(name, normalizedName); } + [Test] [TestCase(null)] [TestCase("")] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs index 79a9456643..35e143277a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentityTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Linq; using System.Security.Claims; @@ -10,7 +13,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice [TestFixture] public class UmbracoBackOfficeIdentityTests { - public const string TestIssuer = "TestIssuer"; [Test] @@ -19,10 +21,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice var securityStamp = Guid.NewGuid().ToString(); var claimsIdentity = new ClaimsIdentity(new[] { - //This is the id that 'identity' uses to check for the user id + // This is the id that 'identity' uses to check for the user id. new Claim(ClaimTypes.NameIdentifier, "1234", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), - //This is the id that 'identity' uses to check for the username + + // This is the id that 'identity' uses to check for the username. new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer), + new Claim(ClaimTypes.GivenName, "hello world", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(Constants.Security.StartContentNodeIdClaimType, "-1", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), new Claim(Constants.Security.StartMediaNodeIdClaimType, "5543", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), @@ -34,18 +38,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice new Claim(Constants.Security.SecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer), }); - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity)) + if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out UmbracoBackOfficeIdentity backofficeIdentity)) + { Assert.Fail(); + } Assert.IsNull(backofficeIdentity.Actor); Assert.AreEqual(1234, backofficeIdentity.Id); - //Assert.AreEqual(sessionId, backofficeIdentity.SessionId); + //// Assert.AreEqual(sessionId, backofficeIdentity.SessionId); Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp); Assert.AreEqual("testing", backofficeIdentity.Username); Assert.AreEqual("hello world", backofficeIdentity.RealName); Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length); Assert.IsTrue(backofficeIdentity.StartMediaNodes.UnsortedSequenceEqual(new[] { 5543, 5555 })); - Assert.IsTrue(new[] {"content", "media"}.SequenceEqual(backofficeIdentity.AllowedApplications)); + Assert.IsTrue(new[] { "content", "media" }.SequenceEqual(backofficeIdentity.AllowedApplications)); Assert.AreEqual("en-us", backofficeIdentity.Culture); Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles)); @@ -62,7 +68,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice }); if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _)) + { Assert.Fail(); + } Assert.Pass(); } @@ -72,8 +80,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice { var claimsIdentity = new ClaimsIdentity(new[] { - //null or empty - new Claim(ClaimTypes.NameIdentifier, "", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), + // Null or empty + new Claim(ClaimTypes.NameIdentifier, string.Empty, ClaimValueTypes.Integer32, TestIssuer, TestIssuer), new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(ClaimTypes.GivenName, "hello world", ClaimValueTypes.String, TestIssuer, TestIssuer), new Claim(Constants.Security.StartContentNodeIdClaimType, "-1", ClaimValueTypes.Integer32, TestIssuer, TestIssuer), @@ -85,12 +93,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice }); if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _)) + { Assert.Fail(); + } Assert.Pass(); } - [Test] public void Create_With_Claims_And_User_Data() { @@ -102,14 +111,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice new Claim("TestClaim1", "test", ClaimValueTypes.Integer32, TestIssuer, TestIssuer) }); - var identity = new UmbracoBackOfficeIdentity(claimsIdentity, - "1234", "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" }); + var identity = new UmbracoBackOfficeIdentity( + claimsIdentity, + "1234", + "testing", + "hello world", + new[] { 654 }, + new[] { 654 }, + "en-us", + securityStamp, + new[] { "content", "media" }, + new[] { "admin" }); Assert.AreEqual(12, identity.Claims.Count()); Assert.IsNull(identity.Actor); } - [Test] public void Clone() { @@ -121,11 +138,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice // this will be filtered out during cloning identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test")); - var cloned = identity.Clone(); + ClaimsIdentity cloned = identity.Clone(); Assert.IsNull(cloned.Actor); Assert.AreEqual(10, cloned.Claims.Count()); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/AppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/AppCacheTests.cs index a30e254235..fe5d1ea819 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/AppCacheTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core.Cache; @@ -14,21 +18,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [SetUp] public virtual void Setup() { - } [TearDown] - public virtual void TearDown() - { - AppCache.Clear(); - } + public virtual void TearDown() => AppCache.Clear(); [Test] public void Throws_On_Reentry() { // don't run for DictionaryAppCache - not making sense - if (GetType() == typeof (DictionaryAppCacheTests)) + if (GetType() == typeof(DictionaryAppCacheTests)) + { Assert.Ignore("Do not run for DictionaryAppCache."); + } Exception exception = null; var result = AppCache.Get("blah", () => @@ -41,6 +43,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache { exception = e; } + return "value"; }); Assert.IsNotNull(exception); @@ -61,7 +64,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache throw new Exception("Do not cache this"); }); } - catch (Exception){} + catch (Exception) + { + } try { @@ -71,10 +76,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache throw new Exception("Do not cache this"); }); } - catch (Exception){} + catch (Exception) + { + } Assert.Greater(counter, 1); - } [Test] @@ -87,17 +93,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache result = AppCache.Get("Blah", () => { counter++; - return ""; + return string.Empty; }); result = AppCache.Get("Blah", () => { counter++; - return ""; + return string.Empty; }); Assert.AreEqual(1, counter); - } [Test] @@ -114,7 +119,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache Assert.AreEqual(4, GetTotalItemCount); - var result = AppCache.SearchByKey("Tes"); + IEnumerable result = AppCache.SearchByKey("Tes"); Assert.AreEqual(3, result.Count()); } @@ -228,7 +233,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache Assert.AreEqual(4, GetTotalItemCount); - //Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); + ////Provider.ClearCacheObjectTypes("umbraco.MacroCacheContent"); AppCache.ClearOfType(); Assert.AreEqual(1, GetTotalItemCount); @@ -253,7 +258,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache Assert.AreEqual(1, GetTotalItemCount); } - //just used for these tests + // Just used for these tests private class MacroCacheContent { } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DeepCloneAppCacheTests.cs index cbcd164b62..f653cf50f1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DeepCloneAppCacheTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; using NUnit.Framework; @@ -40,10 +43,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache new TestClone() }; - var val = _provider.GetCacheItem("test", () => original); + DeepCloneableList val = _provider.GetCacheItem("test", () => original); Assert.AreEqual(original.Count, val.Count); - foreach (var item in val) + foreach (TestClone item in val) { Assert.IsTrue(item.IsClone); } @@ -84,21 +87,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache { Debug.Print("get" + i); if (i < 3) + { throw new Exception("fail"); + } + return "succ" + i; } private class TestClass : BeingDirtyBase, IDeepCloneable { - public TestClass() - { - CloneId = Guid.NewGuid(); - } + public TestClass() => CloneId = Guid.NewGuid(); private string _name; + public string Name { get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs index a8592356e9..9cb6af72fc 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DefaultCachePolicyTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Moq; using NUnit.Framework; @@ -29,10 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var isCached = false; var cache = new Mock(); cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback(() => - { - isCached = true; - }); + .Callback(() => isCached = true); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); @@ -58,15 +58,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cached = new List(); var cache = new Mock(); cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string cacheKey, Func o, TimeSpan? t, bool b, string[] s) => - { - cached.Add(cacheKey); - }); - cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] {}); + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, string[] s) => cached.Add(cacheKey)); + cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - AuditItem[] unused = defaultPolicy.GetAll(new object[] {}, ids => new[] + AuditItem[] unused = defaultPolicy.GetAll(new object[] { }, ids => new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -87,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - AuditItem[] found = defaultPolicy.GetAll(new object[] {}, ids => new[] { (AuditItem)null }); + AuditItem[] found = defaultPolicy.GetAll(new object[] { }, ids => new[] { (AuditItem)null }); Assert.AreEqual(2, found.Length); } @@ -97,10 +94,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cacheCleared = false; var cache = new Mock(); cache.Setup(x => x.Clear(It.IsAny())) - .Callback(() => - { - cacheCleared = true; - }); + .Callback(() => cacheCleared = true); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); try @@ -109,7 +103,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache } catch { - //we need this catch or nunit throw up + // We need this catch or nunit throws up } finally { @@ -123,10 +117,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cacheCleared = false; var cache = new Mock(); cache.Setup(x => x.Clear(It.IsAny())) - .Callback(() => - { - cacheCleared = true; - }); + .Callback(() => cacheCleared = true); var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); try @@ -135,7 +126,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache } catch { - //we need this catch or nunit throw up + // We need this catch or nunit throws up } finally { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DictionaryAppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DictionaryAppCacheTests.cs index 91d71d3144..13c0f16d0c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DictionaryAppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DictionaryAppCacheTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Cache; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache 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 0d5b1edb31..1023e47dfa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCache/DistributedCacheTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -16,15 +19,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache private global::Umbraco.Web.Cache.DistributedCache _distributedCache; private IServerRegistrar ServerRegistrar { get; set; } + private TestServerMessenger ServerMessenger { get; set; } [SetUp] public void Setup() { - ServerRegistrar = new TestServerRegistrar(); - ServerMessenger = new TestServerMessenger(); + ServerRegistrar = new TestServerRegistrar(); + ServerMessenger = new TestServerMessenger(); - var cacheRefresherCollection = new CacheRefresherCollection(new [] + var cacheRefresherCollection = new CacheRefresherCollection(new[] { new TestCacheRefresher() }); @@ -51,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache _distributedCache.Refresh( Guid.Parse("E0F452CB-DCB2-4E84-B5A5-4F01744C5C73"), x => x.Id, - new TestObjectWithId{Id = i}); + new TestObjectWithId { Id = i }); } Assert.AreEqual(10, ServerMessenger.IntIdsRefreshed.Count); @@ -90,8 +94,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache Assert.AreEqual(13, ServerMessenger.CountOfFullRefreshes); } - #region Internal test classes - internal class TestObjectWithId { public int Id { get; set; } @@ -105,18 +107,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache public string Name => "Test Cache Refresher"; - public void RefreshAll() { } + public void RefreshAll() + { + } - public void Refresh(int id) { } + public void Refresh(int id) + { + } - public void Remove(int id) { } + public void Remove(int id) + { + } - public void Refresh(Guid id) { } + public void Refresh(Guid id) + { + } } internal class TestServerMessenger : IServerMessenger { - //used for tests + // Used for tests public List IntIdsRefreshed = new List(); public List GuidIdsRefreshed = new List(); public List IntIdsRemoved = new List(); @@ -129,50 +139,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache // doing nothing } - public void PerformRefresh(ICacheRefresher refresher, string jsonPayload) - { - PayloadsRefreshed.Add(jsonPayload); - } + 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 PerformRefresh(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 PerformRefresh(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, string jsonPayload) => PayloadsRemoved.Add(jsonPayload); - public void PerformRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances) - { - IntIdsRemoved.AddRange(instances.Select(getNumericId)); - } + public void PerformRemove(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 PerformRemove(ICacheRefresher refresher, params int[] numericIds) => IntIdsRemoved.AddRange(numericIds); - public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) - { - IntIdsRefreshed.AddRange(numericIds); - } + public void PerformRefresh(ICacheRefresher refresher, params int[] numericIds) => IntIdsRefreshed.AddRange(numericIds); - public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) - { - GuidIdsRefreshed.AddRange(guidIds); - } + public void PerformRefresh(ICacheRefresher refresher, params Guid[] guidIds) => GuidIdsRefreshed.AddRange(guidIds); - public void PerformRefreshAll(ICacheRefresher refresher) - { - CountOfFullRefreshes++; - } + public void PerformRefreshAll(ICacheRefresher refresher) => CountOfFullRefreshes++; } internal class TestServerRegistrar : IServerRegistrar @@ -182,22 +165,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache.DistributedCache new TestServerAddress("localhost") }; - public ServerRole GetCurrentServerRole() - { - throw new NotImplementedException(); - } + public ServerRole GetCurrentServerRole() => throw new NotImplementedException(); } public class TestServerAddress : IServerAddress { - public TestServerAddress(string address) - { - ServerAddress = address; - } + public TestServerAddress(string address) => ServerAddress = address; public string ServerAddress { get; private set; } } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs index e904f9104e..87b61502c5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/FullDataSetCachePolicyTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -29,7 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void Caches_Single() { - var getAll = new[] + AuditItem[] getAll = new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -38,21 +41,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var isCached = false; var cache = new Mock(); cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback(() => - { - isCached = true; - }); + .Callback(() => isCached = true); var policy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var unused = policy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => getAll); + AuditItem unused = policy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => getAll); Assert.IsTrue(isCached); } [Test] public void Get_Single_From_Cache() { - var getAll = new[] + AuditItem[] getAll = new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -63,14 +63,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var found = defaultPolicy.Get(1, id => null, ids => getAll); + AuditItem found = defaultPolicy.Get(1, id => null, ids => getAll); Assert.IsNotNull(found); } [Test] public void Get_All_Caches_Empty_List() { - var getAll = new AuditItem[] {}; + var getAll = new AuditItem[] { }; var cached = new List(); @@ -84,20 +84,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache list = o() as IList; }); - cache.Setup(x => x.Get(It.IsAny())).Returns(() => - { - //return null if this is the first pass - return cached.Any() ? new DeepCloneableList(ListCloneBehavior.CloneOnce) : null; - }); + + // Return null if this is the first pass. + cache.Setup(x => x.Get(It.IsAny())) + .Returns(() => cached.Any() ? new DeepCloneableList(ListCloneBehavior.CloneOnce) : null); var policy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var found = policy.GetAll(new object[] {}, ids => getAll); + AuditItem[] found = policy.GetAll(new object[] { }, ids => getAll); Assert.AreEqual(1, cached.Count); Assert.IsNotNull(list); - //Do it again, ensure that its coming from the cache! + // Do it again, ensure that its coming from the cache! policy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); found = policy.GetAll(new object[] { }, ids => getAll); @@ -109,7 +108,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void Get_All_Caches_As_Single_List() { - var getAll = new[] + AuditItem[] getAll = new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -130,7 +129,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var found = defaultPolicy.GetAll(new object[] { }, ids => getAll); + AuditItem[] found = defaultPolicy.GetAll(new object[] { }, ids => getAll); Assert.AreEqual(1, cached.Count); Assert.IsNotNull(list); @@ -139,7 +138,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void Get_All_Without_Ids_From_Cache() { - var getAll = new[] { (AuditItem)null }; + AuditItem[] getAll = new[] { (AuditItem)null }; var cache = new Mock(); @@ -151,14 +150,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); - var found = defaultPolicy.GetAll(new object[] { }, ids => getAll); + AuditItem[] found = defaultPolicy.GetAll(new object[] { }, ids => getAll); Assert.AreEqual(2, found.Length); } [Test] public void If_CreateOrUpdate_Throws_Cache_Is_Removed() { - var getAll = new[] + AuditItem[] getAll = new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -167,19 +166,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cacheCleared = false; var cache = new Mock(); cache.Setup(x => x.Clear(It.IsAny())) - .Callback(() => - { - cacheCleared = true; - }); + .Callback(() => cacheCleared = true); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); try { - defaultPolicy.Update(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => { throw new Exception("blah!"); }); + defaultPolicy.Update(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => throw new Exception("blah!")); } catch { - //we need this catch or nunit throw up + // We need this catch or nunit throws up. } finally { @@ -190,7 +186,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void If_Removes_Throws_Cache_Is_Removed() { - var getAll = new[] + AuditItem[] getAll = new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -199,19 +195,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cacheCleared = false; var cache = new Mock(); cache.Setup(x => x.Clear(It.IsAny())) - .Callback(() => - { - cacheCleared = true; - }); + .Callback(() => cacheCleared = true); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, DefaultAccessor, item => item.Id, false); try { - defaultPolicy.Delete(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => { throw new Exception("blah!"); }); + defaultPolicy.Delete(new AuditItem(1, AuditType.Copy, 123, "test", "blah"), item => throw new Exception("blah!")); } catch { - //we need this catch or nunit throw up + // We need this catch or nunit throws up. } finally { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/HttpRequestAppCacheTests.cs index 02a10fcff4..ac8ded80e3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/HttpRequestAppCacheTests.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Http; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Http; using NUnit.Framework; using Umbraco.Core.Cache; @@ -13,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache public override void Setup() { base.Setup(); - _httpContext = new DefaultHttpContext();; + _httpContext = new DefaultHttpContext(); _appCache = new HttpRequestAppCache(() => _httpContext.Items); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ObjectAppCacheTests.cs index 51639d7c49..255c76ff46 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/ObjectAppCacheTests.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using NUnit.Framework; using Umbraco.Core.Cache; @@ -9,10 +12,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache { private ObjectCacheAppCache _provider; - protected override int GetTotalItemCount - { - get { return _provider.MemoryCache.Count(); } - } + protected override int GetTotalItemCount => _provider.MemoryCache.Count(); public override void Setup() { @@ -20,14 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache _provider = new ObjectCacheAppCache(); } - internal override IAppCache AppCache - { - get { return _provider; } - } + internal override IAppCache AppCache => _provider; - internal override IAppPolicyCache AppPolicyCache - { - get { return _provider; } - } + internal override IAppPolicyCache AppPolicyCache => _provider; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs index 59d2f010d2..754dc8b830 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Services.Changes; @@ -7,14 +10,14 @@ using Umbraco.Web.Cache; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache { [TestFixture] - public class RefreshersTests + public class RefresherTests { [Test] public void MediaCacheRefresherCanDeserializeJsonPayload() { - var source = new[] { new MediaCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) }; + MediaCacheRefresher.JsonPayload[] source = new[] { new MediaCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) }; var json = JsonConvert.SerializeObject(source); - var payload = JsonConvert.DeserializeObject(json); + MediaCacheRefresher.JsonPayload[] payload = JsonConvert.DeserializeObject(json); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -23,9 +26,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void ContentCacheRefresherCanDeserializeJsonPayload() { - var source = new[] { new ContentCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) }; + ContentCacheRefresher.JsonPayload[] source = new[] { new ContentCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None) }; var json = JsonConvert.SerializeObject(source); - var payload = JsonConvert.DeserializeObject(json); + ContentCacheRefresher.JsonPayload[] payload = JsonConvert.DeserializeObject(json); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -34,9 +37,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void ContentTypeCacheRefresherCanDeserializeJsonPayload() { - var source = new[] { new ContentTypeCacheRefresher.JsonPayload("xxx", 1234, ContentTypeChangeTypes.None) }; + ContentTypeCacheRefresher.JsonPayload[] source = new[] { new ContentTypeCacheRefresher.JsonPayload("xxx", 1234, ContentTypeChangeTypes.None) }; var json = JsonConvert.SerializeObject(source); - var payload = JsonConvert.DeserializeObject(json); + ContentTypeCacheRefresher.JsonPayload[] payload = JsonConvert.DeserializeObject(json); Assert.AreEqual(source[0].ItemType, payload[0].ItemType); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -45,9 +48,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void DataTypeCacheRefresherCanDeserializeJsonPayload() { - var source = new[] { new DataTypeCacheRefresher.JsonPayload(1234, Guid.NewGuid(), true) }; + DataTypeCacheRefresher.JsonPayload[] source = new[] { new DataTypeCacheRefresher.JsonPayload(1234, Guid.NewGuid(), true) }; var json = JsonConvert.SerializeObject(source); - var payload = JsonConvert.DeserializeObject(json); + DataTypeCacheRefresher.JsonPayload[] payload = JsonConvert.DeserializeObject(json); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].Removed, payload[0].Removed); @@ -56,9 +59,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache [Test] public void DomainCacheRefresherCanDeserializeJsonPayload() { - var source = new[] { new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) }; + DomainCacheRefresher.JsonPayload[] source = new[] { new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) }; var json = JsonConvert.SerializeObject(source); - var payload = JsonConvert.DeserializeObject(json); + DomainCacheRefresher.JsonPayload[] payload = JsonConvert.DeserializeObject(json); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeType, payload[0].ChangeType); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs index 33a71b1044..41214bdb58 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RuntimeAppCacheTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Threading; using NUnit.Framework; using Umbraco.Core.Cache; @@ -18,7 +21,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache Assert.AreEqual(now, AppCache.GetCacheItem("DateTimeTest")); Assert.AreEqual(now, AppCache.GetCacheItem("DateTimeTest")); - Thread.Sleep(300); //sleep longer than the cache expiration + Thread.Sleep(300); // sleep longer than the cache expiration Assert.AreEqual(default(DateTime), AppCache.GetCacheItem("DateTimeTest")); Assert.AreEqual(null, AppCache.GetCacheItem("DateTimeTest")); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs index 323b83699c..ffd5c90ce3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Moq; using NUnit.Framework; @@ -29,15 +32,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var cached = new List(); var cache = new Mock(); cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string cacheKey, Func o, TimeSpan? t, bool b, string[] s) => - { - cached.Add(cacheKey); - }); + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, string[] s) => cached.Add(cacheKey)); cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - var unused = defaultPolicy.GetAll(new object[] { }, ids => new[] + AuditItem[] unused = defaultPolicy.GetAll(new object[] { }, ids => new[] { new AuditItem(1, AuditType.Copy, 123, "test", "blah"), new AuditItem(2, AuditType.Copy, 123, "test", "blah2") @@ -52,14 +52,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Cache var isCached = false; var cache = new Mock(); cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback(() => - { - isCached = true; - }); + .Callback(() => isCached = true); var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); - var unused = defaultPolicy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => null); + AuditItem unused = defaultPolicy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => null); Assert.IsTrue(isCached); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs index 76f928ca46..7125e13e90 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ClaimsIdentityExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Security.Claims; using NUnit.Framework; @@ -27,7 +30,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void FindFirstValue_WhenMatchingClaimPresent_ExpectCorrectValue() { var expectedClaim = new Claim("test", "123", "string", "Umbraco"); - var identity = new ClaimsIdentity(new List {expectedClaim}); + var identity = new ClaimsIdentity(new List { expectedClaim }); var value = identity.FindFirstValue("test"); @@ -39,7 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var expectedClaim = new Claim("test", "123", "string", "Umbraco"); var dupeClaim = new Claim(expectedClaim.Type, Guid.NewGuid().ToString()); - var identity = new ClaimsIdentity(new List {expectedClaim, dupeClaim}); + var identity = new ClaimsIdentity(new List { expectedClaim, dupeClaim }); var value = identity.FindFirstValue("test"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs index bcfe142d8d..57237d907e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/DeepCloneableListTests.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using NUnit.Framework; using Umbraco.Core.Collections; using Umbraco.Tests.Common; @@ -11,33 +14,35 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections [Test] public void Deep_Clones_Each_Item_Once() { - var list = new DeepCloneableList(ListCloneBehavior.CloneOnce); - list.Add(new TestClone()); - list.Add(new TestClone()); - list.Add(new TestClone()); + var list = new DeepCloneableList(ListCloneBehavior.CloneOnce) + { + new TestClone(), + new TestClone(), + new TestClone() + }; var cloned = list.DeepClone() as DeepCloneableList; - //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + // Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) Assert.IsTrue(list.SequenceEqual(cloned)); - //Test that each instance in the list is not the same one - foreach (var item in list) + // Test that each instance in the list is not the same one + foreach (TestClone item in list) { - var clone = cloned.Single(x => x.Id == item.Id); + TestClone clone = cloned.Single(x => x.Id == item.Id); Assert.AreNotSame(item, clone); } - //clone again from the clone - since it's clone once the items should be the same + // Clone again from the clone - since it's clone once the items should be the same var cloned2 = cloned.DeepClone() as DeepCloneableList; - //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + // Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) Assert.IsTrue(cloned.SequenceEqual(cloned2)); - //Test that each instance in the list is the same one - foreach (var item in cloned) + // Test that each instance in the list is the same one + foreach (TestClone item in cloned) { - var clone = cloned2.Single(x => x.Id == item.Id); + TestClone clone = cloned2.Single(x => x.Id == item.Id); Assert.AreSame(item, clone); } } @@ -45,10 +50,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections [Test] public void Deep_Clones_All_Elements() { - var list = new DeepCloneableList(ListCloneBehavior.Always); - list.Add(new TestClone()); - list.Add(new TestClone()); - list.Add(new TestClone()); + var list = new DeepCloneableList(ListCloneBehavior.Always) + { + new TestClone(), + new TestClone(), + new TestClone() + }; var cloned = list.DeepClone() as DeepCloneableList; @@ -60,14 +67,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections [Test] public void Clones_Each_Item() { - var list = new DeepCloneableList(ListCloneBehavior.Always); - list.Add(new TestClone()); - list.Add(new TestClone()); - list.Add(new TestClone()); + var list = new DeepCloneableList(ListCloneBehavior.Always) + { + new TestClone(), + new TestClone(), + new TestClone() + }; - var cloned = (DeepCloneableList) list.DeepClone(); + var cloned = (DeepCloneableList)list.DeepClone(); - foreach (var item in cloned) + foreach (TestClone item in cloned) { Assert.IsTrue(item.IsClone); } @@ -76,20 +85,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections [Test] public void Cloned_Sequence_Equals() { - var list = new DeepCloneableList(ListCloneBehavior.Always); - list.Add(new TestClone()); - list.Add(new TestClone()); - list.Add(new TestClone()); + var list = new DeepCloneableList(ListCloneBehavior.Always) + { + new TestClone(), + new TestClone(), + new TestClone() + }; - var cloned = (DeepCloneableList) list.DeepClone(); + var cloned = (DeepCloneableList)list.DeepClone(); - //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + // Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) Assert.IsTrue(list.SequenceEqual(cloned)); - //Test that each instance in the list is not the same one - foreach (var item in list) + // Test that each instance in the list is not the same one + foreach (TestClone item in list) { - var clone = cloned.Single(x => x.Id == item.Id); + TestClone clone = cloned.Single(x => x.Id == item.Id); Assert.AreNotSame(item, clone); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs index df7f004884..06d935cfd8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Collections/OrderedHashSetTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core.Collections; @@ -11,8 +14,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections public void Keeps_Last() { var list = new OrderedHashSet(keepOldest: false); - var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")}; - foreach (var item in items) + MyClass[] items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") }; + foreach (MyClass item in items) { list.Add(item); } @@ -26,8 +29,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections public void Keeps_First() { var list = new OrderedHashSet(keepOldest: true); - var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")}; - foreach (var item in items) + MyClass[] items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") }; + foreach (MyClass item in items) { list.Add(item); } @@ -45,37 +48,49 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections } public string Name { get; } + public Guid Id { get; } public bool Equals(MyClass other) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if (other is null) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + return string.Equals(Name, other.Name); } public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MyClass) obj); + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((MyClass)obj); } - public override int GetHashCode() - { - return Name.GetHashCode(); - } + public override int GetHashCode() => Name.GetHashCode(); - public static bool operator ==(MyClass left, MyClass right) - { - return Equals(left, right); - } + public static bool operator ==(MyClass left, MyClass right) => Equals(left, right); - public static bool operator !=(MyClass left, MyClass right) - { - return Equals(left, right) == false; - } + public static bool operator !=(MyClass left, MyClass right) => Equals(left, right) == false; } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 6d51603502..dcedfb83d4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.IO; @@ -35,17 +38,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components private static IServiceProvider MockFactory(Action> setup = null) { // FIXME: use IUmbracoDatabaseFactory vs UmbracoDatabaseFactory, clean it all up! - var mock = new Mock(); - var loggerFactory = NullLoggerFactory.Instance; - var logger = loggerFactory.CreateLogger("GenericLogger"); - var typeFinder = TestHelper.GetTypeFinder(); + NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; + ILogger logger = loggerFactory.CreateLogger("GenericLogger"); + ITypeFinder typeFinder = TestHelper.GetTypeFinder(); var globalSettings = new GlobalSettings(); var connectionStrings = new ConnectionStrings(); var f = new UmbracoDatabaseFactory(loggerFactory.CreateLogger(), loggerFactory, Options.Create(globalSettings), Options.Create(connectionStrings), new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, loggerFactory.CreateLogger(), loggerFactory, IOHelper, Options.Create(globalSettings), Mock.Of()); var coreDebug = new CoreDebugSettings(); - var mediaFileSystem = Mock.Of(); + IMediaFileSystem mediaFileSystem = Mock.Of(); var p = new ScopeProvider(f, fs, Options.Create(coreDebug), mediaFileSystem, loggerFactory.CreateLogger(), loggerFactory, typeFinder, NoAppCache.Instance); mock.Setup(x => x.GetService(typeof(ILogger))).Returns(logger); @@ -59,57 +61,63 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components return mock.Object; } - private static IServiceCollection MockRegister() - { - // Why mock something you can spin up an instance of? - return new ServiceCollection(); // Mock.Of(); - } - - private static TypeLoader MockTypeLoader() - { - var ioHelper = IOHelper; - return new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); - } - + private static IServiceCollection MockRegister() => new ServiceCollection(); + private static TypeLoader MockTypeLoader() => new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); [Test] public void Boot1A() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); + // 2 is Core and requires 4 // 3 is User // => reorder components accordingly composers.Compose(); AssertTypeArray(TypeArray(), Composed); - var factory = MockFactory(m => + IServiceProvider factory = MockFactory(m => { m.Setup(x => x.GetService(It.Is(t => t == typeof(ISomeResource)))).Returns(() => new SomeResource()); m.Setup(x => x.GetService(It.IsAny())).Returns((type) => { if (type == typeof(Composer1)) + { return new Composer1(); + } + if (type == typeof(Composer5)) + { return new Composer5(); + } + if (type == typeof(Component5)) + { return new Component5(new SomeResource()); + } + if (type == typeof(IProfilingLogger)) + { return new ProfilingLogger(Mock.Of>(), Mock.Of()); + } + if (type == typeof(ILogger)) + { return Mock.Of>(); + } + throw new NotSupportedException(type.FullName); }); }); - var builder = composition.WithCollectionBuilder(); + ComponentCollectionBuilder builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); - var components = builder.CreateCollection(factory); + ComponentCollection components = builder.CreateCollection(factory); Assert.IsEmpty(components); components.Initialize(); @@ -121,12 +129,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Boot1B() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); + // 2 is Core and requires 4 // 3 is User - stays with RuntimeLevel.Run // => reorder components accordingly @@ -137,12 +146,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Boot2() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); + // 21 is required by 20 // => reorder components accordingly composers.Compose(); @@ -152,12 +162,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Boot3() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); + // i23 requires 22 // 24, 25 implement i23 // 25 required by i23 @@ -169,10 +180,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void BrokenRequire() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); try @@ -192,12 +203,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void BrokenRequired() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = TypeArray(); + Type[] types = TypeArray(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); + // 2 is Core and requires 4 // 13 is required by 1 // 1 is missing @@ -213,42 +225,63 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components Initialized.Clear(); Terminated.Clear(); - var register = MockRegister(); - var typeLoader = MockTypeLoader(); - var factory = MockFactory(m => + IServiceCollection register = MockRegister(); + TypeLoader typeLoader = MockTypeLoader(); + IServiceProvider factory = MockFactory(m => { m.Setup(x => x.GetService(It.Is(t => t == typeof(ISomeResource)))).Returns(() => new SomeResource()); m.Setup(x => x.GetService(It.IsAny())).Returns((type) => { if (type == typeof(Composer1)) + { return new Composer1(); + } + if (type == typeof(Composer5)) + { return new Composer5(); + } + if (type == typeof(Composer5a)) + { return new Composer5a(); + } + if (type == typeof(Component5)) + { return new Component5(new SomeResource()); + } + if (type == typeof(Component5a)) + { return new Component5a(); + } + if (type == typeof(IProfilingLogger)) + { return new ProfilingLogger(Mock.Of>(), Mock.Of()); + } + if (type == typeof(ILogger)) + { return Mock.Of>(); + } + throw new NotSupportedException(type.FullName); }); }); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; + Type[] types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) }; var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Assert.IsEmpty(Composed); composers.Compose(); AssertTypeArray(TypeArray(), Composed); - var builder = composition.WithCollectionBuilder(); + ComponentCollectionBuilder builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); - var components = builder.CreateCollection(factory); + ComponentCollection components = builder.CreateCollection(factory); Assert.IsEmpty(Initialized); components.Initialize(); @@ -262,10 +295,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Requires1() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; + Type[] types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) }; var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); composers.Compose(); @@ -277,10 +310,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Requires2A() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + Type[] types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); composers.Compose(); @@ -292,18 +325,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void Requires2B() { - var register = MockRegister(); - var typeLoader = MockTypeLoader(); - var factory = MockFactory(); + IServiceCollection register = MockRegister(); + TypeLoader typeLoader = MockTypeLoader(); + IServiceProvider factory = MockFactory(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; + Type[] types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) }; var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); composers.Compose(); - var builder = composition.WithCollectionBuilder(); + ComponentCollectionBuilder builder = composition.WithCollectionBuilder(); builder.RegisterWith(register); - var components = builder.CreateCollection(factory); + ComponentCollection components = builder.CreateCollection(factory); Assert.AreEqual(3, Composed.Count); Assert.AreEqual(typeof(Composer4), Composed[0]); Assert.AreEqual(typeof(Composer2), Composed[1]); @@ -313,10 +346,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void WeakDependencies() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer10) }; + Type[] types = new[] { typeof(Composer10) }; var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); composers.Compose(); @@ -329,7 +362,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components Assert.Throws(() => composers.Compose()); Console.WriteLine("throws:"); composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); - var requirements = composers.GetRequirements(false); + Dictionary> requirements = composers.GetRequirements(false); Console.WriteLine(Composers.GetComposersReport(requirements)); types = new[] { typeof(Composer2) }; @@ -352,10 +385,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void DisableMissing() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list + Type[] types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of>()); Composed.Clear(); composers.Compose(); @@ -367,11 +400,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void AttributesPriorities() { - var register = MockRegister(); + IServiceCollection register = MockRegister(); var composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var types = new[] { typeof(Composer26) }; - var enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) }; + Type[] types = new[] { typeof(Composer26) }; + DisableComposerAttribute[] enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) }; var composers = new Composers(composition, types, enableDisableAttributes, Mock.Of>()); Composed.Clear(); composers.Compose(); @@ -389,47 +422,47 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components [Test] public void AllComposers() { - var typeFinder = TestHelper.GetTypeFinder(); + ITypeFinder typeFinder = TestHelper.GetTypeFinder(); var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); - var register = MockRegister(); + IServiceCollection register = MockRegister(); var builder = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); - var allComposers = typeLoader.GetTypes().ToList(); var types = allComposers.Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")).ToList(); var composers = new Composers(builder, types, Enumerable.Empty(), Mock.Of>()); - var requirements = composers.GetRequirements(); - var report = Composers.GetComposersReport(requirements); + Dictionary> requirements = composers.GetRequirements(); + string report = Composers.GetComposersReport(requirements); Console.WriteLine(report); - var composerTypes = composers.SortComposers(requirements); + IEnumerable composerTypes = composers.SortComposers(requirements); - foreach (var type in composerTypes) - Console.WriteLine(type); - } - - #region Compothings - - public class TestComposerBase : IComposer - { - public virtual void Compose(IUmbracoBuilder builder) + foreach (Type type in composerTypes) { - Composed.Add(GetType()); + Console.WriteLine(type); } } + public class TestComposerBase : IComposer + { + public virtual void Compose(IUmbracoBuilder builder) => Composed.Add(GetType()); + } + public class Composer1 : TestComposerBase - { } + { + } [ComposeAfter(typeof(Composer4))] public class Composer2 : TestComposerBase, ICoreComposer - { } + { + } public class Composer3 : TestComposerBase, IUserComposer - { } + { + } public class Composer4 : TestComposerBase - { } + { + } public class Composer5 : TestComposerBase { @@ -452,151 +485,135 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Components public class TestComponentBase : IComponent { - public virtual void Initialize() - { - Initialized.Add(GetType()); - } + public virtual void Initialize() => Initialized.Add(GetType()); - public virtual void Terminate() - { - Terminated.Add(GetType()); - } + public virtual void Terminate() => Terminated.Add(GetType()); } public class Component5 : TestComponentBase { private readonly ISomeResource _resource; - public Component5(ISomeResource resource) - { - _resource = resource; - } + public Component5(ISomeResource resource) => _resource = resource; } public class Component5a : TestComponentBase - { } + { + } [Disable] public class Composer6 : TestComposerBase - { } + { + } public class Composer7 : TestComposerBase - { } + { + } [Disable(typeof(Composer7))] [Enable(typeof(Composer6))] public class Composer8 : TestComposerBase - { } + { + } public interface ITestComposer : IUserComposer - { } + { + } public class Composer9 : TestComposerBase, ITestComposer - { } + { + } [ComposeAfter(typeof(ITestComposer))] public class Composer10 : TestComposerBase - { } + { + } [ComposeAfter(typeof(ITestComposer), false)] public class Composer11 : TestComposerBase - { } + { + } [ComposeAfter(typeof(Composer4), true)] public class Composer12 : TestComposerBase, ICoreComposer - { } + { + } [ComposeBefore(typeof(Composer1))] public class Composer13 : TestComposerBase - { } + { + } - public interface ISomeResource { } + public interface ISomeResource + { + } - public class SomeResource : ISomeResource { } + public class SomeResource : ISomeResource + { + } public class Composer20 : TestComposerBase - { } + { + } [ComposeBefore(typeof(Composer20))] public class Composer21 : TestComposerBase - { } + { + } public class Composer22 : TestComposerBase - { } + { + } [ComposeAfter(typeof(Composer22))] public interface IComposer23 : IComposer - { } + { + } public class Composer24 : TestComposerBase, IComposer23 - { } + { + } // should insert itself between 22 and anything i23 [ComposeBefore(typeof(IComposer23))] - //[RequireComponent(typeof(Component22))] - not needed, implement i23 + ////[RequireComponent(typeof(Component22))] - not needed, implement i23 public class Composer25 : TestComposerBase, IComposer23 - { } + { + } public class Composer26 : TestComposerBase - { } + { + } [Enable(typeof(Composer26))] public class Composer27 : TestComposerBase - { } - - #endregion - - #region TypeArray + { + } // FIXME: move to Testing + private static Type[] TypeArray() => new[] { typeof(T1) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7) }; - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7) }; - } - - private static Type[] TypeArray() - { - return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8) }; - } + private static Type[] TypeArray() => new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8) }; private static void AssertTypeArray(IReadOnlyList expected, IReadOnlyList test) { Assert.AreEqual(expected.Count, test.Count); - for (var i = 0; i < expected.Count; i++) + for (int i = 0; i < expected.Count; i++) + { Assert.AreEqual(expected[i], test[i]); + } } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CollectionBuildersTests.cs index b4131ca790..1c4a401e2e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CollectionBuildersTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; @@ -20,7 +23,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [SetUp] public void Setup() { - var register = TestHelper.GetServiceCollection(); + IServiceCollection register = TestHelper.GetServiceCollection(); _composition = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); } @@ -32,24 +35,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void ContainsTypes() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); - //Assert.IsFalse(col.ContainsType()); // does not compile + //// Assert.IsFalse(col.ContainsType()); // does not compile - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CanClearBuilderBeforeCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); @@ -57,20 +60,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col); } [Test] public void CannotClearBuilderOnceCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => builder.Clear()); } @@ -78,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void CanAppendToBuilder() { - var builder = _composition.WithCollectionBuilder(); + TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); @@ -86,52 +89,48 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(builder.Has()); Assert.IsFalse(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotAppendToBuilderOnceCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder(); + TestCollectionBuilder builder = _composition.WithCollectionBuilder(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); - Assert.Throws(() => - builder.Append() - ); + Assert.Throws(() => builder.Append()); } [Test] public void CanAppendDuplicateToBuilderAndDeDuplicate() { - var builder = _composition.WithCollectionBuilder(); + TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Append(); builder.Append(); - var factory = _composition.CreateServiceProvider(); + IServiceProvider factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CannotAppendInvalidTypeToBUilder() { - var builder = _composition.WithCollectionBuilder(); + TestCollectionBuilder builder = _composition.WithCollectionBuilder(); - //builder.Append(); // does not compile - Assert.Throws(() => - builder.Append(new[] { typeof(Resolved4) }) // throws - ); + ////builder.Append(); // does not compile + Assert.Throws(() => builder.Append(new[] { typeof(Resolved4) })); } [Test] public void CanRemoveFromBuilder() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); @@ -140,42 +139,40 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsFalse(builder.Has()); Assert.IsFalse(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1)); } [Test] public void CanRemoveMissingFromBuilder() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Remove(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotRemoveFromBuilderOnceCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); - Assert.Throws(() => - builder.Remove() // throws - ); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); + Assert.Throws(() => builder.Remove()); } [Test] public void CanInsertIntoBuilder() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); @@ -184,69 +181,63 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved3), typeof(Resolved1), typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderOnceCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); - Assert.Throws(() => - builder.Insert() // throws - ); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); + Assert.Throws(() => builder.Insert()); } [Test] public void CanInsertDuplicateIntoBuilderAndDeDuplicate() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .Insert(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CanInsertIntoEmptyBuilder() { - var builder = _composition.WithCollectionBuilder(); + TestCollectionBuilder builder = _composition.WithCollectionBuilder(); builder.Insert(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2)); } [Test] public void CannotInsertIntoBuilderAtWrongIndex() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); - Assert.Throws(() => - builder.Insert(99) // throws - ); + Assert.Throws(() => builder.Insert(99)); - Assert.Throws(() => - builder.Insert(-1) // throws - ); + Assert.Throws(() => builder.Insert(-1)); } [Test] public void CanInsertIntoBuilderBefore() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); @@ -255,15 +246,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CanInsertIntoBuilderAfter() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertAfter(); @@ -272,15 +263,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved3), typeof(Resolved2)); } [Test] public void CanInsertIntoBuilderAfterLast() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertAfter(); @@ -289,47 +280,45 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(builder.Has()); Assert.IsTrue(builder.Has()); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } [Test] public void CannotInsertIntoBuilderBeforeOnceCollectionIsCreated() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); Assert.Throws(() => - builder.InsertBefore() - ); + builder.InsertBefore()); } [Test] public void CanInsertDuplicateIntoBuilderBeforeAndDeDuplicate() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append() .Append() .InsertBefore(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } [Test] public void CannotInsertIntoBuilderBeforeMissing() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilder builder = _composition.WithCollectionBuilder() .Append(); Assert.Throws(() => - builder.InsertBefore() - ); + builder.InsertBefore()); } [Test] @@ -341,21 +330,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope - // the collection must come from the container + // the collection must come from the container. + IServiceProvider factory = _composition.CreateServiceProvider(); - var factory = _composition.CreateServiceProvider(); - - using (var scope = factory.CreateScope()) + using (IServiceScope scope = factory.CreateScope()) { - var col1 = scope.ServiceProvider.GetRequiredService(); + TestCollection col1 = scope.ServiceProvider.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); - var col2 = scope.ServiceProvider.GetRequiredService(); + TestCollection col2 = scope.ServiceProvider.GetRequiredService(); AssertCollection(col2, typeof(Resolved1), typeof(Resolved2)); AssertSameCollection(scope.ServiceProvider, col1, col2); } - } [Test] @@ -367,14 +354,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope - // the collection must come from the container + // the collection must come from the container. + IServiceProvider factory = _composition.CreateServiceProvider(); - var factory = _composition.CreateServiceProvider(); - - var col1 = factory.GetRequiredService(); + TestCollection col1 = factory.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); - var col2 = factory.GetRequiredService(); + TestCollection col2 = factory.GetRequiredService(); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2)); AssertNotSameCollection(col1, col2); @@ -383,13 +369,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void BuilderRespectsTypesOrder() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilderTransient builder = _composition.WithCollectionBuilder() .Append() .Insert() .InsertBefore(); - var factory = _composition.CreateServiceProvider(); - var col1 = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col1 = builder.CreateCollection(factory); AssertCollection(col1, typeof(Resolved1), typeof(Resolved2), typeof(Resolved3)); } @@ -402,13 +388,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing // CreateCollection creates a new collection each time // but the container manages the scope, so to test the scope - // the collection must come from the container + // the collection must come from the container/ + IServiceProvider serviceProvider = _composition.CreateServiceProvider(); TestCollection col1A, col1B; - - var serviceProvider = _composition.CreateServiceProvider(); - - using (var scope = serviceProvider.CreateScope()) + using (IServiceScope scope = serviceProvider.CreateScope()) { col1A = scope.ServiceProvider.GetRequiredService(); col1B = scope.ServiceProvider.GetRequiredService(); @@ -420,7 +404,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing TestCollection col2; - using (var scope = serviceProvider.CreateScope()) + using (IServiceScope scope = serviceProvider.CreateScope()) { col2 = scope.ServiceProvider.GetRequiredService(); } @@ -432,43 +416,43 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void WeightedBuilderCreatesWeightedCollection() { - var builder = _composition.WithCollectionBuilder() + TestCollectionBuilderWeighted builder = _composition.WithCollectionBuilder() .Add() .Add(); - var factory = _composition.CreateServiceProvider(); - var col = builder.CreateCollection(factory); + IServiceProvider factory = _composition.CreateServiceProvider(); + TestCollection col = builder.CreateCollection(factory); AssertCollection(col, typeof(Resolved2), typeof(Resolved1)); } - #region Assertions - private static void AssertCollection(IEnumerable col, params Type[] expected) { - var colA = col.ToArray(); + Resolved[] colA = col.ToArray(); Assert.AreEqual(expected.Length, colA.Length); - for (var i = 0; i < expected.Length; i++) + for (int i = 0; i < expected.Length; i++) + { Assert.IsInstanceOf(expected[i], colA[i]); + } } private static void AssertSameCollection(IServiceProvider factory, IEnumerable col1, IEnumerable col2) { Assert.AreSame(col1, col2); - var col1A = col1.ToArray(); - var col2A = col2.ToArray(); + Resolved[] col1A = col1.ToArray(); + Resolved[] col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); // Ensure each item in each collection is the same but also // resolve each item from the factory to ensure it's also the same since // it should have the same lifespan. - for (var i = 0; i < col1A.Length; i++) + for (int i = 0; i < col1A.Length; i++) { Assert.AreSame(col1A[i], col2A[i]); - var itemA = factory.GetRequiredService(col1A[i].GetType()); - var itemB = factory.GetRequiredService(col2A[i].GetType()); + object itemA = factory.GetRequiredService(col1A[i].GetType()); + object itemB = factory.GetRequiredService(col2A[i].GetType()); Assert.AreSame(itemA, itemB); } @@ -478,36 +462,37 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { Assert.AreNotSame(col1, col2); - var col1A = col1.ToArray(); - var col2A = col2.ToArray(); + Resolved[] col1A = col1.ToArray(); + Resolved[] col2A = col2.ToArray(); Assert.AreEqual(col1A.Length, col2A.Length); - for (var i = 0; i < col1A.Length; i++) + for (int i = 0; i < col1A.Length; i++) { Assert.AreNotSame(col1A[i], col2A[i]); } } - #endregion - - #region Test Objects - public abstract class Resolved - { } + { + } public class Resolved1 : Resolved - { } + { + } [Weight(50)] // default is 100 public class Resolved2 : Resolved - { } + { + } public class Resolved3 : Resolved - { } + { + } public class Resolved4 // not! : Resolved - { } + { + } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : OrderedCollectionBuilderBase @@ -542,9 +527,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { public TestCollection(IEnumerable items) : base(items) - { } + { + } } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs index 1c8bf139ac..1010424f75 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.IO; using System.Reflection; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CompositionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CompositionTests.cs index 1401d7c66f..984f598ef4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/CompositionTests.cs @@ -1,10 +1,12 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { [TestFixture] public class CompositionTests { - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/LazyCollectionBuilderTests.cs index c87b267c1a..9179e9e086 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/LazyCollectionBuilderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Configuration; @@ -6,118 +9,113 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Logging; +using Umbraco.Core.DependencyInjection; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.UnitTests.TestHelpers; -using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { + /// + /// Tests for lazy collection builder. + /// + /// + /// Lazy collection builder does not throw on duplicate, just uses distinct types + /// so we don't have a test for duplicates as we had with resolvers in v7. + /// [TestFixture] public class LazyCollectionBuilderTests { - private IServiceCollection CreateRegister() - { - return TestHelper.GetServiceCollection(); - } - - // note - // lazy collection builder does not throw on duplicate, just uses distinct types - // so we don't have a test for duplicates as we had with resolvers in v7 + private IServiceCollection CreateRegister() => TestHelper.GetServiceCollection(); [Test] public void LazyCollectionBuilderHandlesTypes() { - var container = CreateRegister(); + IServiceCollection container = CreateRegister(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); - composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); - var values = factory.GetRequiredService(); + TestCollection values = factory.GetRequiredService(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = factory.GetRequiredService(); + TestCollection other = factory.GetRequiredService(); Assert.AreNotSame(values, other); // transient - var o1 = other.FirstOrDefault(x => x is TransientObject1); + ITestInterface o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient } [Test] public void LazyCollectionBuilderHandlesProducers() { - var container = CreateRegister(); + IServiceCollection container = CreateRegister(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); - composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) .Add(() => new[] { typeof(TransientObject1) }); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); - var values = factory.GetRequiredService(); + TestCollection values = factory.GetRequiredService(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = factory.GetRequiredService(); + TestCollection other = factory.GetRequiredService(); Assert.AreNotSame(values, other); // transient - var o1 = other.FirstOrDefault(x => x is TransientObject1); + ITestInterface o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient } [Test] public void LazyCollectionBuilderHandlesTypesAndProducers() { - var container = CreateRegister(); + IServiceCollection container = CreateRegister(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); - composition.WithCollectionBuilder() .Add() .Add() .Add() .Add(() => new[] { typeof(TransientObject1) }); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); - var values = factory.GetRequiredService(); + TestCollection values = factory.GetRequiredService(); Assert.AreEqual(3, values.Count()); Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2), typeof(TransientObject3) })); - var other = factory.GetRequiredService(); + TestCollection other = factory.GetRequiredService(); Assert.AreNotSame(values, other); // transient - var o1 = other.FirstOrDefault(x => x is TransientObject1); + ITestInterface o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient } [Test] public void LazyCollectionBuilderThrowsOnIllegalTypes() { - var container = CreateRegister(); + IServiceCollection container = CreateRegister(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); composition.WithCollectionBuilder() .Add() // illegal, does not implement the interface! - //.Add() + ////.Add() // legal so far... .Add(() => new[] { typeof(TransientObject4) }); @@ -125,14 +123,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.Throws(() => { // but throws here when trying to register the types, right before creating the factory - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); }); } [Test] public void LazyCollectionBuilderCanExcludeTypes() { - var container = CreateRegister(); + IServiceCollection container = CreateRegister(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); composition.WithCollectionBuilder() @@ -140,9 +138,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2), typeof(TransientObject1) }) .Exclude(); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); - var values = factory.GetRequiredService(); + TestCollection values = factory.GetRequiredService(); Assert.AreEqual(2, values.Count()); Assert.IsFalse(values.Select(x => x.GetType()) @@ -150,28 +148,31 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(values.Select(x => x.GetType()) .ContainsAll(new[] { typeof(TransientObject1), typeof(TransientObject2) })); - var other = factory.GetRequiredService(); + TestCollection other = factory.GetRequiredService(); Assert.AreNotSame(values, other); // transient - var o1 = other.FirstOrDefault(x => x is TransientObject1); + ITestInterface o1 = other.FirstOrDefault(x => x is TransientObject1); Assert.IsFalse(values.Contains(o1)); // transient } - #region Test Objects - private interface ITestInterface - { } + { + } private class TransientObject1 : ITestInterface - { } + { + } private class TransientObject2 : ITestInterface - { } + { + } private class TransientObject3 : ITestInterface - { } + { + } private class TransientObject4 - { } + { + } // ReSharper disable once ClassNeverInstantiated.Local private class TestCollectionBuilder : LazyCollectionBuilderBase @@ -186,9 +187,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { public TestCollection(IEnumerable items) : base(items) - { } + { + } } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/PackageActionCollectionTests.cs index 17e406e02f..702d4f9c8a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/PackageActionCollectionTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.Linq; @@ -7,13 +11,11 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Composing; -using Umbraco.Core.Logging; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.PackageActions; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.UnitTests.TestHelpers; -using Umbraco.Core.DependencyInjection; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { @@ -23,76 +25,47 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void PackageActionCollectionBuilderWorks() { - var container = TestHelper.GetServiceCollection(); + IServiceCollection container = TestHelper.GetServiceCollection(); var composition = new UmbracoBuilder(container, Mock.Of(), TestHelper.GetMockedTypeLoader()); - - var expectedPackageActions = TypeLoader.GetPackageActions(); + IEnumerable expectedPackageActions = TypeLoader.GetPackageActions(); composition.WithCollectionBuilder() .Add(() => expectedPackageActions); - var factory = composition.CreateServiceProvider(); + IServiceProvider factory = composition.CreateServiceProvider(); - var actions = factory.GetRequiredService(); + PackageActionCollection actions = factory.GetRequiredService(); Assert.AreEqual(2, actions.Count()); // order is unspecified, but both must be there - var hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; - var hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; + bool hasAction1 = actions.ElementAt(0) is PackageAction1 || actions.ElementAt(1) is PackageAction1; + bool hasAction2 = actions.ElementAt(0) is PackageAction2 || actions.ElementAt(1) is PackageAction2; Assert.IsTrue(hasAction1); Assert.IsTrue(hasAction2); } - #region Test Objects - public class PackageAction1 : IPackageAction { - public bool Execute(string packageName, XElement xmlData) - { - throw new NotImplementedException(); - } + public bool Execute(string packageName, XElement xmlData) => throw new NotImplementedException(); - public string Alias() - { - return "pa1"; - } + public string Alias() => "pa1"; - public bool Undo(string packageName, XElement xmlData) - { - throw new NotImplementedException(); - } + public bool Undo(string packageName, XElement xmlData) => throw new NotImplementedException(); - public XmlNode SampleXml() - { - throw new NotImplementedException(); - } + public XmlNode SampleXml() => throw new NotImplementedException(); } public class PackageAction2 : IPackageAction { - public bool Execute(string packageName, XElement xmlData) - { - throw new NotImplementedException(); - } + public bool Execute(string packageName, XElement xmlData) => throw new NotImplementedException(); - public string Alias() - { - return "pa2"; - } + public string Alias() => "pa2"; - public bool Undo(string packageName, XElement xmlData) - { - throw new NotImplementedException(); - } + public bool Undo(string packageName, XElement xmlData) => throw new NotImplementedException(); - public XmlNode SampleXml() - { - throw new NotImplementedException(); - } + public XmlNode SampleXml() => throw new NotImplementedException(); } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs index 9ef2cf5ae0..1becc88138 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs @@ -1,17 +1,18 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Umbraco.Core.Composing; -using Umbraco.Core.Logging; -using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web.BackOffice.Trees; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { - /// /// Tests for typefinder /// @@ -24,11 +25,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing private Assembly[] _assemblies; [SetUp] - public void Initialize() - { - _assemblies = new[] + public void Initialize() => _assemblies = new[] { - this.GetType().Assembly, + GetType().Assembly, typeof(System.Guid).Assembly, typeof(NUnit.Framework.Assert).Assembly, typeof(System.Xml.NameTable).Assembly, @@ -36,13 +35,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing typeof(TypeFinder).Assembly, }; - } - [Test] public void Find_Class_Of_Type_With_Attribute() { var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); - var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); + IEnumerable typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } @@ -50,10 +47,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing public void Find_Classes_With_Attribute() { var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash()); - var typesFound = typeFinder.FindClassesWithAttribute(_assemblies); + IEnumerable typesFound = typeFinder.FindClassesWithAttribute(_assemblies); Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] - typesFound = typeFinder.FindClassesWithAttribute(new[] { typeof (TreeAttribute).Assembly }); + typesFound = typeFinder.FindClassesWithAttribute(new[] { typeof(TreeAttribute).Assembly }); Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] typesFound = typeFinder.FindClassesWithAttribute(); @@ -63,27 +60,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class MyTestAttribute : Attribute { - } public abstract class TestEditor { - } [MyTest] public class BenchmarkTestEditor : TestEditor { - } [MyTest] public class MyOtherTestEditor : TestEditor { - } - } - - } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs index c78992d68c..3126cce538 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeHelperTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data.Odbc; @@ -7,6 +10,7 @@ using System.Data.SqlClient; using System.Linq; using System.Reflection; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Composing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing @@ -17,19 +21,29 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [TestFixture] public class TypeHelperTests { + private class Base + { + } + private interface IBase + { + } - class Base { } + private interface IDerived : IBase + { + } - interface IBase { } + private class Derived : Base, IBase + { + } - interface IDerived : IBase { } + private class Derived2 : Derived + { + } - class Derived : Base, IBase { } - - class Derived2 : Derived { } - - class DerivedI : IDerived { } + private class DerivedI : IDerived + { + } [Test] public void Is_Static_Class() @@ -41,44 +55,41 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing [Test] public void Find_Common_Base_Class() { - var t1 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), - typeof (OdbcCommand), - typeof (SqlCommand)); + Attempt t1 = TypeHelper.GetLowestBaseType( + typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand)); Assert.IsFalse(t1.Success); - var t2 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), - typeof (OdbcCommand), - typeof (SqlCommand), - typeof (Component)); + Attempt t2 = TypeHelper.GetLowestBaseType( + typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand), + typeof(Component)); Assert.IsTrue(t2.Success); Assert.AreEqual(typeof(Component), t2.Result); - var t3 = TypeHelper.GetLowestBaseType(typeof (OleDbCommand), - typeof (OdbcCommand), - typeof (SqlCommand), - typeof (Component), - typeof (Component).BaseType); + Attempt t3 = TypeHelper.GetLowestBaseType( + typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand), + typeof(Component), + typeof(Component).BaseType); Assert.IsTrue(t3.Success); Assert.AreEqual(typeof(MarshalByRefObject), t3.Result); - var t4 = TypeHelper.GetLowestBaseType(typeof(OleDbCommand), - typeof(OdbcCommand), - typeof(SqlCommand), - typeof(Component), - typeof(Component).BaseType, - typeof(int)); + Attempt t4 = TypeHelper.GetLowestBaseType( + typeof(OleDbCommand), + typeof(OdbcCommand), + typeof(SqlCommand), + typeof(Component), + typeof(Component).BaseType, + typeof(int)); Assert.IsFalse(t4.Success); - var t5 = TypeHelper.GetLowestBaseType(typeof(PropertyAliasDto)); + Attempt t5 = TypeHelper.GetLowestBaseType(typeof(PropertyAliasDto)); Assert.IsTrue(t5.Success); Assert.AreEqual(typeof(PropertyAliasDto), t5.Result); - - //var t6 = TypeHelper.GetLowestBaseType(typeof (IApplicationEventHandler), - // typeof (SchedulerComponent), - // typeof(CacheRefresherComponent)); - //Assert.IsTrue(t6.Success); - //Assert.AreEqual(typeof(IApplicationEventHandler), t6.Result); - } [Test] @@ -96,10 +107,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(TypeHelper.MatchType(typeof(List), typeof(System.Collections.IEnumerable), bindings)); Assert.AreEqual(0, bindings.Count); - var t1 = typeof(IList<>); // IList<> - var a1 = t1.GetGenericArguments()[0]; // + Type t1 = typeof(IList<>); // IList<> + Type a1 = t1.GetGenericArguments()[0]; // t1 = t1.MakeGenericType(a1); // IList - var t2 = a1; + Type t2 = a1; bindings = new Dictionary(); Assert.IsTrue(TypeHelper.MatchType(typeof(int), t2, bindings)); @@ -145,12 +156,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing // both are OK Assert.IsTrue(TypeHelper.MatchType(typeof(List), typeof(IEnumerable<>))); - var t1 = typeof (IDictionary<,>); // IDictionary<,> - var a1 = t1.GetGenericArguments()[0]; + Type t1 = typeof(IDictionary<,>); // IDictionary<,> + Type a1 = t1.GetGenericArguments()[0]; t1 = t1.MakeGenericType(a1, a1); // IDictionary // both are OK - Assert.IsTrue(TypeHelper.MatchType(typeof(Dictionary), t1)); Assert.IsFalse(TypeHelper.MatchType(typeof(Dictionary), t1)); @@ -164,7 +174,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(TypeHelper.MatchType(typeof(Derived2), typeof(IBase<>))); Assert.IsTrue(TypeHelper.MatchType(typeof(int?), typeof(Nullable<>))); - Assert.IsTrue(TypeHelper.MatchType(typeof(Derived), typeof(object))); Assert.IsFalse(TypeHelper.MatchType(typeof(Derived), typeof(List<>))); Assert.IsFalse(TypeHelper.MatchType(typeof(Derived), typeof(IEnumerable<>))); @@ -172,21 +181,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.IsTrue(TypeHelper.MatchType(typeof(List), typeof(IEnumerable))); Assert.IsFalse(TypeHelper.MatchType(typeof(int), typeof(Nullable<>))); - //This get's the "Type" from the Count extension method on IEnumerable, however the type IEnumerable isn't + // This get's the "Type" from the Count extension method on IEnumerable, however the type IEnumerable isn't // IEnumerable<> and it is not a generic definition, this attempts to explain that: // http://blogs.msdn.com/b/haibo_luo/archive/2006/02/17/534480.aspx - - var genericEnumerableNonGenericDefinition = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public) + Type genericEnumerableNonGenericDefinition = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(x => x.Name == "Count" && x.GetParameters().Count() == 1) .GetParameters() .Single() .ParameterType; Assert.IsTrue(TypeHelper.MatchType(typeof(List), genericEnumerableNonGenericDefinition)); - } - [Test] public void CreateOpenGenericTypes() { @@ -198,7 +204,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing // assembly; or null if the current instance represents a generic type parameter, an array type, pointer // type, or byref type based on a type parameter, or a generic type that is not a generic type definition // but contains unresolved type parameters." - var t = Type.GetType("System.Collections.Generic.IList`1"); Assert.IsNotNull(t); Assert.IsTrue(t.IsGenericTypeDefinition); @@ -206,7 +211,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName); Assert.AreEqual("System.Collections.Generic.IList`1[T]", t.ToString()); - t = typeof (IList<>); + t = typeof(IList<>); Assert.IsTrue(t.IsGenericTypeDefinition); Assert.AreEqual("IList`1", t.Name); Assert.AreEqual("System.Collections.Generic.IList`1", t.FullName); @@ -241,7 +246,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing t = typeof(IList); Assert.AreEqual("System.Collections.Generic.IList`1[System.Int32]", t.ToString()); - t = typeof (IDictionary<,>); + t = typeof(IDictionary<,>); t = t.MakeGenericType(typeof(int), t.GetGenericArguments()[1]); Assert.IsFalse(t.IsGenericTypeDefinition); // not anymore Assert.AreEqual("IDictionary`2", t.Name); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderExtensions.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderExtensions.cs index 6bab57ee41..3179985c99 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderExtensions.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderExtensions.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Umbraco.Core.Composing; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs index 36843ad1cc..5049f6cc0b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -1,11 +1,14 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; @@ -26,28 +29,30 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing public void Initialize() { // this ensures it's reset - var typeFinder = TestHelper.GetTypeFinder(); - _typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, - new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), - Mock.Of>(), new ProfilingLogger(Mock.Of>(), Mock.Of()), false, + ITypeFinder typeFinder = TestHelper.GetTypeFinder(); - // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver - // TODO: Should probably update this so it only searches this assembly and add custom types to be found - new[] + // For testing, we'll specify which assemblies are scanned for the PluginTypeResolver + // TODO: Should probably update this so it only searches this assembly and add custom types to be found + Assembly[] assemblies = new[] { - this.GetType().Assembly, - typeof(System.Guid).Assembly, - typeof(NUnit.Framework.Assert).Assembly, + GetType().Assembly, + typeof(Guid).Assembly, + typeof(Assert).Assembly, typeof(System.Xml.NameTable).Assembly, typeof(System.Configuration.GenericEnumConverter).Assembly, - //typeof(TabPage).Assembly, + ////typeof(TabPage).Assembly, typeof(TypeFinder).Assembly, typeof(UmbracoContext).Assembly, typeof(CheckBoxListPropertyEditor).Assembly - }); - - - + }; + _typeLoader = new TypeLoader( + typeFinder, + NoAppCache.Instance, + new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), + Mock.Of>(), + new ProfilingLogger(Mock.Of>(), Mock.Of()), + false, + assemblies); } [TearDown] @@ -55,106 +60,108 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Composing { _typeLoader = null; - // cleanup - var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var tlDir = Path.Combine(assDir.FullName, "TypeLoader"); + DirectoryInfo assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + string tlDir = Path.Combine(assDir.FullName, "TypeLoader"); if (!Directory.Exists(tlDir)) + { return; + } + Directory.Delete(tlDir, true); } private DirectoryInfo PrepareFolder() { - var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var tlDir = Path.Combine(assDir.FullName, "TypeLoader"); - var dir = Directory.CreateDirectory(Path.Combine(tlDir, Guid.NewGuid().ToString("N"))); + DirectoryInfo assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + string tlDir = Path.Combine(assDir.FullName, "TypeLoader"); + DirectoryInfo dir = Directory.CreateDirectory(Path.Combine(tlDir, Guid.NewGuid().ToString("N"))); return dir; } - //[Test] - //public void Scan_Vs_Load_Benchmark() - //{ - // var typeLoader = new TypeLoader(false); - // var watch = new Stopwatch(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var refreshers = typeLoader.GetTypes(false); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); - //} + ////[Test] + ////public void Scan_Vs_Load_Benchmark() + ////{ + //// var typeLoader = new TypeLoader(false); + //// var watch = new Stopwatch(); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); + //// watch.Reset(); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var refreshers = typeLoader.GetTypes(false); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); + ////} - ////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :) - //[Test] - //public void Load_Type_Benchmark() - //{ - // var watch = new Stopwatch(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.macroCacheRefresh"); - // var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.templateCacheRefresh"); - // var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.presentation.cache.MediaLibraryRefreshers"); - // var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") - // .GetType("umbraco.presentation.cache.pageRefresher"); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); - // watch.Reset(); - // watch.Start(); - // for (var i = 0; i < 1000; i++) - // { - // var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); - // } - // watch.Stop(); - // Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); - //} + //////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :) + ////[Test] + ////public void Load_Type_Benchmark() + ////{ + //// var watch = new Stopwatch(); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null"); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); + //// watch.Reset(); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") + //// .GetType("umbraco.macroCacheRefresh"); + //// var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") + //// .GetType("umbraco.templateCacheRefresh"); + //// var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") + //// .GetType("umbraco.presentation.cache.MediaLibraryRefreshers"); + //// var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null") + //// .GetType("umbraco.presentation.cache.pageRefresher"); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds); + //// watch.Reset(); + //// watch.Start(); + //// for (var i = 0; i < 1000; i++) + //// { + //// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true); + //// } + //// watch.Stop(); + //// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds); + ////} [Test] public void Detect_Legacy_Plugin_File_List() { - var filePath = _typeLoader.GetTypesListFilePath(); - var fileDir = Path.GetDirectoryName(filePath); + string filePath = _typeLoader.GetTypesListFilePath(); + string fileDir = Path.GetDirectoryName(filePath); Directory.CreateDirectory(fileDir); File.WriteAllText(filePath, @" @@ -192,35 +199,41 @@ AnotherContentFinder [Test] public void Create_Cached_Plugin_File() { - var types = new[] { typeof(TypeLoader), typeof(TypeLoaderTests), typeof(IUmbracoContext) }; + Type[] types = new[] { typeof(TypeLoader), typeof(TypeLoaderTests), typeof(IUmbracoContext) }; var typeList1 = new TypeLoader.TypeList(typeof(object), null); - foreach (var type in types) typeList1.Add(type); + foreach (Type type in types) + { + typeList1.Add(type); + } + _typeLoader.AddTypeList(typeList1); _typeLoader.WriteCache(); - var plugins = _typeLoader.TryGetCached(typeof(object), null); - var diffType = _typeLoader.TryGetCached(typeof(object), typeof(ObsoleteAttribute)); + Attempt> plugins = _typeLoader.TryGetCached(typeof(object), null); + Attempt> diffType = _typeLoader.TryGetCached(typeof(object), typeof(ObsoleteAttribute)); Assert.IsTrue(plugins.Success); - //this will be false since there is no cache of that type resolution kind + + // This will be false since there is no cache of that type resolution kind Assert.IsFalse(diffType.Success); Assert.AreEqual(3, plugins.Result.Count()); - var shouldContain = types.Select(x => x.AssemblyQualifiedName); - //ensure they are all found + IEnumerable shouldContain = types.Select(x => x.AssemblyQualifiedName); + + // Ensure they are all found Assert.IsTrue(plugins.Result.ContainsAll(shouldContain)); } [Test] public void Get_Plugins_Hash_With_Hash_Generator() { - //Arrange - var dir = PrepareFolder(); - var d1 = dir.CreateSubdirectory("1"); - var d2 = dir.CreateSubdirectory("2"); - var d3 = dir.CreateSubdirectory("3"); - var d4 = dir.CreateSubdirectory("4"); + // Arrange + DirectoryInfo dir = PrepareFolder(); + DirectoryInfo d1 = dir.CreateSubdirectory("1"); + DirectoryInfo d2 = dir.CreateSubdirectory("2"); + DirectoryInfo d3 = dir.CreateSubdirectory("3"); + DirectoryInfo d4 = dir.CreateSubdirectory("4"); var f1 = new FileInfo(Path.Combine(d1.FullName, "test1.dll")); var f2 = new FileInfo(Path.Combine(d1.FullName, "test2.dll")); var f3 = new FileInfo(Path.Combine(d2.FullName, "test1.dll")); @@ -235,16 +248,16 @@ AnotherContentFinder f5.CreateText().Close(); f6.CreateText().Close(); f7.CreateText().Close(); - var list1 = new[] { f1, f2, f3, f4, f5, f6 }; - var list2 = new[] { f1, f3, f5 }; - var list3 = new[] { f1, f3, f5, f7 }; + FileInfo[] list1 = new[] { f1, f2, f3, f4, f5, f6 }; + FileInfo[] list2 = new[] { f1, f3, f5 }; + FileInfo[] list3 = new[] { f1, f3, f5, f7 }; - //Act - var hash1 = GetFileHash(list1, new ProfilingLogger(Mock.Of>(), Mock.Of())); - var hash2 = GetFileHash(list2, new ProfilingLogger(Mock.Of>(), Mock.Of())); - var hash3 = GetFileHash(list3, new ProfilingLogger(Mock.Of>(), Mock.Of())); + // Act + string hash1 = GetFileHash(list1, new ProfilingLogger(Mock.Of>(), Mock.Of())); + string hash2 = GetFileHash(list2, new ProfilingLogger(Mock.Of>(), Mock.Of())); + string hash3 = GetFileHash(list3, new ProfilingLogger(Mock.Of>(), Mock.Of())); - //Assert + // Assert Assert.AreNotEqual(hash1, hash2); Assert.AreNotEqual(hash1, hash3); Assert.AreNotEqual(hash2, hash3); @@ -263,14 +276,14 @@ AnotherContentFinder [Test] public void Resolves_Types() { - var foundTypes1 = _typeLoader.ResolveFindMeTypes(); + IEnumerable foundTypes1 = _typeLoader.ResolveFindMeTypes(); Assert.AreEqual(2, foundTypes1.Count()); } [Test] public void GetDataEditors() { - var types = _typeLoader.GetDataEditors(); + IEnumerable types = _typeLoader.GetDataEditors(); Assert.AreEqual(39, types.Count()); } @@ -287,32 +300,28 @@ AnotherContentFinder propEditors.Add(typeof(LabelPropertyEditor)); types.Add(propEditors); - var found = types.SingleOrDefault(x => x.BaseType == typeof(DataEditor) && x.AttributeType == null); + TypeLoader.TypeList found = types.SingleOrDefault(x => x.BaseType == typeof(DataEditor) && x.AttributeType == null); Assert.IsNotNull(found); - //This should not find a type list of this type - var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof(IDataEditor) && x.AttributeType == null); + // This should not find a type list of this type + TypeLoader.TypeList shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof(IDataEditor) && x.AttributeType == null); Assert.IsNull(shouldNotFind); } public interface IFindMe : IDiscoverable { - } public class FindMe1 : IFindMe { - } public class FindMe2 : IFindMe { - } - /// /// Returns a unique hash for a combination of FileInfo objects. /// @@ -324,19 +333,23 @@ AnotherContentFinder { using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { - using (var generator = new HashGenerator()) - { - // get the distinct file infos to hash - var uniqInfos = new HashSet(); + using var generator = new HashGenerator(); - foreach (var fileOrFolder in filesAndFolders) + // Get the distinct file infos to hash. + var uniqInfos = new HashSet(); + + foreach (FileSystemInfo fileOrFolder in filesAndFolders) + { + if (uniqInfos.Contains(fileOrFolder.FullName)) { - if (uniqInfos.Contains(fileOrFolder.FullName)) continue; - uniqInfos.Add(fileOrFolder.FullName); - generator.AddFileSystemItem(fileOrFolder); + continue; } - return generator.GenerateHash(); + + uniqInfos.Add(fileOrFolder.FullName); + generator.AddFileSystemItem(fileOrFolder); } + + return generator.GenerateHash(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs similarity index 88% rename from src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/GlobalSettingsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs index 3b0d13c584..3c3de455f5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/GlobalSettingsTests.cs @@ -1,4 +1,7 @@ -using AutoFixture.NUnit3; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using AutoFixture.NUnit3; using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Core.Configuration; @@ -6,8 +9,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.Common.AspNetCore; - -namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configurations +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models { [TestFixture] public class GlobalSettingsTests diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs index f286dd42b0..ca20129092 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidatorTests.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { [TestFixture] - public class GlobalSettingsValidationTests + public class GlobalSettingsValidatorTests { [Test] public void Returns_Success_ForValid_Configuration() diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs index 9ae5444134..b645c758dc 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidatorTests.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Configuration.Models.Validation; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration.Models.Validation { [TestFixture] - public class HealthChecksSettingsValidationTests + public class HealthChecksSettingsValidatorTests { [Test] public void Returns_Success_ForValid_Configuration() diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/NCronTabParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/NCronTabParserTests.cs similarity index 89% rename from src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/NCronTabParserTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/NCronTabParserTests.cs index 4a38de831d..8ab3d01882 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configurations/NCronTabParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Configuration/NCronTabParserTests.cs @@ -1,7 +1,10 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Configuration; -namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configurations +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Configuration { [TestFixture] public class NCronTabParserTests diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/CallContextTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/CallContextTests.cs index dec0ff9a29..82eef0eb71 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/CallContextTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/CallContextTests.cs @@ -1,5 +1,7 @@ -using NUnit.Framework; -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Scoping; @@ -8,39 +10,27 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [TestFixture] public class CallContextTests { - private static bool _first; + private static bool s_first; - static CallContextTests() - { - SafeCallContext.Register(() => - { - CallContext.SetData("test1", null); - CallContext.SetData("test2", null); - return null; - }, o => { }); - } + static CallContextTests() => SafeCallContext.Register( + () => + { + CallContext.SetData("test1", null); + CallContext.SetData("test2", null); + return null; + }, o => { }); [OneTimeSetUp] - public void SetUpFixture() - { - _first = true; - } + public void SetUpFixture() => s_first = true; // logical call context leaks between tests // is is required to clear it before tests begin // (don't trust other tests properly tearing down) - [SetUp] - public void Setup() - { - SafeCallContext.Clear(); - } + public void Setup() => SafeCallContext.Clear(); [TearDown] - public void TearDown() - { - SafeCallContext.Clear(); - } + public void TearDown() => SafeCallContext.Clear(); [Test] public void Test1() @@ -50,9 +40,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings CallContext.SetData("test3b", "test3b"); - if (_first) + if (s_first) { - _first = false; + s_first = false; } else { @@ -72,9 +62,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { CallContext.SetData("test3a", "test3a"); - if (_first) + if (s_first) { - _first = false; + s_first = false; } else { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs index f3fa46ef5e..848edddf1c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/ObjectExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -23,16 +26,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings } [TearDown] - public void TestTearDown() - { - Thread.CurrentThread.CurrentCulture = _savedCulture; - } + public void TestTearDown() => Thread.CurrentThread.CurrentCulture = _savedCulture; [Test] public void Can_Convert_List_To_Enumerable() { - var list = new List {"hello", "world", "awesome"}; - var result = list.TryConvertTo>(); + var list = new List { "hello", "world", "awesome" }; + Attempt> result = list.TryConvertTo>(); Assert.IsTrue(result.Success); Assert.AreEqual(3, result.Result.Count()); } @@ -40,16 +40,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ObjectExtensions_Object_To_Dictionary() { - //Arrange - + // Arrange var obj = new { Key1 = "value1", Key2 = "value2", Key3 = "value3" }; - //Act - - var d = obj.ToDictionary(); - - //Assert + // Act + IDictionary d = obj.ToDictionary(); + // Assert Assert.IsTrue(d.Keys.Contains("Key1")); Assert.IsTrue(d.Keys.Contains("Key2")); Assert.IsTrue(d.Keys.Contains("Key3")); @@ -61,8 +58,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void CanConvertIntToNullableInt() { - var i = 1; - var result = i.TryConvertTo(); + int i = 1; + Attempt result = i.TryConvertTo(); Assert.That(result.Success, Is.True); } @@ -70,7 +67,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings public void CanConvertNullableIntToInt() { int? i = 1; - var result = i.TryConvertTo(); + Attempt result = i.TryConvertTo(); Assert.That(result.Success, Is.True); } @@ -79,20 +76,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { var testCases = new Dictionary { - {"TRUE", true}, - {"True", true}, - {"true", true}, - {"1", true}, - {"FALSE", false}, - {"False", false}, - {"false", false}, - {"0", false}, - {"", false} + { "TRUE", true }, + { "True", true }, + { "true", true }, + { "1", true }, + { "FALSE", false }, + { "False", false }, + { "false", false }, + { "0", false }, + { string.Empty, false } }; - foreach (var testCase in testCases) + foreach (KeyValuePair testCase in testCases) { - var result = testCase.Key.TryConvertTo(); + Attempt result = testCase.Key.TryConvertTo(); Assert.IsTrue(result.Success, testCase.Key); Assert.AreEqual(testCase.Value, result.Result, testCase.Key); @@ -113,7 +110,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { var dateTime = new DateTime(2012, 11, 10, 13, 14, 15); - var result = date.TryConvertTo(); + Attempt result = date.TryConvertTo(); Assert.IsTrue(result.Success, date); Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), outcome, date); @@ -122,7 +119,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public virtual void CanConvertBlankStringToNullDateTime() { - var result = "".TryConvertTo(); + Attempt result = string.Empty.TryConvertTo(); Assert.IsTrue(result.Success); Assert.IsNull(result.Result); } @@ -130,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public virtual void CanConvertBlankStringToNullBool() { - var result = "".TryConvertTo(); + Attempt result = string.Empty.TryConvertTo(); Assert.IsTrue(result.Success); Assert.IsNull(result.Result); } @@ -138,7 +135,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public virtual void CanConvertBlankStringToDateTime() { - var result = "".TryConvertTo(); + Attempt result = string.Empty.TryConvertTo(); Assert.IsTrue(result.Success); Assert.AreEqual(DateTime.MinValue, result.Result); } @@ -146,7 +143,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public virtual void CanConvertObjectToString_Using_ToString_Overload() { - var result = new MyTestObject().TryConvertTo(); + Attempt result = new MyTestObject().TryConvertTo(); Assert.IsTrue(result.Success); Assert.AreEqual("Hello world", result.Result); @@ -156,7 +153,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings public virtual void CanConvertObjectToSameObject() { var obj = new MyTestObject(); - var result = obj.TryConvertTo(); + Attempt result = obj.TryConvertTo(); Assert.AreEqual(obj, result.Result); } @@ -164,7 +161,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ConvertToIntegerTest() { - var conv = "100".TryConvertTo(); + Attempt conv = "100".TryConvertTo(); Assert.IsTrue(conv); Assert.AreEqual(100, conv.Result); @@ -198,7 +195,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ConvertToDecimalTest() { - var conv = "100".TryConvertTo(); + Attempt conv = "100".TryConvertTo(); Assert.IsTrue(conv); Assert.AreEqual(100m, conv.Result); @@ -234,7 +231,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ConvertToNullableDecimalTest() { - var conv = "100".TryConvertTo(); + Attempt conv = "100".TryConvertTo(); Assert.IsTrue(conv); Assert.AreEqual(100m, conv.Result); @@ -270,7 +267,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ConvertToDateTimeTest() { - var conv = "2016-06-07".TryConvertTo(); + Attempt conv = "2016-06-07".TryConvertTo(); Assert.IsTrue(conv); Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result); } @@ -278,7 +275,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ConvertToNullableDateTimeTest() { - var conv = "2016-06-07".TryConvertTo(); + Attempt conv = "2016-06-07".TryConvertTo(); Assert.IsTrue(conv); Assert.AreEqual(new DateTime(2016, 6, 7), conv.Result); } @@ -286,19 +283,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void Value_Editor_Can_Convert_Decimal_To_Decimal_Clr_Type() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); - var result = valueEditor.TryConvertValueToCrlType(12.34d); + Attempt result = valueEditor.TryConvertValueToCrlType(12.34d); Assert.IsTrue(result.Success); Assert.AreEqual(12.34d, result.Result); } private class MyTestObject { - public override string ToString() - { - return "Hello world"; - } + public override string ToString() => "Hello world"; } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/TryConvertToTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/TryConvertToTests.cs index a0dddd8b5e..7ebbe72ac1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/TryConvertToTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/TryConvertToTests.cs @@ -1,11 +1,9 @@ -using System; -using Microsoft.Extensions.Options; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Strings; -using Umbraco.Tests.Testing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs index e8fe9d5415..8703d4d7f4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreThings/UdiTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -14,10 +17,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings public class UdiTests { [SetUp] - public void SetUp() - { - UdiParser.ResetUdiTypes(); - } + public void SetUp() => UdiParser.ResetUdiTypes(); [Test] public void StringUdiCtorTest() @@ -31,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void StringUdiParseTest() { - var udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id"); + Udi udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/test-id"); Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; @@ -56,7 +56,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Assert.AreEqual("%2Fthis%20is%20a%20test", Uri.EscapeDataString("/this is a test")); Assert.AreEqual("/this%20is%20a%20test", Uri.EscapeUriString("/this is a test")); - var udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); + Udi udi = UdiParser.Parse("umb://" + Constants.UdiEntityType.AnyString + "/this%20is%20a%20test"); Assert.AreEqual(Constants.UdiEntityType.AnyString, udi.EntityType); Assert.IsInstanceOf(udi); var stringEntityId = udi as StringUdi; @@ -76,13 +76,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = // unreserved = alpha digit - . _ ~ - Assert.AreEqual("%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2B%2C%3B%3D.-_~%25", Uri.EscapeDataString(":/?#[]@!$&'()+,;=.-_~%")); Assert.AreEqual(":/?#[]@!$&'()+,;=.-_~%25", Uri.EscapeUriString(":/?#[]@!$&'()+,;=.-_~%")); // we cannot have reserved chars at random places // we want to keep the / in string udis - var r = string.Join("/", "path/to/View[1].cshtml".Split('/').Select(Uri.EscapeDataString)); Assert.AreEqual("path/to/View%5B1%5D.cshtml", r); Assert.IsTrue(Uri.IsWellFormedUriString("umb://partial-view-macro/" + r, UriKind.Absolute)); @@ -90,8 +88,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings // with the proper fix in StringUdi this should work: var udi1 = new StringUdi("partial-view-macro", "path/to/View[1].cshtml"); Assert.AreEqual("umb://partial-view-macro/path/to/View%5B1%5D.cshtml", udi1.ToString()); - var udi2 = UdiParser.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); - Assert.AreEqual("path/to/View[1].cshtml", ((StringUdi) udi2).Id); + Udi udi2 = UdiParser.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); + Assert.AreEqual("path/to/View[1].cshtml", ((StringUdi)udi2).Id); } [Test] @@ -109,7 +107,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { var guid = Guid.NewGuid(); var s = "umb://" + Constants.UdiEntityType.AnyGuid + "/" + guid.ToString("N"); - var udi = UdiParser.Parse(s); + Udi udi = UdiParser.Parse(s); Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.IsInstanceOf(udi); var gudi = udi as GuidUdi; @@ -127,8 +125,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Assert.IsTrue(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid1))); Assert.IsTrue(new GuidUdi("type", guid1) == new GuidUdi("type", guid1)); - Assert.IsTrue(((Udi)new GuidUdi("type", guid1)).Equals((Udi)new GuidUdi("type", guid1))); - Assert.IsTrue((Udi)new GuidUdi("type", guid1) == (Udi)new GuidUdi("type", guid1)); + Assert.IsTrue(new GuidUdi("type", guid1).Equals(new GuidUdi("type", guid1))); + Assert.IsTrue(new GuidUdi("type", guid1) == new GuidUdi("type", guid1)); Assert.IsFalse(new GuidUdi("type", guid1).Equals(new GuidUdi("typex", guid1))); Assert.IsFalse(new GuidUdi("type", guid1) == new GuidUdi("typex", guid1)); @@ -143,7 +141,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings public void DistinctTest() { var guid1 = Guid.NewGuid(); - var entities = new[] + GuidUdi[] entities = new[] { new GuidUdi(Constants.UdiEntityType.AnyGuid, guid1), new GuidUdi(Constants.UdiEntityType.AnyGuid, guid1), @@ -160,7 +158,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Assert.AreEqual(Constants.UdiEntityType.AnyGuid, udi.EntityType); Assert.AreEqual(guid, ((GuidUdi)udi).Guid); - // *not* testing whether Udi.Create(type, invalidValue) throws // because we don't throw anymore - see U4-10409 } @@ -176,7 +173,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Assert.IsTrue(guidUdi.IsRoot); Assert.AreEqual("umb://any-guid/00000000000000000000000000000000", guidUdi.ToString()); - var udi = UdiParser.Parse("umb://any-string/"); + Udi udi = UdiParser.Parse("umb://any-string/"); Assert.IsTrue(udi.IsRoot); Assert.IsInstanceOf(udi); @@ -194,14 +191,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings { // can parse open string udi var stringUdiString = "umb://" + Constants.UdiEntityType.AnyString; - Udi stringUdi; - Assert.IsTrue(UdiParser.TryParse(stringUdiString, out stringUdi)); + Assert.IsTrue(UdiParser.TryParse(stringUdiString, out Udi stringUdi)); Assert.AreEqual(string.Empty, ((StringUdi)stringUdi).Id); // can parse open guid udi var guidUdiString = "umb://" + Constants.UdiEntityType.AnyGuid; - Udi guidUdi; - Assert.IsTrue(UdiParser.TryParse(guidUdiString, out guidUdi)); + Assert.IsTrue(UdiParser.TryParse(guidUdiString, out Udi guidUdi)); Assert.AreEqual(Guid.Empty, ((GuidUdi)guidUdi).Guid); // can create a range @@ -219,13 +214,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Converters = new JsonConverter[] { new UdiJsonConverter(), new UdiRangeJsonConverter() } }; - var guid = Guid.NewGuid(); var udi = new GuidUdi(Constants.UdiEntityType.AnyGuid, guid); var json = JsonConvert.SerializeObject(udi, settings); Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}\"", guid), json); - var dudi = JsonConvert.DeserializeObject(json, settings); + Udi dudi = JsonConvert.DeserializeObject(json, settings); Assert.AreEqual(Constants.UdiEntityType.AnyGuid, dudi.EntityType); Assert.AreEqual(guid, ((GuidUdi)dudi).Guid); @@ -233,7 +227,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings json = JsonConvert.SerializeObject(range, settings); Assert.AreEqual(string.Format("\"umb://any-guid/{0:N}?children\"", guid), json); - var drange = JsonConvert.DeserializeObject(json, settings); + UdiRange drange = JsonConvert.DeserializeObject(json, settings); Assert.AreEqual(udi, drange.Udi); Assert.AreEqual(string.Format("umb://any-guid/{0:N}", guid), drange.Udi.UriValue.ToString()); Assert.AreEqual(Constants.DeploySelector.ChildrenOfThis, drange.Selector); @@ -242,9 +236,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void ValidateUdiEntityType() { - var types = UdiParser.GetKnownUdiTypes(); + Dictionary types = UdiParser.GetKnownUdiTypes(); - foreach (var fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static)) + foreach (FieldInfo fi in typeof(Constants.UdiEntityType).GetFields(BindingFlags.Public | BindingFlags.Static)) { // IsLiteral determines if its value is written at // compile time and not changeable @@ -257,7 +251,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings var value = fi.GetValue(null).ToString(); if (types.ContainsKey(value) == false) + { Assert.Fail("Error in class Constants.UdiEntityType, type \"{0}\" is not declared by GetTypes.", value); + } + types.Remove(value); } } @@ -268,11 +265,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings [Test] public void KnownTypes() { - Udi udi; - // cannot parse an unknown type, udi is null // this will scan - Assert.IsFalse(UdiParser.TryParse("umb://whatever/1234", out udi)); + Assert.IsFalse(UdiParser.TryParse("umb://whatever/1234", out Udi udi)); Assert.IsNull(udi); UdiParser.ResetUdiTypes(); @@ -299,52 +294,28 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreThings Assert.IsInstanceOf(udi); // can get method for Deploy compatibility - var method = typeof(UdiParser).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool) }, null); + MethodInfo method = typeof(UdiParser).GetMethod("Parse", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool) }, null); Assert.IsNotNull(method); } [UdiDefinition("foo", UdiType.GuidUdi)] public class FooConnector : IServiceConnector { - public IArtifact GetArtifact(Udi udi) - { - throw new NotImplementedException(); - } + public IArtifact GetArtifact(Udi udi) => throw new NotImplementedException(); - public IArtifact GetArtifact(object entity) - { - throw new NotImplementedException(); - } + public IArtifact GetArtifact(object entity) => throw new NotImplementedException(); - public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) - { - throw new NotImplementedException(); - } + public ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context) => throw new NotImplementedException(); - public void Process(ArtifactDeployState dart, IDeployContext context, int pass) - { - throw new NotImplementedException(); - } + public void Process(ArtifactDeployState dart, IDeployContext context, int pass) => throw new NotImplementedException(); - public void Explode(UdiRange range, List udis) - { - throw new NotImplementedException(); - } + public void Explode(UdiRange range, List udis) => throw new NotImplementedException(); - public NamedUdiRange GetRange(Udi udi, string selector) - { - throw new NotImplementedException(); - } + public NamedUdiRange GetRange(Udi udi, string selector) => throw new NotImplementedException(); - public NamedUdiRange GetRange(string entityType, string sid, string selector) - { - throw new NotImplementedException(); - } + public NamedUdiRange GetRange(string entityType, string sid, string selector) => throw new NotImplementedException(); - public bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null) - { - throw new NotImplementedException(); - } + public bool Compare(IArtifact art1, IArtifact art2, ICollection differences = null) => throw new NotImplementedException(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/FrameworkXmlTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/FrameworkXmlTests.cs index 42e3aa7357..e91f47893f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/FrameworkXmlTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/FrameworkXmlTests.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Xml; using System.Xml.XPath; - using NUnit.Framework; namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml @@ -12,7 +10,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml [TestFixture] public class FrameworkXmlTests { - const string Xml1 = @" + private const string Xml1 = @" @@ -27,31 +25,29 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "; - // Umbraco : the following test shows that when legacy imports the whole tree in a // "contentAll" xslt macro parameter, the entire collection of nodes is cloned ie is // duplicated. // // What is the impact on memory? // What happens for non-xslt macros? - [Test] public void ImportNodeClonesImportedNode() { var doc1 = new XmlDocument(); doc1.LoadXml(Xml1); - var node1 = doc1.SelectSingleNode("//item2"); + XmlNode node1 = doc1.SelectSingleNode("//item2"); Assert.IsNotNull(node1); var doc2 = new XmlDocument(); doc2.LoadXml(""); - var node2 = doc2.ImportNode(node1, true); - var root2 = doc2.DocumentElement; + XmlNode node2 = doc2.ImportNode(node1, true); + XmlElement root2 = doc2.DocumentElement; Assert.IsNotNull(root2); root2.AppendChild(node2); - var node3 = doc2.SelectSingleNode("//item2"); + XmlNode node3 = doc2.SelectSingleNode("//item2"); Assert.AreNotSame(node1, node2); // has been cloned Assert.AreSame(node2, node3); // has been appended @@ -59,19 +55,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.AreNotSame(node1.FirstChild, node2.FirstChild); // deep clone } - // Umbraco: the CanRemove...NodeAndNavigate tests shows that if the underlying XmlDocument // is modified while navigating, then strange situations can be created. For xslt macros, // the result depends on what the xslt engine is doing at the moment = unpredictable. // // What happens for non-xslt macros? - [Test] public void CanRemoveCurrentNodeAndNavigate() { var doc1 = new XmlDocument(); doc1.LoadXml(Xml1); - var nav1 = doc1.CreateNavigator(); + XPathNavigator nav1 = doc1.CreateNavigator(); Assert.IsTrue(nav1.MoveToFirstChild()); Assert.AreEqual("root", nav1.Name); @@ -82,15 +76,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsTrue(nav1.MoveToNext()); Assert.AreEqual("item2", nav1.Name); - var node1 = doc1.SelectSingleNode("//item2"); + XmlNode node1 = doc1.SelectSingleNode("//item2"); Assert.IsNotNull(node1); - var parent1 = node1.ParentNode; + XmlNode parent1 = node1.ParentNode; Assert.IsNotNull(parent1); parent1.RemoveChild(node1); // navigator now navigates on an isolated fragment // that is rooted on the node that was removed - Assert.AreEqual("item2", nav1.Name); Assert.IsFalse(nav1.MoveToPrevious()); Assert.IsFalse(nav1.MoveToNext()); @@ -110,7 +103,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml { var doc1 = new XmlDocument(); doc1.LoadXml(Xml1); - var nav1 = doc1.CreateNavigator(); + XPathNavigator nav1 = doc1.CreateNavigator(); Assert.IsTrue(nav1.MoveToFirstChild()); Assert.AreEqual("root", nav1.Name); @@ -123,15 +116,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsTrue(nav1.MoveToFirstChild()); Assert.AreEqual("item21", nav1.Name); - var node1 = doc1.SelectSingleNode("//item2"); + XmlNode node1 = doc1.SelectSingleNode("//item2"); Assert.IsNotNull(node1); - var parent1 = node1.ParentNode; + XmlNode parent1 = node1.ParentNode; Assert.IsNotNull(parent1); parent1.RemoveChild(node1); // navigator now navigates on an isolated fragment // that is rooted on the node that was removed - Assert.AreEqual("item21", nav1.Name); Assert.IsTrue(nav1.MoveToParent()); Assert.AreEqual("item2", nav1.Name); @@ -148,7 +140,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml { var doc1 = new XmlDocument(); doc1.LoadXml(Xml1); - var nav1 = doc1.CreateNavigator(); + XPathNavigator nav1 = doc1.CreateNavigator(); Assert.IsTrue(nav1.MoveToFirstChild()); Assert.AreEqual("root", nav1.Name); @@ -163,14 +155,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsTrue(nav1.MoveToNext()); Assert.AreEqual("item4", nav1.Name); - var node1 = doc1.SelectSingleNode("//item2"); + XmlNode node1 = doc1.SelectSingleNode("//item2"); Assert.IsNotNull(node1); - var parent1 = node1.ParentNode; + XmlNode parent1 = node1.ParentNode; Assert.IsNotNull(parent1); parent1.RemoveChild(node1); // navigator sees the change - Assert.AreEqual("item4", nav1.Name); Assert.IsTrue(nav1.MoveToPrevious()); Assert.AreEqual("item3", nav1.Name); @@ -183,33 +174,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // on what the xslt engine is doing at the moment = unpredictable. // // What happens for non-xslt macros? - [Test] public void CanRemoveNodeAndIterate() { var doc1 = new XmlDocument(); doc1.LoadXml(Xml1); - var nav1 = doc1.CreateNavigator(); + XPathNavigator nav1 = doc1.CreateNavigator(); - var iter1 = nav1.Select("//items/*"); - var iter2 = nav1.Select("//items/*"); + XPathNodeIterator iter1 = nav1.Select("//items/*"); + XPathNodeIterator iter2 = nav1.Select("//items/*"); Assert.AreEqual(6, iter1.Count); - var node1 = doc1.SelectSingleNode("//item2"); + XmlNode node1 = doc1.SelectSingleNode("//item2"); Assert.IsNotNull(node1); - var parent1 = node1.ParentNode; + XmlNode parent1 = node1.ParentNode; Assert.IsNotNull(parent1); parent1.RemoveChild(node1); // iterator partially sees the change - Assert.AreEqual(6, iter1.Count); // has been cached, not updated Assert.AreEqual(5, iter2.Count); // not calculated yet, correct value - var count = 0; + int count = 0; while (iter1.MoveNext()) + { count++; + } Assert.AreEqual(5, count); } @@ -218,22 +209,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml public void OldFrameworkXPathBugIsFixed() { // see http://bytes.com/topic/net/answers/177129-reusing-xpathexpression-multiple-iterations - var doc = new XmlDocument(); doc.LoadXml(""); - var nav = doc.CreateNavigator(); - var expr = nav.Compile("*"); + XPathNavigator nav = doc.CreateNavigator(); + XPathExpression expr = nav.Compile("*"); - nav.MoveToFirstChild(); //root - var iter1 = nav.Select(expr); - iter1.MoveNext(); //root/a - var iter2 = iter1.Current.Select(expr); + nav.MoveToFirstChild(); // root + XPathNodeIterator iter1 = nav.Select(expr); + iter1.MoveNext(); // root/a + XPathNodeIterator iter2 = iter1.Current.Select(expr); iter2.MoveNext(); // /root/a/a1 iter2.MoveNext(); // /root/a/a2 // used to fail because iter1 and iter2 would conflict - Assert.IsTrue(iter1.MoveNext()); //root/b + Assert.IsTrue(iter1.MoveNext()); // root/b } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/NavigableNavigatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/NavigableNavigatorTests.cs index c5629aca10..d0990114e8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/NavigableNavigatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/NavigableNavigatorTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -7,9 +10,9 @@ using System.Net; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; +using NUnit.Framework; using Umbraco.Core.Xml; using Umbraco.Core.Xml.XPath; -using NUnit.Framework; namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml { @@ -20,8 +23,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml public void NewNavigatorIsAtRoot() { const string xml = @""; - var doc = XmlHelper.CreateXPathDocument(xml); - var nav = doc.CreateNavigator(); + XPathDocument doc = XmlHelper.CreateXPathDocument(xml); + XPathNavigator nav = doc.CreateNavigator(); Assert.AreEqual(XPathNodeType.Root, nav.NodeType); Assert.AreEqual(string.Empty, nav.Name); @@ -56,8 +59,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml ]]> "; - var doc = XmlHelper.CreateXPathDocument(xml); - var nav = doc.CreateNavigator(); + XPathDocument doc = XmlHelper.CreateXPathDocument(xml); + XPathNavigator nav = doc.CreateNavigator(); NavigatorValues(nav, true); } @@ -71,7 +74,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml NavigatorValues(nav, false); } - static void NavigatorValues(XPathNavigator nav, bool native) + private static void NavigatorValues(XPathNavigator nav, bool native) { // in non-native we can't have Value dump everything, else // we'd dump the entire database? Makes not much sense. @@ -106,7 +109,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // we have no way to tell the navigable that a value is CDATA // so the rule is, if it's null it's not there, anything else is there // and the filtering has to be done when building the content - Assert.IsTrue(nav.MoveToNext()); Assert.AreEqual("item2c", nav.Name); Assert.AreEqual("\n ", nav.Value.Lf()); // ok since it's a property @@ -166,7 +168,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var nav = new NavigableNavigator(source); nav.MoveToRoot(); - Assert.AreEqual("", nav.Name); // because we're at root + Assert.AreEqual(string.Empty, nav.Name); // because we're at root nav.MoveToFirstChild(); Assert.AreEqual("root", nav.Name); nav.MoveToFirstChild(); @@ -206,7 +208,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml nav.MoveToFirstChild(); // "poo" - Assert.AreEqual(XPathNodeType.Element, nav.NodeType); Assert.AreEqual("data", nav.Name); @@ -232,8 +233,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml { const string xml = @""; - var doc = XmlHelper.CreateXPathDocument(xml); - var nnav = doc.CreateNavigator(); + XPathDocument doc = XmlHelper.CreateXPathDocument(xml); + XPathNavigator nnav = doc.CreateNavigator(); Assert.AreEqual(xml, nnav.OuterXml); var source = new TestSource0(); @@ -285,7 +286,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource1(); var nav = new NavigableNavigator(source); - var iterator = nav.Select("//type1"); + XPathNodeIterator iterator = nav.Select("//type1"); Assert.AreEqual(1, iterator.Count); iterator.MoveNext(); Assert.AreEqual("type1", iterator.Current.Name); @@ -302,17 +303,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource2(); var nav = new NavigableNavigator(source); - var doc = XmlHelper.CreateXPathDocument("poo"); - var docNav = doc.CreateNavigator(); - var docIter = docNav.Select("//item2 [@xx=33]"); + XPathDocument doc = XmlHelper.CreateXPathDocument("poo"); + XPathNavigator docNav = doc.CreateNavigator(); + XPathNodeIterator docIter = docNav.Select("//item2 [@xx=33]"); Assert.AreEqual(1, docIter.Count); - Assert.AreEqual("", docIter.Current.Name); + Assert.AreEqual(string.Empty, docIter.Current.Name); docIter.MoveNext(); Assert.AreEqual("item2", docIter.Current.Name); - var iterator = nav.Select("//item2 [@xx=33]"); + XPathNodeIterator iterator = nav.Select("//item2 [@xx=33]"); Assert.AreEqual(1, iterator.Count); - Assert.AreEqual("", iterator.Current.Name); + Assert.AreEqual(string.Empty, iterator.Current.Name); iterator.MoveNext(); Assert.AreEqual("item2", iterator.Current.Name); } @@ -323,7 +324,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource1(); var nav = new NavigableNavigator(source); - var iterator = nav.Select("//* [@prop1=$var]", new XPathVariable("var", "1:p1")); + XPathNodeIterator iterator = nav.Select("//* [@prop1=$var]", new XPathVariable("var", "1:p1")); Assert.AreEqual(1, iterator.Count); iterator.MoveNext(); Assert.AreEqual("type1", iterator.Current.Name); @@ -335,7 +336,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource2(); var nav = new NavigableNavigator(source); - var iterator = nav.Select("//item2 [@xx=$var]", new XPathVariable("var", "33")); + XPathNodeIterator iterator = nav.Select("//item2 [@xx=$var]", new XPathVariable("var", "33")); Assert.AreEqual(1, iterator.Count); iterator.MoveNext(); Assert.AreEqual("item2", iterator.Current.Name); @@ -347,12 +348,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource4(); var nav = new NavigableNavigator(source); - var doc = XmlHelper.CreateXPathDocument(@" + XPathDocument doc = XmlHelper.CreateXPathDocument(@" dang "); - var docNav = doc.CreateNavigator(); + XPathNavigator docNav = doc.CreateNavigator(); docNav.MoveToRoot(); Assert.IsTrue(docNav.MoveToFirstChild()); @@ -366,7 +367,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsFalse(docNav.MoveToNext()); docNav.MoveToRoot(); - var docOuter = docNav.OuterXml; + string docOuter = docNav.OuterXml; nav.MoveToRoot(); Assert.IsTrue(nav.MoveToFirstChild()); @@ -380,7 +381,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsFalse(nav.MoveToNext()); nav.MoveToRoot(); - var outer = nav.OuterXml; + string outer = nav.OuterXml; Assert.AreEqual(docOuter, outer); } @@ -392,14 +393,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource1(); var nav = new NavigableNavigator(source); - var iterator = nav.Select("/*"); + XPathNodeIterator iterator = nav.Select("/*"); // but, that requires that the underlying navigator implements IHasXmlNode // so it is possible to obtain nodes from the navigator - not possible yet - var nodes = XmlNodeListFactory.CreateNodeList(iterator); + XmlNodeList nodes = XmlNodeListFactory.CreateNodeList(iterator); Assert.AreEqual(nodes.Count, 1); - var node = nodes[0]; + XmlNode node = nodes[0]; Assert.AreEqual(3, node.Attributes.Count); Assert.AreEqual("1", node.Attributes["id"].Value); @@ -434,7 +435,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.AreEqual(3, (nav.UnderlyingObject as TestContent).Id); // at that point nav is at /root/1/3 - var clone = nav.Clone() as NavigableNavigator; // move nav to /root/1/5 and ensure that clone stays at /root/1/3 @@ -475,7 +475,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource5(); var nav = new NavigableNavigator(source); - var iter = nav.Select(string.Format("//* [@id={0}]", id)); + XPathNodeIterator iter = nav.Select(string.Format("//* [@id={0}]", id)); Assert.IsTrue(iter.MoveNext()); var current = iter.Current as NavigableNavigator; Assert.AreEqual(NavigableNavigator.StatePosition.Element, current.InternalState.Position); @@ -493,7 +493,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var source = new TestSource5(); var nav = new NavigableNavigator(source); - var iter = nav.Select("//* [@id=$id]", new XPathVariable("id", id.ToString(CultureInfo.InvariantCulture))); + XPathNodeIterator iter = nav.Select("//* [@id=$id]", new XPathVariable("id", id.ToString(CultureInfo.InvariantCulture))); Assert.IsTrue(iter.MoveNext()); var current = iter.Current as NavigableNavigator; Assert.AreEqual(NavigableNavigator.StatePosition.Element, current.InternalState.Position); @@ -578,8 +578,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // go to (/root) /1/prop1 Assert.IsTrue(nav.MoveToFirstChild()); + // go to (/root) /1/prop2 Assert.IsTrue(nav.MoveToNext()); + // go to (/root) /1/3 Assert.IsTrue(nav.MoveToNext()); Assert.AreEqual(NavigableNavigator.StatePosition.Element, nav.InternalState.Position); @@ -666,13 +668,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // go to /root/1/prop1 Assert.IsTrue(nav.MoveToFirstChild()); + // go to /root/1/prop2 Assert.IsTrue(nav.MoveToNext()); + // can't go to /root/1/3 Assert.IsFalse(nav.MoveToNext()); Assert.IsFalse(nav.MoveToId("3")); - //Assert.AreEqual(NavigableNavigator.StatePosition.Element, nav.InternalState.Position); - //Assert.AreEqual(3, (nav.UnderlyingObject as TestContent).Id); + + //// Assert.AreEqual(NavigableNavigator.StatePosition.Element, nav.InternalState.Position); + //// Assert.AreEqual(3, (nav.UnderlyingObject as TestContent).Id); } [TestCase(true, true)] @@ -755,7 +760,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // see http://www.onenaught.com/posts/352/xslt-performance-tip-dont-indent-output // why aren't we using an XmlWriter here? - var transform = new XslCompiledTransform(debug); var xmlReader = new XmlTextReader(new StringReader(xslt)) { @@ -774,8 +778,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml if (!native) { var source = new TestSource7(); - var nav = new NavigableNavigator(source); - //args.AddParam("currentPage", string.Empty, nav.Clone()); + ////var nav = new NavigableNavigator(source); + ////args.AddParam("currentPage", string.Empty, nav.Clone()); var x = new XmlDocument(); x.LoadXml(xml); @@ -784,14 +788,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml { // it even fails like that => macro nav. issue? new MacroNavigator.MacroParameter("nav", x.CreateNavigator()) // nav.Clone()) - } - ); + }); } else { var doc = new XmlDocument(); doc.LoadXml(""); - var nav = doc.CreateElement("nav"); + XmlElement nav = doc.CreateElement("nav"); doc.DocumentElement.AppendChild(nav); var x = new XmlDocument(); x.LoadXml(xml); @@ -807,22 +810,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml // but was NOT working (changing the order of nodes) with macro nav, debug // was due to an issue with macro nav IsSamePosition, fixed - //Debug.Print("--------"); - //Debug.Print(writer.ToString()); + ////Debug.Print("--------"); + ////Debug.Print(writer.ToString()); Assert.AreEqual(expected.Lf(), writer.ToString().Lf()); } [Test] public void WhiteSpacesAndEmptyValues() { - // "When Microsoft’s DOM builder receives a text node from the parser // that contains only white space, it is thrown away." - so if it's ONLY // spaces, it's nothing, but spaces are NOT trimmed. // For attributes, spaces are preserved even when there's only spaces. - - var doc = XmlHelper.CreateXPathDocument(@" + XPathDocument doc = XmlHelper.CreateXPathDocument(@" @@ -835,9 +836,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "); - var docNav = doc.CreateNavigator(); + XPathNavigator docNav = doc.CreateNavigator(); - Assert.AreEqual(@" + Assert.AreEqual( + @" @@ -859,7 +861,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml -".Lf(), docNav.OuterXml.Lf()); +".Lf(), + docNav.OuterXml.Lf()); docNav.MoveToRoot(); Assert.IsTrue(docNav.MoveToFirstChild()); @@ -870,18 +873,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml Assert.IsTrue(docNav.MoveToNext()); Assert.IsTrue(docNav.MoveToFirstChild()); // prop Assert.IsFalse(docNav.IsEmptyElement); - Assert.AreEqual("", docNav.Value); // contains an empty text node + Assert.AreEqual(string.Empty, docNav.Value); // contains an empty text node Assert.IsTrue(docNav.MoveToParent()); Assert.IsTrue(docNav.MoveToNext()); Assert.IsTrue(docNav.MoveToFirstChild()); // prop Assert.IsFalse(docNav.IsEmptyElement); - Assert.AreEqual("", docNav.Value); // contains an empty text node + Assert.AreEqual(string.Empty, docNav.Value); // contains an empty text node var source = new TestSource8(); var nav = new NavigableNavigator(source); // shows how whitespaces are handled by NavigableNavigator - Assert.AreEqual(@" + Assert.AreEqual( + @" @@ -902,9 +906,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - #region Navigable implementation - - class TestPropertyType : INavigableFieldType + internal class TestPropertyType : INavigableFieldType { public TestPropertyType(string name, bool isXmlContent = false, Func xmlStringConverter = null) { @@ -914,11 +916,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } public string Name { get; private set; } + public bool IsXmlContent { get; private set; } + public Func XmlStringConverter { get; private set; } } - class TestContentType : INavigableContentType + internal class TestContentType : INavigableContentType { public TestContentType(TestSourceBase source, string name, params INavigableFieldType[] properties) { @@ -928,25 +932,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } public TestSourceBase Source { get; private set; } + public string Name { get; private set; } + public INavigableFieldType[] FieldTypes { get; protected set; } } - class TestRootContentType : TestContentType + internal class TestRootContentType : TestContentType { public TestRootContentType(TestSourceBase source, params INavigableFieldType[] properties) - : base(source, "root") - { - FieldTypes = properties; - } + : base(source, "root") => FieldTypes = properties; - public TestContentType CreateType(string name, params INavigableFieldType[] properties) - { - return new TestContentType(Source, name, FieldTypes.Union(properties).ToArray()); - } + public TestContentType CreateType(string name, params INavigableFieldType[] properties) => new TestContentType(Source, name, FieldTypes.Union(properties).ToArray()); } - class TestContent : INavigableContent + internal class TestContent : INavigableContent { public TestContent(TestContentType type, int id, int parentId) { @@ -956,39 +956,56 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } private readonly TestContentType _type; + public int Id { get; private set; } + public int ParentId { get; private set; } - public INavigableContentType Type { get { return _type; } } + + public INavigableContentType Type => _type; + public IList ChildIds { get; private set; } public object Value(int id) { - var fieldType = _type.FieldTypes[id] as TestPropertyType; - if (fieldType == null) throw new Exception("Oops"); + if (!(_type.FieldTypes[id] is TestPropertyType fieldType)) + { + throw new Exception("Oops"); + } - var value = FieldValues[id]; - var isAttr = id <= _type.Source.LastAttributeIndex; + object value = FieldValues[id]; + bool isAttr = id <= _type.Source.LastAttributeIndex; // null => return null - if (value == null) return null; + if (value == null) + { + return null; + } // attribute => return string value - if (isAttr) return value.ToString(); + if (isAttr) + { + return value.ToString(); + } // has a converter => use the converter if (fieldType.XmlStringConverter != null) + { return fieldType.XmlStringConverter(value); + } // not a string => return value as a string - var s = value as string; - if (s == null) return value.ToString(); + if (!(value is string s)) + { + return value.ToString(); + } // xml content... try xml if (fieldType.IsXmlContent) { - XPathDocument doc; - if (XmlHelper.TryCreateXPathDocumentFromPropertyValue(s, out doc)) + if (XmlHelper.TryCreateXPathDocumentFromPropertyValue(s, out XPathDocument doc)) + { return doc.CreateNavigator(); + } } // return the string @@ -1007,37 +1024,31 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml public TestContent WithValues(params object[] values) { - FieldValues = values == null ? new object[] {null} : values; + FieldValues = values ?? (new object[] { null }); return this; } } - class TestRootContent : TestContent + internal class TestRootContent : TestContent { public TestRootContent(TestContentType type) : base(type, -1, -1) - { } + { + } } - abstract class TestSourceBase : INavigableSource + internal abstract class TestSourceBase : INavigableSource { protected readonly Dictionary Content = new Dictionary(); - public INavigableContent Get(int id) - { - return Content.ContainsKey(id) ? Content[id] : null; - } + public INavigableContent Get(int id) => Content.ContainsKey(id) ? Content[id] : null; public int LastAttributeIndex { get; protected set; } public INavigableContent Root { get; protected set; } } - #endregion - - #region Navigable sources - - class TestSource0 : TestSourceBase + internal class TestSource0 : TestSourceBase { public TestSource0() { @@ -1047,7 +1058,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - class TestSource1 : TestSourceBase + internal class TestSource1 : TestSourceBase { public TestSource1() { @@ -1059,15 +1070,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop2 = new TestPropertyType("prop2"); var prop3 = new TestPropertyType("prop3"); var type = new TestRootContentType(this, prop1, prop2); - var type1 = type.CreateType("type1", prop3); + TestContentType type1 = type.CreateType("type1", prop3); Content[1] = new TestContent(type1, 1, -1).WithValues("1:p1", "1:p2", "1:p3"); - Root = new TestRootContent(type).WithValues("", "").WithChildren(1); + Root = new TestRootContent(type).WithValues(string.Empty, string.Empty).WithChildren(1); } } - class TestSource2 : TestSourceBase + internal class TestSource2 : TestSourceBase { public TestSource2() { @@ -1075,7 +1086,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop1 = new TestPropertyType("prop1", true); var type = new TestRootContentType(this); - var type1 = type.CreateType("type1", prop1); + TestContentType type1 = type.CreateType("type1", prop1); const string xml = "poo"; Content[1] = new TestContent(type1, 1, 1).WithValues(xml); @@ -1084,7 +1095,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - class TestSource3 : TestSourceBase + internal class TestSource3 : TestSourceBase { public TestSource3() { @@ -1094,7 +1105,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop2 = new TestPropertyType("prop2"); var prop3 = new TestPropertyType("prop3"); var type = new TestRootContentType(this, prop1, prop2); - var type1 = type.CreateType("type1", prop3); + TestContentType type1 = type.CreateType("type1", prop3); Content[1] = new TestContent(type1, 1, 1).WithValues("1:p1", "1:p2", "1:p3").WithChildren(2); Content[2] = new TestContent(type1, 2, 1).WithValues("2:p1", "2:p2", "2:p3"); @@ -1103,7 +1114,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - class TestSource4 : TestSourceBase + internal class TestSource4 : TestSourceBase { public TestSource4() { @@ -1112,17 +1123,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop1 = new TestPropertyType("prop1", true); var prop2 = new TestPropertyType("prop2"); var type = new TestRootContentType(this); - var type1 = type.CreateType("type1", prop1, prop2); + TestContentType type1 = type.CreateType("type1", prop1, prop2); Content[1] = new TestContent(type1, 1, -1).WithValues("", "dang"); - Content[2] = new TestContent(type1, 2, -1).WithValues(null, ""); + Content[2] = new TestContent(type1, 2, -1).WithValues(null, string.Empty); Content[3] = new TestContent(type1, 3, -1).WithValues(null, null); Root = new TestRootContent(type).WithChildren(1, 2, 3); } } - class TestSource5 : TestSourceBase + internal class TestSource5 : TestSourceBase { public TestSource5() { @@ -1131,7 +1142,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop1 = new TestPropertyType("prop1"); var prop2 = new TestPropertyType("prop2"); var type = new TestRootContentType(this); - var type1 = type.CreateType("type1", prop1, prop2); + TestContentType type1 = type.CreateType("type1", prop1, prop2); Content[1] = new TestContent(type1, 1, -1).WithValues("p11", "p12").WithChildren(3, 5); Content[2] = new TestContent(type1, 2, -1).WithValues("p21", "p22").WithChildren(4, 6); @@ -1144,32 +1155,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - class TestSource6 : TestSourceBase + internal class TestSource6 : TestSourceBase { - // - // - // - // - // - // - // - // - // blah - // - // bam - // - // - // - // - // + //// + //// + //// + //// + //// + //// + //// + //// + //// blah + //// + //// bam + //// + //// + //// + //// + //// public TestSource6() { LastAttributeIndex = -1; var type = new TestRootContentType(this); - var type1 = type.CreateType("wrap", + TestContentType type1 = type.CreateType( + "wrap", new TestPropertyType("item1"), new TestPropertyType("item2"), new TestPropertyType("item2a"), @@ -1178,8 +1190,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml new TestPropertyType("item3"), new TestPropertyType("item3a"), new TestPropertyType("item4", true), - new TestPropertyType("item5", true) - ); + new TestPropertyType("item5", true)); Content[1] = new TestContent(type1, 1, -1) .WithValues( @@ -1191,14 +1202,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "blah", "\n blah\n ", "bam", - "\n " - ); + "\n "); Root = new TestRootContent(type).WithChildren(1); } } - class TestSource7 : TestSourceBase + internal class TestSource7 : TestSourceBase { public TestSource7() { @@ -1207,7 +1217,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var prop1 = new TestPropertyType("isDoc"); var prop2 = new TestPropertyType("title"); var type = new TestRootContentType(this, prop1); - var type1 = type.CreateType("node", prop1, prop2); + TestContentType type1 = type.CreateType("node", prop1, prop2); Content[1] = new TestContent(type1, 1, -1).WithValues(1, "title-1").WithChildren(3, 5); Content[2] = new TestContent(type1, 2, -1).WithValues(1, "title-2").WithChildren(4, 6); @@ -1223,7 +1233,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml } } - class TestSource8 : TestSourceBase + internal class TestSource8 : TestSourceBase { public TestSource8() { @@ -1232,15 +1242,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml var attr = new TestPropertyType("attr"); var prop = new TestPropertyType("prop"); var type = new TestRootContentType(this, attr); - var type1 = type.CreateType("item", attr, prop); + TestContentType type1 = type.CreateType("item", attr, prop); Content[1] = new TestContent(type1, 1, -1).WithValues(null, null); - Content[2] = new TestContent(type1, 2, -1).WithValues("", ""); + Content[2] = new TestContent(type1, 2, -1).WithValues(string.Empty, string.Empty); Content[3] = new TestContent(type1, 3, -1).WithValues(" ", " "); - Content[4] = new TestContent(type1, 4, -1).WithValues("", "\n"); + Content[4] = new TestContent(type1, 4, -1).WithValues(string.Empty, "\n"); Content[5] = new TestContent(type1, 5, -1).WithValues(" ooo ", " ooo "); Root = new TestRootContent(type).WithValues(null).WithChildren(1, 2, 3, 4, 5); } } - - #endregion } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/RenamedRootNavigatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/RenamedRootNavigatorTests.cs index 5afc68955d..2b6b584ca8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/RenamedRootNavigatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/CoreXml/RenamedRootNavigatorTests.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Runtime.InteropServices; using System.Xml; using System.Xml.XPath; using NUnit.Framework; @@ -19,7 +22,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "); var nav = doc.CreateNavigator(); var xml = nav.OuterXml; - Assert.AreEqual(EnsureNativeLineEndings(@" + Assert.AreEqual( + EnsureNativeLineEndings(@" "), xml); @@ -35,7 +39,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "); var nav = doc.CreateNavigator(); var xml = nav.OuterXml; - Assert.AreEqual(EnsureNativeLineEndings(@" + Assert.AreEqual( + EnsureNativeLineEndings(@" "), xml); @@ -51,7 +56,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "); var nav = new RenamedRootNavigator(doc.CreateNavigator(), "test"); var xml = nav.OuterXml; - Assert.AreEqual(EnsureNativeLineEndings(@" + Assert.AreEqual( + EnsureNativeLineEndings(@" "), xml); @@ -73,7 +79,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.CoreXml "); var nav = new RenamedRootNavigator(doc.CreateNavigator(), "test"); var xml = nav.OuterXml; - Assert.AreEqual(EnsureNativeLineEndings(@" + Assert.AreEqual( + EnsureNativeLineEndings(@" "), xml); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs index a3e36c8ae6..cc4b716944 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/DelegateExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Lucene.Net.Index; using NUnit.Framework; using Umbraco.Core; @@ -13,11 +16,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { const int maxTries = 5; var totalTries = 0; - DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => - { - totalTries = currentTry; - return Attempt.Fail(); - }, 5, TimeSpan.FromMilliseconds(10)); + DelegateExtensions.RetryUntilSuccessOrMaxAttempts( + (currentTry) => + { + totalTries = currentTry; + return Attempt.Fail(); + }, + 5, + TimeSpan.FromMilliseconds(10)); Assert.AreEqual(maxTries, totalTries); } @@ -26,11 +32,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void Quits_On_Success_Count() { var totalTries = 0; - DelegateExtensions.RetryUntilSuccessOrMaxAttempts((currentTry) => - { - totalTries = currentTry; - return totalTries == 2 ? Attempt.Succeed() : Attempt.Fail(); - }, 5, TimeSpan.FromMilliseconds(10)); + DelegateExtensions.RetryUntilSuccessOrMaxAttempts( + (currentTry) => + { + totalTries = currentTry; + return totalTries == 2 ? Attempt.Succeed() : Attempt.Fail(); + }, + 5, + TimeSpan.FromMilliseconds(10)); Assert.AreEqual(2, totalTries); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs index d5ea4d2677..bf1e35a5ac 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.Trees; @@ -14,13 +17,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, false)] public void HasFlagTest(TreeUse value, TreeUse test, bool expected) { - // the built-in Enum.HasFlag() method determines whether - // all bits from are set (other bits can be set too) - + // The built-in Enum.HasFlag() method determines whether + // all bits from are set (other bits can be set too). if (expected) + { Assert.IsTrue(value.HasFlag(test)); + } else + { Assert.IsFalse(value.HasFlag(test)); + } } [Obsolete] @@ -30,13 +36,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, false)] public void HasFlagAllTest(TreeUse value, TreeUse test, bool expected) { - // the HasFlagAll() extension method determines whether - // all bits from are set (other bits can be set too) - + // The HasFlagAll() extension method determines whether + // all bits from are set (other bits can be set too). if (expected) + { Assert.IsTrue(value.HasFlagAll(test)); + } else + { Assert.IsFalse(value.HasFlagAll(test)); + } } [TestCase(TreeUse.Dialog, TreeUse.Dialog, true)] @@ -45,13 +54,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [TestCase(TreeUse.Dialog, TreeUse.Dialog | TreeUse.Main, true)] public void HasFlagAnyTest(TreeUse value, TreeUse test, bool expected) { - // the HasFlagAny() extension method determines whether - // at least one bit from is set - + // The HasFlagAny() extension method determines whether + // at least one bit from is set. if (expected) + { Assert.IsTrue(value.HasFlagAny(test)); + } else + { Assert.IsFalse(value.HasFlagAny(test)); + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs index 32e039f26a..72a1ce25c6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/EnumerableExtensionsTests.cs @@ -1,4 +1,8 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -27,9 +31,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void Contains_All() { - var list1 = new[] {1, 2, 3, 4, 5, 6}; - var list2 = new[] {6, 5, 3, 2, 1, 4}; - var list3 = new[] {6, 5, 4, 3}; + var list1 = new[] { 1, 2, 3, 4, 5, 6 }; + var list2 = new[] { 6, 5, 3, 2, 1, 4 }; + var list3 = new[] { 6, 5, 4, 3 }; Assert.IsTrue(list1.ContainsAll(list2)); Assert.IsTrue(list2.ContainsAll(list1)); @@ -42,15 +46,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var hierarchy = new TestItem("1") { - Children = new List - { - new TestItem("1.1"), - new TestItem("1.2"), - new TestItem("1.3") - } - }; + Children = new List + { + new TestItem("1.1"), + new TestItem("1.2"), + new TestItem("1.3") + } + }; - var selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); + IEnumerable selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); Assert.AreEqual(3, selectRecursive.Count()); } @@ -111,7 +115,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core } }; - var selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); + IEnumerable selectRecursive = hierarchy.Children.SelectRecursive(x => x.Children); Assert.AreEqual(10, selectRecursive.Count()); } @@ -122,7 +126,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Children = Enumerable.Empty(); Name = name; } + public string Name { get; } + public IEnumerable Children { get; set; } } @@ -131,7 +137,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var integers = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; - var groupsOfTwo = integers.InGroupsOf(2).ToArray(); + IEnumerable[] groupsOfTwo = integers.InGroupsOf(2).ToArray(); var flattened = groupsOfTwo.SelectMany(x => x).ToArray(); @@ -139,7 +145,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.That(flattened.Length, Is.EqualTo(integers.Length)); CollectionAssert.AreEquivalent(integers, flattened); - var groupsOfMassive = integers.InGroupsOf(100).ToArray(); + IEnumerable[] groupsOfMassive = integers.InGroupsOf(100).ToArray(); Assert.That(groupsOfMassive.Length, Is.EqualTo(1)); flattened = groupsOfMassive.SelectMany(x => x).ToArray(); Assert.That(flattened.Length, Is.EqualTo(integers.Length)); @@ -150,7 +156,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void InGroupsOf_CanRepeat() { var integers = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; - var inGroupsOf = integers.InGroupsOf(2); + IEnumerable> inGroupsOf = integers.InGroupsOf(2); Assert.AreEqual(5, inGroupsOf.Count()); Assert.AreEqual(5, inGroupsOf.Count()); // again } @@ -172,7 +178,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core }; // Act - var iteratorSource = list.DistinctBy(x => x.Item2); + IEnumerable> iteratorSource = list.DistinctBy(x => x.Item2); // Assert // First check distinction diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs index ad0f292fae..7b699e7b0c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/ClaimsPrincipalExtensionsTests.cs @@ -1,11 +1,13 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using System.Collections.Generic; using System.Linq; using System.Security.Claims; -using Umbraco.Extensions; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Security; +using Umbraco.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions { @@ -15,16 +17,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions [Test] public void Get_Remaining_Ticket_Seconds() { - var backOfficeIdentity = new UmbracoBackOfficeIdentity(Constants.Security.SuperUserIdAsString, "test", "test", - Enumerable.Empty(), Enumerable.Empty(), "en-US", Guid.NewGuid().ToString(), - Enumerable.Empty(), Enumerable.Empty()); + var backOfficeIdentity = new UmbracoBackOfficeIdentity( + Constants.Security.SuperUserIdAsString, + "test", + "test", + Enumerable.Empty(), + Enumerable.Empty(), + "en-US", + Guid.NewGuid().ToString(), + Enumerable.Empty(), + Enumerable.Empty()); var principal = new ClaimsPrincipal(backOfficeIdentity); var expireSeconds = 99; var elapsedSeconds = 3; var remainingSeconds = expireSeconds - elapsedSeconds; - var now = DateTimeOffset.Now; - var then = now.AddSeconds(elapsedSeconds); + DateTimeOffset now = DateTimeOffset.Now; + DateTimeOffset then = now.AddSeconds(elapsedSeconds); var expires = now.AddSeconds(expireSeconds).ToString("o"); backOfficeIdentity.AddClaim(new Claim( diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs index e832f13671..a072a1a189 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs @@ -1,12 +1,13 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Tests.Common; -using Umbraco.Tests.Common.Builders; using Umbraco.Web.Common.AspNetCore; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs index 62e0955d78..db16dbeb8b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/GuidUtilsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs index ecd2d13e78..e9b43852c3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashCodeCombinerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Reflection; using NUnit.Framework; @@ -11,10 +14,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { private DirectoryInfo PrepareFolder() { - var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", - Guid.NewGuid().ToString("N"))); - foreach (var f in dir.GetFiles()) + DirectoryInfo assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + DirectoryInfo dir = Directory.CreateDirectory( + Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N"))); + foreach (FileInfo f in dir.GetFiles()) { f.Delete(); } @@ -57,7 +60,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void HashCombiner_Test_DateTime() { - var dt = DateTime.Now; + DateTime dt = DateTime.Now; var combiner1 = new HashCodeCombiner(); combiner1.AddDateTime(dt); @@ -74,19 +77,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void HashCombiner_Test_File() { - var dir = PrepareFolder(); + DirectoryInfo dir = PrepareFolder(); var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); File.Delete(file1Path); - using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) + using (StreamWriter file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) { file1.WriteLine("hello"); } var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); File.Delete(file2Path); - using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) + using (StreamWriter file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) { - //even though files are the same, the dates are different + // even though files are the same, the dates are different file2.WriteLine("hello"); } @@ -110,15 +113,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void HashCombiner_Test_Folder() { - var dir = PrepareFolder(); + DirectoryInfo dir = PrepareFolder(); var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); File.Delete(file1Path); - using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) + using (StreamWriter file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) { file1.WriteLine("hello"); } - //first test the whole folder + // first test the whole folder var combiner1 = new HashCodeCombiner(); combiner1.AddFolder(dir); @@ -127,13 +130,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.AreEqual(combiner1.GetCombinedHashCode(), combiner2.GetCombinedHashCode()); - //now add a file to the folder - + // now add a file to the folder var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); File.Delete(file2Path); - using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) + using (StreamWriter file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) { - //even though files are the same, the dates are different + // even though files are the same, the dates are different file2.WriteLine("hello"); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs index 08f5b69cab..a0e75352dd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HashGeneratorTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Reflection; using NUnit.Framework; @@ -11,18 +14,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { private string Generate(bool isCaseSensitive, params string[] strs) { - using (var generator = new HashGenerator()) + using var generator = new HashGenerator(); + foreach (var str in strs) { - foreach (var str in strs) + if (isCaseSensitive) { - if (isCaseSensitive) - generator.AddString(str); - else - generator.AddCaseInsensitiveString(str); + generator.AddString(str); + } + else + { + generator.AddCaseInsensitiveString(str); } - - return generator.GenerateHash(); } + + return generator.GenerateHash(); } [Test] @@ -53,10 +58,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core private DirectoryInfo PrepareFolder() { - var assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; - var dir = Directory.CreateDirectory(Path.Combine(assDir.FullName, "HashCombiner", - Guid.NewGuid().ToString("N"))); - foreach (var f in dir.GetFiles()) + DirectoryInfo assDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory; + DirectoryInfo dir = Directory.CreateDirectory( + Path.Combine(assDir.FullName, "HashCombiner", Guid.NewGuid().ToString("N"))); + foreach (FileInfo f in dir.GetFiles()) { f.Delete(); } @@ -95,92 +100,85 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void HashCombiner_Test_DateTime() { - using (var combiner1 = new HashGenerator()) - using (var combiner2 = new HashGenerator()) - { - var dt = DateTime.Now; - combiner1.AddDateTime(dt); - combiner2.AddDateTime(dt); - Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); - combiner2.AddDateTime(DateTime.Now); - Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); - } + using var combiner1 = new HashGenerator(); + using var combiner2 = new HashGenerator(); + DateTime dt = DateTime.Now; + combiner1.AddDateTime(dt); + combiner2.AddDateTime(dt); + Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); + combiner2.AddDateTime(DateTime.Now); + Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); } [Test] public void HashCombiner_Test_File() { - using (var combiner1 = new HashGenerator()) - using (var combiner2 = new HashGenerator()) - using (var combiner3 = new HashGenerator()) + using var combiner1 = new HashGenerator(); + using var combiner2 = new HashGenerator(); + using var combiner3 = new HashGenerator(); + DirectoryInfo dir = PrepareFolder(); + var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); + File.Delete(file1Path); + using (StreamWriter file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) { - var dir = PrepareFolder(); - var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); - File.Delete(file1Path); - using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) - { - file1.WriteLine("hello"); - } - - var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); - File.Delete(file2Path); - using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) - { - //even though files are the same, the dates are different - file2.WriteLine("hello"); - } - - combiner1.AddFile(new FileInfo(file1Path)); - - combiner2.AddFile(new FileInfo(file1Path)); - - combiner3.AddFile(new FileInfo(file2Path)); - - Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); - Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash()); - - combiner2.AddFile(new FileInfo(file2Path)); - - Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); + file1.WriteLine("hello"); } + + var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); + File.Delete(file2Path); + using (StreamWriter file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) + { + // even though files are the same, the dates are different + file2.WriteLine("hello"); + } + + combiner1.AddFile(new FileInfo(file1Path)); + + combiner2.AddFile(new FileInfo(file1Path)); + + combiner3.AddFile(new FileInfo(file2Path)); + + Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); + Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash()); + + combiner2.AddFile(new FileInfo(file2Path)); + + Assert.AreNotEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); } [Test] public void HashCombiner_Test_Folder() { - using (var combiner1 = new HashGenerator()) - using (var combiner2 = new HashGenerator()) - using (var combiner3 = new HashGenerator()) + using var combiner1 = new HashGenerator(); + using var combiner2 = new HashGenerator(); + using var combiner3 = new HashGenerator(); + DirectoryInfo dir = PrepareFolder(); + var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); + File.Delete(file1Path); + using (StreamWriter file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) { - var dir = PrepareFolder(); - var file1Path = Path.Combine(dir.FullName, "hastest1.txt"); - File.Delete(file1Path); - using (var file1 = File.CreateText(Path.Combine(dir.FullName, "hastest1.txt"))) - { - file1.WriteLine("hello"); - } - - //first test the whole folder - combiner1.AddFolder(dir); - - combiner2.AddFolder(dir); - - Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); - - //now add a file to the folder - - var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); - File.Delete(file2Path); - using (var file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) - { - //even though files are the same, the dates are different - file2.WriteLine("hello"); - } - - combiner3.AddFolder(dir); - - Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash()); + file1.WriteLine("hello"); } + + // first test the whole folder + combiner1.AddFolder(dir); + + combiner2.AddFolder(dir); + + Assert.AreEqual(combiner1.GenerateHash(), combiner2.GenerateHash()); + + // now add a file to the folder + var file2Path = Path.Combine(dir.FullName, "hastest2.txt"); + File.Delete(file2Path); + using (StreamWriter file2 = File.CreateText(Path.Combine(dir.FullName, "hastest2.txt"))) + { + // even though files are the same, the dates are different + file2.WriteLine("hello"); + } + + combiner3.AddFolder(dir); + + Assert.AreNotEqual(combiner1.GenerateHash(), combiner3.GenerateHash()); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs index f22c3f2ac1..47d97ffaf8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/HexEncoderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Text; using NUnit.Framework; using Umbraco.Core; @@ -39,7 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.AreEqual(expected, actual); } - private static readonly char[] _bytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + private static readonly char[] s_bytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; // Reference implementation taken from original extension method. private static string ToHexString(byte[] bytes, char separator, int blockSize, int blockCount) @@ -49,8 +52,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core for (var i = 0; i < bytesLength; i++) { var b = bytes[i]; - chars[p++] = _bytesToHexStringLookup[b / 0x10]; - chars[p++] = _bytesToHexStringLookup[b % 0x10]; + chars[p++] = s_bytesToHexStringLookup[b / 0x10]; + chars[p++] = s_bytesToHexStringLookup[b % 0x10]; if (count == blockCount) { continue; @@ -65,6 +68,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core size = 0; count++; } + return new string(chars, 0, chars.Length); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/AbstractFileSystemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/AbstractFileSystemTests.cs index 3502c74494..71757c788d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/AbstractFileSystemTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/AbstractFileSystemTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -14,10 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO { protected IFileSystem _fileSystem; - protected AbstractFileSystemTests(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } + protected AbstractFileSystemTests(IFileSystem fileSystem) => _fileSystem = fileSystem; [Test] public void Can_Create_And_Delete_Files() @@ -28,7 +29,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.DeleteFile("test.txt"); - Assert.IsFalse(_fileSystem.FileExists("test.txt")); + Assert.IsFalse(_fileSystem.FileExists("test.txt")); } [Test] @@ -37,7 +38,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.AddFile("test/test.txt", CreateStream()); _fileSystem.AddFile("test/test.txt", CreateStream()); - var files = _fileSystem.GetFiles("test"); + IEnumerable files = _fileSystem.GetFiles("test"); Assert.AreEqual(1, files.Count()); @@ -50,16 +51,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO } [Test] - public void Cant_Overwrite_File() - { + public void Cant_Overwrite_File() => Assert.Throws(() => - { - _fileSystem.AddFile("test.txt", CreateStream()); - _fileSystem.AddFile("test.txt", CreateStream(), false); + { + _fileSystem.AddFile("test.txt", CreateStream()); + _fileSystem.AddFile("test.txt", CreateStream(), false); - _fileSystem.DeleteFile("test.txt"); - }); - } + _fileSystem.DeleteFile("test.txt"); + }); [Test] public void Can_Get_Files() @@ -69,7 +68,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.AddFile("test/test3.txt", CreateStream()); _fileSystem.AddFile("test/test4.bak", CreateStream()); - var files = _fileSystem.GetFiles("test"); + IEnumerable files = _fileSystem.GetFiles("test"); Assert.AreEqual(4, files.Count()); @@ -85,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO { _fileSystem.AddFile("test.txt", CreateStream("hello world")); - var stream = _fileSystem.OpenFile("test.txt"); + Stream stream = _fileSystem.OpenFile("test.txt"); var reader = new StreamReader(stream); var contents = reader.ReadToEnd(); reader.Close(); @@ -102,7 +101,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.AddFile("test/sub2/test.txt", CreateStream()); _fileSystem.AddFile("test/sub3/test.txt", CreateStream()); - var dirs = _fileSystem.GetDirectories("test"); + IEnumerable dirs = _fileSystem.GetDirectories("test"); Assert.AreEqual(3, dirs.Count()); Assert.IsTrue(_fileSystem.DirectoryExists("test/sub1")); @@ -119,8 +118,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.AddFile("test.txt", CreateStream()); - var created = _fileSystem.GetCreated("test.txt"); - var modified = _fileSystem.GetLastModified("test.txt"); + DateTimeOffset created = _fileSystem.GetCreated("test.txt"); + DateTimeOffset modified = _fileSystem.GetLastModified("test.txt"); Assert.AreEqual(DateTime.UtcNow.Year, created.Year); Assert.AreEqual(DateTime.UtcNow.Month, created.Month); @@ -142,7 +141,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO Assert.AreEqual(ConstructUrl("test.txt"), url); - _fileSystem.DeleteFile("test.txt"); + _fileSystem.DeleteFile("test.txt"); } [Test] @@ -165,7 +164,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO [Test] public void Can_Get_Size() { - var stream = CreateStream(); + Stream stream = CreateStream(); var streamLength = stream.Length; _fileSystem.AddFile("test.txt", stream); @@ -174,12 +173,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO _fileSystem.DeleteFile("test.txt"); } - #region Helper Methods - protected Stream CreateStream(string contents = null) { if (string.IsNullOrEmpty(contents)) + { contents = "test"; + } var bytes = Encoding.UTF8.GetBytes(contents); var stream = new MemoryStream(bytes); @@ -188,7 +187,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO } protected abstract string ConstructUrl(string path); - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/PhysicalFileSystemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/PhysicalFileSystemTests.cs index 7b98d77f58..5ec7e5873e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/PhysicalFileSystemTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/IO/PhysicalFileSystemTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.IO; using System.Text; using System.Threading; @@ -16,19 +19,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO { public PhysicalFileSystemTests() : base(new PhysicalFileSystem(TestHelper.IOHelper, TestHelper.GetHostingEnvironment(), Mock.Of>(), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"), "/Media/")) - { } + { + } [SetUp] public void Setup() { - } [TearDown] public void TearDown() { var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); - if (Directory.Exists(path) == false) return; + if (Directory.Exists(path) == false) + { + return; + } var files = Directory.GetFiles(path); foreach (var f in files) @@ -39,16 +45,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO Directory.Delete(path, true); } - protected override string ConstructUrl(string path) - { - return "/Media/" + path; - } + protected override string ConstructUrl(string path) => "/Media/" + path; private string Repeat(string pattern, int count) { var text = new StringBuilder(); for (var i = 0; i < count; i++) + { text.Append(pattern); + } + return text.ToString(); } @@ -58,7 +64,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo"))) + { _fileSystem.AddFile("sub/f3.txt", ms); + } Assert.IsTrue(File.Exists(Path.Combine(basePath, "sub/f3.txt"))); @@ -80,14 +88,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.IO // here we initialize the PhysicalFileSystem with // rootPath = /path/to/FileSysTests // rootUrl = /Media/ - var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"); // ensure that GetFullPath // - does return the proper full path // - does properly normalize separators // - does throw on invalid paths - // works var path = _fileSystem.GetFullPath("foo.tmp"); Assert.AreEqual(Path.Combine(basePath, @"foo.tmp"), path); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs index 92876f1b98..b3c75adfde 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestContentAppTests.cs @@ -1,14 +1,16 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using Moq; using Newtonsoft.Json; using NUnit.Framework; -using Umbraco.Core.IO; using Umbraco.Core.Manifest; using Umbraco.Core.Models; +using Umbraco.Core.Models.ContentEditing; using Umbraco.Core.Models.Membership; using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Composing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest { @@ -18,64 +20,68 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest [Test] public void Test() { - var contentType = Mock.Of(); + IContentType contentType = Mock.Of(); Mock.Get(contentType).Setup(x => x.Alias).Returns("type1"); - var content = Mock.Of(); + IContent content = Mock.Of(); Mock.Get(content).Setup(x => x.ContentType).Returns(new SimpleContentType(contentType)); - var group1 = Mock.Of(); + IReadOnlyUserGroup group1 = Mock.Of(); Mock.Get(group1).Setup(x => x.Alias).Returns("group1"); - var group2 = Mock.Of(); + IReadOnlyUserGroup group2 = Mock.Of(); Mock.Get(group2).Setup(x => x.Alias).Returns("group2"); // no rule = ok - AssertDefinition(content, true, Array.Empty(), new [] { group1, group2 }); + AssertDefinition(content, true, Array.Empty(), new[] { group1, group2 }); // wildcards = ok - AssertDefinition(content, true, new [] { "+content/*" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "+media/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/*" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/*" }, new[] { group1, group2 }); // explicitly enabling / disabling - AssertDefinition(content, true, new[] { "+content/type1" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "-content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/type1" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "-content/type1" }, new[] { group1, group2 }); // when there are type rules, failing to approve the type = no app - AssertDefinition(content, false, new[] { "+content/type2" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "+media/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+content/type2" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "+media/type1" }, new[] { group1, group2 }); // can have multiple rule, first one that matches = end - AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new [] { group1, group2 }); - AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new [] { group1, group2 }); - AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "-content/type1", "+content/*" }, new[] { group1, group2 }); + AssertDefinition(content, true, new[] { "-content/type2", "+content/*" }, new[] { group1, group2 }); + AssertDefinition(content, true, new[] { "+content/*", "-content/type1" }, new[] { group1, group2 }); // when there are role rules, failing to approve a role = no app - AssertDefinition(content, false, new[] { "+role/group33" }, new [] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group33" }, new[] { group1, group2 }); // wildcards = ok - AssertDefinition(content, true, new[] { "+role/*" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+role/*" }, new[] { group1, group2 }); // explicitly enabling / disabling - AssertDefinition(content, true, new[] { "+role/group1" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "-role/group1" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+role/group1" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "-role/group1" }, new[] { group1, group2 }); // can have multiple rule, first one that matches = end - AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+role/group1", "-role/group2" }, new[] { group1, group2 }); // mixed type and role rules, both are evaluated and need to match - AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new [] { group1, group2 }); - AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new [] { group1, group2 }); + AssertDefinition(content, true, new[] { "+role/group1", "+content/type1" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group1", "+content/type2" }, new[] { group1, group2 }); + AssertDefinition(content, false, new[] { "+role/group33", "+content/type1" }, new[] { group1, group2 }); } private void AssertDefinition(object source, bool expected, string[] show, IReadOnlyUserGroup[] groups) { - var definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? "" : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); + ManifestContentAppDefinition definition = JsonConvert.DeserializeObject("{" + (show.Length == 0 ? string.Empty : " \"show\": [" + string.Join(",", show.Select(x => "\"" + x + "\"")) + "] ") + "}"); var factory = new ManifestContentAppFactory(definition, TestHelper.IOHelper); - var app = factory.GetContentAppFor(source, groups); + ContentApp app = factory.GetContentAppFor(source, groups); if (expected) + { Assert.IsNotNull(app); + } else + { Assert.IsNull(app); + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs index e1a34e38a3..ccab1885bb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/ManifestParserTests.cs @@ -1,21 +1,24 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; -using Moq; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using NUnit.Framework; +using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.Logging; +using Umbraco.Core.Dashboards; +using Umbraco.Core.IO; using Umbraco.Core.Manifest; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.Validators; -using Umbraco.Core.Services; -using Umbraco.Core.Dashboards; -using Umbraco.Core.IO; using Umbraco.Core.Serialization; +using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.TestHelpers; @@ -37,14 +40,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest new DelimitedValueValidator(), }; _ioHelper = TestHelper.IOHelper; - var loggerFactory = NullLoggerFactory.Instance; + NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; _parser = new ManifestParser(AppCaches.Disabled, new ManifestValueValidatorCollection(validators), new ManifestFilterCollection(Array.Empty()), loggerFactory.CreateLogger(), loggerFactory, _ioHelper, TestHelper.GetHostingEnvironment(), Mock.Of(), Mock.Of(), new JsonNetSerializer(), Mock.Of(), Mock.Of()); } [Test] public void DelimitedValueValidator() { - const string json = @"{'propertyEditors': [ { alias: 'Test.Test2', @@ -64,7 +66,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(1, manifest.ParameterEditors.Length); Assert.AreEqual(1, manifest.ParameterEditors[0].GetValueEditor().Validators.Count); @@ -80,7 +82,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest [Test] public void CanParseComments() { - const string json1 = @" // this is a single-line comment { @@ -92,7 +93,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Manifest } "; - var jobject = (JObject) JsonConvert.DeserializeObject(json1); + var jobject = (JObject)JsonConvert.DeserializeObject(json1); Assert.AreEqual("2", jobject.Property("x").Value.ToString()); Assert.AreEqual("3", jobject.Property("y").Value.ToString()); Assert.AreEqual("4", jobject.Property("z").Value.ToString()); @@ -115,8 +116,8 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 [Test] public void CanParseManifest_ScriptsAndStylesheets() { - var json = "{}"; - var manifest = _parser.ParseManifest(json); + string json = "{}"; + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(0, manifest.Scripts.Length); json = "{javascript: []}"; @@ -139,7 +140,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.Throws(() => _parser.ParseManifest(json)); json = "{}"; - manifest = _parser.ParseManifest(json); + manifest = _parser.ParseManifest(json); Assert.AreEqual(0, manifest.Stylesheets.Length); json = "{css: []}"; @@ -154,8 +155,6 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Stylesheets.Length); - - json = "{propertyEditors: [], javascript: ['~/test.js', '~/test2.js'], css: ['~/stylesheet.css', '~/random-long-name.css']}"; manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Scripts.Length); @@ -212,10 +211,10 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.PropertyEditors.Length); - var editor = manifest.PropertyEditors[1]; + IDataEditor editor = manifest.PropertyEditors[1]; Assert.IsTrue((editor.Type & EditorType.MacroParameter) > 0); editor = manifest.PropertyEditors[0]; @@ -223,18 +222,18 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("Test 1", editor.Name); Assert.IsFalse((editor.Type & EditorType.MacroParameter) > 0); - var valueEditor = editor.GetValueEditor(); + IDataValueEditor valueEditor = editor.GetValueEditor(); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/MyEditor.html"), valueEditor.View); Assert.AreEqual("int", valueEditor.ValueType); Assert.IsTrue(valueEditor.HideLabel); // these two don't make much sense here - // valueEditor.RegexValidator; - // valueEditor.RequiredValidator; + //// valueEditor.RegexValidator; + //// valueEditor.RequiredValidator; - var validators = valueEditor.Validators; + List validators = valueEditor.Validators; Assert.AreEqual(2, validators.Count); - var validator = validators[0]; + IValueValidator validator = validators[0]; var v1 = validator as RequiredValidator; Assert.IsNotNull(v1); Assert.AreEqual("Required", v1.ValidationName); @@ -245,19 +244,19 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("\\d*", v2.Configuration); // this is not part of the manifest - var preValues = editor.GetConfigurationEditor().DefaultConfiguration; + IDictionary preValues = editor.GetConfigurationEditor().DefaultConfiguration; Assert.IsEmpty(preValues); - var preValueEditor = editor.GetConfigurationEditor(); + IConfigurationEditor preValueEditor = editor.GetConfigurationEditor(); Assert.IsNotNull(preValueEditor); Assert.IsNotNull(preValueEditor.Fields); Assert.AreEqual(2, preValueEditor.Fields.Count); - var f = preValueEditor.Fields[0]; + ConfigurationField f = preValueEditor.Fields[0]; Assert.AreEqual("key1", f.Key); Assert.AreEqual("Some config 1", f.Name); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/Views/pre-val1.html"), f.View); - var fvalidators = f.Validators; + List fvalidators = f.Validators; Assert.IsNotNull(fvalidators); Assert.AreEqual(1, fvalidators.Count); var fv = fvalidators[0] as RequiredValidator; @@ -294,27 +293,27 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(3, manifest.ParameterEditors.Length); Assert.IsTrue(manifest.ParameterEditors.All(x => (x.Type & EditorType.MacroParameter) > 0)); - var editor = manifest.ParameterEditors[1]; + IDataEditor editor = manifest.ParameterEditors[1]; Assert.AreEqual("parameter2", editor.Alias); Assert.AreEqual("Another parameter", editor.Name); - var config = editor.DefaultConfiguration; + IDictionary config = editor.DefaultConfiguration; Assert.AreEqual(1, config.Count); Assert.IsTrue(config.ContainsKey("key1")); Assert.AreEqual("some config val", config["key1"]); - var valueEditor = editor.GetValueEditor(); + IDataValueEditor valueEditor = editor.GetValueEditor(); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/PropertyEditors/CsvEditor.html"), valueEditor.View); editor = manifest.ParameterEditors[2]; Assert.Throws(() => { - var _ = editor.GetValueEditor(); + IDataValueEditor valueEditor = editor.GetValueEditor(); }); } @@ -353,20 +352,20 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 } ] }"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.GridEditors.Length); - var editor = manifest.GridEditors[0]; + GridEditor editor = manifest.GridEditors[0]; Assert.AreEqual("small-hero", editor.Alias); Assert.AreEqual("Small Hero", editor.Name); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPlugin/small-hero/editortemplate.html"), editor.View); Assert.AreEqual(_ioHelper.ResolveUrl("/Views/Partials/Grid/Editors/SmallHero.cshtml"), editor.Render); Assert.AreEqual("icon-presentation", editor.Icon); - var config = editor.Config; + IDictionary config = editor.Config; Assert.AreEqual(2, config.Count); Assert.IsTrue(config.ContainsKey("image")); - var c = config["image"]; + object c = config["image"]; Assert.IsInstanceOf(c); // FIXME: is this what we want? Assert.IsTrue(config.ContainsKey("link")); c = config["link"]; @@ -394,11 +393,11 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.ContentApps.Length); Assert.IsInstanceOf(manifest.ContentApps[0]); - var app0 = (ManifestContentAppDefinition) manifest.ContentApps[0]; + var app0 = (ManifestContentAppDefinition)manifest.ContentApps[0]; Assert.AreEqual("myPackageApp1", app0.Alias); Assert.AreEqual("My App1", app0.Name); Assert.AreEqual("icon-foo", app0.Icon); @@ -431,11 +430,11 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Dashboards.Length); Assert.IsInstanceOf(manifest.Dashboards[0]); - var db0 = manifest.Dashboards[0]; + ManifestDashboard db0 = manifest.Dashboards[0]; Assert.AreEqual("something", db0.Alias); Assert.AreEqual(100, db0.Weight); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/one.html"), db0.View); @@ -448,7 +447,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 Assert.AreEqual("foo", db0.AccessRules[1].Value); Assert.IsInstanceOf(manifest.Dashboards[1]); - var db1 = manifest.Dashboards[1]; + ManifestDashboard db1 = manifest.Dashboards[1]; Assert.AreEqual("something.else", db1.Alias); Assert.AreEqual(-1, db1.Weight); Assert.AreEqual(_ioHelper.ResolveUrl("/App_Plugins/MyPackage/Dashboards/two.html"), db1.View); @@ -464,7 +463,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 { ""alias"": ""hello"", ""name"": ""World"" } ]}"; - var manifest = _parser.ParseManifest(json); + PackageManifest manifest = _parser.ParseManifest(json); Assert.AreEqual(2, manifest.Sections.Length); Assert.AreEqual("content", manifest.Sections[0].Alias); Assert.AreEqual("hello", manifest.Sections[1].Alias); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/Item.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/Item.cs index 8593d01946..192daee0bf 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/Item.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/Item.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -16,13 +19,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections private int _id; private Guid _key; - protected Item() - { - _propertyChangedInfo = new Dictionary(); - } + protected Item() => _propertyChangedInfo = new Dictionary(); /// - /// Integer Id + /// Gets or sets the integer Id /// [DataMember] public int Id @@ -36,7 +36,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections } /// - /// Guid based Id + /// Gets or sets the Guid based Id /// /// The key is currectly used to store the Unique Id from the /// umbracoNode table, which many of the entities are based on. @@ -66,8 +66,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections public DateTime? DeleteDate { get; set; } /// - /// Gets or sets the WasCancelled flag, which is used to track - /// whether some action against an entity was cancelled through some event. + /// Gets or sets a value indicating whether some action against an entity was cancelled through some event. /// This only exists so we have a way to check if an event was cancelled through /// the new api, which also needs to take effect in the legacy api. /// @@ -86,7 +85,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) { if (_withChanges == false) + { return; + } _propertyChangedInfo[propertyInfo.Name] = true; @@ -96,7 +97,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections public virtual void ResetIdentity() { _hasIdentity = false; - _id = default(int); + _id = default; } /// @@ -111,15 +112,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections /// /// Method to call on entity saved/updated /// - internal virtual void UpdatingEntity() - { - UpdateDate = DateTime.Now; - } + internal virtual void UpdatingEntity() => UpdateDate = DateTime.Now; /// /// Tracks the properties that have changed /// - //private readonly IDictionary _propertyChangedInfo = new Dictionary(); private readonly IDictionary _propertyChangedInfo; /// @@ -127,24 +124,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections /// /// Name of the property to check /// True if Property is dirty, otherwise False - public virtual bool IsPropertyDirty(string propertyName) - { - return _propertyChangedInfo.Any(x => x.Key == propertyName); - } + public virtual bool IsPropertyDirty(string propertyName) => _propertyChangedInfo.Any(x => x.Key == propertyName); - public virtual IEnumerable GetDirtyProperties() - { - return _propertyChangedInfo.Keys; - } + public virtual IEnumerable GetDirtyProperties() => _propertyChangedInfo.Keys; /// /// Indicates whether the current entity is dirty. /// /// True if entity is dirty, otherwise False - public virtual bool IsDirty() - { - return _propertyChangedInfo.Any(); - } + public virtual bool IsDirty() => _propertyChangedInfo.Any(); /// /// Resets dirty properties by clearing the dictionary used to track changes. @@ -153,29 +141,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections /// Please note that resetting the dirty properties could potentially /// obstruct the saving of a new or updated entity. /// - public virtual void ResetDirtyProperties() - { - _propertyChangedInfo.Clear(); - } + public virtual void ResetDirtyProperties() => _propertyChangedInfo.Clear(); /// /// Disables change tracking. /// - public void DisableChangeTracking() - { - _withChanges = false; - } + public void DisableChangeTracking() => _withChanges = false; /// /// Enables change tracking. /// - public void EnableChangeTracking() - { - _withChanges = true; - } + public void EnableChangeTracking() => _withChanges = true; /// - /// Indicates whether the current entity has an identity, eg. Id. + /// Gets or sets a value indicating whether the current entity has an identity, eg. Id. /// public virtual bool HasIdentity { @@ -183,15 +162,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections protected set => _hasIdentity = value; } - public static bool operator ==(Item left, Item right) - { - return ReferenceEquals(left, right); - } + public static bool operator ==(Item left, Item right) => ReferenceEquals(left, right); - public static bool operator !=(Item left, Item right) - { - return !(left == right); - } + public static bool operator !=(Item left, Item right) => !(left == right); /*public virtual bool SameIdentityAs(IEntity other) { @@ -249,9 +222,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections return _hash.Value; }*/ - public object DeepClone() - { - return this.MemberwiseClone(); - } + public object DeepClone() => this.MemberwiseClone(); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/OrderItem.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/OrderItem.cs index 60c57dd1e5..91ccc3cac8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/OrderItem.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/OrderItem.cs @@ -1,7 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections -{ +{ public class OrderItem : Item { public readonly int PartNumber; @@ -10,8 +13,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections private int _quantity = 0; - public OrderItem(int partNumber, string description, - int quantity, double unitPrice) + public OrderItem(int partNumber, string description, int quantity, double unitPrice) { PartNumber = partNumber; Description = description; @@ -21,22 +23,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections public int Quantity { - get { return _quantity; } + get => _quantity; + set { if (value < 0) + { throw new ArgumentException("Quantity cannot be negative."); + } _quantity = value; } } - public override string ToString() - { - return string.Format( + public override string ToString() => + string.Format( "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", - PartNumber, _quantity, Description, UnitPrice, + PartNumber, + _quantity, + Description, + UnitPrice, UnitPrice * _quantity); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/PropertyCollectionTests.cs index 6c76cc29a2..643ad1f64e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/PropertyCollectionTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/SimpleOrder.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/SimpleOrder.cs index d8c47f45f4..86022f094b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/SimpleOrder.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/Collections/SimpleOrder.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -10,31 +13,32 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections // The parameterless constructor of the base class creates a // KeyedCollection with an internal dictionary. For this code // example, no other constructors are exposed. - // - public SimpleOrder() : base() { } - - public SimpleOrder(IEnumerable properties) + public SimpleOrder() + : base() { - Reset(properties); } + public SimpleOrder(IEnumerable properties) => Reset(properties); + // This is the only method that absolutely must be overridden, // because without it the KeyedCollection cannot extract the // keys from the items. The input parameter type is the // second generic type argument, in this case OrderItem, and // the return value type is the first generic type argument, // in this case int. - // - protected override int GetKeyForItem(OrderItem item) - { + protected override int GetKeyForItem(OrderItem item) => + // In this example, the key is the part number. - return item.PartNumber; - } + item.PartNumber; internal void Reset(IEnumerable properties) { Clear(); - foreach (var property in properties) Add(property); + foreach (OrderItem property in properties) + { + Add(property); + } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } @@ -46,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections protected override void RemoveItem(int index) { - var removed = this[index]; + OrderItem removed = this[index]; base.RemoveItem(index); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } @@ -63,16 +67,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models.Collections OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public new bool Contains(int partNumber) - { - return this.Any(x => x.PartNumber == partNumber); - } + public new bool Contains(int partNumber) => this.Any(x => x.PartNumber == partNumber); public event NotifyCollectionChangedEventHandler CollectionChanged; - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - CollectionChanged?.Invoke(this, args); - } + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentExtensionsTests.cs index eb46a2b919..556e855c83 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using Moq; using NUnit.Framework; @@ -15,11 +18,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_Reset_Clears_SavedPublishedState() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.PublishedState = PublishedState.Publishing; Assert.IsFalse(content.Published); @@ -31,15 +34,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_OnlyIfActuallyChanged_Content() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // if you assign a content property with its value it is not dirty // if you assign it with another value then back, it is dirty - content.ResetDirtyProperties(false); Assert.IsFalse(content.IsPropertyDirty("Published")); content.Published = true; @@ -56,16 +58,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_OnlyIfActuallyChanged_User() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - var prop = content.Properties.First(); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + IProperty prop = content.Properties.First(); // if you assign a user property with its value it is not dirty // if you assign it with another value then back, it is dirty - prop.SetValue("A"); content.ResetDirtyProperties(false); Assert.IsFalse(prop.IsDirty()); @@ -83,15 +84,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_UpdateDate() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - var prop = content.Properties.First(); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + IProperty prop = content.Properties.First(); content.ResetDirtyProperties(false); - var d = content.UpdateDate; + DateTime d = content.UpdateDate; prop.SetValue("A"); Assert.IsTrue(content.IsAnyUserPropertyDirty()); Assert.IsFalse(content.IsEntityDirty()); @@ -109,11 +110,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_WasDirty_ContentProperty() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); Assert.IsFalse(content.WasDirty()); @@ -139,11 +140,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_WasDirty_ContentSortOrder() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); Assert.IsFalse(content.WasDirty()); @@ -169,12 +170,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void DirtyProperty_WasDirty_UserProperty() { - var contentTypeService = Mock.Of(); - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + IContentTypeService contentTypeService = Mock.Of(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - var prop = content.Properties.First(); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + IProperty prop = content.Properties.First(); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); Assert.IsFalse(content.WasDirty()); @@ -189,13 +190,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models prop.SetValue("b"); content.ResetDirtyProperties(true); // what PersistUpdatedItem does Assert.IsFalse(content.IsDirty()); - //Assert.IsFalse(content.WasDirty()); // not impacted by user properties + //// Assert.IsFalse(content.WasDirty()); // not impacted by user properties Assert.IsTrue(content.WasDirty()); // now it is! prop.SetValue("a"); prop.SetValue("b"); content.ResetDirtyProperties(); // what PersistUpdatedItem does Assert.IsFalse(content.IsDirty()); - //Assert.IsFalse(content.WasDirty()); // not impacted by user properties + //// Assert.IsFalse(content.WasDirty()); // not impacted by user properties Assert.IsTrue(content.WasDirty()); // now it is! } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentScheduleTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentScheduleTests.cs index 36bf5418b3..bdf1591301 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentScheduleTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentScheduleTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; @@ -11,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Release_Date_Less_Than_Expire_Date() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); Assert.IsFalse(schedule.Add(now, now)); } @@ -19,7 +23,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Cannot_Add_Duplicate_Dates_Invariant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, null); Assert.Throws(() => schedule.Add(null, now)); @@ -28,7 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Cannot_Add_Duplicate_Dates_Variant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, null); schedule.Add("en-US", now, null); @@ -39,10 +43,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Remove_Invariant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, null); - var invariantSched = schedule.GetSchedule(string.Empty); + IEnumerable invariantSched = schedule.GetSchedule(string.Empty); schedule.Remove(invariantSched.First()); Assert.AreEqual(0, schedule.FullSchedule.Count()); } @@ -50,15 +54,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Remove_Variant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, null); schedule.Add("en-US", now, null); - var invariantSched = schedule.GetSchedule(string.Empty); + IEnumerable invariantSched = schedule.GetSchedule(string.Empty); schedule.Remove(invariantSched.First()); Assert.AreEqual(0, schedule.GetSchedule(string.Empty).Count()); Assert.AreEqual(1, schedule.FullSchedule.Count()); - var variantSched = schedule.GetSchedule("en-US"); + IEnumerable variantSched = schedule.GetSchedule("en-US"); schedule.Remove(variantSched.First()); Assert.AreEqual(0, schedule.GetSchedule("en-US").Count()); Assert.AreEqual(0, schedule.FullSchedule.Count()); @@ -67,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Clear_Start_Invariant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, now.AddDays(1)); @@ -81,7 +85,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Clear_End_Variant() { - var now = DateTime.Now; + DateTime now = DateTime.Now; var schedule = new ContentScheduleCollection(); schedule.Add(now, now.AddDays(1)); schedule.Add("en-US", now, now.AddDays(1)); @@ -102,6 +106,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(1, schedule.GetSchedule("en-US", ContentScheduleAction.Release).Count()); Assert.AreEqual(2, schedule.FullSchedule.Count()); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs index 6b226e7e59..d59dafcda0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -25,18 +28,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [TestFixture] public class ContentTests { - private IContentTypeService _contentTypeService = Mock.Of(); + private readonly IContentTypeService _contentTypeService = Mock.Of(); [Test] public void Variant_Culture_Names_Track_Dirty_Changes() { - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .WithContentVariation(ContentVariation.Culture) .Build(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithId(1) .WithVersionId(1) .WithName("content") @@ -45,33 +48,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models const string langFr = "fr-FR"; - Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); //hasn't been changed + Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); // hasn't been changed - Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + Thread.Sleep(500); // The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); //now it will be changed since the collection has changed - var frCultureName = content.CultureInfos[langFr]; + Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); // now it will be changed since the collection has changed + ContentCultureInfos frCultureName = content.CultureInfos[langFr]; Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); content.ResetDirtyProperties(); - Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); //it's been reset + Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); // it's been reset Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); - Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + Thread.Sleep(500); // The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); - Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); //it's true now since we've updated a name + Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); // it's true now since we've updated a name } [Test] public void Variant_Published_Culture_Names_Track_Dirty_Changes() { - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .WithContentVariation(ContentVariation.Culture) .Build(); - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithId(1) .WithVersionId(1) .WithName("content") @@ -82,38 +85,38 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models content.ChangeContentType(contentType); - Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); //hasn't been changed + Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); // hasn't been changed - Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + Thread.Sleep(500); // The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(CultureImpact.Explicit(langFr, false)); //we've set the name, now we're publishing it - Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //now it will be changed since the collection has changed - var frCultureName = content.PublishCultureInfos[langFr]; + content.PublishCulture(CultureImpact.Explicit(langFr, false)); // we've set the name, now we're publishing it + Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); // now it will be changed since the collection has changed + ContentCultureInfos frCultureName = content.PublishCultureInfos[langFr]; Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); content.ResetDirtyProperties(); - Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); //it's been reset + Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); // it's been reset Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); - Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date + Thread.Sleep(500); // The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(CultureImpact.Explicit(langFr, false)); //we've set the name, now we're publishing it + content.PublishCulture(CultureImpact.Explicit(langFr, false)); // we've set the name, now we're publishing it Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); - Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name + Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); // it's true now since we've updated a name } [Test] public void Get_Non_Grouped_Properties() { - var contentType = ContentTypeBuilder.CreateSimpleContentType(); + ContentType contentType = ContentTypeBuilder.CreateSimpleContentType(); // Add non-grouped properties - var pt1 = new PropertyTypeBuilder() + PropertyType pt1 = new PropertyTypeBuilder() .WithAlias("nonGrouped1") .WithName("Non Grouped 1") .Build(); - var pt2 = new PropertyTypeBuilder() + PropertyType pt2 = new PropertyTypeBuilder() .WithAlias("nonGrouped2") .WithName("Non Grouped 2") .Build(); @@ -124,9 +127,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models contentType.ResetDirtyProperties(false); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateSimpleContent(contentType); + Content content = ContentBuilder.CreateSimpleContent(contentType); - var nonGrouped = content.GetNonGroupedProperties(); + IEnumerable nonGrouped = content.GetNonGroupedProperties(); Assert.AreEqual(2, nonGrouped.Count()); Assert.AreEqual(5, content.Properties.Count()); @@ -135,15 +138,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void All_Dirty_Properties_Get_Reset() { - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(false); Assert.IsFalse(content.IsDirty()); - foreach (var prop in content.Properties) + foreach (IProperty prop in content.Properties) { Assert.IsFalse(prop.IsDirty()); } @@ -153,10 +156,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Verify_Mocked_Content() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act @@ -168,10 +171,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_Property_Value() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.Properties["title"].SetValue("This is the new title"); @@ -186,10 +189,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Set_Property_Value_As_String() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.SetValue("title", "This is the new title"); @@ -204,15 +207,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Clone_Content_With_Reset_Identity() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.Id = 10; content.Key = new Guid("29181B97-CB8F-403F-86DE-5FEB497F4800"); // Act - var clone = content.DeepCloneWithResetIdentities(); + IContent clone = content.DeepCloneWithResetIdentities(); // Assert Assert.AreNotSame(clone, content); @@ -225,7 +228,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private static IProfilingLogger GetTestProfilingLogger() { - var logger = NullLoggerFactory.Instance.CreateLogger(); + ILogger logger = NullLoggerFactory.Instance.CreateLogger(); var profiler = new TestProfiler(); return new ProfilingLogger(logger, profiler); } @@ -235,14 +238,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Deep_Clone_Perf_Test() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.Id = 99; - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - var i = 200; - foreach (var property in content.Properties) + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + int i = 200; + foreach (IProperty property in content.Properties) { property.Id = ++i; } + content.Id = 10; content.CreateDate = DateTime.Now; content.CreatorId = 22; @@ -250,7 +254,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models content.Level = 3; content.Path = "-1,4,10"; content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1)); - //content.ChangePublishedState(PublishedState.Published); + //// content.ChangePublishedState(PublishedState.Published); content.SortOrder = 5; content.TemplateId = 88; content.Trashed = false; @@ -260,13 +264,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models var runtimeCache = new ObjectCacheAppCache(); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); - var proflog = GetTestProfilingLogger(); + IProfilingLogger proflog = GetTestProfilingLogger(); using (proflog.DebugDuration("STARTING PERF TEST WITH RUNTIME CACHE")) { for (int j = 0; j < 1000; j++) { - var clone = runtimeCache.Get(content.Id.ToString(CultureInfo.InvariantCulture)); + object clone = runtimeCache.Get(content.Id.ToString(CultureInfo.InvariantCulture)); } } @@ -283,12 +287,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Deep_Clone() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.Id = 99; contentType.Variations = ContentVariation.Culture; Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); @@ -297,11 +301,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // should not try to clone something that's not Published or Unpublished // (and in fact it will not work) // but we cannot directly set the state to Published - hence this trick - //content.ChangePublishedState(PublishedState.Publishing); + // content.ChangePublishedState(PublishedState.Publishing); content.ResetDirtyProperties(false); // => .Published - var i = 200; - foreach (var property in content.Properties) + int i = 200; + foreach (IProperty property in content.Properties) { property.Id = ++i; } @@ -347,7 +351,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.WriterId, content.WriterId); Assert.AreNotSame(clone.Properties, content.Properties); Assert.AreEqual(clone.Properties.Count(), content.Properties.Count()); - for (var index = 0; index < content.Properties.Count; index++) + for (int index = 0; index < content.Properties.Count; index++) { Assert.AreNotSame(clone.Properties[index], content.Properties[index]); Assert.AreEqual(clone.Properties[index], content.Properties[index]); @@ -355,7 +359,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.PublishCultureInfos, content.PublishCultureInfos); Assert.AreEqual(clone.PublishCultureInfos.Count, content.PublishCultureInfos.Count); - foreach (var key in content.PublishCultureInfos.Keys) + foreach (string key in content.PublishCultureInfos.Keys) { Assert.AreNotSame(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); Assert.AreEqual(clone.PublishCultureInfos[key], content.PublishCultureInfos[key]); @@ -363,28 +367,27 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.CultureInfos, content.CultureInfos); Assert.AreEqual(clone.CultureInfos.Count, content.CultureInfos.Count); - foreach (var key in content.CultureInfos.Keys) + foreach (string key in content.CultureInfos.Keys) { Assert.AreNotSame(clone.CultureInfos[key], content.CultureInfos[key]); Assert.AreEqual(clone.CultureInfos[key], content.CultureInfos[key]); } // This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + System.Reflection.PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (System.Reflection.PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(content, null)); } // Need to ensure the event handlers are wired - var asDirty = (ICanBeDirty)clone; Assert.IsFalse(asDirty.IsPropertyDirty("Properties")); - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("blah") .Build(); - var newProperty = new PropertyBuilder() + IProperty newProperty = new PropertyBuilder() .WithId(1) .WithPropertyType(propertyType) .Build(); @@ -398,19 +401,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Remember_Dirty_Properties() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.Id = 99; contentType.Variations = ContentVariation.Culture; Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); content.PublishCulture(CultureImpact.All); - var i = 200; - foreach (var property in content.Properties) + int i = 200; + foreach (IProperty property in content.Properties) { property.Id = ++i; } @@ -445,14 +448,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.IsTrue(content.WasPropertyDirty(nameof(Content.Trashed))); Assert.IsTrue(content.WasPropertyDirty(nameof(Content.UpdateDate))); Assert.IsTrue(content.WasPropertyDirty(nameof(Content.WriterId))); - foreach (var prop in content.Properties) + foreach (IProperty prop in content.Properties) { Assert.IsTrue(prop.WasDirty()); Assert.IsTrue(prop.WasPropertyDirty("Id")); } Assert.IsTrue(content.WasPropertyDirty("CultureInfos")); - foreach (var culture in content.CultureInfos) + foreach (ContentCultureInfos culture in content.CultureInfos) { Assert.IsTrue(culture.WasDirty()); Assert.IsTrue(culture.WasPropertyDirty("Name")); @@ -460,7 +463,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models } Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos")); - foreach (var culture in content.PublishCultureInfos) + foreach (ContentCultureInfos culture in content.PublishCultureInfos) { Assert.IsTrue(culture.WasDirty()); Assert.IsTrue(culture.WasPropertyDirty("Name")); @@ -472,13 +475,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Serialize_Without_Error() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.Id = 99; Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - var i = 200; - foreach (var property in content.Properties) + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + int i = 200; + foreach (IProperty property in content.Properties) { property.Id = ++i; } @@ -490,14 +493,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models content.Level = 3; content.Path = "-1,4,10"; content.ContentSchedule.Add(DateTime.Now, DateTime.Now.AddDays(1)); - //content.ChangePublishedState(PublishedState.Publishing); + //// content.ChangePublishedState(PublishedState.Publishing); content.SortOrder = 5; content.TemplateId = 88; content.Trashed = false; content.UpdateDate = DateTime.Now; content.WriterId = 23; - var json = JsonConvert.SerializeObject(content); + string json = JsonConvert.SerializeObject(content); Debug.Print(json); } @@ -526,10 +529,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_Property_Value_Through_Anonymous_Object() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.PropertyValues(new { title = "This is the new title" }); @@ -546,10 +549,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Verify_Dirty_Property_On_Content() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.ResetDirtyProperties(); @@ -564,7 +567,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Add_PropertyGroup_On_ContentType() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); // Act contentType.PropertyGroups.Add(new PropertyGroup(true) { Name = "Test Group", SortOrder = 3 }); @@ -577,7 +580,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Remove_PropertyGroup_From_ContentType() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.ResetDirtyProperties(); // Act @@ -585,17 +588,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // Assert Assert.That(contentType.PropertyGroups.Count, Is.EqualTo(1)); - //Assert.That(contentType.IsPropertyDirty("PropertyGroups"), Is.True); + //// Assert.That(contentType.IsPropertyDirty("PropertyGroups"), Is.True); } [Test] public void Can_Add_PropertyType_To_Group_On_ContentType() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); // Act - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("subtitle") .WithName("Subtitle") .Build(); @@ -609,13 +612,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Add_New_Property_To_New_PropertyType() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("subtitle") .WithName("Subtitle") .Build(); @@ -633,13 +636,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Add_New_Property_To_New_PropertyType_In_New_PropertyGroup() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("subtitle") .WithName("Subtitle") .Build(); @@ -660,13 +663,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Update_PropertyType_Through_Content_Properties() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act - note that the PropertyType's properties like SortOrder is not updated through the Content object - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("title") .WithName("Title") .Build(); @@ -682,18 +685,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_ContentType_On_Content() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); - var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.ChangeContentType(simpleContentType); // Assert Assert.That(content.Properties.Contains("author"), Is.True); - //Note: There was 4 properties, after changing ContentType 1 has been added (no properties are deleted) + + // Note: There were 4 properties, after changing ContentType 1 has been added (no properties are deleted). Assert.That(content.Properties.Count, Is.EqualTo(5)); } @@ -701,11 +705,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_ContentType_On_Content_And_Set_Property_Value() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); - var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.ChangeContentType(simpleContentType); @@ -720,11 +724,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_ContentType_On_Content_And_Still_Get_Old_Properties() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); - var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); // Act content.ChangeContentType(simpleContentType); @@ -742,29 +746,30 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Change_ContentType_On_Content_And_Clear_Old_PropertyTypes() { throw new NotImplementedException(); - //Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - //// Arrange - //var contentType = ContentTypeBuilder.CreateTextPageContentType(); - //var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); - //var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + //// Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - //// Act - //content.ChangeContentType(simpleContentType, true); + ////// Arrange + //// var contentType = ContentTypeBuilder.CreateTextPageContentType(); + //// var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + //// var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); - //// Assert - //Assert.That(content.Properties.Contains("author"), Is.True); - //Assert.That(content.Properties.Contains("keywords"), Is.False); - //Assert.That(content.Properties.Contains("description"), Is.False); + ////// Act + //// content.ChangeContentType(simpleContentType, true); + + ////// Assert + //// Assert.That(content.Properties.Contains("author"), Is.True); + //// Assert.That(content.Properties.Contains("keywords"), Is.False); + //// Assert.That(content.Properties.Contains("description"), Is.False); } [Test] public void Can_Verify_Content_Is_Published() { - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "Textpage", -1); content.ResetDirtyProperties(); content.PublishedState = PublishedState.Publishing; @@ -797,7 +802,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Adding_PropertyGroup_To_ContentType_Results_In_Dirty_Entity() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.ResetDirtyProperties(); // Act @@ -807,19 +812,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // Assert Assert.That(contentType.IsDirty(), Is.True); Assert.That(contentType.PropertyGroups.Any(x => x.Name == "Test Group"), Is.True); - //Assert.That(contentType.IsPropertyDirty("PropertyGroups"), Is.True); + //// Assert.That(contentType.IsPropertyDirty("PropertyGroups"), Is.True); } [Test] public void After_Committing_Changes_Was_Dirty_Is_True() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); - contentType.ResetDirtyProperties(); //reset + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); + contentType.ResetDirtyProperties(); // reset // Act contentType.Alias = "newAlias"; - contentType.ResetDirtyProperties(); //this would be like committing the entity + contentType.ResetDirtyProperties(); // this would be like committing the entity // Assert Assert.That(contentType.IsDirty(), Is.False); @@ -831,11 +836,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void After_Committing_Changes_Was_Dirty_Is_True_On_Changed_Property() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); - contentType.ResetDirtyProperties(); //reset + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); + contentType.ResetDirtyProperties(); // reset Mock.Get(_contentTypeService).As().Setup(x => x.Get(It.IsAny())).Returns(contentType); - var content = ContentBuilder.CreateTextpageContent(contentType, "test", -1); + Content content = ContentBuilder.CreateTextpageContent(contentType, "test", -1); content.ResetDirtyProperties(); // Act @@ -848,13 +853,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.That(content.Properties[0].IsDirty(), Is.True); Assert.That(content.Properties["title"].IsDirty(), Is.True); - content.ResetDirtyProperties(); //this would be like committing the entity + content.ResetDirtyProperties(); // this would be like committing the entity // Assert Assert.That(content.WasDirty(), Is.True); Assert.That(content.Properties[0].WasDirty(), Is.True); - Assert.That(content.WasPropertyDirty("title"), Is.True); Assert.That(content.Properties["title"].IsDirty(), Is.False); Assert.That(content.Properties["title"].WasDirty(), Is.True); @@ -864,7 +868,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void If_Not_Committed_Was_Dirty_Is_False() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); // Act contentType.Alias = "newAlias"; @@ -878,7 +882,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Detect_That_A_Property_Is_Removed() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); Assert.That(contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"), Is.False); // Act @@ -892,11 +896,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Adding_PropertyType_To_PropertyGroup_On_ContentType_Results_In_Dirty_Entity() { // Arrange - var contentType = ContentTypeBuilder.CreateTextPageContentType(); + ContentType contentType = ContentTypeBuilder.CreateTextPageContentType(); contentType.ResetDirtyProperties(); // Act - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("subtitle") .WithName("Subtitle") .Build(); @@ -912,18 +916,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Compose_Composite_ContentType_Collection() { // Arrange - var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); - var propertyType = new PropertyTypeBuilder() + ContentType simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("coauthor") .WithName("Co-author") .Build(); - var simple2ContentType = ContentTypeBuilder.CreateSimpleContentType("anotherSimple", "Another Simple Page", + ContentType simple2ContentType = ContentTypeBuilder.CreateSimpleContentType( + "anotherSimple", + "Another Simple Page", propertyTypeCollection: new PropertyTypeCollection(true, new List { propertyType })); // Act - var added = simpleContentType.AddContentType(simple2ContentType); - var compositionPropertyGroups = simpleContentType.CompositionPropertyGroups; - var compositionPropertyTypes = simpleContentType.CompositionPropertyTypes; + bool added = simpleContentType.AddContentType(simple2ContentType); + IEnumerable compositionPropertyGroups = simpleContentType.CompositionPropertyGroups; + IEnumerable compositionPropertyTypes = simpleContentType.CompositionPropertyTypes; // Assert Assert.That(added, Is.True); @@ -935,20 +941,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Compose_Nested_Composite_ContentType_Collection() { // Arrange - var metaContentType = ContentTypeBuilder.CreateMetaContentType(); - var simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); - var propertyType = new PropertyTypeBuilder() + ContentType metaContentType = ContentTypeBuilder.CreateMetaContentType(); + ContentType simpleContentType = ContentTypeBuilder.CreateSimpleContentType(); + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("coauthor") .WithName("Co-author") .Build(); - var simple2ContentType = ContentTypeBuilder.CreateSimpleContentType("anotherSimple", "Another Simple Page", + ContentType simple2ContentType = ContentTypeBuilder.CreateSimpleContentType( + "anotherSimple", + "Another Simple Page", propertyTypeCollection: new PropertyTypeCollection(true, new List { propertyType })); // Act - var addedMeta = simple2ContentType.AddContentType(metaContentType); - var added = simpleContentType.AddContentType(simple2ContentType); - var compositionPropertyGroups = simpleContentType.CompositionPropertyGroups; - var compositionPropertyTypes = simpleContentType.CompositionPropertyTypes; + bool addedMeta = simple2ContentType.AddContentType(metaContentType); + bool added = simpleContentType.AddContentType(simple2ContentType); + IEnumerable compositionPropertyGroups = simpleContentType.CompositionPropertyGroups; + IEnumerable compositionPropertyTypes = simpleContentType.CompositionPropertyTypes; // Assert Assert.That(addedMeta, Is.True); @@ -961,35 +969,39 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Avoid_Circular_Dependencies_In_Composition() { - var textPage = ContentTypeBuilder.CreateTextPageContentType(); - var parent = ContentTypeBuilder.CreateSimpleContentType("parent", "Parent", null, randomizeAliases: true); - var meta = ContentTypeBuilder.CreateMetaContentType(); - var propertyType1 = new PropertyTypeBuilder() + ContentType textPage = ContentTypeBuilder.CreateTextPageContentType(); + ContentType parent = ContentTypeBuilder.CreateSimpleContentType("parent", "Parent", null, randomizeAliases: true); + ContentType meta = ContentTypeBuilder.CreateMetaContentType(); + PropertyType propertyType1 = new PropertyTypeBuilder() .WithAlias("coauthor") .WithName("Co-author") .Build(); - var mixin1 = ContentTypeBuilder.CreateSimpleContentType("mixin1", "Mixin1", + ContentType mixin1 = ContentTypeBuilder.CreateSimpleContentType( + "mixin1", + "Mixin1", propertyTypeCollection: new PropertyTypeCollection(true, new List { propertyType1 })); - var propertyType2 = new PropertyTypeBuilder() + PropertyType propertyType2 = new PropertyTypeBuilder() .WithAlias("author") .WithName("Author") .Build(); - var mixin2 = ContentTypeBuilder.CreateSimpleContentType("mixin2", "Mixin2", + ContentType mixin2 = ContentTypeBuilder.CreateSimpleContentType( + "mixin2", + "Mixin2", propertyTypeCollection: new PropertyTypeCollection(true, new List { propertyType2 })); // Act - var addedMetaMixin2 = mixin2.AddContentType(meta); - var addedMixin2 = mixin1.AddContentType(mixin2); - var addedMeta = parent.AddContentType(meta); + bool addedMetaMixin2 = mixin2.AddContentType(meta); + bool addedMixin2 = mixin1.AddContentType(mixin2); + bool addedMeta = parent.AddContentType(meta); - var addedMixin1 = parent.AddContentType(mixin1); + bool addedMixin1 = parent.AddContentType(mixin1); - var addedMixin1Textpage = textPage.AddContentType(mixin1); - var addedTextpageParent = parent.AddContentType(textPage); + bool addedMixin1Textpage = textPage.AddContentType(mixin1); + bool addedTextpageParent = parent.AddContentType(textPage); - var aliases = textPage.CompositionAliases(); - var propertyTypes = textPage.CompositionPropertyTypes; - var propertyGroups = textPage.CompositionPropertyGroups; + IEnumerable aliases = textPage.CompositionAliases(); + IEnumerable propertyTypes = textPage.CompositionPropertyTypes; + IEnumerable propertyGroups = textPage.CompositionPropertyGroups; // Assert Assert.That(mixin2.ContentTypeCompositionExists("meta"), Is.True); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeTests.cs index c571a79785..3970fb9f53 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTypeTests.cs @@ -1,6 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -17,10 +21,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")] public void Cannot_Add_Duplicate_Property_Aliases() { - var contentType = BuildContentType(); + ContentType contentType = BuildContentType(); var propertyTypeBuilder = new PropertyTypeBuilder(); - var additionalPropertyType = propertyTypeBuilder + PropertyType additionalPropertyType = propertyTypeBuilder .WithAlias("title") .Build(); @@ -32,16 +36,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Ignore("Ignoring this test until we actually enforce this, see comments in ContentTypeBase.PropertyTypesChanged")] public void Cannot_Update_Duplicate_Property_Aliases() { - var contentType = BuildContentType(); + ContentType contentType = BuildContentType(); var propertyTypeBuilder = new PropertyTypeBuilder(); - var additionalPropertyType = propertyTypeBuilder + PropertyType additionalPropertyType = propertyTypeBuilder .WithAlias("title") .Build(); contentType.PropertyTypeCollection.Add(additionalPropertyType); - var toUpdate = contentType.PropertyTypeCollection["myPropertyType2"]; + IPropertyType toUpdate = contentType.PropertyTypeCollection["myPropertyType2"]; Assert.Throws(() => toUpdate.Alias = "myPropertyType"); } @@ -49,7 +53,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Deep_Clone_Content_Type_Sort() { - var contentType = BuildContentTypeSort(); + ContentTypeSort contentType = BuildContentTypeSort(); var clone = (ContentTypeSort)contentType.DeepClone(); Assert.AreNotSame(clone, contentType); Assert.AreEqual(clone, contentType); @@ -71,7 +75,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Deep_Clone_Content_Type_With_Reset_Identities() { - var contentType = BuildContentType(); + ContentType contentType = BuildContentType(); var clone = (ContentType)contentType.DeepCloneWithResetIdentities("newAlias"); @@ -79,22 +83,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotEqual("newAlias", contentType.Alias); Assert.IsFalse(clone.HasIdentity); - foreach (var propertyGroup in clone.PropertyGroups) + foreach (PropertyGroup propertyGroup in clone.PropertyGroups) { Assert.IsFalse(propertyGroup.HasIdentity); - foreach (var propertyType in propertyGroup.PropertyTypes) + foreach (IPropertyType propertyType in propertyGroup.PropertyTypes) + { Assert.IsFalse(propertyType.HasIdentity); + } } - foreach (var propertyType in clone.PropertyTypes.Where(x => x.HasIdentity)) + foreach (IPropertyType propertyType in clone.PropertyTypes.Where(x => x.HasIdentity)) + { Assert.IsFalse(propertyType.HasIdentity); - } + } + } [Test] public void Can_Deep_Clone_Content_Type() { // Arrange - var contentType = BuildContentType(); + ContentType contentType = BuildContentType(); // Act var clone = (ContentType)contentType.DeepClone(); @@ -109,6 +117,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); Assert.AreEqual(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); } + Assert.AreNotSame(clone.PropertyGroups, contentType.PropertyGroups); Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); for (var index = 0; index < contentType.PropertyGroups.Count; index++) @@ -116,6 +125,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); } + Assert.AreNotSame(clone.PropertyTypes, contentType.PropertyTypes); Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); Assert.AreEqual(0, clone.NoGroupPropertyTypes.Count()); @@ -140,19 +150,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.Icon, contentType.Icon); Assert.AreEqual(clone.IsContainer, contentType.IsContainer); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } - //need to ensure the event handlers are wired - + // Need to ensure the event handlers are wired var asDirty = (ICanBeDirty)clone; Assert.IsFalse(asDirty.IsPropertyDirty("PropertyTypes")); var propertyTypeBuilder = new PropertyTypeBuilder(); - var additionalPropertyType = propertyTypeBuilder + PropertyType additionalPropertyType = propertyTypeBuilder .WithAlias("blah") .Build(); @@ -167,7 +178,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Serialize_Content_Type_Without_Error() { // Arrange - var contentType = BuildContentType(); + ContentType contentType = BuildContentType(); var json = JsonConvert.SerializeObject(contentType); Debug.Print(json); @@ -183,7 +194,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Deep_Clone_Media_Type() { // Arrange - var contentType = BuildMediaType(); + MediaType contentType = BuildMediaType(); // Act var clone = (MediaType)contentType.DeepClone(); @@ -198,12 +209,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); for (var index = 0; index < contentType.PropertyTypes.Count(); index++) { Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); Assert.AreEqual(clone.CreatorId, contentType.CreatorId); Assert.AreEqual(clone.Key, contentType.Key); @@ -216,17 +229,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.Icon, contentType.Icon); Assert.AreEqual(clone.IsContainer, contentType.IsContainer); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } } [Test] public void Can_Serialize_Media_Type_Without_Error() { // Arrange - var contentType = BuildMediaType(); + MediaType contentType = BuildMediaType(); var json = JsonConvert.SerializeObject(contentType); Debug.Print(json); @@ -242,7 +257,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Deep_Clone_Member_Type() { // Arrange - var contentType = BuildMemberType(); + MemberType contentType = BuildMemberType(); // Act var clone = (MemberType)contentType.DeepClone(); @@ -257,12 +272,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); for (var index = 0; index < contentType.PropertyTypes.Count(); index++) { Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); Assert.AreEqual(clone.CreatorId, contentType.CreatorId); Assert.AreEqual(clone.Key, contentType.Key); @@ -276,16 +293,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.IsContainer, contentType.IsContainer); // This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } } [Test] public void Can_Serialize_Member_Type_Without_Error() { // Arrange - var contentType = BuildMemberType(); + MemberType contentType = BuildMemberType(); var json = JsonConvert.SerializeObject(contentType); Debug.Print(json); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/CultureImpactTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/CultureImpactTests.cs index 753c0e2b4d..f72a28b0f3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/CultureImpactTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/CultureImpactTests.cs @@ -1,4 +1,7 @@ -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Moq; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,26 +17,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Mock.Of(x => x.Published == true), new[] { "en-US", "fr-FR" }, "en-US"); - Assert.AreEqual("en-US", result); //default culture is being saved so use it + Assert.AreEqual("en-US", result); // default culture is being saved so use it result = CultureImpact.GetCultureForInvariantErrors( Mock.Of(x => x.Published == false), new[] { "fr-FR" }, "en-US"); - Assert.AreEqual("fr-FR", result); //default culture not being saved with not published version, use the first culture being saved + Assert.AreEqual("fr-FR", result); // default culture not being saved with not published version, use the first culture being saved result = CultureImpact.GetCultureForInvariantErrors( Mock.Of(x => x.Published == true), new[] { "fr-FR" }, "en-US"); - Assert.AreEqual(null, result); //default culture not being saved with published version, use null - + Assert.AreEqual(null, result); // default culture not being saved with published version, use null } [Test] public void All_Cultures() { - var impact = CultureImpact.All; + CultureImpact impact = CultureImpact.All; Assert.AreEqual(impact.Culture, "*"); @@ -48,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Invariant_Culture() { - var impact = CultureImpact.Invariant; + CultureImpact impact = CultureImpact.Invariant; Assert.AreEqual(impact.Culture, null); @@ -93,7 +95,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void TryCreate_Explicit_Default_Culture() { - var success = CultureImpact.TryCreate("en-US", true, ContentVariation.Culture, false, out var impact); + var success = CultureImpact.TryCreate("en-US", true, ContentVariation.Culture, false, out CultureImpact impact); Assert.IsTrue(success); Assert.AreEqual(impact.Culture, "en-US"); @@ -109,7 +111,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void TryCreate_Explicit_NonDefault_Culture() { - var success = CultureImpact.TryCreate("en-US", false, ContentVariation.Culture, false, out var impact); + var success = CultureImpact.TryCreate("en-US", false, ContentVariation.Culture, false, out CultureImpact impact); Assert.IsTrue(success); Assert.AreEqual(impact.Culture, "en-US"); @@ -125,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void TryCreate_AllCultures_For_Invariant() { - var success = CultureImpact.TryCreate("*", false, ContentVariation.Nothing, false, out var impact); + var success = CultureImpact.TryCreate("*", false, ContentVariation.Nothing, false, out CultureImpact impact); Assert.IsTrue(success); Assert.AreEqual(impact.Culture, null); @@ -136,7 +138,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void TryCreate_AllCultures_For_Variant() { - var success = CultureImpact.TryCreate("*", false, ContentVariation.Culture, false, out var impact); + var success = CultureImpact.TryCreate("*", false, ContentVariation.Culture, false, out CultureImpact impact); Assert.IsTrue(success); Assert.AreEqual(impact.Culture, "*"); @@ -147,14 +149,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void TryCreate_Invariant_For_Variant() { - var success = CultureImpact.TryCreate(null, false, ContentVariation.Culture, false, out var impact); + var success = CultureImpact.TryCreate(null, false, ContentVariation.Culture, false, out CultureImpact impact); Assert.IsFalse(success); } [Test] public void TryCreate_Invariant_For_Invariant() { - var success = CultureImpact.TryCreate(null, false, ContentVariation.Nothing, false, out var impact); + var success = CultureImpact.TryCreate(null, false, ContentVariation.Nothing, false, out CultureImpact impact); Assert.IsTrue(success); Assert.AreSame(CultureImpact.Invariant, impact); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DeepCloneHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DeepCloneHelperTests.cs index fcad67c221..6dc251d9f8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DeepCloneHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DeepCloneHelperTests.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections; using System.Collections.Generic; using System.Linq; @@ -149,74 +151,76 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models MyTest1 = new object[] { new Test1(), "hello", - //not cloneable so this property will get skipped + + // Not cloneable so this property will get skipped. new Test2() } }; var clone = (Test3)test1.DeepClone(); - //it skipped this property so these will now be the same + // It skipped this property so these will now be the same. Assert.AreSame(clone.MyTest1, test1.MyTest1); - } public class Test1 : BaseCloneable { public string Name { get; set; } + public int Age { get; set; } public Test1 MyTest1 { get; set; } - public Test2 MyTest2 { get; set; } + public Test2 MyTest2 { get; set; } } public class Test2 { public string Name { get; set; } + public Test1 MyTest1 { get; set; } } public class Test3 : BaseCloneable { public string Name { get; set; } - public object[] MyTest1 { get; set; } + public object[] MyTest1 { get; set; } } public class Test4 : BaseCloneable { public string Name { get; set; } - public Test1[] MyTest1 { get; set; } + public Test1[] MyTest1 { get; set; } } public class Test5 : BaseCloneable { public string Name { get; set; } - public IEnumerable MyTest1 { get; set; } + public IEnumerable MyTest1 { get; set; } } public class Test6 : BaseCloneable { public string Name { get; set; } - public IEnumerable MyTest1 { get; set; } + public IEnumerable MyTest1 { get; set; } } public class Test7 : BaseCloneable { public string Name { get; set; } - public List MyTest1 { get; set; } + public List MyTest1 { get; set; } } public class Test8 : BaseCloneable { public string Name { get; set; } - public ICollection MyTest1 { get; set; } + public ICollection MyTest1 { get; set; } } public abstract class BaseCloneable : IDeepCloneable diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryItemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryItemTests.cs index 54a935c891..4994c12f84 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryItemTests.cs @@ -1,4 +1,8 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Linq; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -12,15 +16,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private DictionaryItemBuilder _builder = new DictionaryItemBuilder(); [SetUp] - public void SetUp() - { - _builder = new DictionaryItemBuilder(); - } + public void SetUp() => _builder = new DictionaryItemBuilder(); [Test] public void Can_Deep_Clone() { - var item = _builder + DictionaryItem item = _builder .WithRandomTranslations(2) .Build(); @@ -41,19 +42,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); } - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } - } [Test] public void Can_Serialize_Without_Error() { - var item = _builder + DictionaryItem item = _builder .WithRandomTranslations(2) .Build(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryTranslationTests.cs index 957acf293e..073a168e23 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DictionaryTranslationTests.cs @@ -1,5 +1,9 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,15 +18,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private DictionaryTranslationBuilder _builder = new DictionaryTranslationBuilder(); [SetUp] - public void SetUp() - { - _builder = new DictionaryTranslationBuilder(); - } + public void SetUp() => _builder = new DictionaryTranslationBuilder(); [Test] public void Can_Deep_Clone() { - var item = BuildDictionaryTranslation(); + IDictionaryTranslation item = BuildDictionaryTranslation(); var clone = (DictionaryTranslation)item.DeepClone(); @@ -33,35 +34,36 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.Key, item.Key); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); Assert.AreNotSame(clone.Language, item.Language); - //This is null because we are ignoring it from cloning due to caching/cloning issues - we don't really want + + // This is null because we are ignoring it from cloning due to caching/cloning issues - we don't really want // this entity attached to this item but we're stuck with it for now Assert.IsNull(clone.Language); Assert.AreEqual(clone.LanguageId, item.LanguageId); Assert.AreEqual(clone.Value, item.Value); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps.Where(x => x.Name != "Language")) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps.Where(x => x.Name != "Language")) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } } [Test] public void Can_Serialize_Without_Error() { - var item = BuildDictionaryTranslation(); + IDictionaryTranslation item = BuildDictionaryTranslation(); var json = JsonConvert.SerializeObject(item); Debug.Print(json); } - private IDictionaryTranslation BuildDictionaryTranslation() - { - return _builder + private IDictionaryTranslation BuildDictionaryTranslation() => + _builder .AddLanguage() .WithCultureInfo("en-AU") .Done() .WithValue("colour") - .Build(); - } + .Build(); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DocumentEntityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DocumentEntityTests.cs index 1829720aad..ec3ed57bd6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DocumentEntityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/DocumentEntityTests.cs @@ -1,6 +1,10 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; +using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -12,15 +16,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private DocumentEntitySlimBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new DocumentEntitySlimBuilder(); - } + public void SetUp() => _builder = new DocumentEntitySlimBuilder(); [Test] public void Can_Serialize_Without_Error() { - var item = _builder + DocumentEntitySlim item = _builder .WithId(3) .WithCreatorId(4) .WithName("Test") diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/LanguageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/LanguageTests.cs index 5b1fc2d56b..5523f57680 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/LanguageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/LanguageTests.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Reflection; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; @@ -12,19 +16,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private LanguageBuilder _builder = new LanguageBuilder(); [SetUp] - public void SetUp() - { - _builder = new LanguageBuilder(); - } + public void SetUp() => _builder = new LanguageBuilder(); [Test] public void Can_Deep_Clone() { - var item = _builder + ILanguage item = _builder .WithId(1) .Build(); - var clone = (Language) item.DeepClone(); + var clone = (Language)item.DeepClone(); Assert.AreNotSame(clone, item); Assert.AreEqual(clone, item); Assert.AreEqual(clone.CreateDate, item.CreateDate); @@ -34,9 +35,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.Key, item.Key); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } @@ -45,7 +46,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var item = _builder.Build(); + ILanguage item = _builder.Build(); Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MacroTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MacroTests.cs index 9d22387cd6..d7964e45b2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MacroTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MacroTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; +using System.Reflection; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -14,15 +18,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private MacroBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new MacroBuilder(); - } + public void SetUp() => _builder = new MacroBuilder(); [Test] public void Can_Deep_Clone() { - var macro = _builder + Macro macro = _builder .WithId(1) .WithUseInEditor(true) .WithCacheDuration(3) @@ -58,13 +59,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreNotSame(clone.AddedProperties, macro.AddedProperties); Assert.AreNotSame(clone.RemovedProperties, macro.RemovedProperties); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(macro, null)); + } - //need to ensure the event handlers are wired - + // Need to ensure the event handlers are wired. var asDirty = (ICanBeDirty)clone; Assert.IsFalse(asDirty.IsPropertyDirty("Properties")); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberGroupTests.cs index 47138a5fb4..165a42b225 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberGroupTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,16 +18,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private MemberGroupBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new MemberGroupBuilder(); - } + public void SetUp() => _builder = new MemberGroupBuilder(); [Test] public void Can_Deep_Clone() { // Arrange - var group = BuildMemberGroup(); + MemberGroup group = BuildMemberGroup(); // Act var clone = (MemberGroup)group.DeepClone(); @@ -40,24 +41,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.UpdateDate, group.UpdateDate); Assert.AreEqual(clone.Name, group.Name); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); + } } [Test] public void Can_Serialize_Without_Error() { - var group = BuildMemberGroup(); + MemberGroup group = BuildMemberGroup(); var json = JsonConvert.SerializeObject(group); Debug.Print(json); } - private MemberGroup BuildMemberGroup() - { - return _builder + private MemberGroup BuildMemberGroup() => + _builder .WithId(6) .WithKey(Guid.NewGuid()) .WithName("Test Group") @@ -69,6 +71,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithKeyValue("test2", "hello") .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberTests.cs index 58f88affa1..8ca253c224 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/MemberTests.cs @@ -1,5 +1,9 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,16 +18,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private MemberBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new MemberBuilder(); - } + public void SetUp() => _builder = new MemberBuilder(); [Test] public void Can_Deep_Clone() { // Arrange - var member = BuildMember(); + Member member = BuildMember(); // Act var clone = (Member)member.DeepClone(); @@ -66,24 +67,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // this can be the same, it is immutable Assert.AreSame(clone.ContentType, member.ContentType); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); + } } [Test] public void Can_Serialize_Without_Error() { - var member = BuildMember(); + Member member = BuildMember(); var json = JsonConvert.SerializeObject(member); Debug.Print(json); } - private Member BuildMember() - { - return _builder + private Member BuildMember() => + _builder .AddMemberType() .WithId(99) .WithAlias("memberType") @@ -117,7 +119,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithEmail("email@email.com") .WithFailedPasswordAttempts(22) .WithIsApproved(true) - .WithIsLockedOut(true) + .WithIsLockedOut(true) .WithTrashed(false) .AddMemberGroups() .WithValue("Group 1") @@ -134,6 +136,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithKeyValue("author", "John Doe") .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyGroupTests.cs index 08e025a6b1..647684ff2f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyGroupTests.cs @@ -1,5 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Diagnostics; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,15 +17,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private PropertyGroupBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new PropertyGroupBuilder(); - } + public void SetUp() => _builder = new PropertyGroupBuilder(); [Test] public void Can_Deep_Clone() { - var pg = BuildPropertyGroup(); + PropertyGroup pg = BuildPropertyGroup(); var clone = (PropertyGroup)pg.DeepClone(); @@ -43,9 +43,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.PropertyTypes[i], pg.PropertyTypes[i]); } - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pg, null)); } @@ -54,15 +54,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var pg = BuildPropertyGroup(); + PropertyGroup pg = BuildPropertyGroup(); var json = JsonConvert.SerializeObject(pg); Debug.Print(json); } - private PropertyGroup BuildPropertyGroup() - { - return _builder + private PropertyGroup BuildPropertyGroup() => + _builder .WithId(77) .WithName("Group1") .AddPropertyType() @@ -71,15 +70,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithName("Test") .WithDescription("testing") .WithPropertyGroupId(11) - .WithMandatory(true) + .WithMandatory(true) .WithValidationRegExp("xxxx") .Done() .AddPropertyType() .WithId(4) .WithAlias("test2") - .WithName("Test2") + .WithName("Test2") .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTests.cs index ce87174d7c..99188759af 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTests.cs @@ -1,27 +1,27 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Linq; +using System.Reflection; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models -{ +{ [TestFixture] public class PropertyTests { private PropertyBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new PropertyBuilder(); - } + public void SetUp() => _builder = new PropertyBuilder(); [Test] public void Can_Deep_Clone() { - var property = BuildProperty(); + IProperty property = BuildProperty(); property.SetValue("hello"); property.PublishValues(); @@ -37,16 +37,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models } // This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(property, null)); } } - private IProperty BuildProperty() - { - return _builder + private IProperty BuildProperty() => + _builder .WithId(4) .AddPropertyType() .WithId(3) @@ -61,6 +60,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithValidationRegExp("xxxx") .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTypeTests.cs index 6fa061b1d2..cce3f43db0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/PropertyTypeTests.cs @@ -1,8 +1,11 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; -using Newtonsoft.Json; using System.Linq; using System.Reflection; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; @@ -16,15 +19,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private PropertyTypeBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new PropertyTypeBuilder(); - } + public void SetUp() => _builder = new PropertyTypeBuilder(); [Test] public void Can_Deep_Clone() { - var pt = BuildPropertyType(); + PropertyType pt = BuildPropertyType(); var clone = (PropertyType)pt.DeepClone(); @@ -45,9 +45,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.ValidationRegExp, pt.ValidationRegExp); Assert.AreEqual(clone.ValueStorageType, pt.ValueStorageType); - //This double verifies by reflection (don't test properties marked with [DoNotClone] - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps.Where(p => p.GetCustomAttribute(false) == null)) + // This double verifies by reflection (don't test properties marked with [DoNotClone] + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps.Where(p => p.GetCustomAttribute(false) == null)) { var expected = propertyInfo.GetValue(pt, null); var actual = propertyInfo.GetValue(clone, null); @@ -64,15 +64,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var pt = BuildPropertyType(); + PropertyType pt = BuildPropertyType(); var json = JsonConvert.SerializeObject(pt); Debug.Print(json); } - private PropertyType BuildPropertyType() - { - return _builder + private PropertyType BuildPropertyType() => + _builder .WithId(3) .WithPropertyEditorAlias("TestPropertyEditor") .WithAlias("test") @@ -84,6 +83,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithMandatory(true) .WithValidationRegExp("xxxx") .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTests.cs index fa7e540533..66fce7f93f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -14,17 +18,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private RelationBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new RelationBuilder(); - } + public void SetUp() => _builder = new RelationBuilder(); [Test] public void Can_Deep_Clone() { - var relation = BuildRelation(); + Relation relation = BuildRelation(); - var clone = (Relation) relation.DeepClone(); + var clone = (Relation)relation.DeepClone(); Assert.AreNotSame(clone, relation); Assert.AreEqual(clone, relation); @@ -39,9 +40,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.RelationTypeId, relation.RelationTypeId); Assert.AreEqual(clone.UpdateDate, relation.UpdateDate); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(relation, null)); } @@ -50,15 +51,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var relation = BuildRelation(); + Relation relation = BuildRelation(); var json = JsonConvert.SerializeObject(relation); Debug.Print(json); } - private Relation BuildRelation() - { - return _builder + private Relation BuildRelation() => + _builder .BetweenIds(9, 8) .WithId(4) .WithComment("test comment") @@ -74,6 +74,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithChildObjectType(Guid.NewGuid()) .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTypeTests.cs index 2d5b88d945..d29f89283d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/RelationTypeTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; @@ -13,21 +17,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private RelationTypeBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new RelationTypeBuilder(); - } + public void SetUp() => _builder = new RelationTypeBuilder(); [Test] public void Can_Deep_Clone() { - var item = _builder + IRelationType item = _builder .WithId(1) .WithParentObjectType(Guid.NewGuid()) .WithChildObjectType(Guid.NewGuid()) .Build(); - var clone = (RelationType) item.DeepClone(); + var clone = (RelationType)item.DeepClone(); Assert.AreNotSame(clone, item); Assert.AreEqual(clone, item); @@ -41,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.CreateDate, item.CreateDate); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } @@ -52,7 +53,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var item = _builder.Build(); + IRelationType item = _builder.Build(); Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/StylesheetTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/StylesheetTests.cs index f744fee829..a2d9ed18f8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/StylesheetTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Newtonsoft.Json; @@ -14,17 +18,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private StylesheetBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new StylesheetBuilder(); - } + public void SetUp() => _builder = new StylesheetBuilder(); [Test] public void Can_Create_Stylesheet() { // Arrange // Act - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"body { color:#000; } .bold {font-weight:bold;}") .Build(); @@ -38,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Add_Property() { // Arrange - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"body { color:#000; } .bold {font-weight:bold;}") .Build(); @@ -50,14 +51,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(1, stylesheet.Properties.Count()); Assert.AreEqual("Test", stylesheet.Properties.Single().Name); Assert.AreEqual("p", stylesheet.Properties.Single().Alias); - Assert.AreEqual("font-weight:bold;"+Environment.NewLine+"font-family:Arial;", stylesheet.Properties.Single().Value); + Assert.AreEqual("font-weight:bold;" + Environment.NewLine + "font-family:Arial;", stylesheet.Properties.Single().Value); } [Test] public void Can_Remove_Property() { // Arrange - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}") .Build(); @@ -75,13 +76,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Can_Update_Property() { // Arrange - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"body { color:#000; } /**umb_name:Hello*/p{font-size:2em;} .bold {font-weight:bold;}") .Build(); // Act - var prop = stylesheet.Properties.Single(); + IStylesheetProperty prop = stylesheet.Properties.Single(); prop.Alias = "li"; prop.Value = "font-size:5em;"; @@ -91,20 +92,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // Assert Assert.AreEqual("li", prop.Alias); Assert.AreEqual("font-size:5em;", prop.Value); - Assert.AreEqual("body { color:#000; } /**umb_name:Hello*/" +Environment.NewLine+ "li {" +Environment.NewLine+ "\tfont-size:5em;" +Environment.NewLine+ "} .bold {font-weight:bold;}", stylesheet.Content); + Assert.AreEqual("body { color:#000; } /**umb_name:Hello*/" + Environment.NewLine + "li {" + Environment.NewLine + "\tfont-size:5em;" + Environment.NewLine + "} .bold {font-weight:bold;}", stylesheet.Content); } [Test] public void Can_Get_Properties_From_Css() { // Arrange - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"body { color:#000; } .bold {font-weight:bold;} /**umb_name:Hello */ p { font-size: 1em; } /**umb_name:testing123*/ li:first-child {padding:0px;}") .Build(); // Act - var properties = stylesheet.Properties; + IEnumerable properties = stylesheet.Properties; // Assert Assert.AreEqual(2, properties.Count()); @@ -116,12 +117,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual("li:first-child", properties.Last().Alias); } - [Test] public void Can_Serialize_Without_Error() { // Arrange - var stylesheet = _builder + Stylesheet stylesheet = _builder .WithPath("/css/styles.css") .WithContent(@"@media screen and (min-width: 600px) and (min-width: 900px) { .class { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/TemplateTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/TemplateTests.cs index d1c731184e..04a49456b6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/TemplateTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/TemplateTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -16,15 +19,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private TemplateBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new TemplateBuilder(); - } + public void SetUp() => _builder = new TemplateBuilder(); [Test] public void Can_Deep_Clone() { - var template = BuildTemplate(); + ITemplate template = BuildTemplate(); var clone = (Template)template.DeepClone(); @@ -42,15 +42,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.UpdateDate, template.UpdateDate); // clone.Content should be null but getting it would lazy-load - var type = clone.GetType(); - var contentField = type.BaseType.GetField("_content", BindingFlags.Instance | BindingFlags.NonPublic); + Type type = clone.GetType(); + FieldInfo contentField = type.BaseType.GetField("_content", BindingFlags.Instance | BindingFlags.NonPublic); var value = contentField.GetValue(clone); Assert.IsNull(value); // this double verifies by reflection // need to exclude content else it would lazy-load - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps.Where(x => x.Name != "Content")) + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps.Where(x => x.Name != "Content")) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(template, null)); } @@ -59,15 +59,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void Can_Serialize_Without_Error() { - var template = BuildTemplate(); + ITemplate template = BuildTemplate(); var json = JsonConvert.SerializeObject(template); Debug.Print(json); } - private ITemplate BuildTemplate() - { - return _builder + private ITemplate BuildTemplate() => + _builder .WithId(3) .WithAlias("test") .WithName("Test") @@ -77,6 +76,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithContent("blah") .AsMasterTemplate("master", 88) .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs index faf96a6457..426f698969 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Moq; @@ -7,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Tests.Common.Builders; +using User = Umbraco.Core.Models.Membership.User; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models { @@ -16,10 +20,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private UserBuilder _userBuilder; [SetUp] - public void SetUp() - { - _userBuilder = new UserBuilder(); - } + public void SetUp() => _userBuilder = new UserBuilder(); [TestCase(-1, "-1", "-1,1,2,3,4,5", true)] // below root start node [TestCase(2, "-1,1,2", "-1,1,2,3,4,5", true)] // below start node @@ -32,11 +33,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models public void Determines_Path_Based_Access_To_Content(int startNodeId, string startNodePath, string contentPath, bool outcome) { - var user = _userBuilder + User user = _userBuilder .WithStartContentIds(new[] { startNodeId }) .Build(); - var content = Mock.Of(c => c.Path == contentPath && c.Id == 5); + IContent content = Mock.Of(c => c.Path == contentPath && c.Id == 5); var esmock = new Mock(); esmock @@ -80,7 +81,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models // 6 // 7 // 8 - var paths = new Dictionary { { 1, "-1,1" }, @@ -106,10 +106,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models var expectedA = expected.Split(comma, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).OrderBy(x => x).ToArray(); var ok = combinedA.Length == expectedA.Length; - if (ok) ok = expectedA.Where((t, i) => t != combinedA[i]).Any() == false; + if (ok) + { + ok = expectedA.Where((t, i) => t != combinedA[i]).Any() == false; + } if (ok == false) + { Assert.Fail("Expected \"" + string.Join(",", expectedA) + "\" but got \"" + string.Join(",", combinedA) + "\"."); + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserTests.cs index e4b9f89067..9f0613f9d6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserTests.cs @@ -1,5 +1,9 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -14,15 +18,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models private UserBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new UserBuilder(); - } + public void SetUp() => _builder = new UserBuilder(); [Test] public void Can_Deep_Clone() { - var item = BuildUser(); + User item = BuildUser(); var clone = (User)item.DeepClone(); @@ -31,24 +32,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.AreEqual(clone.AllowedSections.Count(), item.AllowedSections.Count()); - //Verify normal properties with reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // Verify normal properties with reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) + { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } } [Test] public void Can_Serialize_Without_Error() { - var item = BuildUser(); + User item = BuildUser(); var json = JsonConvert.SerializeObject(item); Debug.Print(json); } - private User BuildUser() - { - return _builder + private User BuildUser() => + _builder .WithId(3) .WithLogin("username", "test pass") .WithName("Test") @@ -61,6 +63,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models .WithStartContentIds(new[] { 3 }) .WithStartMediaIds(new[] { 8 }) .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index cab26fd7d2..f92ba1ddeb 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.Extensions.Logging.Abstractions; using Moq; using NUnit.Framework; @@ -22,110 +25,112 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void ValidateVariationTests() { - // All tests: // 1. if exact is set to true: culture cannot be null when the ContentVariation.Culture flag is set // 2. if wildcards is set to false: fail when "*" is passed in as either culture or segment. // 3. ContentVariation flag is ignored when wildcards are used. // 4. Empty string is considered the same as null + AssertForNovariation(); + AssertForCultureVariation(); + AssertForSegmentVariation(); + AssertForCultureAndSegmentVariation(); + } - #region Nothing - + private static void AssertForNovariation() + { Assert4A(ContentVariation.Nothing, null, null, true); - Assert4A(ContentVariation.Nothing, null, "", true); + Assert4A(ContentVariation.Nothing, null, string.Empty, true); Assert4B(ContentVariation.Nothing, null, "*", true, false, false, true); Assert4A(ContentVariation.Nothing, null, "segment", false); - Assert4A(ContentVariation.Nothing, "", null, true); - Assert4A(ContentVariation.Nothing, "", "", true); - Assert4B(ContentVariation.Nothing, "", "*", true, false, false, true); - Assert4A(ContentVariation.Nothing, "", "segment", false); + Assert4A(ContentVariation.Nothing, string.Empty, null, true); + Assert4A(ContentVariation.Nothing, string.Empty, string.Empty, true); + Assert4B(ContentVariation.Nothing, string.Empty, "*", true, false, false, true); + Assert4A(ContentVariation.Nothing, string.Empty, "segment", false); Assert4B(ContentVariation.Nothing, "*", null, true, false, false, true); - Assert4B(ContentVariation.Nothing, "*", "", true, false, false, true); + Assert4B(ContentVariation.Nothing, "*", string.Empty, true, false, false, true); Assert4B(ContentVariation.Nothing, "*", "*", true, false, false, true); Assert4A(ContentVariation.Nothing, "*", "segment", false); Assert4A(ContentVariation.Nothing, "culture", null, false); - Assert4A(ContentVariation.Nothing, "culture", "", false); + Assert4A(ContentVariation.Nothing, "culture", string.Empty, false); Assert4A(ContentVariation.Nothing, "culture", "*", false); Assert4A(ContentVariation.Nothing, "culture", "segment", false); + } - #endregion - - #region Culture - + private static void AssertForCultureVariation() + { Assert4B(ContentVariation.Culture, null, null, false, true, false, true); - Assert4B(ContentVariation.Culture, null, "", false, true, false, true); + Assert4B(ContentVariation.Culture, null, string.Empty, false, true, false, true); Assert4B(ContentVariation.Culture, null, "*", false, false, false, true); Assert4A(ContentVariation.Culture, null, "segment", false); - Assert4B(ContentVariation.Culture, "", null, false, true, false, true); - Assert4B(ContentVariation.Culture, "", "", false, true, false, true); - Assert4B(ContentVariation.Culture, "", "*", false, false, false, true); - Assert4A(ContentVariation.Culture, "", "segment", false); + Assert4B(ContentVariation.Culture, string.Empty, null, false, true, false, true); + Assert4B(ContentVariation.Culture, string.Empty, string.Empty, false, true, false, true); + Assert4B(ContentVariation.Culture, string.Empty, "*", false, false, false, true); + Assert4A(ContentVariation.Culture, string.Empty, "segment", false); Assert4B(ContentVariation.Culture, "*", null, true, false, false, true); - Assert4B(ContentVariation.Culture, "*", "", true, false, false, true); + Assert4B(ContentVariation.Culture, "*", string.Empty, true, false, false, true); Assert4B(ContentVariation.Culture, "*", "*", true, false, false, true); Assert4A(ContentVariation.Culture, "*", "segment", false); Assert4A(ContentVariation.Culture, "culture", null, true); - Assert4A(ContentVariation.Culture, "culture", "", true); + Assert4A(ContentVariation.Culture, "culture", string.Empty, true); Assert4B(ContentVariation.Culture, "culture", "*", true, false, false, true); Assert4A(ContentVariation.Culture, "culture", "segment", false); + } - #endregion - - #region Segment - + private static void AssertForSegmentVariation() + { Assert4B(ContentVariation.Segment, null, null, true, true, true, true); - Assert4B(ContentVariation.Segment, null, "", true, true, true, true); + Assert4B(ContentVariation.Segment, null, string.Empty, true, true, true, true); Assert4B(ContentVariation.Segment, null, "*", true, false, false, true); Assert4A(ContentVariation.Segment, null, "segment", true); - Assert4B(ContentVariation.Segment, "", null, true, true, true, true); - Assert4B(ContentVariation.Segment, "", "", true, true, true, true); - Assert4B(ContentVariation.Segment, "", "*", true, false, false, true); - Assert4A(ContentVariation.Segment, "", "segment", true); + Assert4B(ContentVariation.Segment, string.Empty, null, true, true, true, true); + Assert4B(ContentVariation.Segment, string.Empty, string.Empty, true, true, true, true); + Assert4B(ContentVariation.Segment, string.Empty, "*", true, false, false, true); + Assert4A(ContentVariation.Segment, string.Empty, "segment", true); Assert4B(ContentVariation.Segment, "*", null, true, false, false, true); - Assert4B(ContentVariation.Segment, "*", "", true, false, false, true); + Assert4B(ContentVariation.Segment, "*", string.Empty, true, false, false, true); Assert4B(ContentVariation.Segment, "*", "*", true, false, false, true); Assert4B(ContentVariation.Segment, "*", "segment", true, false, false, true); Assert4A(ContentVariation.Segment, "culture", null, false); - Assert4A(ContentVariation.Segment, "culture", "", false); + Assert4A(ContentVariation.Segment, "culture", string.Empty, false); Assert4A(ContentVariation.Segment, "culture", "*", false); Assert4A(ContentVariation.Segment, "culture", "segment", false); + } - #endregion - - #region CultureAndSegment - + private static void AssertForCultureAndSegmentVariation() + { Assert4B(ContentVariation.CultureAndSegment, null, null, false, true, false, true); - Assert4B(ContentVariation.CultureAndSegment, null, "", false, true, false, true); + Assert4B(ContentVariation.CultureAndSegment, null, string.Empty, false, true, false, true); Assert4B(ContentVariation.CultureAndSegment, null, "*", false, false, false, true); Assert4B(ContentVariation.CultureAndSegment, null, "segment", false, true, false, true); - Assert4B(ContentVariation.CultureAndSegment, "", null, false, true, false, true); - Assert4B(ContentVariation.CultureAndSegment, "", "", false, true, false, true); - Assert4B(ContentVariation.CultureAndSegment, "", "*", false, false, false, true); - Assert4B(ContentVariation.CultureAndSegment, "", "segment", false, true, false, true); + Assert4B(ContentVariation.CultureAndSegment, string.Empty, null, false, true, false, true); + Assert4B(ContentVariation.CultureAndSegment, string.Empty, string.Empty, false, true, false, true); + Assert4B(ContentVariation.CultureAndSegment, string.Empty, "*", false, false, false, true); + Assert4B(ContentVariation.CultureAndSegment, string.Empty, "segment", false, true, false, true); Assert4B(ContentVariation.CultureAndSegment, "*", null, true, false, false, true); - Assert4B(ContentVariation.CultureAndSegment, "*", "", true, false, false, true); + Assert4B(ContentVariation.CultureAndSegment, "*", string.Empty, true, false, false, true); Assert4B(ContentVariation.CultureAndSegment, "*", "*", true, false, false, true); Assert4B(ContentVariation.CultureAndSegment, "*", "segment", true, false, false, true); Assert4B(ContentVariation.CultureAndSegment, "culture", null, true, true, true, true); - Assert4B(ContentVariation.CultureAndSegment, "culture", "", true, true, true, true); + Assert4B(ContentVariation.CultureAndSegment, "culture", string.Empty, true, true, true, true); Assert4B(ContentVariation.CultureAndSegment, "culture", "*", true, false, false, true); Assert4B(ContentVariation.CultureAndSegment, "culture", "segment", true, true, true, true); - - #endregion } /// /// Asserts the result of /// - /// - /// - /// /// Validate using Exact + Wildcards flags /// Validate using non Exact + no Wildcard flags /// Validate using Exact + no Wildcard flags /// Validate using non Exact + Wildcard flags - private static void Assert4B(ContentVariation variation, string culture, string segment, - bool exactAndWildcards, bool nonExactAndNoWildcards, bool exactAndNoWildcards, bool nonExactAndWildcards) + private static void Assert4B( + ContentVariation variation, + string culture, + string segment, + bool exactAndWildcards, + bool nonExactAndNoWildcards, + bool exactAndNoWildcards, + bool nonExactAndWildcards) { Assert.AreEqual(exactAndWildcards, variation.ValidateVariation(culture, segment, true, true, false)); Assert.AreEqual(nonExactAndNoWildcards, variation.ValidateVariation(culture, segment, false, false, false)); @@ -137,14 +142,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models /// Asserts the result of /// where expectedResult matches all combinations of Exact + Wildcard /// - /// - /// - /// - /// - private static void Assert4A(ContentVariation variation, string culture, string segment, bool expectedResult) - { + private static void Assert4A(ContentVariation variation, string culture, string segment, bool expectedResult) => Assert4B(variation, culture, segment, expectedResult, expectedResult, expectedResult, expectedResult); - } [Test] public void PropertyTests() @@ -245,10 +244,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void ContentNames() { - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .Build(); - var content = CreateContent(contentType); + Content content = CreateContent(contentType); const string langFr = "fr-FR"; const string langUk = "en-UK"; @@ -288,15 +287,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models { const string langFr = "fr-FR"; - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("prop") .Build(); - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .Build(); contentType.AddPropertyType(propertyType); - var content = CreateContent(contentType); + Content content = CreateContent(contentType); // can set value // and get edited value, published is null @@ -378,7 +377,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - var other = CreateContent(contentType, 2, "other"); + Content other = CreateContent(contentType, 2, "other"); Assert.Throws(() => other.SetValue("prop", "o")); // don't even try other.SetValue("prop", "o1", langFr); @@ -401,24 +400,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void ContentPublishValuesWithMixedPropertyTypeVariations() { - var propertyValidationService = GetPropertyValidationService(); + PropertyValidationService propertyValidationService = GetPropertyValidationService(); const string langFr = "fr-FR"; // content type varies by Culture // prop1 varies by Culture // prop2 is invariant - - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .Build(); contentType.Variations |= ContentVariation.Culture; - var variantPropType = new PropertyTypeBuilder() + PropertyType variantPropType = new PropertyTypeBuilder() .WithAlias("prop1") .WithVariations(ContentVariation.Culture) .WithMandatory(true) .Build(); - var invariantPropType = new PropertyTypeBuilder() + PropertyType invariantPropType = new PropertyTypeBuilder() .WithAlias("prop2") .WithVariations(ContentVariation.Nothing) .WithMandatory(true) @@ -426,24 +424,25 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models contentType.AddPropertyType(variantPropType); contentType.AddPropertyType(invariantPropType); - var content = CreateContent(contentType); + Content content = CreateContent(contentType); content.SetCultureName("hello", langFr); - //for this test we'll make the french culture the default one - this is needed for publishing invariant property values + // for this test we'll make the french culture the default one - this is needed for publishing invariant property values var langFrImpact = CultureImpact.Explicit(langFr, true); Assert.IsTrue(content.PublishCulture(langFrImpact)); // succeeds because names are ok (not validating properties here) - Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// fails because prop1 is mandatory + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact)); // fails because prop1 is mandatory content.SetValue("prop1", "a", langFr); Assert.IsTrue(content.PublishCulture(langFrImpact)); // succeeds because names are ok (not validating properties here) - // fails because prop2 is mandatory and invariant and the item isn't published. + + // Fails because prop2 is mandatory and invariant and the item isn't published. // Invariant is validated against the default language except when there isn't a published version, in that case it's always validated. Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact)); content.SetValue("prop2", "x"); Assert.IsTrue(content.PublishCulture(langFrImpact)); // still ok... - Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// now it's ok + Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact)); // now it's ok Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); Assert.AreEqual("x", content.GetValue("prop2", published: true)); @@ -456,15 +455,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models const string langUk = "en-UK"; const string langEs = "es-ES"; - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("prop") .Build(); - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .Build(); contentType.AddPropertyType(propertyType); - var content = CreateContent(contentType); + Content content = CreateContent(contentType); // change - now we vary by culture contentType.Variations |= ContentVariation.Culture; @@ -515,16 +514,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models [Test] public void IsDirtyTests() { - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("prop") .Build(); var prop = new Property(propertyType); - var contentType = new ContentTypeBuilder() + IContentType contentType = new ContentTypeBuilder() .WithAlias("contentType") .Build(); contentType.AddPropertyType(propertyType); - var content = CreateContent(contentType); + Content content = CreateContent(contentType); prop.SetValue("a"); Assert.AreEqual("a", prop.GetValue()); @@ -538,13 +537,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models Assert.IsTrue(content.IsDirty()); Assert.IsTrue(content.IsAnyUserPropertyDirty()); - // how can we tell which variation was dirty? + //// how can we tell which variation was dirty? } [Test] public void ValidationTests() { - var propertyType = new PropertyTypeBuilder() + PropertyType propertyType = new PropertyTypeBuilder() .WithAlias("prop") .WithSupportsPublishing(true) .Build(); @@ -554,7 +553,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models prop.SetValue("a"); Assert.AreEqual("a", prop.GetValue()); Assert.IsNull(prop.GetValue(published: true)); - var propertyValidationService = GetPropertyValidationService(); + PropertyValidationService propertyValidationService = GetPropertyValidationService(); Assert.IsTrue(propertyValidationService.IsPropertyValid(prop)); @@ -568,24 +567,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models prop.PublishValues(); } - private static Content CreateContent(IContentType contentType, int id = 1, string name = "content") - { - return new ContentBuilder() + private static Content CreateContent(IContentType contentType, int id = 1, string name = "content") => + new ContentBuilder() .WithId(id) .WithVersionId(1) .WithName(name) .WithContentType(contentType) .Build(); - } private static PropertyValidationService GetPropertyValidationService() { - var ioHelper = Mock.Of(); - var dataTypeService = Mock.Of(); - var localizedTextService = Mock.Of(); - var localizationService = Mock.Of(); - var shortStringHelper = Mock.Of(); - var jsonSerializer = Mock.Of(); + IIOHelper ioHelper = Mock.Of(); + IDataTypeService dataTypeService = Mock.Of(); + ILocalizedTextService localizedTextService = Mock.Of(); + ILocalizationService localizationService = Mock.Of(); + IShortStringHelper shortStringHelper = Mock.Of(); + IJsonSerializer jsonSerializer = Mock.Of(); var textBoxEditor = new TextboxPropertyEditor( NullLoggerFactory.Instance, @@ -594,8 +591,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Models ioHelper, shortStringHelper, localizedTextService, - jsonSerializer - ); + jsonSerializer); var serializer = new ConfigurationEditorJsonSerializer(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PackageExtractionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PackageExtractionTests.cs index 48f26f210c..a5025b6ec5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PackageExtractionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Packaging/PackageExtractionTests.cs @@ -1,8 +1,10 @@ -using System.IO; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.IO; using System.Linq; -using Moq; using NUnit.Framework; -using Umbraco.Core.Hosting; using Umbraco.Core.Packaging; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Packaging @@ -14,7 +16,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Packaging private static FileInfo GetTestPackagePath(string packageName) { - var testPackagesDirName = Path.Combine("Umbraco.Core","Packaging","Packages"); + var testPackagesDirName = Path.Combine("Umbraco.Core", "Packaging", "Packages"); var testDir = TestContext.CurrentContext.TestDirectory.Split("bin")[0]; var path = Path.Combine(testDir, testPackagesDirName, packageName); return new FileInfo(path); @@ -27,7 +29,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Packaging var sut = new PackageExtraction(); // Act - var result = sut.ReadFilesFromArchive(GetTestPackagePath(PackageFileName), new[] { "Package.xml" }); + IEnumerable result = sut.ReadFilesFromArchive(GetTestPackagePath(PackageFileName), new[] { "Package.xml" }); // Assert Assert.AreEqual(1, result.Count()); @@ -40,7 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Packaging var sut = new PackageExtraction(); // Act - var result = sut.FindMissingFiles(GetTestPackagePath(PackageFileName), new[] { "DoesNotExists.XYZ" }); + IEnumerable result = sut.FindMissingFiles(GetTestPackagePath(PackageFileName), new[] { "DoesNotExists.XYZ" }); // Assert Assert.AreEqual(1, result.Count()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs index bc54a7b999..438d2053e8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockEditorComponentTests.cs @@ -1,8 +1,11 @@ -using Newtonsoft.Json; -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.Compose; @@ -15,15 +18,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { Formatting = Formatting.None, NullValueHandling = NullValueHandling.Ignore, - }; - private const string _contentGuid1 = "036ce82586a64dfba2d523a99ed80f58"; - private const string _contentGuid2 = "48288c21a38a40ef82deb3eda90a58f6"; - private const string _settingsGuid1 = "ffd35c4e2eea4900abfa5611b67b2492"; - private const string _subContentGuid1 = "4c44ce6b3a5c4f5f8f15e3dc24819a9e"; - private const string _subContentGuid2 = "a062c06d6b0b44ac892b35d90309c7f8"; - private const string _subSettingsGuid1 = "4d998d980ffa4eee8afdc23c4abd6d29"; + private const string ContentGuid1 = "036ce82586a64dfba2d523a99ed80f58"; + private const string ContentGuid2 = "48288c21a38a40ef82deb3eda90a58f6"; + private const string SettingsGuid1 = "ffd35c4e2eea4900abfa5611b67b2492"; + private const string SubContentGuid1 = "4c44ce6b3a5c4f5f8f15e3dc24819a9e"; + private const string SubContentGuid2 = "a062c06d6b0b44ac892b35d90309c7f8"; + private const string SubSettingsGuid1 = "4d998d980ffa4eee8afdc23c4abd6d29"; [Test] public void Cannot_Have_Null_Udi() @@ -38,14 +40,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { var guids = Enumerable.Range(0, 3).Select(x => Guid.NewGuid()).ToList(); var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; var json = GetBlockListJson(null); - var expected = ReplaceGuids(json, guids, _contentGuid1, _contentGuid2, _settingsGuid1); + var expected = ReplaceGuids(json, guids, ContentGuid1, ContentGuid2, SettingsGuid1); var component = new BlockEditorComponent(); - var result = component.ReplaceBlockListUdis(json, guidFactory); + var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); @@ -60,9 +62,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; - var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var innerJson = GetBlockListJson(null, SubContentGuid1, SubContentGuid2, SubSettingsGuid1); // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -72,12 +74,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var json = GetBlockListJson(innerJsonEscaped); var component = new BlockEditorComponent(); - var result = component.ReplaceBlockListUdis(json, guidFactory); + var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped - var expected = ReplaceGuids(GetBlockListJson(innerJson), guids, - _contentGuid1, _contentGuid2, _settingsGuid1, - _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var expected = ReplaceGuids( + GetBlockListJson(innerJson), + guids, + ContentGuid1, + ContentGuid2, + SettingsGuid1, + SubContentGuid1, + SubContentGuid2, + SubSettingsGuid1); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); @@ -91,20 +99,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; // nested blocks without property value escaping used in the conversion - var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var innerJson = GetBlockListJson(null, SubContentGuid1, SubContentGuid2, SubSettingsGuid1); // get the json with the subFeatures as unescaped var json = GetBlockListJson(innerJson); - var expected = ReplaceGuids(GetBlockListJson(innerJson), guids, - _contentGuid1, _contentGuid2, _settingsGuid1, - _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var expected = ReplaceGuids( + GetBlockListJson(innerJson), + guids, + ContentGuid1, + ContentGuid2, + SettingsGuid1, + SubContentGuid1, + SubContentGuid2, + SubSettingsGuid1); var component = new BlockEditorComponent(); - var result = component.ReplaceBlockListUdis(json, guidFactory); + var result = component.ReplaceBlockListUdis(json, GuidFactory); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); @@ -118,9 +132,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { var guids = Enumerable.Range(0, 6).Select(x => Guid.NewGuid()).ToList(); var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; - var innerJson = GetBlockListJson(null, _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var innerJson = GetBlockListJson(null, SubContentGuid1, SubContentGuid2, SubSettingsGuid1); // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -132,12 +146,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var json = GetBlockListJson(complexEditorJsonEscaped); var component = new BlockEditorComponent(); - var result = component.ReplaceBlockListUdis(json, guidFactory); + var result = component.ReplaceBlockListUdis(json, GuidFactory); // the expected result is that the subFeatures data is no longer escaped - var expected = ReplaceGuids(GetBlockListJson(GetGridJson(innerJson)), guids, - _contentGuid1, _contentGuid2, _settingsGuid1, - _subContentGuid1, _subContentGuid2, _subSettingsGuid1); + var expected = ReplaceGuids( + GetBlockListJson(GetGridJson(innerJson)), + guids, + ContentGuid1, + ContentGuid2, + SettingsGuid1, + SubContentGuid1, + SubContentGuid2, + SubSettingsGuid1); var expectedJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(expected, _serializerSettings), _serializerSettings); var resultJson = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result, _serializerSettings), _serializerSettings); @@ -146,10 +166,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors Assert.AreEqual(expectedJson, resultJson); } - private string GetBlockListJson(string subFeatures, - string contentGuid1 = _contentGuid1, - string contentGuid2 = _contentGuid2, - string settingsGuid1 = _settingsGuid1) + private string GetBlockListJson( + string subFeatures, + string contentGuid1 = ContentGuid1, + string contentGuid2 = ContentGuid2, + string settingsGuid1 = SettingsGuid1) { return @"{ ""layout"": @@ -254,8 +275,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var old = oldGuids[i]; json = json.Replace(old, newGuids[i].ToString("N")); } + return json; } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs index eb77ad2e1c..a174a7e511 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs @@ -1,9 +1,9 @@ -using Moq; -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using Moq; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Models.Blocks; @@ -18,50 +18,49 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [TestFixture] public class BlockListPropertyValueConverterTests { - private readonly Guid ContentKey1 = Guid.NewGuid(); - private readonly Guid ContentKey2 = Guid.NewGuid(); + private readonly Guid _contentKey1 = Guid.NewGuid(); + private readonly Guid _contentKey2 = Guid.NewGuid(); private const string ContentAlias1 = "Test1"; private const string ContentAlias2 = "Test2"; - private readonly Guid SettingKey1 = Guid.NewGuid(); - private readonly Guid SettingKey2 = Guid.NewGuid(); + private readonly Guid _settingKey1 = Guid.NewGuid(); + private readonly Guid _settingKey2 = Guid.NewGuid(); private const string SettingAlias1 = "Setting1"; private const string SettingAlias2 = "Setting2"; /// /// Setup mocks for IPublishedSnapshotAccessor /// - /// private IPublishedSnapshotAccessor GetPublishedSnapshotAccessor() { - var test1ContentType = Mock.Of(x => + IPublishedContentType2 test1ContentType = Mock.Of(x => x.IsElement == true - && x.Key == ContentKey1 + && x.Key == _contentKey1 && x.Alias == ContentAlias1); - var test2ContentType = Mock.Of(x => + IPublishedContentType2 test2ContentType = Mock.Of(x => x.IsElement == true - && x.Key == ContentKey2 + && x.Key == _contentKey2 && x.Alias == ContentAlias2); - var test3ContentType = Mock.Of(x => + IPublishedContentType2 test3ContentType = Mock.Of(x => x.IsElement == true - && x.Key == SettingKey1 + && x.Key == _settingKey1 && x.Alias == SettingAlias1); - var test4ContentType = Mock.Of(x => + IPublishedContentType2 test4ContentType = Mock.Of(x => x.IsElement == true - && x.Key == SettingKey2 + && x.Key == _settingKey2 && x.Alias == SettingAlias2); var contentCache = new Mock(); - contentCache.Setup(x => x.GetContentType(ContentKey1)).Returns(test1ContentType); - contentCache.Setup(x => x.GetContentType(ContentKey2)).Returns(test2ContentType); - contentCache.Setup(x => x.GetContentType(SettingKey1)).Returns(test3ContentType); - contentCache.Setup(x => x.GetContentType(SettingKey2)).Returns(test4ContentType); - var publishedSnapshot = Mock.Of(x => x.Content == contentCache.Object); - var publishedSnapshotAccessor = Mock.Of(x => x.PublishedSnapshot == publishedSnapshot); + contentCache.Setup(x => x.GetContentType(_contentKey1)).Returns(test1ContentType); + contentCache.Setup(x => x.GetContentType(_contentKey2)).Returns(test2ContentType); + contentCache.Setup(x => x.GetContentType(_settingKey1)).Returns(test3ContentType); + contentCache.Setup(x => x.GetContentType(_settingKey2)).Returns(test4ContentType); + IPublishedSnapshot publishedSnapshot = Mock.Of(x => x.Content == contentCache.Object); + IPublishedSnapshotAccessor publishedSnapshotAccessor = Mock.Of(x => x.PublishedSnapshot == publishedSnapshot); return publishedSnapshotAccessor; } private BlockListPropertyValueConverter CreateConverter() { - var publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); + IPublishedSnapshotAccessor publishedSnapshotAccessor = GetPublishedSnapshotAccessor(); var publishedModelFactory = new NoopPublishedModelFactory(); var editor = new BlockListPropertyValueConverter( Mock.Of(), @@ -71,34 +70,36 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors private BlockListConfiguration ConfigForMany() => new BlockListConfiguration { - Blocks = new[] { - new BlockListConfiguration.BlockConfiguration - { - ContentElementTypeKey = ContentKey1, - SettingsElementTypeKey = SettingKey2 - }, - new BlockListConfiguration.BlockConfiguration - { - ContentElementTypeKey = ContentKey2, - SettingsElementTypeKey = SettingKey1 - } + Blocks = new[] + { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = _contentKey1, + SettingsElementTypeKey = _settingKey2 + }, + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = _contentKey2, + SettingsElementTypeKey = _settingKey1 } + } }; private BlockListConfiguration ConfigForSingle() => new BlockListConfiguration { - Blocks = new[] { - new BlockListConfiguration.BlockConfiguration - { - ContentElementTypeKey = ContentKey1 - } + Blocks = new[] + { + new BlockListConfiguration.BlockConfiguration + { + ContentElementTypeKey = _contentKey1 } + } }; private IPublishedPropertyType GetPropertyType(BlockListConfiguration config) { var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); - var propertyType = Mock.Of(x => + IPublishedPropertyType propertyType = Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList && x.DataType == dataType); return propertyType; @@ -107,7 +108,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Is_Converter_For() { - var editor = CreateConverter(); + BlockListPropertyValueConverter editor = CreateConverter(); Assert.IsTrue(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.BlockList))); Assert.IsFalse(editor.IsConverter(Mock.Of(x => x.EditorAlias == Constants.PropertyEditors.Aliases.NestedContent))); } @@ -115,13 +116,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Get_Value_Type_Multiple() { - var editor = CreateConverter(); - var config = ConfigForMany(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForMany(); var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); - var propType = Mock.Of(x => x.DataType == dataType); + IPublishedPropertyType propType = Mock.Of(x => x.DataType == dataType); - var valueType = editor.GetPropertyValueType(propType); + Type valueType = editor.GetPropertyValueType(propType); // the result is always block list model Assert.AreEqual(typeof(BlockListModel), valueType); @@ -130,13 +131,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Get_Value_Type_Single() { - var editor = CreateConverter(); - var config = ConfigForSingle(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForSingle(); var dataType = new PublishedDataType(1, "test", new Lazy(() => config)); - var propType = Mock.Of(x => x.DataType == dataType); + IPublishedPropertyType propType = Mock.Of(x => x.DataType == dataType); - var valueType = editor.GetPropertyValueType(propType); + Type valueType = editor.GetPropertyValueType(propType); // the result is always block list model Assert.AreEqual(typeof(BlockListModel), valueType); @@ -145,10 +146,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Convert_Null_Empty() { - var editor = CreateConverter(); - var config = ConfigForMany(); - var propertyType = GetPropertyType(config); - var publishedElement = Mock.Of(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForMany(); + IPublishedPropertyType propertyType = GetPropertyType(config); + IPublishedElement publishedElement = Mock.Of(); string json = null; var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; @@ -166,12 +167,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Convert_Valid_Empty_Json() { - var editor = CreateConverter(); - var config = ConfigForMany(); - var propertyType = GetPropertyType(config); - var publishedElement = Mock.Of(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForMany(); + IPublishedPropertyType propertyType = GetPropertyType(config); + IPublishedElement publishedElement = Mock.Of(); - var json = "{}"; + string json = "{}"; var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel; Assert.IsNotNull(converted); @@ -237,7 +238,7 @@ data: []}"; }, contentData: [ { - 'contentTypeKey': '" + ContentKey1 + @"', + 'contentTypeKey': '" + _contentKey1 + @"', 'key': '1304E1DD-0000-4396-84FE-8A399231CB3D' } ] @@ -252,12 +253,12 @@ data: []}"; [Test] public void Convert_Valid_Json() { - var editor = CreateConverter(); - var config = ConfigForMany(); - var propertyType = GetPropertyType(config); - var publishedElement = Mock.Of(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForMany(); + IPublishedPropertyType propertyType = GetPropertyType(config); + IPublishedElement publishedElement = Mock.Of(); - var json = @" + string json = @" { layout: { '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ @@ -268,7 +269,7 @@ data: []}"; }, contentData: [ { - 'contentTypeKey': '" + ContentKey1 + @"', + 'contentTypeKey': '" + _contentKey1 + @"', 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' } ] @@ -277,7 +278,7 @@ data: []}"; Assert.IsNotNull(converted); Assert.AreEqual(1, converted.Count); - var item0 = converted[0].Content; + IPublishedElement item0 = converted[0].Content; Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key); Assert.AreEqual("Test1", item0.ContentType.Alias); Assert.IsNull(converted[0].Settings); @@ -287,12 +288,12 @@ data: []}"; [Test] public void Get_Data_From_Layout_Item() { - var editor = CreateConverter(); - var config = ConfigForMany(); - var propertyType = GetPropertyType(config); - var publishedElement = Mock.Of(); + BlockListPropertyValueConverter editor = CreateConverter(); + BlockListConfiguration config = ConfigForMany(); + IPublishedPropertyType propertyType = GetPropertyType(config); + IPublishedElement publishedElement = Mock.Of(); - var json = @" + string json = @" { layout: { '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ @@ -308,29 +309,29 @@ data: []}"; }, contentData: [ { - 'contentTypeKey': '" + ContentKey1 + @"', + 'contentTypeKey': '" + _contentKey1 + @"', 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' }, { - 'contentTypeKey': '" + ContentKey2 + @"', + 'contentTypeKey': '" + _contentKey2 + @"', 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' }, { - 'contentTypeKey': '" + ContentKey2 + @"', + 'contentTypeKey': '" + _contentKey2 + @"', 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' } ], settingsData: [ { - 'contentTypeKey': '" + SettingKey1 + @"', + 'contentTypeKey': '" + _settingKey1 + @"', 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' }, { - 'contentTypeKey': '" + SettingKey2 + @"', + 'contentTypeKey': '" + _settingKey2 + @"', 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' }, { - 'contentTypeKey': '" + SettingKey2 + @"', + 'contentTypeKey': '" + _settingKey2 + @"', 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' } ], @@ -341,42 +342,42 @@ data: []}"; Assert.IsNotNull(converted); Assert.AreEqual(2, converted.Count); - var item0 = converted[0]; + BlockListItem item0 = converted[0]; Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Content.Key); Assert.AreEqual("Test1", item0.Content.ContentType.Alias); Assert.AreEqual(Guid.Parse("1F613E26CE274898908A561437AF5100"), item0.Settings.Key); Assert.AreEqual("Setting2", item0.Settings.ContentType.Alias); - var item1 = converted[1]; + BlockListItem item1 = converted[1]; Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item1.Content.Key); Assert.AreEqual("Test2", item1.Content.ContentType.Alias); Assert.AreEqual(Guid.Parse("63027539B0DB45E7B70459762D4E83DD"), item1.Settings.Key); Assert.AreEqual("Setting1", item1.Settings.ContentType.Alias); - } [Test] public void Data_Item_Removed_If_Removed_From_Config() { - var editor = CreateConverter(); + BlockListPropertyValueConverter editor = CreateConverter(); // The data below expects that ContentKey1 + ContentKey2 + SettingsKey1 + SettingsKey2 exist but only ContentKey2 exists so // the data should all be filtered. var config = new BlockListConfiguration { - Blocks = new[] { + Blocks = new[] + { new BlockListConfiguration.BlockConfiguration { - ContentElementTypeKey = ContentKey2, + ContentElementTypeKey = _contentKey2, SettingsElementTypeKey = null } } }; - var propertyType = GetPropertyType(config); - var publishedElement = Mock.Of(); + IPublishedPropertyType propertyType = GetPropertyType(config); + IPublishedElement publishedElement = Mock.Of(); - var json = @" + string json = @" { layout: { '" + Constants.PropertyEditors.Aliases.BlockList + @"': [ @@ -392,29 +393,29 @@ data: []}"; }, contentData: [ { - 'contentTypeKey': '" + ContentKey1 + @"', + 'contentTypeKey': '" + _contentKey1 + @"', 'udi': 'umb://element/1304E1DDAC87439684FE8A399231CB3D' }, { - 'contentTypeKey': '" + ContentKey2 + @"', + 'contentTypeKey': '" + _contentKey2 + @"', 'udi': 'umb://element/E05A034704424AB3A520E048E6197E79' }, { - 'contentTypeKey': '" + ContentKey2 + @"', + 'contentTypeKey': '" + _contentKey2 + @"', 'udi': 'umb://element/0A4A416E547D464FABCC6F345C17809A' } ], settingsData: [ { - 'contentTypeKey': '" + SettingKey1 + @"', + 'contentTypeKey': '" + _settingKey1 + @"', 'udi': 'umb://element/63027539B0DB45E7B70459762D4E83DD' }, { - 'contentTypeKey': '" + SettingKey2 + @"', + 'contentTypeKey': '" + _settingKey2 + @"', 'udi': 'umb://element/1F613E26CE274898908A561437AF5100' }, { - 'contentTypeKey': '" + SettingKey2 + @"', + 'contentTypeKey': '" + _settingKey2 + @"', 'udi': 'umb://element/BCF4BA3DA40C496C93EC58FAC85F18B9' } ], @@ -425,12 +426,10 @@ data: []}"; Assert.IsNotNull(converted); Assert.AreEqual(1, converted.Count); - var item0 = converted[0]; + BlockListItem item0 = converted[0]; Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item0.Content.Key); Assert.AreEqual("Test2", item0.Content.ContentType.Alias); Assert.IsNull(item0.Settings); - } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorListValidatorTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorListValidatorTest.cs index 98820ccac9..1264e24e58 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorListValidatorTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ColorListValidatorTest.cs @@ -1,4 +1,9 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -15,13 +20,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [TestFixture] public class ColorListValidatorTest { - private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; + private readonly ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; [Test] public void Only_Tests_On_JArray() { var validator = new ColorPickerConfigurationEditor.ColorListValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + "hello", + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(0, result.Count()); } @@ -29,7 +45,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ColorPickerConfigurationEditor.ColorListValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray("hello", "world"), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(0, result.Count()); } @@ -37,12 +64,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Validates_Color_Vals() { var validator = new ColorPickerConfigurationEditor.ColorListValidator(); - var result = validator.Validate(new JArray( - JObject.FromObject(new { value = "CC0000" }), - JObject.FromObject(new { value = "zxcvzxcvxzcv" }), - JObject.FromObject(new { value = "ABC" }), - JObject.FromObject(new { value = "1234567" })), - null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray( + JObject.FromObject(new { value = "CC0000" }), + JObject.FromObject(new { value = "zxcvzxcvxzcv" }), + JObject.FromObject(new { value = "ABC" }), + JObject.FromObject(new { value = "1234567" })), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ConvertersTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ConvertersTests.cs index f41a950c27..f78a72b42a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ConvertersTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/ConvertersTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Configuration; @@ -45,7 +48,6 @@ namespace Umbraco.Tests.Published }, Mock.Of()); register.AddTransient(f => factory); - var cacheMock = new Mock(); var cacheContent = new Dictionary(); cacheMock.Setup(x => x.GetById(It.IsAny())).Returns(id => @@ -62,11 +64,24 @@ namespace Umbraco.Tests.Published var serializer = new ConfigurationEditorJsonSerializer(); var dataTypeServiceMock = new Mock(); - var dataType1 = new DataType(new VoidEditor(NullLoggerFactory.Instance, dataTypeServiceMock.Object, - Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), + var dataType1 = new DataType( + new VoidEditor( + NullLoggerFactory.Instance, + dataTypeServiceMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer()), serializer) { Id = 1 }; - var dataType2 = new DataType(new VoidEditor("2", NullLoggerFactory.Instance, Mock.Of(), - Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), + var dataType2 = new DataType( + new VoidEditor( + "2", + NullLoggerFactory.Instance, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer()), serializer) { Id = 2 }; dataTypeServiceMock.Setup(x => x.GetAll()).Returns(new[] { dataType1, dataType2 }); @@ -87,10 +102,16 @@ namespace Umbraco.Tests.Published IPublishedContentType contentType2 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1003, "content2", t => CreatePropertyTypes(t, 2)); - var element1 = new PublishedElement(elementType1, Guid.NewGuid(), - new Dictionary { { "prop1", "val1" } }, false); - var element2 = new PublishedElement(elementType2, Guid.NewGuid(), - new Dictionary { { "prop2", "1003" } }, false); + var element1 = new PublishedElement( + elementType1, + Guid.NewGuid(), + new Dictionary { { "prop1", "val1" } }, + false); + var element2 = new PublishedElement( + elementType2, + Guid.NewGuid(), + new Dictionary { { "prop2", "1003" } }, + false); var cnt1 = new SolidPublishedContent(contentType1) { Id = 1003, @@ -115,9 +136,11 @@ namespace Umbraco.Tests.Published // can get the actual property Clr type // ie ModelType gets properly mapped by IPublishedContentModelFactory // must test ModelClrType with special equals 'cos they are not ref-equals - Assert.IsTrue(ModelType.Equals(typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")), + Assert.IsTrue(ModelType.Equals( + typeof(IEnumerable<>).MakeGenericType(ModelType.For("content1")), contentType2.GetPropertyType("prop2").ModelClrType)); - Assert.AreEqual(typeof(IEnumerable), + Assert.AreEqual( + typeof(IEnumerable), contentType2.GetPropertyType("prop2").ClrType); // can create a model for an element @@ -133,9 +156,9 @@ namespace Umbraco.Tests.Published // and get direct property Assert.IsInstanceOf( model2.Value(Mock.Of(), "prop2")); - Assert.AreEqual(1, - ((PublishedSnapshotTestObjects.TestContentModel1[])model2.Value(Mock.Of(), - "prop2")).Length); + Assert.AreEqual( + 1, + ((PublishedSnapshotTestObjects.TestContentModel1[])model2.Value(Mock.Of(), "prop2")).Length); // and get model property Assert.IsInstanceOf>(mmodel2.Prop2); @@ -174,15 +197,21 @@ namespace Umbraco.Tests.Published public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public override object ConvertSourceToIntermediate(IPublishedElement owner, - IPublishedPropertyType propertyType, object source, bool preview) + public override object ConvertSourceToIntermediate( + IPublishedElement owner, + IPublishedPropertyType propertyType, + object source, + bool preview) { var s = source as string; return s?.Split(',').Select(int.Parse).ToArray() ?? Array.Empty(); } - public override object ConvertIntermediateToObject(IPublishedElement owner, - IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, + public override object ConvertIntermediateToObject( + IPublishedElement owner, + IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, + object inter, bool preview) => ((int[])inter).Select(x => (PublishedSnapshotTestObjects.TestContentModel1)_publishedSnapshotAccessor.PublishedSnapshot.Content .GetById(x)).ToArray(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs index 3229c3652c..d29ba45531 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging.Abstractions; @@ -20,19 +23,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [TestFixture] public class DataValueReferenceFactoryCollectionTests { - IDataTypeService DataTypeService { get; } = Mock.Of(); + private IDataTypeService DataTypeService { get; } = Mock.Of(); + private IIOHelper IOHelper { get; } = Mock.Of(); - ILocalizedTextService LocalizedTextService { get; } = Mock.Of(); - ILocalizationService LocalizationService { get; } = Mock.Of(); - IShortStringHelper ShortStringHelper { get; } = Mock.Of(); - IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer(); + + private ILocalizedTextService LocalizedTextService { get; } = Mock.Of(); + + private ILocalizationService LocalizationService { get; } = Mock.Of(); + + private IShortStringHelper ShortStringHelper { get; } = Mock.Of(); + + private IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer(); [Test] public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory() { var collection = new DataValueReferenceFactoryCollection(new TestDataValueReferenceFactory().Yield()); - // label does not implement IDataValueReference var labelEditor = new LabelPropertyEditor( NullLoggerFactory.Instance, @@ -41,8 +48,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors LocalizedTextService, LocalizationService, ShortStringHelper, - JsonSerializer - ); + JsonSerializer); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(labelEditor.Yield())); var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); @@ -72,13 +78,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors Segment = "A", EditedValue = trackedUdi3 }, + // Ignored (no culture) new PropertyValue { Segment = "A", EditedValue = trackedUdi4 }, - // duplicate + + // Duplicate new PropertyValue { Culture = "en-US", @@ -91,7 +99,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { property }; - var result = collection.GetAllReferences(properties, propertyEditors); + IEnumerable result = collection.GetAllReferences(properties, propertyEditors); Assert.AreEqual(2, result.Count()); Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString()); @@ -111,8 +119,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors IOHelper, ShortStringHelper, LocalizedTextService, - JsonSerializer - ); + JsonSerializer); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield())); var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); @@ -142,13 +149,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors Segment = "A", EditedValue = trackedUdi3 }, + // Ignored (no culture) new PropertyValue { Segment = "A", EditedValue = trackedUdi4 }, - // duplicate + + // Duplicate new PropertyValue { Culture = "en-US", @@ -161,7 +170,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { property }; - var result = collection.GetAllReferences(properties, propertyEditors); + IEnumerable result = collection.GetAllReferences(properties, propertyEditors); Assert.AreEqual(2, result.Count()); Assert.AreEqual(trackedUdi2, result.ElementAt(0).Udi.ToString()); @@ -181,8 +190,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors IOHelper, ShortStringHelper, LocalizedTextService, - JsonSerializer - ); + JsonSerializer); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(mediaPicker.Yield())); var trackedUdi1 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); var trackedUdi2 = Udi.Create(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(); @@ -200,12 +208,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { EditedValue = trackedUdi1 }, + // Ignored (has culture) new PropertyValue { Culture = "en-US", EditedValue = trackedUdi2 }, + // Ignored (has culture) new PropertyValue { @@ -218,7 +228,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors Segment = "A", EditedValue = trackedUdi4 }, - // duplicate + + // Duplicate new PropertyValue { Segment = "B", @@ -230,7 +241,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { property }; - var result = collection.GetAllReferences(properties, propertyEditors); + IEnumerable result = collection.GetAllReferences(properties, propertyEditors); Assert.AreEqual(2, result.Count()); Assert.AreEqual(trackedUdi1, result.ElementAt(0).Udi.ToString()); @@ -247,14 +258,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { public IEnumerable GetReferences(object value) { - // This is the same as the media picker, it will just try to parse the value directly as a UDI - + // This is the same as the media picker, it will just try to parse the value directly as a UDI. var asString = value is string str ? str : value?.ToString(); - if (string.IsNullOrEmpty(asString)) yield break; + if (string.IsNullOrEmpty(asString)) + { + yield break; + } - if (UdiParser.TryParse(asString, out var udi)) + if (UdiParser.TryParse(asString, out Udi udi)) + { yield return new UmbracoEntityReference(udi); + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/EnsureUniqueValuesValidatorTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/EnsureUniqueValuesValidatorTest.cs index 2b81620b27..230be138ce 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/EnsureUniqueValuesValidatorTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/EnsureUniqueValuesValidatorTest.cs @@ -1,4 +1,9 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -15,12 +20,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [TestFixture] public class EnsureUniqueValuesValidatorTest { - private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; + private readonly ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; + [Test] public void Only_Tests_On_JArray() { var validator = new ValueListUniqueValueValidator(); - var result = validator.Validate("hello", null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + "hello", + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(0, result.Count()); } @@ -28,7 +45,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Only_Tests_On_JArray_Of_Item_JObject() { var validator = new ValueListUniqueValueValidator(); - var result = validator.Validate(new JArray("hello", "world"), null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray("hello", "world"), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(0, result.Count()); } @@ -36,7 +64,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Allows_Unique_Values() { var validator = new ValueListUniqueValueValidator(); - var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "world" })), null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray( + JObject.FromObject(new { value = "hello" }), + JObject.FromObject(new { value = "world" })), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(0, result.Count()); } @@ -44,8 +85,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Does_Not_Allow_Multiple_Values() { var validator = new ValueListUniqueValueValidator(); - var result = validator.Validate(new JArray(JObject.FromObject(new { value = "hello" }), JObject.FromObject(new { value = "hello" })), - null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray( + JObject.FromObject(new { value = "hello" }), + JObject.FromObject(new { value = "hello" })), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(1, result.Count()); } @@ -53,12 +106,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors public void Validates_Multiple_Duplicate_Values() { var validator = new ValueListUniqueValueValidator(); - var result = validator.Validate(new JArray( - JObject.FromObject(new { value = "hello" }), - JObject.FromObject(new { value = "hello" }), - JObject.FromObject(new { value = "world" }), - JObject.FromObject(new { value = "world" })), - null, new ColorPickerPropertyEditor(_loggerFactory, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer())); + IEnumerable result = + validator.Validate( + new JArray( + JObject.FromObject(new { value = "hello" }), + JObject.FromObject(new { value = "hello" }), + JObject.FromObject(new { value = "world" }), + JObject.FromObject(new { value = "world" })), + null, + new ColorPickerPropertyEditor( + _loggerFactory, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer())); Assert.AreEqual(2, result.Count()); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs index 2952ac032d..0b069d9662 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -45,14 +48,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var dataTypeServiceMock = new Mock(); dataTypeServiceMock - .Setup(x=>x.GetDataType(It.IsAny())) + .Setup(x => x.GetDataType(It.IsAny())) .Returns(dataType); var prop = new Property(1, new PropertyType(Mock.Of(), dataType)); prop.SetValue("Value 1,Value 2,Value 3"); - var valueEditor = dataType.Editor.GetValueEditor(); - ((DataValueEditor) valueEditor).Configuration = dataType.Configuration; + IDataValueEditor valueEditor = dataType.Editor.GetValueEditor(); + ((DataValueEditor)valueEditor).Configuration = dataType.Configuration; var result = valueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeServiceMock.Object); Assert.AreEqual("Value 1,Value 2,Value 3", result); @@ -77,9 +80,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors }; var dataTypeServiceMock = new Mock(); - dataTypeServiceMock - .Setup(x=>x.GetDataType(It.IsAny())) - .Returns(dataType); + dataTypeServiceMock + .Setup(x => x.GetDataType(It.IsAny())) + .Returns(dataType); var prop = new Property(1, new PropertyType(Mock.Of(), dataType)); prop.SetValue("Value 2"); @@ -96,17 +99,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors // (that should be fixed with proper injection) var textService = new Mock(); textService.Setup(x => x.Localize(It.IsAny(), It.IsAny(), It.IsAny>())).Returns("blah"); - //var appContext = new ApplicationContext( - // new DatabaseContext(TestObjects.GetIDatabaseFactoryMock(), logger, Mock.Of(), Mock.Of()), - // new ServiceContext( - // localizedTextService: textService.Object - // ), - // Mock.Of(), - // new ProfilingLogger(logger, Mock.Of())) - //{ - // //IsReady = true - //}; - //Current.ApplicationContext = appContext; + + //// var appContext = new ApplicationContext( + //// new DatabaseContext(TestObjects.GetIDatabaseFactoryMock(), logger, Mock.Of(), Mock.Of()), + //// new ServiceContext( + //// localizedTextService: textService.Object + //// ), + //// Mock.Of(), + //// new ProfilingLogger(logger, Mock.Of())) + //// { + //// //IsReady = true + //// }; + //// Current.ApplicationContext = appContext; var configuration = new ValueListConfiguration { @@ -120,7 +124,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var editor = new ValueListConfigurationEditor(Mock.Of(), Mock.Of()); - var result = editor.ToConfigurationEditor(configuration); + Dictionary result = editor.ToConfigurationEditor(configuration); // 'result' is meant to be serialized, is built with anonymous objects // so we cannot really test what's in it - but by serializing it diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs index 930f1b623f..022e19c18e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/NestedContentPropertyComponentTests.cs @@ -1,15 +1,13 @@ -using Newtonsoft.Json; -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Newtonsoft.Json; +using NUnit.Framework; using Umbraco.Web.Compose; namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors { - [TestFixture] public class NestedContentPropertyComponentTests { @@ -24,9 +22,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void No_Nesting() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; var json = @"[ {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, @@ -37,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); } @@ -45,9 +43,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void One_Level_Nesting_Unescaped() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; var json = @"[{ ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", @@ -81,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); } @@ -89,9 +87,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void One_Level_Nesting_Escaped() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -129,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); } @@ -137,9 +135,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Nested_In_Complex_Editor_Escaped() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -212,7 +210,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors }] }"; - var json = @"[{ ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", ""name"": ""Item 1"", @@ -234,18 +231,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors .Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString()); var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, false, guidFactory); + var result = component.CreateNestedContentKeys(json, false, GuidFactory); Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString()); } - [Test] public void No_Nesting_Generates_Keys_For_Missing_Items() { - var guids = new[] { Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; var json = @"[ {""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""}, @@ -253,7 +249,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors ]"; var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, true, guidFactory); + var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); @@ -265,9 +261,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void One_Level_Nesting_Escaped_Generates_Keys_For_Missing_Items() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -296,7 +292,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors ]"; var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, true, guidFactory); + var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); @@ -307,9 +303,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Nested_In_Complex_Editor_Escaped_Generates_Keys_For_Missing_Items() { - var guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; + Guid[] guids = new[] { Guid.NewGuid(), Guid.NewGuid() }; var guidCounter = 0; - Func guidFactory = () => guids[guidCounter++]; + Guid GuidFactory() => guids[guidCounter++]; // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing // and this is how to do that, the result will also include quotes around it. @@ -381,7 +377,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors }] }"; - var json = @"[{ ""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"", ""name"": ""Item 1"", @@ -396,7 +391,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors ]"; var component = new NestedContentPropertyComponent(); - var result = component.CreateNestedContentKeys(json, true, guidFactory); + var result = component.CreateNestedContentKeys(json, true, GuidFactory); // Ensure the new GUID is put in a key into the JSON for each item Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString())); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs index be72eff1e7..94e68af881 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueConverterTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Moq; @@ -9,8 +12,6 @@ using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Serialization; using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.PropertyEditors; using Umbraco.Web.PropertyEditors.ValueConverters; namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors @@ -21,15 +22,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors // see notes in the converter // only ONE date format is expected here - //[TestCase("2012-11-10", true)] - //[TestCase("2012/11/10", true)] - //[TestCase("10/11/2012", true)] - //[TestCase("11/10/2012", false)] - //[TestCase("Sat 10, Nov 2012", true)] - //[TestCase("Saturday 10, Nov 2012", true)] - //[TestCase("Sat 10, November 2012", true)] - //[TestCase("Saturday 10, November 2012", true)] - //[TestCase("2012-11-10 13:14:15", true)] + // [TestCase("2012-11-10", true)] + // [TestCase("2012/11/10", true)] + // [TestCase("10/11/2012", true)] + // [TestCase("11/10/2012", false)] + // [TestCase("Sat 10, Nov 2012", true)] + // [TestCase("Saturday 10, Nov 2012", true)] + // [TestCase("Sat 10, November 2012", true)] + // [TestCase("Saturday 10, November 2012", true)] + // [TestCase("2012-11-10 13:14:15", true)] [TestCase("2012-11-10 13:14:15", true)] [TestCase("2012-11-10T13:14:15", true)] [TestCase("", false)] @@ -40,9 +41,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var result = converter.ConvertSourceToIntermediate(null, null, date, false); // does not use type for conversion if (expected) - Assert.AreEqual(dateTime.Date, ((DateTime) result).Date); + { + Assert.AreEqual(dateTime.Date, ((DateTime)result).Date); + } else + { Assert.AreNotEqual(dateTime.Date, ((DateTime)result).Date); + } } [TestCase("TRUE", true)] @@ -96,10 +101,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors }))); var publishedPropType = new PublishedPropertyType( - new PublishedContentType(Guid.NewGuid(),1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), + new PublishedContentType(Guid.NewGuid(), 1234, "test", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing), new PropertyType(Mock.Of(), "test", ValueStorageType.Nvarchar) { DataTypeId = 123 }, new PropertyValueConverterCollection(Enumerable.Empty()), - Mock.Of(), mockPublishedContentTypeFactory.Object); + Mock.Of(), + mockPublishedContentTypeFactory.Object); var converter = new FlexibleDropdownPropertyValueConverter(); var inter = converter.ConvertSourceToIntermediate(null, publishedPropType, value, false); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueEditorTests.cs index 4fe391d1e9..a351439c90 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -1,10 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Strings; @@ -24,23 +24,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors var prop = new Property(1, new PropertyType(Mock.Of(), "test", ValueStorageType.Nvarchar)); prop.SetValue(value); - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.String); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.String); - var result = valueEditor.ToEditor(prop); + object result = valueEditor.ToEditor(prop); Assert.AreEqual(isOk, !(result is string)); - } [TestCase("STRING", "hello", "hello")] [TestCase("TEXT", "hello", "hello")] [TestCase("INT", "123", 123)] - [TestCase("INT", "", null)] //test empty string for int - [TestCase("DATETIME", "", null)] //test empty string for date + [TestCase("INT", "", null)] // test empty string for int + [TestCase("DATETIME", "", null)] // test empty string for date public void Value_Editor_Can_Convert_To_Clr_Type(string valueType, string val, object expected) { - var valueEditor = MockedValueEditors.CreateDataValueEditor(valueType); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(valueType); - var result = valueEditor.TryConvertValueToCrlType(val); + Attempt result = valueEditor.TryConvertValueToCrlType(val); Assert.IsTrue(result.Success); Assert.AreEqual(expected, result.Result); } @@ -50,9 +49,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Value_Editor_Can_Convert_To_Decimal_Clr_Type() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); - var result = valueEditor.TryConvertValueToCrlType("12.34"); + Attempt result = valueEditor.TryConvertValueToCrlType("12.34"); Assert.IsTrue(result.Success); Assert.AreEqual(12.34M, result.Result); } @@ -60,9 +59,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Value_Editor_Can_Convert_To_Decimal_Clr_Type_With_Other_Separator() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); - var result = valueEditor.TryConvertValueToCrlType("12,34"); + Attempt result = valueEditor.TryConvertValueToCrlType("12,34"); Assert.IsTrue(result.Success); Assert.AreEqual(12.34M, result.Result); } @@ -70,9 +69,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Value_Editor_Can_Convert_To_Decimal_Clr_Type_With_Empty_String() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); - var result = valueEditor.TryConvertValueToCrlType(string.Empty); + Attempt result = valueEditor.TryConvertValueToCrlType(string.Empty); Assert.IsTrue(result.Success); Assert.IsNull(result.Result); } @@ -80,9 +79,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [Test] public void Value_Editor_Can_Convert_To_Date_Clr_Type() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date); - var result = valueEditor.TryConvertValueToCrlType("2010-02-05"); + Attempt result = valueEditor.TryConvertValueToCrlType("2010-02-05"); Assert.IsTrue(result.Success); Assert.AreEqual(new DateTime(2010, 2, 5), result.Result); } @@ -90,54 +89,54 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.PropertyEditors [TestCase(ValueTypes.String, "hello", "hello")] [TestCase(ValueTypes.Text, "hello", "hello")] [TestCase(ValueTypes.Integer, 123, "123")] - [TestCase(ValueTypes.Integer, "", "")] //test empty string for int - [TestCase(ValueTypes.DateTime, "", "")] //test empty string for date + [TestCase(ValueTypes.Integer, "", "")] // test empty string for int + [TestCase(ValueTypes.DateTime, "", "")] // test empty string for date public void Value_Editor_Can_Serialize_Value(string valueType, object val, string expected) { var prop = new Property(1, new PropertyType(Mock.Of(), "test", ValueStorageType.Nvarchar)); prop.SetValue(val); - var valueEditor = MockedValueEditors.CreateDataValueEditor(valueType); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(valueType); - var result = valueEditor.ToEditor(prop); + object result = valueEditor.ToEditor(prop); Assert.AreEqual(expected, result); } [Test] public void Value_Editor_Can_Serialize_Decimal_Value() { - var value = 12.34M; - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + decimal value = 12.34M; + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); var prop = new Property(1, new PropertyType(Mock.Of(), "test", ValueStorageType.Decimal)); prop.SetValue(value); - var result = valueEditor.ToEditor(prop); + object result = valueEditor.ToEditor(prop); Assert.AreEqual("12.34", result); } [Test] public void Value_Editor_Can_Serialize_Decimal_Value_With_Empty_String() { - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Decimal); var prop = new Property(1, new PropertyType(Mock.Of(), "test", ValueStorageType.Decimal)); prop.SetValue(string.Empty); - var result = valueEditor.ToEditor(prop); + object result = valueEditor.ToEditor(prop); Assert.AreEqual(string.Empty, result); } [Test] public void Value_Editor_Can_Serialize_Date_Value() { - var now = DateTime.Now; - var valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date); + DateTime now = DateTime.Now; + DataValueEditor valueEditor = MockedValueEditors.CreateDataValueEditor(ValueTypes.Date); var prop = new Property(1, new PropertyType(Mock.Of(), "test", ValueStorageType.Date)); prop.SetValue(now); - var result = valueEditor.ToEditor(prop); + object result = valueEditor.ToEditor(prop); Assert.AreEqual(now.ToIsoString(), result); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ConvertersTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ConvertersTests.cs index 13c4ae071c..352a1f45cd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ConvertersTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -19,8 +22,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [TestFixture] public class ConvertersTests { - #region SimpleConverter1 - [Test] public void SimpleConverter1Test() { @@ -31,8 +32,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published var serializer = new ConfigurationEditorJsonSerializer(); var dataTypeServiceMock = new Mock(); - var dataType = new DataType(new VoidEditor(Mock.Of(), dataTypeServiceMock.Object, - Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), serializer) + var dataType = new DataType( + new VoidEditor( + Mock.Of(), + dataTypeServiceMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer()), serializer) { Id = 1 }; dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield); @@ -43,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); + IPublishedContentType elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); @@ -92,10 +99,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published => ((int)inter).ToString(); } - #endregion - - #region SimpleConverter2 - [Test] public void SimpleConverter2Test() { @@ -106,7 +109,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published publishedSnapshotMock.Setup(x => x.Content).Returns(cacheMock.Object); var publishedSnapshotAccessorMock = new Mock(); publishedSnapshotAccessorMock.Setup(x => x.PublishedSnapshot).Returns(publishedSnapshotMock.Object); - var publishedSnapshotAccessor = publishedSnapshotAccessorMock.Object; + IPublishedSnapshotAccessor publishedSnapshotAccessor = publishedSnapshotAccessorMock.Object; var converters = new PropertyValueConverterCollection(new IPropertyValueConverter[] { @@ -115,8 +118,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published var serializer = new ConfigurationEditorJsonSerializer(); var dataTypeServiceMock = new Mock(); - var dataType = new DataType(new VoidEditor(Mock.Of(), dataTypeServiceMock.Object, - Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), serializer) + var dataType = new DataType( + new VoidEditor( + Mock.Of(), + dataTypeServiceMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer()), serializer) { Id = 1 }; dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield); @@ -127,11 +136,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return contentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); + IPublishedContentType elementType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "element1", CreatePropertyTypes); var element1 = new PublishedElement(elementType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); - var cntType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "cnt1", t => Enumerable.Empty()); + IPublishedContentType cntType1 = contentTypeFactory.CreateContentType(Guid.NewGuid(), 1001, "cnt1", t => Enumerable.Empty()); var cnt1 = new SolidPublishedContent(cntType1) { Id = 1234 }; cacheContent[cnt1.Id] = cnt1; @@ -156,10 +165,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); public Type GetPropertyValueType(IPublishedPropertyType propertyType) - // the first version would be the "generic" version, but say we want to be more precise + + // The first version would be the "generic" version, but say we want to be more precise // and return: whatever Clr type is generated for content type with alias "cnt1" -- which // we cannot really typeof() at the moment because it has not been generated, hence ModelType. - // => typeof (IPublishedContent); + // => typeof(IPublishedContent); => ModelType.For("cnt1"); public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) @@ -174,7 +184,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) => ((int)inter).ToString(); } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs index 9b4f377247..456cfeb170 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/ModelTypeTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using NUnit.Framework; @@ -27,7 +30,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [Test] public void TypeToStringTests() { - var type = typeof(int); + Type type = typeof(int); Assert.AreEqual("System.Int32", type.ToString()); Assert.AreEqual("System.Int32[]", type.MakeArrayType().ToString()); Assert.AreEqual("System.Collections.Generic.IEnumerable`1[System.Int32[]]", typeof(IEnumerable<>).MakeGenericType(type.MakeArrayType()).ToString()); @@ -36,10 +39,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [Test] public void TypeFullNameTests() { - var type = typeof(int); + Type type = typeof(int); Assert.AreEqual("System.Int32", type.FullName); Assert.AreEqual("System.Int32[]", type.MakeArrayType().FullName); - // note the inner assembly qualified name + + // Note the inner assembly qualified name Assert.AreEqual("System.Collections.Generic.IEnumerable`1[[System.Int32[], System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]", typeof(IEnumerable<>).MakeGenericType(type.MakeArrayType()).FullName); } @@ -48,17 +52,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published { var map = new Dictionary { - { "alias1", typeof (PublishedSnapshotTestObjects.TestElementModel1) }, - { "alias2", typeof (PublishedSnapshotTestObjects.TestElementModel2) }, + { "alias1", typeof(PublishedSnapshotTestObjects.TestElementModel1) }, + { "alias2", typeof(PublishedSnapshotTestObjects.TestElementModel2) }, }; - Assert.AreEqual("Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1", + Assert.AreEqual( + "Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1", ModelType.Map(ModelType.For("alias1"), map).ToString()); - Assert.AreEqual("Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1[]", + Assert.AreEqual( + "Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1[]", ModelType.Map(ModelType.For("alias1").MakeArrayType(), map).ToString()); - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1]", + Assert.AreEqual( + "System.Collections.Generic.IEnumerable`1[Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1]", ModelType.Map(typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1")), map).ToString()); - Assert.AreEqual("System.Collections.Generic.IEnumerable`1[Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1[]]", + Assert.AreEqual( + "System.Collections.Generic.IEnumerable`1[Umbraco.Tests.Published.PublishedSnapshotTestObjects+TestElementModel1[]]", ModelType.Map(typeof(IEnumerable<>).MakeGenericType(ModelType.For("alias1").MakeArrayType()), map).ToString()); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs index 7493d59d72..102dd6388e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/NestedContentTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -27,14 +30,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published { private (IPublishedContentType, IPublishedContentType) CreateContentTypes() { - var logger = Mock.Of>(); - var loggerFactory = NullLoggerFactory.Instance; - var profiler = Mock.Of(); + ILogger logger = Mock.Of>(); + NullLoggerFactory loggerFactory = NullLoggerFactory.Instance; + IProfiler profiler = Mock.Of(); var proflog = new ProfilingLogger(logger, profiler); - var localizationService = Mock.Of(); + ILocalizationService localizationService = Mock.Of(); PropertyEditorCollection editors = null; - var editor = new NestedContentPropertyEditor(loggerFactory, new Lazy(() => editors), Mock.Of(),localizationService, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()); + var editor = new NestedContentPropertyEditor(loggerFactory, new Lazy(() => editors), Mock.Of(), localizationService, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()); editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor })); var serializer = new ConfigurationEditorJsonSerializer(); @@ -74,7 +77,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published // mocked dataservice returns nested content preValues var dataTypeServiceMock = new Mock(); - dataTypeServiceMock.Setup(x => x.GetAll()).Returns(new []{dataType1, dataType2, dataType3}); + dataTypeServiceMock.Setup(x => x.GetAll()).Returns(new[] { dataType1, dataType2, dataType3 }); var publishedModelFactory = new Mock(); @@ -93,7 +96,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published .Returns((IPublishedElement element) => { if (element.ContentType.Alias.InvariantEquals("contentN1")) + { return new TestElementModel(element); + } + return element; }); @@ -101,11 +107,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published publishedModelFactory .Setup(x => x.CreateModelList(It.IsAny())) .Returns((string alias) => - { - return alias == "contentN1" + alias == "contentN1" ? (IList)new List() - : (IList)new List(); - }); + : (IList)new List()); var contentCache = new Mock(); var publishedSnapshot = new Mock(); @@ -145,16 +149,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return factory.CreatePropertyType(contentType, "propertyN1", 3); } - var contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1); - var contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2); - var contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true); + IPublishedContentType contentType1 = factory.CreateContentType(Guid.NewGuid(), 1, "content1", CreatePropertyTypes1); + IPublishedContentType contentType2 = factory.CreateContentType(Guid.NewGuid(), 2, "content2", CreatePropertyTypes2); + IPublishedContentType contentTypeN1 = factory.CreateContentType(Guid.NewGuid(), 2, "contentN1", CreatePropertyTypesN1, isElement: true); // mocked content cache returns content types contentCache .Setup(x => x.GetContentType(It.IsAny())) .Returns((string alias) => { - if (alias.InvariantEquals("contentN1")) return contentTypeN1; + if (alias.InvariantEquals("contentN1")) + { + return contentTypeN1; + } + return null; }); @@ -164,7 +172,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [Test] public void SingleNestedTest() { - (var contentType1, _) = CreateContentTypes(); + (IPublishedContentType contentType1, _) = CreateContentTypes(); // nested single converter returns the proper value clr type TestModel, and cache level Assert.AreEqual(typeof(TestElementModel), contentType1.GetPropertyType("property1").ClrType); @@ -177,12 +185,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published Key = key, Properties = new[] { - new TestPublishedProperty(contentType1.GetPropertyType("property1"), $@"[ + new TestPublishedProperty( + contentType1.GetPropertyType("property1"), $@"[ {{ ""key"": ""{keyA}"", ""propertyN1"": ""foo"", ""ncContentTypeAlias"": ""contentN1"" }} ]") } }; - var value = content.Value(Mock.Of(),"property1"); + var value = content.Value(Mock.Of(), "property1"); // nested single converter returns proper TestModel value Assert.IsInstanceOf(value); @@ -194,7 +203,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [Test] public void ManyNestedTest() { - (_, var contentType2) = CreateContentTypes(); + (_, IPublishedContentType contentType2) = CreateContentTypes(); // nested many converter returns the proper value clr type IEnumerable, and cache level Assert.AreEqual(typeof(IEnumerable), contentType2.GetPropertyType("property2").ClrType); @@ -214,7 +223,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published ]") } }; - var value = content.Value(Mock.Of(), ("property2")); + var value = content.Value(Mock.Of(), "property2"); // nested many converter returns proper IEnumerable value Assert.IsInstanceOf>(value); @@ -230,12 +239,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published { public TestElementModel(IPublishedElement content) : base(content) - { } + { + } - public string PropValue => this.Value(Mock.Of(),"propertyN1"); + public string PropValue => this.Value(Mock.Of(), "propertyN1"); } - class TestPublishedProperty : PublishedPropertyBase + public class TestPublishedProperty : PublishedPropertyBase { private readonly bool _preview; private readonly object _sourceValue; @@ -260,14 +270,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published private object InterValue => PropertyType.ConvertSourceToInter(null, _sourceValue, false); - internal void SetOwner(IPublishedElement owner) - { - _owner = owner; - } + internal void SetOwner(IPublishedElement owner) => _owner = owner; public override bool HasValue(string culture = null, string segment = null) => _hasValue; + public override object GetSourceValue(string culture = null, string segment = null) => _sourceValue; + public override object GetValue(string culture = null, string segment = null) => PropertyType.ConvertInterToObject(_owner, ReferenceCacheLevel, InterValue, _preview); + public override object GetXPathValue(string culture = null, string segment = null) => throw new InvalidOperationException("This method won't be implemented."); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheLevelTests.cs index 163020b6f6..d020fb01d5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheLevelTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -34,12 +37,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published var configurationEditorJsonSerializer = new ConfigurationEditorJsonSerializer(); var jsonSerializer = new JsonNetSerializer(); var dataTypeServiceMock = new Mock(); - var dataType = new DataType(new VoidEditor(NullLoggerFactory.Instance, dataTypeServiceMock.Object, - Mock.Of(), Mock.Of(), Mock.Of(), jsonSerializer), configurationEditorJsonSerializer) + var dataType = new DataType( + new VoidEditor( + NullLoggerFactory.Instance, + dataTypeServiceMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + jsonSerializer), configurationEditorJsonSerializer) { Id = 1 }; dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield); - var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of(), converters, dataTypeServiceMock.Object); IEnumerable CreatePropertyTypes(IPublishedContentType contentType) @@ -47,7 +55,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", dataType.Id); } - var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); + IPublishedContentType setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); // PublishedElementPropertyBase.GetCacheLevels: // @@ -61,7 +69,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published // anything else is not > None, use Content // // for standalone elements, it's only None or Content - var set1 = new PublishedElement(setType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false); Assert.AreEqual(1234, set1.Value(Mock.Of(), "prop1")); @@ -70,7 +77,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published // source is always converted once and cached per content // inter conversion depends on the specified cache level - Assert.AreEqual(1234, set1.Value(Mock.Of(), "prop1")); Assert.AreEqual(1, converter.SourceConverts); Assert.AreEqual(interConverts, converter.InterConverts); @@ -108,8 +114,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published [TestCase(PropertyCacheLevel.Snapshot, PropertyCacheLevel.Elements, 1, 0, 0, 0, 0)] [TestCase(PropertyCacheLevel.Snapshot, PropertyCacheLevel.Snapshot, 1, 0, 0, 0, 0)] - public void CachePublishedSnapshotTest(PropertyCacheLevel referenceCacheLevel, PropertyCacheLevel converterCacheLevel, int interConverts, - int elementsCount1, int snapshotCount1, int elementsCount2, int snapshotCount2) + public void CachePublishedSnapshotTest( + PropertyCacheLevel referenceCacheLevel, + PropertyCacheLevel converterCacheLevel, + int interConverts, + int elementsCount1, + int snapshotCount1, + int elementsCount2, + int snapshotCount2) { var converter = new CacheConverter1(converterCacheLevel); @@ -119,8 +131,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published }); var dataTypeServiceMock = new Mock(); - var dataType = new DataType(new VoidEditor(NullLoggerFactory.Instance, dataTypeServiceMock.Object, - Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), new ConfigurationEditorJsonSerializer()) + var dataType = new DataType( + new VoidEditor( + NullLoggerFactory.Instance, + dataTypeServiceMock.Object, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new JsonNetSerializer()), new ConfigurationEditorJsonSerializer()) { Id = 1 }; dataTypeServiceMock.Setup(x => x.GetAll()).Returns(dataType.Yield); @@ -131,7 +149,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); + IPublishedContentType setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); var elementsCache = new FastDictionaryAppCache(); var snapshotCache = new FastDictionaryAppCache(); @@ -146,7 +164,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published // pretend we're creating this set as a value for a property // referenceCacheLevel is the cache level for this fictious property // converterCacheLevel is the cache level specified by the converter - var set1 = new PublishedElement(setType1, Guid.NewGuid(), new Dictionary { { "prop1", "1234" } }, false, referenceCacheLevel, publishedSnapshotAccessor.Object); Assert.AreEqual(1234, set1.Value(Mock.Of(), "prop1")); @@ -163,7 +180,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published Assert.AreEqual(elementsCount2, elementsCache.Count); Assert.AreEqual(snapshotCount2, snapshotCache.Count); - var oldSnapshotCache = snapshotCache; + FastDictionaryAppCache oldSnapshotCache = snapshotCache; snapshotCache.Clear(); Assert.AreEqual(1234, set1.Value(Mock.Of(), "prop1")); @@ -175,7 +192,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published Assert.AreEqual((interConverts == 1 ? 1 : 3) + snapshotCache.Count, converter.InterConverts); - var oldElementsCache = elementsCache; + FastDictionaryAppCache oldElementsCache = elementsCache; elementsCache.Clear(); Assert.AreEqual(1234, set1.Value(Mock.Of(), "prop1")); @@ -203,7 +220,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published new VoidEditor( NullLoggerFactory.Instance, dataTypeServiceMock.Object, - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), @@ -218,7 +235,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published yield return publishedContentTypeFactory.CreatePropertyType(contentType, "prop1", 1); } - var setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); + IPublishedContentType setType1 = publishedContentTypeFactory.CreateContentType(Guid.NewGuid(), 1000, "set1", CreatePropertyTypes); Assert.Throws(() => { @@ -230,16 +247,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published { private readonly PropertyCacheLevel _cacheLevel; - public CacheConverter1(PropertyCacheLevel cacheLevel) - { - _cacheLevel = cacheLevel; - } + public CacheConverter1(PropertyCacheLevel cacheLevel) => _cacheLevel = cacheLevel; public int SourceConverts { get; private set; } + public int InterConverts { get; private set; } public bool? IsValue(object value, PropertyValueLevel level) - => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + => value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); public bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals("Umbraco.Void"); @@ -259,11 +274,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Published public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) { InterConverts++; - return (int) inter; + return (int)inter; } public object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - => ((int) inter).ToString(); + => ((int)inter).ToString(); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs index 063a8c2621..6950c50cc0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionTests.cs @@ -1,4 +1,8 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -11,9 +15,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void GetBaseTypesIsOk() { // tests that the GetBaseTypes extension method works. - - var type = typeof(Class2); - var types = type.GetBaseTypes(true).ToArray(); + Type type = typeof(Class2); + Type[] types = type.GetBaseTypes(true).ToArray(); Assert.AreEqual(3, types.Length); Assert.Contains(typeof(Class2), types); Assert.Contains(typeof(Class1), types); @@ -25,8 +28,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.Contains(typeof(object), types); } - #region Test Objects - private class Class1 { } @@ -34,7 +35,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core private class Class2 : Class1 { } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs index 0f48f2cea2..ba397522ae 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ReflectionUtilitiesTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -14,28 +17,28 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void EmitCtorEmits() { - var ctor1 = ReflectionUtilities.EmitConstructor>(); + Func ctor1 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor1()); - var ctor2 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class1)); + Func ctor2 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class1)); Assert.IsInstanceOf(ctor2()); - var ctor3 = ReflectionUtilities.EmitConstructor>(); + Func ctor3 = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor3(42)); - var ctor4 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class3)); + Func ctor4 = ReflectionUtilities.EmitConstructor>(declaring: typeof(Class3)); Assert.IsInstanceOf(ctor4(42)); } [Test] public void EmitCtorEmitsFromInfo() { - var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); - var ctor1 = ReflectionUtilities.EmitConstructor>(ctorInfo); + ConstructorInfo ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); + Func ctor1 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor1()); ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new[] { typeof(int) }, null); - var ctor3 = ReflectionUtilities.EmitConstructor>(ctorInfo); + Func ctor3 = ReflectionUtilities.EmitConstructor>(ctorInfo); Assert.IsInstanceOf(ctor3(42)); Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); @@ -44,67 +47,63 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void EmitCtorEmitsPrivateCtor() { - var ctor = ReflectionUtilities.EmitConstructor>(); + Func ctor = ReflectionUtilities.EmitConstructor>(); Assert.IsInstanceOf(ctor("foo")); } [Test] - public void EmitCtorThrowsIfNotFound() - { + public void EmitCtorThrowsIfNotFound() => Assert.Throws(() => ReflectionUtilities.EmitConstructor>()); - } [Test] public void EmitCtorThrowsIfInvalid() { - var ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); + ConstructorInfo ctorInfo = typeof(Class1).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, Array.Empty(), null); Assert.Throws(() => ReflectionUtilities.EmitConstructor>(ctorInfo)); } [Test] - public void EmitCtorReturnsNull() - { + public void EmitCtorReturnsNull() => Assert.IsNull(ReflectionUtilities.EmitConstructor>(false)); - } [Test] public void EmitMethodEmitsInstance() { var class1 = new Class1(); - var method1 = ReflectionUtilities.EmitMethod>("Method1"); + Action method1 = ReflectionUtilities.EmitMethod>("Method1"); method1(class1); - var method2 = ReflectionUtilities.EmitMethod>("Method2"); + Action method2 = ReflectionUtilities.EmitMethod>("Method2"); method2(class1, 42); - var method3 = ReflectionUtilities.EmitMethod>("Method3"); + Func method3 = ReflectionUtilities.EmitMethod>("Method3"); Assert.AreEqual(42, method3(class1)); - var method4 = ReflectionUtilities.EmitMethod>("Method4"); + Func method4 = ReflectionUtilities.EmitMethod>("Method4"); Assert.AreEqual(42, method4(class1, "42")); } [Test] public void EmitMethodEmitsStatic() { - var method1 = ReflectionUtilities.EmitMethod("SMethod1"); + Action method1 = ReflectionUtilities.EmitMethod("SMethod1"); method1(); - var method2 = ReflectionUtilities.EmitMethod>("SMethod2"); + Action method2 = ReflectionUtilities.EmitMethod>("SMethod2"); method2(42); - var method3 = ReflectionUtilities.EmitMethod>("SMethod3"); + Func method3 = ReflectionUtilities.EmitMethod>("SMethod3"); Assert.AreEqual(42, method3()); - var method4 = ReflectionUtilities.EmitMethod>("SMethod4"); + Func method4 = ReflectionUtilities.EmitMethod>("SMethod4"); Assert.AreEqual(42, method4("42")); } [Test] public void EmitMethodEmitsStaticStatic() { - var method = ReflectionUtilities.EmitMethod(typeof (StaticClass1), "Method"); + Action method = ReflectionUtilities.EmitMethod(typeof(StaticClass1), "Method"); method(); } @@ -113,40 +112,40 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var class1 = new Class1(); - var methodInfo = typeof (Class1).GetMethod("Method1", BindingFlags.Instance | BindingFlags.Public); - var method1 = ReflectionUtilities.EmitMethod>(methodInfo); + MethodInfo methodInfo = typeof(Class1).GetMethod("Method1", BindingFlags.Instance | BindingFlags.Public); + Action method1 = ReflectionUtilities.EmitMethod>(methodInfo); method1(class1); - methodInfo = typeof(Class1).GetMethod("Method2", BindingFlags.Instance | BindingFlags.Public, null, new [] { typeof(int) }, null); - var method2 = ReflectionUtilities.EmitMethod>(methodInfo); + methodInfo = typeof(Class1).GetMethod("Method2", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, null); + Action method2 = ReflectionUtilities.EmitMethod>(methodInfo); method2(class1, 42); methodInfo = typeof(Class1).GetMethod("Method3", BindingFlags.Instance | BindingFlags.Public); - var method3 = ReflectionUtilities.EmitMethod>(methodInfo); + Func method3 = ReflectionUtilities.EmitMethod>(methodInfo); Assert.AreEqual(42, method3(class1)); methodInfo = typeof(Class1).GetMethod("Method4", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, null); - var method4 = ReflectionUtilities.EmitMethod>(methodInfo); + Func method4 = ReflectionUtilities.EmitMethod>(methodInfo); Assert.AreEqual(42, method4(class1, "42")); methodInfo = typeof(Class1).GetMethod("SMethod1", BindingFlags.Static | BindingFlags.Public); - var smethod1 = ReflectionUtilities.EmitMethod(methodInfo); + Action smethod1 = ReflectionUtilities.EmitMethod(methodInfo); smethod1(); methodInfo = typeof(Class1).GetMethod("SMethod2", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(int) }, null); - var smethod2 = ReflectionUtilities.EmitMethod>(methodInfo); + Action smethod2 = ReflectionUtilities.EmitMethod>(methodInfo); smethod2(42); methodInfo = typeof(Class1).GetMethod("SMethod3", BindingFlags.Static | BindingFlags.Public); - var smethod3 = ReflectionUtilities.EmitMethod>(methodInfo); + Func smethod3 = ReflectionUtilities.EmitMethod>(methodInfo); Assert.AreEqual(42, smethod3()); methodInfo = typeof(Class1).GetMethod("SMethod4", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null); - var smethod4 = ReflectionUtilities.EmitMethod>(methodInfo); + Func smethod4 = ReflectionUtilities.EmitMethod>(methodInfo); Assert.AreEqual(42, smethod4("42")); methodInfo = typeof(StaticClass1).GetMethod("Method", BindingFlags.Static | BindingFlags.Public); - var method = ReflectionUtilities.EmitMethod(methodInfo); + Action method = ReflectionUtilities.EmitMethod(methodInfo); method(); } @@ -155,10 +154,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var class1 = new Class1(); - var method1 = ReflectionUtilities.EmitMethod>("MethodP1"); + Action method1 = ReflectionUtilities.EmitMethod>("MethodP1"); method1(class1); - var method2 = ReflectionUtilities.EmitMethod("SMethodP1"); + Action method2 = ReflectionUtilities.EmitMethod("SMethodP1"); method2(); } @@ -172,7 +171,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void EmitMethodThrowsIfInvalid() { - var methodInfo = typeof(Class1).GetMethod("Method1", BindingFlags.Instance | BindingFlags.Public); + MethodInfo methodInfo = typeof(Class1).GetMethod("Method1", BindingFlags.Instance | BindingFlags.Public); Assert.Throws(() => ReflectionUtilities.EmitMethod>(methodInfo)); } @@ -188,19 +187,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var class1 = new Class1(); - var getter1 = ReflectionUtilities.EmitPropertyGetter("Value1"); + Func getter1 = ReflectionUtilities.EmitPropertyGetter("Value1"); Assert.AreEqual(42, getter1(class1)); - var getter2 = ReflectionUtilities.EmitPropertyGetter("Value3"); + Func getter2 = ReflectionUtilities.EmitPropertyGetter("Value3"); Assert.AreEqual(42, getter2(class1)); - var setter1 = ReflectionUtilities.EmitPropertySetter("Value2"); + Action setter1 = ReflectionUtilities.EmitPropertySetter("Value2"); setter1(class1, 42); - var setter2 = ReflectionUtilities.EmitPropertySetter("Value3"); + Action setter2 = ReflectionUtilities.EmitPropertySetter("Value3"); setter2(class1, 42); - (var getter3, var setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter("Value3"); + (Func getter3, Action setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter("Value3"); Assert.AreEqual(42, getter3(class1)); setter3(class1, 42); } @@ -210,23 +209,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var class1 = new Class1(); - var propertyInfo = typeof (Class1).GetProperty("Value1"); - var getter1 = ReflectionUtilities.EmitPropertyGetter(propertyInfo); + PropertyInfo propertyInfo = typeof(Class1).GetProperty("Value1"); + Func getter1 = ReflectionUtilities.EmitPropertyGetter(propertyInfo); Assert.AreEqual(42, getter1(class1)); propertyInfo = typeof(Class1).GetProperty("Value3"); - var getter2 = ReflectionUtilities.EmitPropertyGetter(propertyInfo); + Func getter2 = ReflectionUtilities.EmitPropertyGetter(propertyInfo); Assert.AreEqual(42, getter2(class1)); propertyInfo = typeof(Class1).GetProperty("Value2"); - var setter1 = ReflectionUtilities.EmitPropertySetter(propertyInfo); + Action setter1 = ReflectionUtilities.EmitPropertySetter(propertyInfo); setter1(class1, 42); propertyInfo = typeof(Class1).GetProperty("Value3"); - var setter2 = ReflectionUtilities.EmitPropertySetter(propertyInfo); + Action setter2 = ReflectionUtilities.EmitPropertySetter(propertyInfo); setter2(class1, 42); - (var getter3, var setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter(propertyInfo); + (Func getter3, Action setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter(propertyInfo); Assert.AreEqual(42, getter3(class1)); setter3(class1, 42); } @@ -236,7 +235,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var class1 = new Class1(); - var getter1 = ReflectionUtilities.EmitPropertyGetter("ValueP1"); + Func getter1 = ReflectionUtilities.EmitPropertyGetter("ValueP1"); Assert.AreEqual(42, getter1(class1)); } @@ -246,15 +245,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.Throws(() => ReflectionUtilities.EmitPropertyGetter("Zalue1")); Assert.Throws(() => ReflectionUtilities.EmitPropertyGetter("Value2")); - var propertyInfo = typeof(Class1).GetProperty("Value1"); + PropertyInfo propertyInfo = typeof(Class1).GetProperty("Value1"); Assert.Throws(() => ReflectionUtilities.EmitPropertySetter(propertyInfo)); } [Test] - public void EmitPropertyThrowsIfInvalid() - { + public void EmitPropertyThrowsIfInvalid() => Assert.Throws(() => ReflectionUtilities.EmitPropertyGetter("Value1")); - } [Test] public void EmitPropertyReturnsNull() @@ -267,18 +264,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void PropertySetterCanCastUnsafeValue() { // test that we can emit property setters that cast from eg 'object' - - var type4 = typeof(Class4); - var propInt4 = type4.GetProperty("IntValue"); - var propString4 = type4.GetProperty("StringValue"); - var propClassA4 = type4.GetProperty("ClassAValue"); + Type type4 = typeof(Class4); + PropertyInfo propInt4 = type4.GetProperty("IntValue"); + PropertyInfo propString4 = type4.GetProperty("StringValue"); + PropertyInfo propClassA4 = type4.GetProperty("ClassAValue"); var object4 = new Class4(); var object2A = new Class2A(); // works with a string property Assert.IsNotNull(propString4); - var setterString4 = ReflectionUtilities.EmitPropertySetterUnsafe(propString4); + Action setterString4 = ReflectionUtilities.EmitPropertySetterUnsafe(propString4); Assert.IsNotNull(setterString4); setterString4(object4, "foo"); Assert.IsNotNull(object4.StringValue); @@ -289,7 +285,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core // works with a reference property Assert.IsNotNull(propClassA4); - var setterClassA4 = ReflectionUtilities.EmitPropertySetterUnsafe(propClassA4); + Action setterClassA4 = ReflectionUtilities.EmitPropertySetterUnsafe(propClassA4); Assert.IsNotNull(setterClassA4); setterClassA4(object4, object2A); Assert.IsNotNull(object4.ClassAValue); @@ -297,7 +293,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core // works with a boxed value type Assert.IsNotNull(propInt4); - var setterInt4 = ReflectionUtilities.EmitPropertySetterUnsafe(propInt4); + Action setterInt4 = ReflectionUtilities.EmitPropertySetterUnsafe(propInt4); Assert.IsNotNull(setterInt4); setterInt4(object4, 42); @@ -318,14 +314,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void PropertySetterCanCastObject() { // Class5 inherits from Class4 and ClassValue is defined on Class4 - - var type5 = typeof(Class5); - var propClass4 = type5.GetProperty("ClassValue"); + Type type5 = typeof(Class5); + PropertyInfo propClass4 = type5.GetProperty("ClassValue"); var object2 = new Class2(); // can cast the object type from Class5 to Class4 - var setterClass4 = ReflectionUtilities.EmitPropertySetter(propClass4); + Action setterClass4 = ReflectionUtilities.EmitPropertySetter(propClass4); var object4 = new Class5(); setterClass4(object4, object2); @@ -336,13 +331,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void PropertySetterCanCastUnsafeObject() { - var type5 = typeof(Class5); - var propClass4 = type5.GetProperty("ClassValue"); + Type type5 = typeof(Class5); + PropertyInfo propClass4 = type5.GetProperty("ClassValue"); var object2 = new Class2(); // can cast the object type from object to Class4 - var setterClass4 = ReflectionUtilities.EmitPropertySetterUnsafe(propClass4); + Action setterClass4 = ReflectionUtilities.EmitPropertySetterUnsafe(propClass4); var object4 = new Class5(); setterClass4(object4, object2); @@ -353,17 +348,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void PropertyGetterCanCastValue() { - var type4 = typeof(Class4); - var propClassA4 = type4.GetProperty("ClassAValue"); - var propInt4 = type4.GetProperty("IntValue"); + Type type4 = typeof(Class4); + PropertyInfo propClassA4 = type4.GetProperty("ClassAValue"); + PropertyInfo propInt4 = type4.GetProperty("IntValue"); var object2A = new Class2A(); var object4 = new Class4 { ClassAValue = object2A, IntValue = 159 }; // can cast the return type from Class2A to Class2 - var getterClassA4 = ReflectionUtilities.EmitPropertyGetter(propClassA4); + Func getterClassA4 = ReflectionUtilities.EmitPropertyGetter(propClassA4); - var valueClass4A = getterClassA4(object4); + Class2 valueClass4A = getterClassA4(object4); Assert.IsNotNull(valueClass4A); Assert.AreSame(object2A, valueClass4A); @@ -372,9 +367,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core => ReflectionUtilities.EmitPropertyGetter(propClassA4)); // can cast and box the return type from int to object - var getterInt4 = ReflectionUtilities.EmitPropertyGetter(propInt4); + Func getterInt4 = ReflectionUtilities.EmitPropertyGetter(propInt4); - var valueInt4 = getterInt4(object4); + object valueInt4 = getterInt4(object4); Assert.IsTrue(valueInt4 is int); Assert.AreEqual(159, valueInt4); @@ -386,16 +381,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void PropertyGetterCanCastObject() { - var type5 = typeof(Class5); - var propClass4 = type5.GetProperty("ClassValue"); + Type type5 = typeof(Class5); + PropertyInfo propClass4 = type5.GetProperty("ClassValue"); var object2 = new Class2(); var object4 = new Class5 { ClassValue = object2 }; // can cast the object type from Class5 to Class4 - var getterClass4 = ReflectionUtilities.EmitPropertyGetter(propClass4); + Func getterClass4 = ReflectionUtilities.EmitPropertyGetter(propClass4); - var valueClass4 = getterClass4(object4); + Class2 valueClass4 = getterClass4(object4); Assert.IsNotNull(valueClass4); Assert.AreSame(object2, valueClass4); @@ -408,10 +403,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public void EmitPropertyCastGetterEmits() { // test that we can emit property getters that cast the returned value to 'object' - // test simple class - - var type4 = typeof(Class4); + Type type4 = typeof(Class4); var object4 = new Class4 { @@ -421,69 +414,71 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core }; // works with a string property - var propString4 = type4.GetProperty("StringValue"); + PropertyInfo propString4 = type4.GetProperty("StringValue"); Assert.IsNotNull(propString4); - var getterString4 = ReflectionUtilities.EmitPropertyGetter(propString4); + Func getterString4 = ReflectionUtilities.EmitPropertyGetter(propString4); Assert.IsNotNull(getterString4); - var valueString4 = getterString4(object4); + object valueString4 = getterString4(object4); Assert.IsNotNull(valueString4); Assert.AreEqual("foo", valueString4); // works with a reference property - var propClass4 = type4.GetProperty("ClassValue"); + PropertyInfo propClass4 = type4.GetProperty("ClassValue"); Assert.IsNotNull(propClass4); - var getterClass4 = ReflectionUtilities.EmitPropertyGetter(propClass4); + Func getterClass4 = ReflectionUtilities.EmitPropertyGetter(propClass4); Assert.IsNotNull(getterClass4); - var valueClass4 = getterClass4(object4); + object valueClass4 = getterClass4(object4); Assert.IsNotNull(valueClass4); Assert.IsInstanceOf(valueClass4); // works with a value type property - var propInt4 = type4.GetProperty("IntValue"); + PropertyInfo propInt4 = type4.GetProperty("IntValue"); Assert.IsNotNull(propInt4); // ... if explicitly getting a value type - var getterInt4T = ReflectionUtilities.EmitPropertyGetter(propInt4); + Func getterInt4T = ReflectionUtilities.EmitPropertyGetter(propInt4); Assert.IsNotNull(getterInt4T); - var valueInt4T = getterInt4T(object4); + int valueInt4T = getterInt4T(object4); Assert.AreEqual(1, valueInt4T); // ... if using a compiled getter - var valueInt4D = GetIntValue(object4); + object valueInt4D = GetIntValue(object4); Assert.IsNotNull(valueInt4D); Assert.IsTrue(valueInt4D is int); Assert.AreEqual(1, valueInt4D); // ... if getting a non-value type (emit adds a box) - var getterInt4 = ReflectionUtilities.EmitPropertyGetter(propInt4); + Func getterInt4 = ReflectionUtilities.EmitPropertyGetter(propInt4); Assert.IsNotNull(getterInt4); - var valueInt4 = getterInt4(object4); + object valueInt4 = getterInt4(object4); Assert.IsNotNull(valueInt4); Assert.IsTrue(valueInt4 is int); Assert.AreEqual(1, valueInt4); var getters4 = type4 .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy) - .ToDictionary(x => x.Name, x => (object) ReflectionUtilities.EmitPropertyGetter(x)); + .ToDictionary(x => x.Name, x => (object)ReflectionUtilities.EmitPropertyGetter(x)); Console.WriteLine("Getting object4 values..."); - var values4 = getters4.ToDictionary(kvp => kvp.Key, kvp => ((Func) kvp.Value)(object4)); + var values4 = getters4.ToDictionary(kvp => kvp.Key, kvp => ((Func)kvp.Value)(object4)); Console.WriteLine("Writing object4 values..."); - foreach ((var name, var value) in values4) + foreach ((string name, object value) in values4) + { Console.WriteLine($"{name}: {value}"); + } + Assert.AreEqual(4, values4.Count); Assert.AreEqual("foo", values4["StringValue"]); Assert.IsInstanceOf(values4["ClassValue"]); Assert.AreEqual(1, values4["IntValue"]); // test hierarchy - - var type5 = typeof(Class5); + Type type5 = typeof(Class5); var getters5 = type5 .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy) - .ToDictionary(x => x.Name, x => (object) ReflectionUtilities.EmitPropertyGetter(x)); + .ToDictionary(x => x.Name, x => (object)ReflectionUtilities.EmitPropertyGetter(x)); var object5 = new Class5 { @@ -496,11 +491,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core }; Console.WriteLine("Getting object5 values..."); - var values5 = getters5.ToDictionary(kvp => kvp.Key, kvp => ((Func) kvp.Value)(object5)); + var values5 = getters5.ToDictionary(kvp => kvp.Key, kvp => ((Func)kvp.Value)(object5)); Console.WriteLine("Writing object5 values..."); - foreach ((var name, var value) in values5) + foreach ((string name, object value) in values5) + { Console.WriteLine($"{name}: {value}"); + } + Assert.AreEqual(7, values5.Count); Assert.AreEqual("foo", values5["StringValue"]); Assert.IsInstanceOf(values5["ClassValue"]); @@ -510,13 +508,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.AreEqual(1, values5["IntValue2"]); // test object extensions - Console.WriteLine("Getting object5D values..."); - var values5D = ObjectJsonExtensions.ToObjectDictionary(object5); + Dictionary values5D = ObjectJsonExtensions.ToObjectDictionary(object5); Console.WriteLine("Writing object5D values..."); - foreach ((var name, var value) in values5) + foreach ((string name, object value) in values5) + { Console.WriteLine($"{name}: {value}"); + } + Assert.AreEqual(7, values5.Count); Assert.AreEqual("foo", values5D["StringValue"]); Assert.IsInstanceOf(values5D["ClassValue"]); @@ -529,18 +529,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core [Test] public void EmitFieldGetterSetterEmits() { - var getter1 = ReflectionUtilities.EmitFieldGetter("Field1"); - var getter2 = ReflectionUtilities.EmitFieldGetter("Field2"); + Func getter1 = ReflectionUtilities.EmitFieldGetter("Field1"); + Func getter2 = ReflectionUtilities.EmitFieldGetter("Field2"); var c = new Class1(); Assert.AreEqual(33, getter1(c)); Assert.AreEqual(66, getter2(c)); - var setter2 = ReflectionUtilities.EmitFieldSetter("Field2"); + Action setter2 = ReflectionUtilities.EmitFieldSetter("Field2"); setter2(c, 99); Assert.AreEqual(99, getter2(c)); // works on readonly fields! - var (getter3, setter3) = ReflectionUtilities.EmitFieldGetterAndSetter("Field3"); + (Func getter3, Action setter3) = ReflectionUtilities.EmitFieldGetterAndSetter("Field3"); Assert.AreEqual(22, getter3(c)); setter3(c, 44); Assert.AreEqual(44, getter3(c)); @@ -554,8 +554,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core var o = new { a = 1, b = "hello" }; var getters = new Dictionary>(); - foreach (var prop in o.GetType().GetProperties()) + foreach (PropertyInfo prop in o.GetType().GetProperties()) + { getters[prop.Name] = ReflectionUtilities.EmitMethodUnsafe>(prop.GetMethod); + } Assert.AreEqual(2, getters.Count); Assert.IsTrue(getters.ContainsKey("a")); @@ -564,40 +566,46 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core Assert.AreEqual("hello", getters["b"](o)); } - #region IL Code - // these functions can be examined in eg DotPeek to understand IL works // box [mscorlib]System.Int32 public object GetIntValue(Class4 object4) => object4.IntValue; // unbox.any [mscorlib]System.Int32 - public void SetIntValue(Class4 object4, object i) => object4.IntValue = (int) i; + public void SetIntValue(Class4 object4, object i) => object4.IntValue = (int)i; // castclass [mscorlib]System.String - public void SetStringValue(Class4 object4, object s) => object4.StringValue = (string) s; + public void SetStringValue(Class4 object4, object s) => object4.StringValue = (string)s; // conv.i4 - public void SetIntValue(Class4 object4, double d) => object4.IntValue = (int) d; + public void SetIntValue(Class4 object4, double d) => object4.IntValue = (int)d; // unbox.any [mscorlib]System.Double // conv.i4 - public void SetIntValue2(Class4 object4, object d) => object4.IntValue = (int) (double) d; + public void SetIntValue2(Class4 object4, object d) => object4.IntValue = (int)(double)d; public void SetIntValue3(Class4 object4, object v) { if (v is int i) + { object4.IntValue = i; + } else + { object4.IntValue = Convert.ToInt32(v); + } } public void SetIntValue4(Class4 object4, object v) { if (v is int i) + { object4.IntValue = i; + } else - object4.IntValue = (int) Convert.ChangeType(v, typeof(int)); + { + object4.IntValue = (int)Convert.ChangeType(v, typeof(int)); + } } // get field @@ -606,38 +614,69 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core // set field public void SetIntField(Class1 object1, int i) => object1.Field1 = i; - #endregion - - #region Test Objects - public static class StaticClass1 { - public static void Method() { } + public static void Method() + { + } } public class Class1 { - public Class1() { } - public Class1(int i) { } + public Class1() + { + } + + public Class1(int i) + { + } + + public void Method1() + { + } + + public void Method2(int i) + { + } - public void Method1() { } - public void Method2(int i) { } public int Method3() => 42; + public int Method4(string s) => int.Parse(s); public string Method5() => "foo"; - public static void SMethod1() { } - public static void SMethod2(int i) { } + public static void SMethod1() + { + } + + public static void SMethod2(int i) + { + } + public static int SMethod3() => 42; + public static int SMethod4(string s) => int.Parse(s); - private void MethodP1() { } - private static void SMethodP1() { } + private void MethodP1() + { + } + + private static void SMethodP1() + { + } public int Value1 => 42; - public int Value2 { set { } } - public int Value3 { get { return 42; } set { } } + + public int Value2 + { + set { } + } + + public int Value3 + { + get => 42; set { } + } + private int ValueP1 => 42; public int Field1 = 33; @@ -645,22 +684,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core public readonly int Field3 = 22; } - public class Class2 { } + public class Class2 + { + } - public class Class2A : Class2 { } + public class Class2A : Class2 + { + } public class Class3 { - public Class3(int i) { } + public Class3(int i) + { + } - private Class3(string s) { } + private Class3(string s) + { + } } public class Class4 { public int IntValue { get; set; } + public string StringValue { get; set; } - public Class2 ClassValue { get;set; } + + public Class2 ClassValue { get; set; } + public Class2A ClassAValue { get; set; } } @@ -668,10 +718,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { [JsonProperty("intValue2")] public int IntValue2 { get; set; } - public string StringValue2 { get; set; } - public Class2 ClassValue2 { get;set; } - } - #endregion + public string StringValue2 { get; set; } + + public Class2 ClassValue2 { get; set; } + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs index d32340b6b0..2aed3e0216 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/SiteDomainHelperTests.cs @@ -1,4 +1,8 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using NUnit.Framework; @@ -10,19 +14,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing public class SiteDomainHelperTests { [SetUp] - public void SetUp() - { - SiteDomainHelper.Clear(); // assuming this works! - } + public void SetUp() => SiteDomainHelper.Clear(); // assuming this works! [TearDown] - public void TearDown() - { - SiteDomainHelper.Clear(); // assuming this works! - } + public void TearDown() => SiteDomainHelper.Clear(); // assuming this works! - private static CultureInfo _cultureFr = CultureInfo.GetCultureInfo("fr-fr"); - private static CultureInfo _cultureGb = CultureInfo.GetCultureInfo("en-gb"); + private static readonly CultureInfo s_cultureFr = CultureInfo.GetCultureInfo("fr-fr"); + private static readonly CultureInfo s_cultureGb = CultureInfo.GetCultureInfo("en-gb"); [Test] public void AddSites() @@ -30,14 +28,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); - var sites = SiteDomainHelper.Sites; + Dictionary sites = SiteDomainHelper.Sites; Assert.AreEqual(2, sites.Count); Assert.Contains("site1", sites.Keys); Assert.Contains("site2", sites.Keys); - var domains = sites["site1"]; + string[] domains = sites["site1"]; Assert.AreEqual(3, domains.Length); Assert.Contains("domain1.com", domains); Assert.Contains("domain1.net", domains); @@ -60,18 +58,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("http://www.domain.com:12/")] [TestCase("https://foo.www.domain.com")] [TestCase("https://foo.www.domain.com:5478/")] - public void AddValidSite(string domain) - { - SiteDomainHelper.AddSite("site1", domain); - } + public void AddValidSite(string domain) => SiteDomainHelper.AddSite("site1", domain); [TestCase("domain.com/foo")] [TestCase("http:/domain.com")] [TestCase("*")] - public void AddInvalidSite(string domain) - { - Assert.Throws(() => SiteDomainHelper.AddSite("site1", domain)); - } + public void AddInvalidSite(string domain) => Assert.Throws(() => SiteDomainHelper.AddSite("site1", domain)); [Test] public void AddRemoveSites() @@ -79,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); SiteDomainHelper.AddSite("site2", "domain2.com", "domain2.net", "domain2.org"); - var sites = SiteDomainHelper.Sites; + Dictionary sites = SiteDomainHelper.Sites; SiteDomainHelper.RemoveSite("site1"); SiteDomainHelper.RemoveSite("site3"); @@ -95,13 +87,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.AddSite("site1", "domain1.com", "domain1.net", "domain1.org"); SiteDomainHelper.AddSite("site1", "domain2.com", "domain1.net"); - var sites = SiteDomainHelper.Sites; + Dictionary sites = SiteDomainHelper.Sites; Assert.AreEqual(1, sites.Count); Assert.Contains("site1", sites.Keys); - var domains = sites["site1"]; + string[] domains = sites["site1"]; Assert.AreEqual(2, domains.Count()); Assert.Contains("domain2.com", domains); Assert.Contains("domain1.net", domains); @@ -117,13 +109,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.BindSites("site1", "site2"); - var bindings = SiteDomainHelper.Bindings; + Dictionary> bindings = SiteDomainHelper.Bindings; Assert.AreEqual(2, bindings.Count); Assert.Contains("site1", bindings.Keys); Assert.Contains("site2", bindings.Keys); - var others = bindings["site1"]; + List others = bindings["site1"]; Assert.AreEqual(1, others.Count); Assert.Contains("site2", others); @@ -143,14 +135,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.BindSites("site1", "site2"); SiteDomainHelper.BindSites("site1", "site3"); - var bindings = SiteDomainHelper.Bindings; + Dictionary> bindings = SiteDomainHelper.Bindings; Assert.AreEqual(3, bindings.Count); Assert.Contains("site1", bindings.Keys); Assert.Contains("site2", bindings.Keys); Assert.Contains("site3", bindings.Keys); - var others = bindings["site1"]; + List others = bindings["site1"]; Assert.AreEqual(2, others.Count); Assert.Contains("site2", others); Assert.Contains("site3", others); @@ -166,14 +158,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing Assert.Contains("site2", others); } - private DomainAndUri[] DomainAndUris(Uri current, Domain[] domains) - { - return domains + private DomainAndUri[] DomainAndUris(Uri current, Domain[] domains) => + domains .Where(d => d.IsWildcard == false) .Select(d => new DomainAndUri(d, current)) .OrderByDescending(d => d.Uri.ToString()) .ToArray(); - } [Test] public void MapDomainWithScheme() @@ -189,40 +179,44 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing // this works, but it's purely by chance / arbitrary // don't use the www in tests here! var current = new Uri("https://www.domain1.com/foo/bar"); - var domainAndUris = DomainAndUris(current, new[] - { - new Domain(1, "domain2.com", -1, _cultureFr, false), - new Domain(1, "domain1.com", -1, _cultureGb, false), - }); - var output = helper.MapDomain(domainAndUris, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + Domain[] domains = new[] + { + new Domain(1, "domain2.com", -1, s_cultureFr, false), + 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(); Assert.AreEqual("https://domain1.com/", output); // will pick it all right current = new Uri("https://domain1.com/foo/bar"); - domainAndUris = DomainAndUris(current, new[] - { - new Domain(1, "https://domain1.com", -1, _cultureFr, false), - new Domain(1, "https://domain2.com", -1, _cultureGb, false) - }); - output = helper.MapDomain(domainAndUris, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + domains = new[] + { + new Domain(1, "https://domain1.com", -1, s_cultureFr, false), + 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(); Assert.AreEqual("https://domain1.com/", output); current = new Uri("https://domain1.com/foo/bar"); - domainAndUris = DomainAndUris(current, new[] - { - new Domain(1, "https://domain1.com", -1, _cultureFr, false), - new Domain(1, "https://domain4.com", -1, _cultureGb, false) - }); - output = helper.MapDomain(domainAndUris, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + domains = new[] + { + new Domain(1, "https://domain1.com", -1, s_cultureFr, false), + 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(); Assert.AreEqual("https://domain1.com/", output); current = new Uri("https://domain4.com/foo/bar"); - domainAndUris = DomainAndUris(current, new[] + domains = new[] { - new Domain(1, "https://domain1.com", -1, _cultureFr, false), - new Domain(1, "https://domain4.com", -1, _cultureGb, false) - }); - output = helper.MapDomain(domainAndUris, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + new Domain(1, "https://domain1.com", -1, s_cultureFr, false), + 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(); Assert.AreEqual("https://domain4.com/", output); } @@ -239,36 +233,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing // current is a site1 uri, domains contain current // so we'll get current - // var current = new Uri("http://domain1.com/foo/bar"); - var output = helper.MapDomain(new[] + string output = helper.MapDomain( + new[] { - new DomainAndUri(new Domain(1, "domain1.com", -1, _cultureFr, false), current), - new DomainAndUri(new Domain(1, "domain2.com", -1, _cultureGb, false), current), - }, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + 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(); Assert.AreEqual("http://domain1.com/", output); // current is a site1 uri, domains do not contain current // so we'll get the corresponding site1 domain - // current = new Uri("http://domain1.com/foo/bar"); - output = helper.MapDomain(new[] + output = helper.MapDomain( + new[] { - new DomainAndUri(new Domain(1, "domain1.net", -1, _cultureFr, false), current), - new DomainAndUri(new Domain(1, "domain2.net", -1, _cultureGb, false), current) - }, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + 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(); Assert.AreEqual("http://domain1.net/", output); // current is a site1 uri, domains do not contain current // so we'll get the corresponding site1 domain // order does not matter - // current = new Uri("http://domain1.com/foo/bar"); - output = helper.MapDomain(new[] + output = helper.MapDomain( + new[] { - new DomainAndUri(new Domain(1, "domain2.net", -1, _cultureFr, false), current), - new DomainAndUri(new Domain(1, "domain1.net", -1, _cultureGb, false), current) - }, current, _cultureFr.Name, _cultureFr.Name).Uri.ToString(); + 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(); Assert.AreEqual("http://domain1.net/", output); } @@ -289,31 +289,39 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing // - return all domains from same site, or bound sites // current is a site1 uri, domains contains current - // var current = new Uri("http://domain1.com/foo/bar"); - var output = helper.MapDomains(new[] + DomainAndUri[] output = helper.MapDomains( + new[] { - new DomainAndUri(new Domain(1, "domain1.com", -1, _cultureFr, false), current), // no: current + what MapDomain would pick - new DomainAndUri(new Domain(1, "domain2.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain3.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain4.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain1.org", -1, _cultureGb, false), current), // yes: same site (though bogus setup) - }, current, true, _cultureFr.Name, _cultureFr.Name).ToArray(); + new DomainAndUri(new Domain(1, "domain1.com", -1, s_cultureFr, false), current), // no: current + what MapDomain would pick + new DomainAndUri(new Domain(1, "domain2.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain3.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain4.com", -1, s_cultureGb, false), current), // no: not same site + 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(); Assert.AreEqual(1, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); // current is a site1 uri, domains does not contain current - // current = new Uri("http://domain1.com/foo/bar"); - output = helper.MapDomains(new[] + output = helper.MapDomains( + new[] { - new DomainAndUri(new Domain(1, "domain1.net", -1, _cultureFr, false), current), // no: what MapDomain would pick - new DomainAndUri(new Domain(1, "domain2.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain3.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain4.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain1.org", -1, _cultureGb, false), current), // yes: same site (though bogus setup) - }, current, true, _cultureFr.Name, _cultureFr.Name).ToArray(); + new DomainAndUri(new Domain(1, "domain1.net", -1, s_cultureFr, false), current), // no: what MapDomain would pick + new DomainAndUri(new Domain(1, "domain2.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain3.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain4.com", -1, s_cultureGb, false), current), // no: not same site + 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(); Assert.AreEqual(1, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); @@ -322,17 +330,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing SiteDomainHelper.BindSites("site2", "site4"); // current is a site1 uri, domains contains current - // current = new Uri("http://domain1.com/foo/bar"); - output = helper.MapDomains(new[] + output = helper.MapDomains( + new[] { - new DomainAndUri(new Domain(1, "domain1.com", -1, _cultureFr, false), current), // no: current + what MapDomain would pick - new DomainAndUri(new Domain(1, "domain2.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain3.com", -1, _cultureGb, false), current), // yes: bound site - new DomainAndUri(new Domain(1, "domain3.org", -1, _cultureGb, false), current), // yes: bound site - new DomainAndUri(new Domain(1, "domain4.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain1.org", -1, _cultureGb, false), current), // yes: same site (though bogus setup) - }, current, true, _cultureFr.Name, _cultureFr.Name).ToArray(); + new DomainAndUri(new Domain(1, "domain1.com", -1, s_cultureFr, false), current), // no: current + what MapDomain would pick + new DomainAndUri(new Domain(1, "domain2.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain3.com", -1, s_cultureGb, false), current), // yes: bound site + new DomainAndUri(new Domain(1, "domain3.org", -1, s_cultureGb, false), current), // yes: bound site + new DomainAndUri(new Domain(1, "domain4.com", -1, s_cultureGb, false), current), // no: not same site + 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(); Assert.AreEqual(3, output.Count()); Assert.Contains("http://domain1.org/", output.Select(d => d.Uri.ToString()).ToArray()); @@ -340,17 +352,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing Assert.Contains("http://domain3.org/", output.Select(d => d.Uri.ToString()).ToArray()); // current is a site1 uri, domains does not contain current - // current = new Uri("http://domain1.com/foo/bar"); - output = helper.MapDomains(new[] + output = helper.MapDomains( + new[] { - new DomainAndUri(new Domain(1, "domain1.net", -1, _cultureFr, false), current), // no: what MapDomain would pick - new DomainAndUri(new Domain(1, "domain2.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain3.com", -1, _cultureGb, false), current), // yes: bound site - new DomainAndUri(new Domain(1, "domain3.org", -1, _cultureGb, false), current), // yes: bound site - new DomainAndUri(new Domain(1, "domain4.com", -1, _cultureGb, false), current), // no: not same site - new DomainAndUri(new Domain(1, "domain1.org", -1, _cultureGb, false), current), // yes: same site (though bogus setup) - }, current, true, _cultureFr.Name, _cultureFr.Name).ToArray(); + new DomainAndUri(new Domain(1, "domain1.net", -1, s_cultureFr, false), current), // no: what MapDomain would pick + new DomainAndUri(new Domain(1, "domain2.com", -1, s_cultureGb, false), current), // no: not same site + new DomainAndUri(new Domain(1, "domain3.com", -1, s_cultureGb, false), current), // yes: bound site + new DomainAndUri(new Domain(1, "domain3.org", -1, s_cultureGb, false), current), // yes: bound site + new DomainAndUri(new Domain(1, "domain4.com", -1, s_cultureGb, false), current), // no: not same site + 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(); 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/Routing/UriUtilityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs index e577fc28fa..dee487621f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.Models; @@ -46,10 +49,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing { // Arrange var sourceUri = new Uri(sourceUrl); - var uriUtility = BuildUriUtility("/"); + UriUtility uriUtility = BuildUriUtility("/"); // Act - var resultUri = uriUtility.UriToUmbraco(sourceUri); + Uri resultUri = uriUtility.UriToUmbraco(sourceUri); // Assert var expectedUri = new Uri(expectedUrl); @@ -70,10 +73,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing // Arrange var sourceUri = new Uri(sourceUrl, UriKind.Relative); var requestHandlerSettings = new RequestHandlerSettings { AddTrailingSlash = trailingSlash }; - var uriUtility = BuildUriUtility("/"); + UriUtility uriUtility = BuildUriUtility("/"); // Act - var resultUri = uriUtility.UriFromUmbraco(sourceUri, requestHandlerSettings); + Uri resultUri = uriUtility.UriFromUmbraco(sourceUri, requestHandlerSettings); // Assert var expectedUri = new Uri(expectedUrl, UriKind.Relative); @@ -90,7 +93,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing public void Uri_To_Absolute(string virtualPath, string sourceUrl, string expectedUrl) { // Arrange - var uriUtility = BuildUriUtility(virtualPath); + UriUtility uriUtility = BuildUriUtility(virtualPath); // Act var resultUrl = uriUtility.ToAbsolute(sourceUrl); @@ -109,7 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing public void Url_To_App_Relative(string virtualPath, string sourceUrl, string expectedUrl) { // Arrange - var uriUtility = BuildUriUtility(virtualPath); + UriUtility uriUtility = BuildUriUtility(virtualPath); // Act var resultUrl = uriUtility.ToAppRelative(sourceUrl); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs index 6c2e5f3fcb..ec1602e629 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/WebPathTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using NUnit.Framework; using Umbraco.Core.Routing; @@ -19,21 +22,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing [TestCase("~/umbraco/", "/config/", "/lang/", ExpectedResult = "~/umbraco/config/lang")] [TestCase("~/umbraco/", "config/", "lang/", ExpectedResult = "~/umbraco/config/lang")] [TestCase("~/umbraco", ExpectedResult = "~/umbraco")] - public string Combine(params string[] parts) - { - return WebPath.Combine(parts); - } + public string Combine(params string[] parts) => WebPath.Combine(parts); [Test] - public void Combine_must_handle_empty_array() - { - Assert.AreEqual(string.Empty,WebPath.Combine(Array.Empty())); - } + public void Combine_must_handle_empty_array() => Assert.AreEqual(string.Empty, WebPath.Combine(Array.Empty())); [Test] - public void Combine_must_handle_null() - { - Assert.Throws(() => WebPath.Combine(null)); - } + public void Combine_must_handle_null() => Assert.Throws(() => WebPath.Combine(null)); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/EventNameExtractorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/EventNameExtractorTests.cs index be3129aab0..60e3915325 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/EventNameExtractorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Scoping/EventNameExtractorTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Events; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Scoping @@ -10,7 +14,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Scoping [Test] public void Find_Event_Ing() { - var found = EventNameExtractor.FindEvent(this, new SaveEventArgs("test"), EventNameExtractor.MatchIngNames); + Attempt found = EventNameExtractor.FindEvent(this, new SaveEventArgs("test"), EventNameExtractor.MatchIngNames); Assert.IsTrue(found.Success); Assert.AreEqual("FoundMe", found.Result.Name); } @@ -18,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Scoping [Test] public void Find_Event_Non_Ing() { - var found = EventNameExtractor.FindEvent(this, new SaveEventArgs("test"), EventNameExtractor.MatchNonIngNames); + Attempt found = EventNameExtractor.FindEvent(this, new SaveEventArgs("test"), EventNameExtractor.MatchNonIngNames); Assert.IsTrue(found.Success); Assert.AreEqual("FindingMe", found.Result.Name); } @@ -26,7 +30,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Scoping [Test] public void Ambiguous_Match() { - var found = EventNameExtractor.FindEvent(this, new SaveEventArgs(0), EventNameExtractor.MatchIngNames); + Attempt found = EventNameExtractor.FindEvent(this, new SaveEventArgs(0), EventNameExtractor.MatchIngNames); Assert.IsFalse(found.Success); Assert.AreEqual(EventNameExtractorError.Ambiguous, found.Result.Error); } @@ -34,18 +38,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Scoping [Test] public void No_Match() { - var found = EventNameExtractor.FindEvent(this, new SaveEventArgs(0), EventNameExtractor.MatchIngNames); + Attempt found = EventNameExtractor.FindEvent(this, new SaveEventArgs(0), EventNameExtractor.MatchIngNames); Assert.IsFalse(found.Success); Assert.AreEqual(EventNameExtractorError.NoneFound, found.Result.Error); } public static event EventHandler> FindingMe; + public static event EventHandler> FoundMe; - //will lead to ambiguous matches + // will lead to ambiguous matches public static event EventHandler> SavingThis; + public static event EventHandler> SavedThis; + public static event EventHandler> SavingThat; + public static event EventHandler> SavedThat; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs index 8a7b26c6cf..f4bd63f1f8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/ContentPermissionsTests.cs @@ -1,4 +1,7 @@ -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Moq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -16,352 +19,353 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Access_Allowed_By_Path() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; + IContent content = contentMock.Object; var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void No_Content_Found() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; + IContent content = contentMock.Object; var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(0)).Returns(content); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection(); var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.NotFound, result); } [Test] public void No_Access_By_Path() { - //arrange - var user = CreateUser(id: 9, startContentId: 9876); + // Arrange + IUser user = CreateUser(id: 9, startContentId: 9876); var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; + IContent content = contentMock.Object; var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection(); var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 9876 && entity.Path == "-1,9876") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } [Test] public void No_Access_By_Permission() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; + IContent content = contentMock.Object; var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A", "B", "C" }) + new EntityPermission(9876, 1234, new string[] { "A", "B", "C" }) }; var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } [Test] public void Access_Allowed_By_Permission() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var contentMock = new Mock(); contentMock.Setup(c => c.Path).Returns("-1,1234,5678"); - var content = contentMock.Object; + IContent content = contentMock.Object; var contentServiceMock = new Mock(); contentServiceMock.Setup(x => x.GetById(1234)).Returns(content); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A", "F", "C" }) + new EntityPermission(9876, 1234, new string[] { "A", "F", "C" }) }; var permissionSet = new EntityPermissionSet(1234, permissions); var userServiceMock = new Mock(); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1,1234,5678")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(1234, user, out IContent foundContent, new[] { 'F' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void Access_To_Root_By_Path() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-1, user, out IContent _); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent _); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void Access_To_Recycle_Bin_By_Path() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-20, user, out IContent _); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent _); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void No_Access_To_Recycle_Bin_By_Path() { - //arrange - var user = CreateUser(startContentId: 1234); + // Arrange + IUser user = CreateUser(startContentId: 1234); var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } [Test] public void No_Access_To_Root_By_Path() { - //arrange - var user = CreateUser(startContentId: 1234); + // Arrange + IUser user = CreateUser(startContentId: 1234); var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } [Test] public void Access_To_Root_By_Permission() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[] { "A" }) }; var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1")).Returns(permissionSet); var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; - var userService = userServiceMock.Object; + IContentService contentService = contentServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'A' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'A' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void No_Access_To_Root_By_Permission() { - //arrange - var user = CreateUser(withUserGroup: false); + // Arrange + IUser user = CreateUser(withUserGroup: false); var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[] { "A" }) }; var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-1")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'B' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-1, user, out IContent foundContent, new[] { 'B' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } [Test] public void Access_To_Recycle_Bin_By_Permission() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[] { "A" }) }; var permissionSet = new EntityPermissionSet(-20, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-20")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'A' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'A' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Granted, result); } [Test] public void No_Access_To_Recycle_Bin_By_Permission() { - //arrange - var user = CreateUser(withUserGroup: false); + // Arrange + IUser user = CreateUser(withUserGroup: false); var userServiceMock = new Mock(); var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1234, new string[]{ "A" }) + new EntityPermission(9876, 1234, new string[] { "A" }) }; var permissionSet = new EntityPermissionSet(1234, permissions); userServiceMock.Setup(x => x.GetPermissionsForPath(user, "-20")).Returns(permissionSet); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var contentServiceMock = new Mock(); - var contentService = contentServiceMock.Object; + IContentService contentService = contentServiceMock.Object; var contentPermissions = new ContentPermissions(userService, contentService, entityService); - //act - var result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'B' }); + // Act + ContentPermissions.ContentAccess result = contentPermissions.CheckPermissions(-20, user, out IContent foundContent, new[] { 'B' }); - //assert + // Assert Assert.AreEqual(ContentPermissions.ContentAccess.Denied, result); } private IUser CreateUser(int id = 0, int? startContentId = null, bool withUserGroup = true) { - var builder = new UserBuilder() + UserBuilder builder = new UserBuilder() .WithId(id) .WithStartContentIds(startContentId.HasValue ? new[] { startContentId.Value } : new int[0]); if (withUserGroup) + { builder = builder .AddUserGroup() .WithId(1) .WithName("admin") .WithAlias("admin") .Done(); + } return builder.Build(); } } - } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs index c070657dd5..c41b2b60a0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs @@ -1,4 +1,7 @@ -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -9,16 +12,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [TestFixture] public class LegacyPasswordSecurityTests { - [Test] public void Check_Password_Hashed_Non_KeyedHashAlgorithm() { - var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA256"); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA256"); var passwordSecurity = new LegacyPasswordSecurity(); - string salt; var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out string salt); var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); @@ -29,12 +30,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Hashed_KeyedHashAlgorithm() { - var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); var passwordSecurity = new LegacyPasswordSecurity(); - string salt; var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out string salt); var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); @@ -45,12 +45,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Check_Password_Legacy_v4_SHA1() { - var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); var passwordSecurity = new LegacyPasswordSecurity(); - string salt; var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out string salt); var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); @@ -61,7 +60,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Format_Pass_For_Storage_Hashed() { - var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); var passwordSecurity = new LegacyPasswordSecurity(); var salt = LegacyPasswordSecurity.GenerateSalt(); @@ -75,14 +74,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Get_Stored_Password_Hashed() { - var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + IPasswordConfiguration passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); var passwordSecurity = new LegacyPasswordSecurity(); var salt = LegacyPasswordSecurity.GenerateSalt(); var stored = salt + "ThisIsAHashedPassword"; - string initSalt; - var result = passwordSecurity.ParseStoredHashPassword(passwordConfiguration.HashAlgorithmType, stored, out initSalt); + var result = passwordSecurity.ParseStoredHashPassword(passwordConfiguration.HashAlgorithmType, stored, out string initSalt); Assert.AreEqual("ThisIsAHashedPassword", result); } @@ -99,11 +97,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security var result = LegacyPasswordSecurity.GenerateSalt(); if (i > 0) + { Assert.AreEqual(lastLength, result.Length); + } lastLength = result.Length; } } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs index c9b0324f06..52c37717d9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/MediaPermissionsTests.cs @@ -1,4 +1,7 @@ -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Moq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -16,148 +19,147 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security [Test] public void Access_Allowed_By_Path() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); - var media = mediaMock.Object; + IMedia media = mediaMock.Object; var mediaServiceMock = new Mock(); mediaServiceMock.Setup(x => x.GetById(1234)).Returns(media); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, 1234, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Granted, result); } [Test] public void Returns_Not_Found_When_No_Media_Found() { - //arrange - var user = CreateUser(id: 9); + // Arrange + IUser user = CreateUser(id: 9); var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); - var media = mediaMock.Object; + IMedia media = mediaMock.Object; var mediaServiceMock = new Mock(); mediaServiceMock.Setup(x => x.GetById(0)).Returns(media); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act/assert - var result = mediaPermissions.CheckPermissions(user, 1234, out _); + // Act/assert + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _); Assert.AreEqual(MediaPermissions.MediaAccess.NotFound, result); } [Test] public void No_Access_By_Path() { - //arrange - var user = CreateUser(id: 9, startMediaId: 9876); + // Arrange + IUser user = CreateUser(id: 9, startMediaId: 9876); var mediaMock = new Mock(); mediaMock.Setup(m => m.Path).Returns("-1,1234,5678"); - var media = mediaMock.Object; + IMedia media = mediaMock.Object; var mediaServiceMock = new Mock(); mediaServiceMock.Setup(x => x.GetById(1234)).Returns(media); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 9876 && entity.Path == "-1,9876") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, 1234, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, 1234, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Denied, result); } [Test] public void Access_To_Root_By_Path() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var mediaServiceMock = new Mock(); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, -1, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -1, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Granted, result); } [Test] public void No_Access_To_Root_By_Path() { - //arrange - var user = CreateUser(startMediaId: 1234); + // Arrange + IUser user = CreateUser(startMediaId: 1234); var mediaServiceMock = new Mock(); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, -1, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -1, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Denied, result); } [Test] public void Access_To_Recycle_Bin_By_Path() { - //arrange - var user = CreateUser(); + // Arrange + IUser user = CreateUser(); var mediaServiceMock = new Mock(); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, -21, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -21, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Granted, result); } [Test] public void No_Access_To_Recycle_Bin_By_Path() { - //arrange - var user = CreateUser(startMediaId: 1234); + // Arrange + IUser user = CreateUser(startMediaId: 1234); var mediaServiceMock = new Mock(); - var mediaService = mediaServiceMock.Object; + IMediaService mediaService = mediaServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 1234 && entity.Path == "-1,1234") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var mediaPermissions = new MediaPermissions(mediaService, entityService); - //act - var result = mediaPermissions.CheckPermissions(user, -21, out _); + // Act + MediaPermissions.MediaAccess result = mediaPermissions.CheckPermissions(user, -21, out _); - //assert + // Assert Assert.AreEqual(MediaPermissions.MediaAccess.Denied, result); } - private IUser CreateUser(int id = 0, int? startMediaId = null) - { - return new UserBuilder() + private IUser CreateUser(int id = 0, int? startMediaId = null) => + new UserBuilder() .WithId(id) .WithStartMediaIds(startMediaId.HasValue ? new[] { startMediaId.Value } : new int[0]) .AddUserGroup() @@ -166,6 +168,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security .WithAlias("admin") .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentTypeServiceExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentTypeServiceExtensionsTests.cs index 171a71b114..af91e78afa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentTypeServiceExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Services/ContentTypeServiceExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using Moq; using NUnit.Framework; @@ -7,7 +10,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Core.Strings; using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services { @@ -19,24 +21,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_No_Overlap_By_Content_Type_And_Property_Type_Alias() { - Action addPropType = (alias, ct) => + void AddPropType(string alias, IContentType ct) { var contentCollection = new PropertyTypeCollection(true) { - new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) {Alias = alias, Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88} + new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = alias, Name = "Title", Description = string.Empty, Mandatory = false, SortOrder = 1, DataTypeId = -88 } }; var pg = new PropertyGroup(contentCollection) { Name = "test", SortOrder = 1 }; ct.PropertyGroups.Add(pg); - }; + } - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); - addPropType("title", ct2); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); - addPropType("title", ct3); - var ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); - var ct5 = ContentTypeBuilder.CreateBasicContentType("ct5", "CT5", null); - addPropType("blah", ct5); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + AddPropType("title", ct2); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); + AddPropType("title", ct3); + ContentType ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); + ContentType ct5 = ContentTypeBuilder.CreateBasicContentType("ct5", "CT5", null); + AddPropType("blah", ct5); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -45,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3, ct4, ct5 }, new[] { ct2.Alias }, @@ -59,22 +61,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_No_Overlap_By_Property_Type_Alias() { - Action addPropType = ct => + void AddPropType(IContentType ct) { var contentCollection = new PropertyTypeCollection(true) { - new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) {Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88} + new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = string.Empty, Mandatory = false, SortOrder = 1, DataTypeId = -88 } }; var pg = new PropertyGroup(contentCollection) { Name = "test", SortOrder = 1 }; ct.PropertyGroups.Add(pg); - }; + } - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); - addPropType(ct2); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); - addPropType(ct3); - var ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + AddPropType(ct2); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); + AddPropType(ct3); + ContentType ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -82,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3, ct4 }, new string[] { }, @@ -96,22 +98,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_No_Overlap_By_Content_Type() { - Action addPropType = ct => + void AddPropType(IContentType ct) { var contentCollection = new PropertyTypeCollection(true) { - new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) {Alias = "title", Name = "Title", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -88} + new PropertyType(ShortStringHelper, Constants.PropertyEditors.Aliases.TextBox, ValueStorageType.Ntext) { Alias = "title", Name = "Title", Description = string.Empty, Mandatory = false, SortOrder = 1, DataTypeId = -88 } }; var pg = new PropertyGroup(contentCollection) { Name = "test", SortOrder = 1 }; ct.PropertyGroups.Add(pg); - }; + } - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); - addPropType(ct2); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); - addPropType(ct3); - var ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + AddPropType(ct2); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); + AddPropType(ct3); + ContentType ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4", null); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -119,10 +121,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3, ct4 }, - new [] {ct2.Alias}) + new[] { ct2.Alias }) .Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray(); Assert.AreEqual(1, availableTypes.Count()); @@ -132,18 +134,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_Not_Itself() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1", null); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", null); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3", null); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, - new[] {ct1, ct2, ct3}) + new[] { ct1, ct2, ct3 }) .Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray(); Assert.AreEqual(2, availableTypes.Count()); @@ -151,33 +153,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services Assert.AreEqual(ct3.Id, availableTypes.ElementAt(1).Id); } - //This shows that a nested comp is not allowed + // This shows that a nested comp is not allowed [Test] public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition_By_Parent() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); ct1.Id = 1; - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", ct1); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2", ct1); ct2.Id = 2; - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); ct3.Id = 3; var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + System.Collections.Generic.IEnumerable availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3 }).Results; Assert.AreEqual(0, availableTypes.Count()); } - //This shows that a nested comp is not allowed + // This shows that a nested comp is not allowed [Test] public void GetAvailableCompositeContentTypes_No_Results_If_Already_A_Composition() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -186,7 +188,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + System.Collections.Generic.IEnumerable availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3 }).Results; @@ -196,9 +198,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_Do_Not_Include_Other_Composed_Types() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -207,7 +209,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3 }) .Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray(); @@ -219,9 +221,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_Include_Direct_Composed_Types() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; @@ -230,7 +232,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3 }) .Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray(); @@ -243,21 +245,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Services [Test] public void GetAvailableCompositeContentTypes_Include_Indirect_Composed_Types() { - var ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); - var ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); - var ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); - var ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4"); + ContentType ct1 = ContentTypeBuilder.CreateBasicContentType("ct1", "CT1"); + ContentType ct2 = ContentTypeBuilder.CreateBasicContentType("ct2", "CT2"); + ContentType ct3 = ContentTypeBuilder.CreateBasicContentType("ct3", "CT3"); + ContentType ct4 = ContentTypeBuilder.CreateBasicContentType("ct4", "CT4"); ct1.Id = 1; ct2.Id = 2; ct3.Id = 3; ct4.Id = 4; - ct1.AddContentType(ct3); //ct3 is direct to ct1 - ct3.AddContentType(ct4); //ct4 is indirect to ct1 + ct1.AddContentType(ct3); // ct3 is direct to ct1 + ct3.AddContentType(ct4); // ct4 is indirect to ct1 var service = new Mock(); - var availableTypes = service.Object.GetAvailableCompositeContentTypes( + IContentTypeComposition[] availableTypes = service.Object.GetAvailableCompositeContentTypes( ct1, new[] { ct1, ct2, ct3 }) .Results.Where(x => x.Allowed).Select(x => x.Composition).ToArray(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/CmsHelperCasingTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/CmsHelperCasingTests.cs index 5be6a58692..9dabbc7f36 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/CmsHelperCasingTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/CmsHelperCasingTests.cs @@ -1,14 +1,11 @@ -using Microsoft.Extensions.Options; -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.Extensions.Options; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; -using Umbraco.Tests.Common.Builders; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper { @@ -22,7 +19,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("t", "t")] [TestCase("thisis", "Thisis")] [TestCase("ThisIsTheEnd", "This Is The End")] - //[TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // note the issue with Number6In + //// [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6In The Village")] // note the issue with Number6In [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // now fixed since DefaultShortStringHelper is the default public void SpaceCamelCasing(string input, string expected) { @@ -38,7 +35,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("WhoIsNumber6InTheVillage", "Who Is Number6 In The Village")] // issue is fixed public void CompatibleDefaultReplacement(string input, string expected) { - var output = input.Length < 2 ? input : ShortStringHelper.SplitPascalCasing(input, ' ').ToFirstUpperInvariant(); Assert.AreEqual(expected, output); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTests.cs index 3241285fec..b6380ecfa5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTests.cs @@ -1,17 +1,12 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Text.RegularExpressions; -using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper { @@ -21,16 +16,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper private IShortStringHelper ShortStringHelper { get; set; } [SetUp] - public void SetUp() - { + public void SetUp() => // NOTE pre-filters runs _before_ Recode takes place // so there still may be utf8 chars even though you want ascii - ShortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(new RequestHandlerSettings()) .WithConfig(CleanStringType.FileName, new DefaultShortStringHelperConfig.Config { - //PreFilter = ClearFileChars, // done in IsTerm + // PreFilter = ClearFileChars, // done in IsTerm IsTerm = (c, leading) => (char.IsLetterOrDigit(c) || c == '_') && DefaultShortStringHelper.IsValidFileNameChar(c), StringType = CleanStringType.LowerCase | CleanStringType.Ascii, Separator = '-' @@ -68,18 +61,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper StringType = CleanStringType.Ascii, BreakTermsOnUpper = true })); - } - private static readonly Regex FrenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex s_frenchElisionsRegex = new Regex("\\b(c|d|j|l|m|n|qu|s|t)('|\u8217)", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static string FilterFrenchElisions(string s) - { - return FrenchElisionsRegex.Replace(s, ""); - } + private static string FilterFrenchElisions(string s) => s_frenchElisionsRegex.Replace(s, string.Empty); private static string StripQuotes(string s) { - s = s.ReplaceMany(new Dictionary {{"'", ""}, {"\u8217", ""}}); + s = s.ReplaceMany(new Dictionary { { "'", string.Empty }, { "\u8217", string.Empty } }); return s; } @@ -89,7 +78,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper return s; } - #region Cases [TestCase("foo", "foo")] [TestCase(" foo ", "foo")] [TestCase("Foo", "Foo")] @@ -121,14 +109,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("whatIfWeDoItAgain", "whatIfWeDoItAgain")] [TestCase("WhatIfWEDOITAgain", "WhatIfWEDOITAgain")] [TestCase("WhatIfWe doItAgain", "WhatIfWeDoItAgain")] - #endregion public void CleanStringForSafeAlias(string input, string expected) { var output = ShortStringHelper.CleanStringForSafeAlias(input); Assert.AreEqual(expected, output); } - #region Cases [TestCase("Home Page", "home-page")] [TestCase("Shannon's Home Page!", "shannons-home-page")] [TestCase("#Someones's Twitter $h1z%n", "someoness-twitter-h1z-n")] @@ -137,14 +123,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("汉#字*/漢?字", "")] [TestCase("Réalösk fix bran#lo'sk", "realosk-fix-bran-losk")] [TestCase("200 ways to be happy", "200-ways-to-be-happy")] - #endregion public void CleanStringForUrlSegment(string input, string expected) { var output = ShortStringHelper.CleanStringForUrlSegment(input); Assert.AreEqual(expected, output); } - #region Cases [TestCase("ThisIsTheEndMyFriend", "This Is The End My Friend")] [TestCase("ThisIsTHEEndMyFriend", "This Is THE End My Friend")] [TestCase("THISIsTHEEndMyFriend", "THIS Is THE End My Friend")] @@ -153,7 +137,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("ThisIsTHEEndMyFriendXYZ", "This Is THE End My Friend XYZ")] [TestCase("ThisIsTHEEndMyFriendXYZt", "This Is THE End My Friend XY Zt")] [TestCase("UneÉlévationÀPartir", "Une Élévation À Partir")] - #endregion public void SplitPascalCasing(string input, string expected) { var output = ShortStringHelper.SplitPascalCasing(input, ' '); @@ -164,13 +147,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper Assert.AreEqual(expected, output); } - #region Cases [TestCase("sauté dans l'espace", "saute-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] [TestCase("sauté dans l'espace", "sauté-dans-espace", "fr-FR", CleanStringType.UrlSegment | CleanStringType.Utf8 | CleanStringType.LowerCase)] [TestCase("sauté dans l'espace", "SauteDansLEspace", "fr-FR", CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.PascalCase)] [TestCase("he doesn't want", "he-doesnt-want", null, CleanStringType.UrlSegment | CleanStringType.Ascii | CleanStringType.LowerCase)] [TestCase("he doesn't want", "heDoesntWant", null, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase)] - #endregion public void CleanStringWithTypeAndCulture(string input, string expected, string culture, CleanStringType stringType) { // picks the proper config per culture @@ -179,7 +160,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper Assert.AreEqual(expected, output); } - #region Cases [TestCase("foo.txt", "foo.txt")] [TestCase("foo", "foo")] [TestCase(".txt", ".txt")] @@ -192,7 +172,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("yop.Straße Zvöskî", "yop.strasse-zvoski")] [TestCase("yop.Straße Zvös--kî", "yop.strasse-zvos-ki")] [TestCase("ma--ma---ma.ma-----ma", "ma-ma-ma.ma-ma")] - #endregion public void CleanStringForSafeFileName(string input, string expected) { var output = ShortStringHelper.CleanStringForSafeFileName(input); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs index 98e00a69ef..f8b6f258e0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/DefaultShortStringHelperTestsWithoutSetup.cs @@ -1,7 +1,9 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using System.Linq; using System.Text; -using Moq; using NUnit.Framework; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.UmbracoSettings; @@ -13,7 +15,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestFixture] public class DefaultShortStringHelperTestsWithoutSetup { - [Test] public void U4_4056() { @@ -115,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123foo_bar 543 nil 321", CleanStringType.Alias)); Assert.AreEqual("foo*bar*543*nil*321", helper.CleanString("0123 foo_bar 543 nil 321", CleanStringType.Alias)); - helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(new RequestHandlerSettings() )); + helper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(new RequestHandlerSettings())); Assert.AreEqual("child2", helper.CleanStringForSafeAlias("1child2")); } @@ -126,6 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter means new term BreakTermsOnUpper = true, Separator = '*' @@ -136,6 +138,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // uppercase letter is part of term BreakTermsOnUpper = false, Separator = '*' @@ -150,6 +153,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means cut acronym CutAcronymOnNonUpper = true, Separator = '*' @@ -163,6 +167,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper .WithConfig(CleanStringType.Alias, new DefaultShortStringHelperConfig.Config { StringType = CleanStringType.Utf8 | CleanStringType.Unchanged, + // non-uppercase letter means word CutAcronymOnNonUpper = false, Separator = '*' @@ -287,12 +292,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper var bytes = Encoding.UTF8.GetBytes(str); Assert.AreEqual(10, bytes.Length); Assert.AreEqual('a', bytes[0]); - // then next string element is two chars (surrogate pair) or 4 bytes, 21 bits of code point + + // Then next string element is two chars (surrogate pair) or 4 bytes, 21 bits of code point. Assert.AreEqual('z', bytes[5]); - // then next string element is one char and 3 bytes, 16 bits of code point + + // Then next string element is one char and 3 bytes, 16 bits of code point. Assert.AreEqual('t', bytes[9]); - //foreach (var b in bytes) - // Debug.Print("{0:X}", b); + + //// foreach (var b in bytes) + //// Debug.Print("{0:X}", b); Debug.Print("\U00010B70"); } @@ -323,7 +331,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper StringType = CleanStringType.Ascii | CleanStringType.Unchanged, Separator = '*' })); - Assert.AreEqual("", helper.CleanString("中文测试", CleanStringType.Alias)); + Assert.AreEqual(string.Empty, helper.CleanString("中文测试", CleanStringType.Alias)); Assert.AreEqual("leger*ZORG", helper.CleanString("léger 中文测试 ZÔRG", CleanStringType.Alias)); } @@ -370,7 +378,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper // FIXME: "C" can't be an acronym // FIXME: "DBXreview" = acronym?! - Assert.AreEqual("aaa BBB CCc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias)); // unchanged Assert.AreEqual("aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.CamelCase)); Assert.AreEqual("Aaa Bbb Ccc Ddd E FF", helper.CleanString("aaa BBB CCc Ddd E FF", CleanStringType.Alias | CleanStringType.PascalCase)); @@ -385,7 +392,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. // eg "xmlWriter" or "dbWriter" (camel) - Assert.AreEqual("aaa BB Ccc", helper.CleanString("aaa BB ccc", CleanStringType.Alias | CleanStringType.CamelCase)); Assert.AreEqual("aa Bb Ccc", helper.CleanString("AA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); Assert.AreEqual("aaa Bb Ccc", helper.CleanString("AAA bb ccc", CleanStringType.Alias | CleanStringType.CamelCase)); @@ -426,6 +432,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper // var output = helper.CleanString(input, CleanStringType.Alias | CleanStringType.Ascii | CleanStringType.CamelCase); // Assert.AreEqual(expected, output); // } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/MockShortStringHelper.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/MockShortStringHelper.cs index 442a655805..ac17cb1199 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/MockShortStringHelper.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/MockShortStringHelper.cs @@ -1,69 +1,36 @@ -using Umbraco.Core.Strings; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Strings; namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper { - class MockShortStringHelper : IShortStringHelper + internal class MockShortStringHelper : IShortStringHelper { - public void Freeze() - { - IsFrozen = true; - } + public void Freeze() => IsFrozen = true; public bool IsFrozen { get; private set; } - public string CleanStringForSafeAlias(string text) - { - return "SAFE-ALIAS::" + text; - } + public string CleanStringForSafeAlias(string text) => "SAFE-ALIAS::" + text; - public string CleanStringForSafeAlias(string text, string culture) - { - return "SAFE-ALIAS-CULTURE::" + text; - } + public string CleanStringForSafeAlias(string text, string culture) => "SAFE-ALIAS-CULTURE::" + text; - public string CleanStringForUrlSegment(string text) - { - return "URL-SEGMENT::" + text; - } + public string CleanStringForUrlSegment(string text) => "URL-SEGMENT::" + text; - public string CleanStringForUrlSegment(string text, string culture) - { - return "URL-SEGMENT-CULTURE::" + text; - } + public string CleanStringForUrlSegment(string text, string culture) => "URL-SEGMENT-CULTURE::" + text; - public string CleanStringForSafeFileName(string text) - { - return "SAFE-FILE-NAME::" + text; - } + public string CleanStringForSafeFileName(string text) => "SAFE-FILE-NAME::" + text; - public string CleanStringForSafeFileName(string text, string culture) - { - return "SAFE-FILE-NAME-CULTURE::" + text; - } + public string CleanStringForSafeFileName(string text, string culture) => "SAFE-FILE-NAME-CULTURE::" + text; - public string SplitPascalCasing(string text, char separator) - { - return "SPLIT-PASCAL-CASING::" + text; - } + public string SplitPascalCasing(string text, char separator) => "SPLIT-PASCAL-CASING::" + text; - public string CleanString(string text, CleanStringType stringType) - { - return "CLEAN-STRING-A::" + text; - } + public string CleanString(string text, CleanStringType stringType) => "CLEAN-STRING-A::" + text; - public string CleanString(string text, CleanStringType stringType, char separator) - { - return "CLEAN-STRING-B::" + text; - } + public string CleanString(string text, CleanStringType stringType, char separator) => "CLEAN-STRING-B::" + text; - public string CleanString(string text, CleanStringType stringType, string culture) - { - return "CLEAN-STRING-C::" + text; - } + public string CleanString(string text, CleanStringType stringType, string culture) => "CLEAN-STRING-C::" + text; - public string CleanString(string text, CleanStringType stringType, char separator, string culture) - { - return "CLEAN-STRING-D::" + text; - } + public string CleanString(string text, CleanStringType stringType, char separator, string culture) => "CLEAN-STRING-D::" + text; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs index 03c84a03b8..19103c2ec6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -19,10 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("hello-world.png", "Hello World")] [TestCase("hello-world .png", "Hello World")] [TestCase("_hello-world __1.png", "Hello World 1")] - public void To_Friendly_Name(string first, string second) - { - Assert.AreEqual(first.ToFriendlyName(), second); - } + public void To_Friendly_Name(string first, string second) => Assert.AreEqual(first.ToFriendlyName(), second); [TestCase("hello", "world", false)] [TestCase("hello", "hello", true)] @@ -45,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("/Test.js function(){return true;}", false)] public void Detect_Is_JavaScript_Path(string input, bool result) { - var output = input.DetectIsJavaScriptPath(Mock.Of()); + Attempt output = input.DetectIsJavaScriptPath(Mock.Of()); Assert.AreEqual(result, output.Success); } @@ -114,6 +114,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase("Hello this is hello my string", "hello", "replaced", "replaced this is replaced my string", StringComparison.CurrentCultureIgnoreCase)] [TestCase("Hello this is my string", "nonexistent", "replaced", "Hello this is my string", StringComparison.CurrentCultureIgnoreCase)] [TestCase("Hellohello this is my string", "hello", "replaced", "replacedreplaced this is my string", StringComparison.CurrentCultureIgnoreCase)] + // Ensure replacing with the same string doesn't cause infinite loop. [TestCase("Hello this is my string", "hello", "hello", "hello this is my string", StringComparison.CurrentCultureIgnoreCase)] public void ReplaceWithStringComparison(string input, string oldString, string newString, string shouldBe, StringComparison stringComparison) @@ -161,7 +162,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [TestCase(" ", true)] [TestCase("\r\n\r\n", true)] [TestCase("\r\n", true)] - [TestCase(@" + [TestCase( + @" Hello ", false)] [TestCase(null, true)] @@ -300,6 +302,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper var output = input.ReplaceMany(toReplace.ToArray(), replacement); Assert.AreEqual(expected, output); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringValidationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringValidationTests.cs index cc786413f3..80eaf2df98 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringValidationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringValidationTests.cs @@ -1,7 +1,8 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel.DataAnnotations; using NUnit.Framework; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper { @@ -30,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper // // since Umbraco is now 4.7.2+, the setting is required for the following tests to pass - //[TestCase("fdsa@fdsa", ExpectedResult = false)] - //[TestCase("fdsa@fdsa.", ExpectedResult = false)] + // [TestCase("fdsa@fdsa", ExpectedResult = false)] + // [TestCase("fdsa@fdsa.", ExpectedResult = false)] public bool Validate_Email_Address(string input) { var foo = new EmailAddressAttribute(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StylesheetHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StylesheetHelperTests.cs index 950a103560..8be740a9a0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StylesheetHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StylesheetHelperTests.cs @@ -1,4 +1,8 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Strings.Css; @@ -11,33 +15,35 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.ShortStringHelper [Test] public void Replace_Rule() { - var css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}"; - var results = StylesheetHelper.ParseRules(css); + string css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}"; + IEnumerable results = StylesheetHelper.ParseRules(css); - var result = StylesheetHelper.ReplaceRule(css, results.First().Name, new StylesheetRule() + string result = StylesheetHelper.ReplaceRule(css, results.First().Name, new StylesheetRule() { Name = "My new rule", Selector = "p", Styles = "font-size:1em; color:blue;" }); - Assert.AreEqual(@"body {font-family:Arial;}/**umb_name:My new rule*/ + Assert.AreEqual( + @"body {font-family:Arial;}/**umb_name:My new rule*/ p{font-size:1em; color:blue;} /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}".StripWhitespace(), result.StripWhitespace()); } [Test] public void Append_Rule() { - var css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}"; + string css = @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;}"; - var result = StylesheetHelper.AppendRule(css, new StylesheetRule() + string result = StylesheetHelper.AppendRule(css, new StylesheetRule() { Name = "My new rule", Selector = "p", Styles = "font-size:1em; color:blue;" }); - Assert.AreEqual(@"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;} + Assert.AreEqual( + @"body {font-family:Arial;}/** Umb_Name: Test1 */ p { font-size: 1em; } /** umb_name: Test2 */ li {padding:0px;} table {margin:0;} /**umb_name:My new rule*/ p{font-size:1em; color:blue;}".StripWhitespace(), result.StripWhitespace()); @@ -46,8 +52,8 @@ p{font-size:1em; color:blue;}".StripWhitespace(), result.StripWhitespace()); [Test] public void Duplicate_Names() { - var css = @"/** Umb_Name: Test */ p { font-size: 1em; } /** umb_name: Test */ li {padding:0px;}"; - var results = StylesheetHelper.ParseRules(css); + string css = @"/** Umb_Name: Test */ p { font-size: 1em; } /** umb_name: Test */ li {padding:0px;}"; + IEnumerable results = StylesheetHelper.ParseRules(css); Assert.AreEqual(1, results.Count()); } @@ -85,14 +91,13 @@ font-size: 1em; }")] public void ParseRules_Parses(string name, string selector, string styles, string css) { - // Act - var results = StylesheetHelper.ParseRules(css); + IEnumerable results = StylesheetHelper.ParseRules(css); // Assert Assert.AreEqual(1, results.Count()); - //Assert.IsTrue(results.First().RuleId.Value.Value.ToString() == file.Id.Value.Value + "/" + name); + // Assert.IsTrue(results.First().RuleId.Value.Value.ToString() == file.Id.Value.Value + "/" + name); Assert.AreEqual(name, results.First().Name); Assert.AreEqual(selector, results.First().Selector); Assert.AreEqual(styles.StripWhitespace(), results.First().Styles.StripWhitespace()); @@ -104,26 +109,29 @@ p { font-size: 1em; }")] + // Has a Name: keyword, but applies to 2 rules, so shouldn't parse [TestCase(@"/** umb_name: Test2 */ p, h2 { font-size: 1em; }")] + // Has it's name wrapping over two lines [TestCase("/** umb_name: Test\r\n2 */ p { font-size: 1em; }")] [TestCase("/** umb_name: Test\n2 */ p { font-size: 1em; }")] - //Only a single asterisk + + // Only a single asterisk [TestCase("/* umb_name: Test */ p { font-size: 1em; }")] + // Has a name with spaces over multiple lines [TestCase(@"/**UMB_NAME:Hello world */p{font-size: 1em;}")] public void ParseRules_DoesntParse(string css) { - // Act - var results = StylesheetHelper.ParseRules(css); + IEnumerable results = StylesheetHelper.ParseRules(css); // Assert Assert.IsTrue(results.Any() == false); @@ -133,12 +141,12 @@ world */p{font-size: 1em;}")] public void AppendRules_IsFormatted() { // base CSS - var css = Tabbed( + string css = Tabbed( @"body { #font-family:Arial; }"); // add a couple of rules - var result = StylesheetHelper.AppendRule(css, new StylesheetRule + string result = StylesheetHelper.AppendRule(css, new StylesheetRule { Name = "Test", Selector = ".test", @@ -152,7 +160,8 @@ world */p{font-size: 1em;}")] }); // verify the CSS formatting including the indents - Assert.AreEqual(Tabbed( + Assert.AreEqual( + Tabbed( @"body { #font-family:Arial; } @@ -166,15 +175,14 @@ world */p{font-size: 1em;}")] /**umb_name:Test2*/ .test2 { #font-color: green; -}"), result - ); +}"), result); } [Test] public void ParseFormattedRules_CanParse() { // base CSS - var css = Tabbed( + string css = Tabbed( @"body { #font-family:Arial; } @@ -189,7 +197,7 @@ world */p{font-size: 1em;}")] .test2 { #font-color: green; }"); - var rules = StylesheetHelper.ParseRules(css); + IEnumerable rules = StylesheetHelper.ParseRules(css); Assert.AreEqual(2, rules.Count()); Assert.AreEqual("Test", rules.First().Name); @@ -206,6 +214,5 @@ margin: 1rem;", rules.First().Styles); // can't put tabs in verbatim strings, so this will replace # with \t to test the CSS indents // - and it's tabs because the editor uses tabs, not spaces... private static string Tabbed(string input) => input.Replace("#", "\t"); - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlImageSourceParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlImageSourceParserTests.cs index 542d273954..b49a229133 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlImageSourceParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlImageSourceParserTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; using Microsoft.Extensions.Options; @@ -10,6 +13,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.Common; using Umbraco.Tests.UnitTests.TestHelpers.Objects; +using Umbraco.Web; using Umbraco.Web.Routing; using Umbraco.Web.Templates; @@ -48,7 +52,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates

"); - Assert.AreEqual(@"

+ Assert.AreEqual( + @"

@@ -60,8 +65,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates [Test] public void Ensure_Image_Sources() { - //setup a mock URL provider which we'll use for testing - + // setup a mock URL provider which we'll use for testing var mediaType = new PublishedContentType(Guid.NewGuid(), 777, "image", PublishedItemType.Media, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var media = new Mock(); media.Setup(x => x.ContentType).Returns(mediaType); @@ -71,17 +75,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var umbracoContextFactory = TestUmbracoContextFactory.Create( + IUmbracoContextFactory umbracoContextFactory = TestUmbracoContextFactory.Create( umbracoContextAccessor: umbracoContextAccessor); var webRoutingSettings = new WebRoutingSettings(); - var publishedUrlProvider = new UrlProvider(umbracoContextAccessor, + var publishedUrlProvider = new UrlProvider( + umbracoContextAccessor, Options.Create(webRoutingSettings), new UrlProviderCollection(Enumerable.Empty()), - new MediaUrlProviderCollection(new []{mediaUrlProvider.Object}), - Mock.Of() - ); - using (var reference = umbracoContextFactory.EnsureUmbracoContext()) + new MediaUrlProviderCollection(new[] { mediaUrlProvider.Object }), + Mock.Of()); + using (UmbracoContextReference reference = umbracoContextFactory.EnsureUmbracoContext()) { var mediaCache = Mock.Get(reference.UmbracoContext.Media); mediaCache.Setup(x => x.GetById(It.IsAny())).Returns(media.Object); @@ -99,7 +103,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates

"); - Assert.AreEqual(@"

+ Assert.AreEqual( + @"

@@ -115,33 +120,27 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates [TestCase( @"
", ExpectedResult = @"
", - TestName = "Empty source is not updated with no data-udi set" - )] + TestName = "Empty source is not updated with no data-udi set")] [TestCase( @"
", ExpectedResult = @"
", - TestName = "Empty source is updated with data-udi set" - )] + TestName = "Empty source is updated with data-udi set")] [TestCase( @"
", ExpectedResult = @"
", - TestName = "Filled source is overwritten with data-udi set" - )] + TestName = "Filled source is overwritten with data-udi set")] [TestCase( @"
", ExpectedResult = @"
", - TestName = "Attributes are persisted" - )] + TestName = "Attributes are persisted")] [TestCase( @"
", ExpectedResult = @"
", - TestName = "Source is trimmed and parameters are prefixed" - )] + TestName = "Source is trimmed and parameters are prefixed")] [TestCase( @"
", ExpectedResult = @"
", - TestName = "Parameters are prefixed" - )] + TestName = "Parameters are prefixed")] [TestCase( @"
@@ -154,8 +153,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates
", - TestName = "Multiple img tags are handled" - )] + TestName = "Multiple img tags are handled")] [Category("Ensure image sources")] public string Ensure_ImageSources_Processing(string sourceHtml) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs index 043481b9d0..726741cd1c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using Moq; using NUnit.Framework; @@ -8,6 +11,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Tests.Common; using Umbraco.Tests.UnitTests.TestHelpers.Objects; +using Umbraco.Web; using Umbraco.Web.Routing; using Umbraco.Web.Templates; @@ -43,15 +47,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates [TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] [TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] [TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")] - //this one has an invalid char so won't match + + // This one has an invalid char so won't match. [TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] [TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")] public void ParseLocalLinks(string input, string result) { - //setup a mock URL provider which we'll use for testing + // setup a mock URL provider which we'll use for testing var contentUrlProvider = new Mock(); contentUrlProvider - .Setup(x => x.GetUrl( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(UrlInfo.Url("/my-test-url")); var contentType = new PublishedContentType(Guid.NewGuid(), 666, "alias", PublishedItemType.Content, Enumerable.Empty(), Enumerable.Empty(), ContentVariation.Nothing); var publishedContent = new Mock(); @@ -67,17 +72,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var umbracoContextAccessor = new TestUmbracoContextAccessor(); - var umbracoContextFactory = TestUmbracoContextFactory.Create( + IUmbracoContextFactory umbracoContextFactory = TestUmbracoContextFactory.Create( umbracoContextAccessor: umbracoContextAccessor); var webRoutingSettings = new WebRoutingSettings(); - var publishedUrlProvider = new UrlProvider(umbracoContextAccessor, + var publishedUrlProvider = new UrlProvider( + umbracoContextAccessor, Microsoft.Extensions.Options.Options.Create(webRoutingSettings), - new UrlProviderCollection(new []{contentUrlProvider.Object}), - new MediaUrlProviderCollection(new []{mediaUrlProvider.Object}), - Mock.Of() - ); - using (var reference = umbracoContextFactory.EnsureUmbracoContext()) + new UrlProviderCollection(new[] { contentUrlProvider.Object }), + new MediaUrlProviderCollection(new[] { mediaUrlProvider.Object }), + Mock.Of()); + using (UmbracoContextReference reference = umbracoContextFactory.EnsureUmbracoContext()) { var contentCache = Mock.Get(reference.UmbracoContext.Content); contentCache.Setup(x => x.GetById(It.IsAny())).Returns(publishedContent.Object); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs index 27c78d7a68..85c96cf5be 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.IO; namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates @@ -10,7 +13,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void NoOptions() { var view = ViewHelper.GetDefaultFileContent(); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @{ Layout = null; @@ -21,7 +25,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void Layout() { var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @{ Layout = ""Dharznoik.cshtml""; @@ -32,7 +37,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void ClassName() { var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @{ Layout = null; @@ -43,7 +49,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void Namespace() { var view = ViewHelper.GetDefaultFileContent(modelNamespace: "Models"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @{ Layout = null; @@ -54,7 +61,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void ClassNameAndNamespace() { var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @using ContentModels = My.Models; @{ @@ -66,7 +74,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void ClassNameAndNamespaceAndAlias() { var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @using MyModels = My.Models; @{ @@ -78,7 +87,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates public void Combined() { var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik", modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); - Assert.AreEqual(FixView(@"@using Umbraco.Web.PublishedModels; + Assert.AreEqual( + FixView(@"@using Umbraco.Web.PublishedModels; @inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @using MyModels = My.Models; @{ diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs index a4ab15afe7..df80f3b38b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/VersionExtensionTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core; @@ -22,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core { var version = new Version(major, minor, build, rev); - var result = version.SubtractRevision(); + Version result = version.SubtractRevision(); Assert.AreEqual(new Version(outcome), result); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs index 495ca0186d..8d4f8aef0f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Xml/XmlHelperTests.cs @@ -1,4 +1,7 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; using System.Linq; using System.Xml; using System.Xml.XPath; @@ -15,17 +18,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml private XmlDocumentBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new XmlDocumentBuilder(); - } + public void SetUp() => _builder = new XmlDocumentBuilder(); [Ignore("This is a benchmark test so is ignored by default")] [Test] public void Sort_Nodes_Benchmark_Legacy() { - var xml = _builder.Build(); - var original = xml.GetElementById(1173.ToString()); + XmlDocument xml = _builder.Build(); + XmlElement original = xml.GetElementById(1173.ToString()); Assert.IsNotNull(original); long totalTime = 0; @@ -34,26 +34,27 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml for (var i = 0; i < iterations; i++) { - //don't measure the time for clone! - var parentNode = original.Clone(); + // don't measure the time for clone! + XmlNode parentNode = original.Clone(); watch.Start(); LegacySortNodes(ref parentNode); watch.Stop(); totalTime += watch.ElapsedMilliseconds; watch.Reset(); - //do assertions just to make sure it is working properly. + // Do assertions just to make sure it is working properly. var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + foreach (XmlNode child in parentNode.SelectNodes("./* [@id]").Cast()) { Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); currSort++; } - //ensure the parent node's properties still exist first + // Ensure the parent node's properties still exist first. Assert.AreEqual("content", parentNode.ChildNodes[0].Name); Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after + + // Then the child nodes should come straight after. Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); } @@ -64,8 +65,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml [Test] public void Sort_Nodes_Benchmark_New() { - var xml = _builder.Build(); - var original = xml.GetElementById(1173.ToString()); + XmlDocument xml = _builder.Build(); + XmlElement original = xml.GetElementById(1173.ToString()); Assert.IsNotNull(original); long totalTime = 0; @@ -74,8 +75,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml for (var i = 0; i < iterations; i++) { - //don't measure the time for clone! - var parentNode = (XmlElement) original.Clone(); + // don't measure the time for clone! + var parentNode = (XmlElement)original.Clone(); watch.Start(); XmlHelper.SortNodes( parentNode, @@ -85,18 +86,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml totalTime += watch.ElapsedMilliseconds; watch.Reset(); - //do assertions just to make sure it is working properly. + // do assertions just to make sure it is working properly. var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + foreach (XmlNode child in parentNode.SelectNodes("./* [@id]").Cast()) { Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); currSort++; } - //ensure the parent node's properties still exist first + // ensure the parent node's properties still exist first Assert.AreEqual("content", parentNode.ChildNodes[0].Name); Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after + + // then the child nodes should come straight after Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); } @@ -106,52 +108,56 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Xml [Test] public void Sort_Nodes() { - var xml = _builder.Build(); - var original = xml.GetElementById(1173.ToString()); + XmlDocument xml = _builder.Build(); + XmlElement original = xml.GetElementById(1173.ToString()); Assert.IsNotNull(original); - var parentNode = (XmlElement) original.Clone(); + var parentNode = (XmlElement)original.Clone(); XmlHelper.SortNodes( parentNode, "./* [@id]", x => x.AttributeValue("sortOrder")); - //do assertions just to make sure it is working properly. + // do assertions just to make sure it is working properly. var currSort = 0; - foreach (var child in parentNode.SelectNodes("./* [@id]").Cast()) + foreach (XmlNode child in parentNode.SelectNodes("./* [@id]").Cast()) { Assert.AreEqual(currSort, int.Parse(child.Attributes["sortOrder"].Value)); currSort++; } - //ensure the parent node's properties still exist first + // ensure the parent node's properties still exist first Assert.AreEqual("content", parentNode.ChildNodes[0].Name); Assert.AreEqual("umbracoUrlAlias", parentNode.ChildNodes[1].Name); - //then the child nodes should come straight after + + // then the child nodes should come straight after Assert.IsTrue(parentNode.ChildNodes[2].Attributes["id"] != null); } /// /// This was the logic to sort before and now lives here just to show the benchmarks tests above. /// - /// private static void LegacySortNodes(ref XmlNode parentNode) { - var n = parentNode.CloneNode(true); + XmlNode n = parentNode.CloneNode(true); // remove all children from original node var xpath = "./* [@id]"; foreach (XmlNode child in parentNode.SelectNodes(xpath)) + { parentNode.RemoveChild(child); + } - var nav = n.CreateNavigator(); - var expr = nav.Compile(xpath); - expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number); - var iterator = nav.Select(expr); + XPathNavigator nav = n.CreateNavigator(); + XPathExpression expr = nav.Compile(xpath); + expr.AddSort("@sortOrder", XmlSortOrder.Ascending, XmlCaseOrder.None, string.Empty, XmlDataType.Number); + XPathNodeIterator iterator = nav.Select(expr); while (iterator.MoveNext()) + { parentNode.AppendChild( - ((IHasXmlNode) iterator.Current).GetNode()); + ((IHasXmlNode)iterator.Current).GetNode()); + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs index ae3762ef57..2d2fb9d85b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/XmlExtensionsTests.cs @@ -1,4 +1,7 @@ -using System.Xml; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Xml; using System.Xml.Linq; using NUnit.Framework; using Umbraco.Core; @@ -14,7 +17,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core var cdata = new XElement("test", new XCData("hello world")); var xdoc = new XmlDocument(); - var xmlNode = cdata.GetXmlNode(xdoc); + XmlNode xmlNode = cdata.GetXmlNode(xdoc); Assert.AreEqual("hello world", xmlNode.InnerText); } @@ -25,7 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core var cdata = new XElement("test", new XText("hello world")); var xdoc = new XmlDocument(); - var xmlNode = cdata.GetXmlNode(xdoc); + XmlNode xmlNode = cdata.GetXmlNode(xdoc); Assert.AreEqual("hello world", xmlNode.InnerText); } @@ -39,7 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core var xdoc = new XmlDocument(); xdoc.LoadXml(xml); - var xmlNode = cdata.GetXmlNode(xdoc); + XmlNode xmlNode = cdata.GetXmlNode(xdoc); Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs index 8172a712d8..9ce3a8a3c3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/BackOffice/BackOfficeLookupNormalizerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using NUnit.Framework; using Umbraco.Core.Security; @@ -29,6 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice Assert.AreEqual(name, normalizedName); } + [Test] [TestCase(null)] [TestCase("")] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs index 2bf9a541b9..971a378fe8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; @@ -19,8 +22,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors [Test] public void Admin_Is_Authorized() { - var currentUser = CreateAdminUser(); - var savingUser = CreateUser(); + IUser currentUser = CreateAdminUser(); + IUser savingUser = CreateUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -32,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors mediaService.Object, entityService.Object); - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); Assert.IsTrue(result.Success); } @@ -40,8 +43,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors [Test] public void Non_Admin_Cannot_Save_Admin() { - var currentUser = CreateUser(); - var savingUser = CreateAdminUser(); + IUser currentUser = CreateUser(); + IUser savingUser = CreateAdminUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -53,7 +56,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors mediaService.Object, entityService.Object); - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new string[0]); Assert.IsFalse(result.Success); } @@ -61,8 +64,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors [Test] public void Cannot_Grant_Group_Membership_Without_Being_A_Member() { - var currentUser = CreateUser(withGroup: true); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(withGroup: true); + IUser savingUser = CreateUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -74,7 +77,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors mediaService.Object, entityService.Object); - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] {"FunGroup"}); + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "FunGroup" }); Assert.IsFalse(result.Success); } @@ -82,8 +85,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors [Test] public void Can_Grant_Group_Membership_With_Being_A_Member() { - var currentUser = CreateUser(withGroup: true); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(withGroup: true); + IUser savingUser = CreateUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -95,7 +98,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors mediaService.Object, entityService.Object); - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" }); + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new int[0], new[] { "test" }); Assert.IsTrue(result.Success); } @@ -105,14 +108,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startContentIds: new[] { 9876 }); - var savingUser = CreateUser(startContentIds: new[] { 1234 }); + IUser currentUser = CreateUser(startContentIds: new[] { 9876 }); + IUser savingUser = CreateUser(startContentIds: new[] { 1234 }); var contentService = new Mock(); contentService.Setup(x => x.GetById(It.IsAny())) @@ -121,18 +124,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath {Path = nodePaths[x], Id = x}); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 - var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]); + // adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234, 5555 }, new int[0], new string[0]); Assert.IsTrue(result.Success); } @@ -142,14 +142,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startContentIds: new[] { 9876 }); - var savingUser = CreateUser(startContentIds: new[] { 1234, 4567 }); + IUser currentUser = CreateUser(startContentIds: new[] { 9876 }); + IUser savingUser = CreateUser(startContentIds: new[] { 1234, 4567 }); var contentService = new Mock(); contentService.Setup(x => x.GetById(It.IsAny())) @@ -158,18 +158,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok - var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]); + // removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]); Assert.IsTrue(result.Success); } @@ -179,14 +176,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startContentIds: new[] { 9876 }); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(startContentIds: new[] { 9876 }); + IUser savingUser = CreateUser(); var contentService = new Mock(); contentService.Setup(x => x.GetById(It.IsAny())) @@ -195,18 +192,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 1234 but currentUser doesn't have access to it ... nope - var result = authHelper.IsAuthorized(currentUser, savingUser, new []{1234}, new int[0], new string[0]); + // adding 1234 but currentUser doesn't have access to it ... nope + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 1234 }, new int[0], new string[0]); Assert.IsFalse(result.Success); } @@ -216,14 +210,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startContentIds: new[] { 9876 }); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(startContentIds: new[] { 9876 }); + IUser savingUser = CreateUser(); var contentService = new Mock(); contentService.Setup(x => x.GetById(It.IsAny())) @@ -232,18 +226,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 5555 which currentUser has access to since it's a child of 9876 ... ok - var result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]); + // adding 5555 which currentUser has access to since it's a child of 9876 ... ok + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new[] { 5555 }, new int[0], new string[0]); Assert.IsTrue(result.Success); } @@ -253,15 +244,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - - var currentUser = CreateUser(startMediaIds: new[] { 9876 }); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(startMediaIds: new[] { 9876 }); + IUser savingUser = CreateUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -270,18 +260,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 1234 but currentUser doesn't have access to it ... nope - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] {1234}, new string[0]); + // adding 1234 but currentUser doesn't have access to it ... nope + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]); Assert.IsFalse(result.Success); } @@ -291,14 +278,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startMediaIds: new[] { 9876 }); - var savingUser = CreateUser(); + IUser currentUser = CreateUser(startMediaIds: new[] { 9876 }); + IUser savingUser = CreateUser(); var contentService = new Mock(); var mediaService = new Mock(); @@ -307,18 +294,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 5555 which currentUser has access to since it's a child of 9876 ... ok - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]); + // adding 5555 which currentUser has access to since it's a child of 9876 ... ok + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 5555 }, new string[0]); Assert.IsTrue(result.Success); } @@ -328,14 +312,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startMediaIds: new[] { 9876 }); - var savingUser = CreateUser(startMediaIds: new[] { 1234 }); + IUser currentUser = CreateUser(startMediaIds: new[] { 9876 }); + IUser savingUser = CreateUser(startMediaIds: new[] { 1234 }); var contentService = new Mock(); var mediaService = new Mock(); @@ -344,18 +328,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]); + // adding 5555 which currentUser has access to since it's a child of 9876 ... adding is still ok even though currentUser doesn't have access to 1234 + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234, 5555 }, new string[0]); Assert.IsTrue(result.Success); } @@ -365,14 +346,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors { var nodePaths = new Dictionary { - {1234, "-1,1234"}, - {9876, "-1,9876"}, - {5555, "-1,9876,5555"}, - {4567, "-1,4567"}, + { 1234, "-1,1234" }, + { 9876, "-1,9876" }, + { 5555, "-1,9876,5555" }, + { 4567, "-1,4567" }, }; - var currentUser = CreateUser(startMediaIds: new[] { 9876 }); - var savingUser = CreateUser(startMediaIds: new[] { 1234, 4567 }); + IUser currentUser = CreateUser(startMediaIds: new[] { 9876 }); + IUser savingUser = CreateUser(startMediaIds: new[] { 1234, 4567 }); var contentService = new Mock(); var mediaService = new Mock(); @@ -381,27 +362,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors var userService = new Mock(); var entityService = new Mock(); entityService.Setup(service => service.GetAllPaths(It.IsAny(), It.IsAny())) - .Returns((UmbracoObjectTypes objType, int[] ids) => - { - return ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x }); - }); + .Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath { Path = nodePaths[x], Id = x })); var authHelper = new UserEditorAuthorizationHelper( contentService.Object, mediaService.Object, entityService.Object); - //removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok - var result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]); + // removing 4567 start node even though currentUser doesn't have acces to it ... removing is ok + Attempt result = authHelper.IsAuthorized(currentUser, savingUser, new int[0], new[] { 1234 }, new string[0]); Assert.IsTrue(result.Success); } private static IUser CreateUser(bool withGroup = false, int[] startContentIds = null, int[] startMediaIds = null) { - var builder = new UserBuilder() - .WithStartContentIds(startContentIds != null ? startContentIds : new int[0]) - .WithStartMediaIds(startMediaIds != null ? startMediaIds : new int[0]); + UserBuilder builder = new UserBuilder() + .WithStartContentIds(startContentIds ?? (new int[0])) + .WithStartMediaIds(startMediaIds ?? (new int[0])); if (withGroup) { builder = (UserBuilder)builder @@ -414,15 +392,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Editors return builder.Build(); } - private static IUser CreateAdminUser() - { - return new UserBuilder() + private static IUser CreateAdminUser() => + new UserBuilder() .AddUserGroup() .WithId(1) .WithName("Admin") .WithAlias(Constants.Security.AdminGroupAlias) .Done() .Build(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs index 449e005220..a1b00c9ab2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs @@ -1,13 +1,16 @@ -using Examine; -using NUnit.Framework; -using Umbraco.Examine; -using Moq; -using Umbraco.Core.Services; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; +using System.Linq; +using Examine; +using Moq; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; -using System; -using System.Linq; +using Umbraco.Core.Services; +using Umbraco.Examine; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { @@ -19,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(false, true, Mock.Of()); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); result = validator.Validate(ValueSet.FromObject("777", IndexTypes.Media, new { hello = "world", path = "-1,555" })); @@ -27,7 +30,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine result = validator.Validate(ValueSet.FromObject("555", "invalid-category", new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); - } [Test] @@ -35,7 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(false, true, Mock.Of()); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -47,7 +49,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(false, true, Mock.Of(), 555); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,444" })); @@ -63,12 +65,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Inclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, null); var valueSet = ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555", world = "your oyster" }); - var result = validator.Validate(valueSet); + ValueSetValidationResult result = validator.Validate(valueSet); Assert.AreEqual(ValueSetValidationResult.Filtered, result); Assert.IsFalse(valueSet.Values.ContainsKey("path")); @@ -79,12 +83,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, null, new[] { "hello", "world" }); var valueSet = ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555", world = "your oyster" }); - var result = validator.Validate(valueSet); + ValueSetValidationResult result = validator.Validate(valueSet); Assert.AreEqual(ValueSetValidationResult.Filtered, result); Assert.IsTrue(valueSet.Values.ContainsKey("path")); @@ -95,12 +101,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Inclusion_Exclusion_Field_List() { - var validator = new ValueSetValidator(null, null, + var validator = new ValueSetValidator( + null, + null, new[] { "hello", "world" }, new[] { "world" }); var valueSet = ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555", world = "your oyster" }); - var result = validator.Validate(valueSet); + ValueSetValidationResult result = validator.Validate(valueSet); Assert.AreEqual(ValueSetValidationResult.Filtered, result); Assert.IsFalse(valueSet.Values.ContainsKey("path")); @@ -111,10 +119,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Inclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), includeItemTypes: new List { "include-content" }); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -127,10 +138,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), excludeItemTypes: new List { "exclude-content" }); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -143,11 +157,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine [Test] public void Inclusion_Exclusion_Type_List() { - var validator = new ContentValueSetValidator(false, true, Mock.Of(), + var validator = new ContentValueSetValidator( + false, + true, + Mock.Of(), includeItemTypes: new List { "include-content", "exclude-content" }, excludeItemTypes: new List { "exclude-content" }); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, "test-content", new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); @@ -165,7 +182,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(true, false, Mock.Of()); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,-20,555,777" })); @@ -174,7 +191,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); - result = validator.Validate(new ValueSet("555", IndexTypes.Content, + result = validator.Validate(new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -189,7 +208,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(true, false, Mock.Of()); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,-21,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,-21,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,-21,555,777" })); @@ -197,7 +216,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Media, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Valid, result); - } [Test] @@ -205,10 +223,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(true, true, Mock.Of()); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Failed, result); - result = validator.Validate(new ValueSet("555", IndexTypes.Content, + result = validator.Validate(new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -217,7 +237,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine })); Assert.AreEqual(ValueSetValidationResult.Failed, result); - result = validator.Validate(new ValueSet("555", IndexTypes.Content, + result = validator.Validate(new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -232,7 +254,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine { var validator = new ContentValueSetValidator(true, true, Mock.Of()); - var result = validator.Validate(new ValueSet("555", IndexTypes.Content, + ValueSetValidationResult result = validator.Validate(new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -242,7 +266,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine })); Assert.AreEqual(ValueSetValidationResult.Failed, result); - result = validator.Validate(new ValueSet("555", IndexTypes.Content, + result = validator.Validate(new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -252,7 +278,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine })); Assert.AreEqual(ValueSetValidationResult.Valid, result); - var valueSet = new ValueSet("555", IndexTypes.Content, + var valueSet = new ValueSet( + "555", + IndexTypes.Content, new Dictionary { ["hello"] = "world", @@ -274,7 +302,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine result = validator.Validate(valueSet); Assert.AreEqual(ValueSetValidationResult.Filtered, result); - Assert.AreEqual(7, valueSet.Values.Count()); //filtered to 7 values (removes es-es values) + Assert.AreEqual(7, valueSet.Values.Count()); // filtered to 7 values (removes es-es values) Assert.IsFalse(valueSet.Values.ContainsKey($"{UmbracoExamineFieldNames.PublishedFieldName}_es-es")); Assert.IsFalse(valueSet.Values.ContainsKey("hello_es-ES")); Assert.IsFalse(valueSet.Values.ContainsKey("title_es-ES")); @@ -290,7 +318,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Examine .Returns(Attempt.Fail()); var validator = new ContentValueSetValidator(false, false, publicAccessService.Object); - var result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); + ValueSetValidationResult result = validator.Validate(ValueSet.FromObject("555", IndexTypes.Content, new { hello = "world", path = "-1,555" })); Assert.AreEqual(ValueSetValidationResult.Filtered, result); result = validator.Validate(ValueSet.FromObject("777", IndexTypes.Content, new { hello = "world", path = "-1,777" })); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs index 387d8aff44..10349a4f9e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HealthChecks/HealthCheckResultsTests.cs @@ -1,18 +1,17 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using NUnit.Framework; using Umbraco.Core.HealthCheck; -using Umbraco.Core.HealthCheck.Checks; using Umbraco.Infrastructure.HealthCheck; -using Umbraco.Web.HealthCheck; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks { [TestFixture] public class HealthCheckResultsTests { - #region Stub checks - [HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A1", "Stub check")] public abstract class StubHealthCheck : HealthCheck { @@ -25,27 +24,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks _message = message; } - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - { - throw new NotImplementedException(); - } + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new NotImplementedException(); - public override IEnumerable GetStatus() - { - return new List + public override IEnumerable GetStatus() => + new List { new HealthCheckStatus(_message) { ResultType = _resultType } }; - } } [HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A1", "Stub check 1")] public class StubHealthCheck1 : StubHealthCheck { - public StubHealthCheck1(StatusResultType resultType, string message) : base(resultType, message) + public StubHealthCheck1(StatusResultType resultType, string message) + : base(resultType, message) { } } @@ -53,7 +48,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks [HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A2", "Stub check 2")] public class StubHealthCheck2 : StubHealthCheck { - public StubHealthCheck2(StatusResultType resultType, string message) : base(resultType, message) + public StubHealthCheck2(StatusResultType resultType, string message) + : base(resultType, message) { } } @@ -61,18 +57,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HealthChecks [HealthCheck("CFD6FC34-59C9-4402-B55F-C8BC96B628A3", "Stub check 3")] public class StubHealthCheck3 : StubHealthCheck { - public StubHealthCheck3(StatusResultType resultType, string message) : base(resultType, message) + public StubHealthCheck3(StatusResultType resultType, string message) + : base(resultType, message) { } - public override IEnumerable GetStatus() - { - throw new Exception("Check threw exception"); - } + public override IEnumerable GetStatus() => throw new Exception("Check threw exception"); } - #endregion - [Test] public void HealthCheckResults_WithSuccessfulChecks_ReturnsCorrectResultDescription() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs index 183e87e5ca..42e4c3e1e6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs @@ -1,15 +1,23 @@ -using Moq; -using NUnit.Framework; -using Serilog; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.Extensions.Logging; -using StackExchange.Profiling.Internal; +using Moq; +using NUnit.Framework; +using Serilog; using Umbraco.Core; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Logging.Viewer; +using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; +using File = System.IO.File; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging { @@ -18,8 +26,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging { private ILogViewer _logViewer; - const string _logfileName = "UmbracoTraceLog.UNITTEST.20181112.json"; - const string _searchfileName = "logviewer.searches.config.js"; + private const string LogfileName = "UmbracoTraceLog.UNITTEST.20181112.json"; + private const string SearchfileName = "logviewer.searches.config.js"; private string _newLogfilePath; private string _newLogfileDirPath; @@ -27,38 +35,39 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging private string _newSearchfilePath; private string _newSearchfileDirPath; - private LogTimePeriod _logTimePeriod = new LogTimePeriod( - new DateTime(year: 2018, month: 11, day: 12, hour:0, minute:0, second:0), - new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0) - ); + private readonly LogTimePeriod _logTimePeriod = new LogTimePeriod( + new DateTime(year: 2018, month: 11, day: 12, hour: 0, minute: 0, second: 0), + new DateTime(year: 2018, month: 11, day: 13, hour: 0, minute: 0, second: 0)); + [OneTimeSetUp] public void Setup() { var testRoot = TestContext.CurrentContext.TestDirectory.Split("bin")[0]; - //Create an example JSON log file to check results - //As a one time setup for all tets in this class/fixture - var ioHelper = TestHelper.IOHelper; - var hostingEnv = TestHelper.GetHostingEnvironment(); - var loggingConfiguration = TestHelper.GetLoggingConfiguration(hostingEnv); + // Create an example JSON log file to check results + // As a one time setup for all tets in this class/fixture + IIOHelper ioHelper = TestHelper.IOHelper; + IHostingEnvironment hostingEnv = TestHelper.GetHostingEnvironment(); - var exampleLogfilePath = Path.Combine(testRoot, "TestHelpers","Assets", _logfileName); + ILoggingConfiguration loggingConfiguration = TestHelper.GetLoggingConfiguration(hostingEnv); + + var exampleLogfilePath = Path.Combine(testRoot, "TestHelpers", "Assets", LogfileName); _newLogfileDirPath = loggingConfiguration.LogDirectory; - _newLogfilePath = Path.Combine(_newLogfileDirPath, _logfileName); + _newLogfilePath = Path.Combine(_newLogfileDirPath, LogfileName); - var exampleSearchfilePath = Path.Combine(testRoot, "TestHelpers","Assets", _searchfileName); + var exampleSearchfilePath = Path.Combine(testRoot, "TestHelpers", "Assets", SearchfileName); _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"config"); - _newSearchfilePath = Path.Combine(_newSearchfileDirPath, _searchfileName); + _newSearchfilePath = Path.Combine(_newSearchfileDirPath, SearchfileName); - //Create/ensure Directory exists + // Create/ensure Directory exists ioHelper.EnsurePathExists(_newLogfileDirPath); ioHelper.EnsurePathExists(_newSearchfileDirPath); - //Copy the sample files + // Copy the sample files File.Copy(exampleLogfilePath, _newLogfilePath, true); File.Copy(exampleSearchfilePath, _newSearchfilePath, true); - var logger = Mock.Of>(); + ILogger logger = Mock.Of>(); var logViewerConfig = new LogViewerConfig(hostingEnv); _logViewer = new SerilogJsonLogViewer(logger, logViewerConfig, loggingConfiguration, Log.Logger); } @@ -66,13 +75,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging [OneTimeTearDown] public void TearDown() { - //Cleanup & delete the example log & search files off disk - //Once all tests in this class/fixture have run + // Cleanup & delete the example log & search files off disk + // Once all tests in this class/fixture have run if (File.Exists(_newLogfilePath)) + { File.Delete(_newLogfilePath); + } if (File.Exists(_newSearchfilePath)) + { File.Delete(_newSearchfilePath); + } } [Test] @@ -80,14 +93,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging { var numberOfErrors = _logViewer.GetNumberOfErrors(_logTimePeriod); - //Our dummy log should contain 2 errors + // Our dummy log should contain 2 errors Assert.AreEqual(1, numberOfErrors); } [Test] public void Logs_Contain_Correct_Log_Level_Counts() { - var logCounts = _logViewer.GetLogLevelCounts(_logTimePeriod); + LogLevelCounts logCounts = _logViewer.GetLogLevelCounts(_logTimePeriod); Assert.AreEqual(55, logCounts.Debug); Assert.AreEqual(1, logCounts.Error); @@ -99,19 +112,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging [Test] public void Logs_Contains_Correct_Message_Templates() { - var templates = _logViewer.GetMessageTemplates(_logTimePeriod); + IEnumerable templates = _logViewer.GetMessageTemplates(_logTimePeriod); - //Count no of templates + // Count no of templates Assert.AreEqual(25, templates.Count()); - //Verify all templates & counts are unique + // Verify all templates & counts are unique CollectionAssert.AllItemsAreUnique(templates); - //Ensure the collection contains LogTemplate objects + // Ensure the collection contains LogTemplate objects CollectionAssert.AllItemsAreInstancesOfType(templates, typeof(LogTemplate)); - //Get first item & verify its template & count are what we expect - var popularTemplate = templates.FirstOrDefault(); + // Get first item & verify its template & count are what we expect + LogTemplate popularTemplate = templates.FirstOrDefault(); Assert.IsNotNull(popularTemplate); Assert.AreEqual("{EndMessage} ({Duration}ms) [Timing {TimingId}]", popularTemplate.MessageTemplate); @@ -121,8 +134,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging [Test] public void Logs_Can_Open_As_Small_File() { - //We are just testing a return value (as we know the example file is less than 200MB) - //But this test method does not test/check that + // We are just testing a return value (as we know the example file is less than 200MB) + // But this test method does not test/check that var canOpenLogs = _logViewer.CheckCanOpenLogs(_logTimePeriod); Assert.IsTrue(canOpenLogs); } @@ -132,51 +145,49 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging { var sw = new Stopwatch(); sw.Start(); - //Should get me the most 100 recent log entries & using default overloads for remaining params - var allLogs = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1); + + // Should get me the most 100 recent log entries & using default overloads for remaining params + PagedResult allLogs = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1); sw.Stop(); - //Check we get 100 results back for a page & total items all good :) + + // Check we get 100 results back for a page & total items all good :) Assert.AreEqual(100, allLogs.Items.Count()); Assert.AreEqual(102, allLogs.TotalItems); Assert.AreEqual(2, allLogs.TotalPages); - //Check collection all contain same object type + // Check collection all contain same object type CollectionAssert.AllItemsAreInstancesOfType(allLogs.Items, typeof(LogMessage)); - //Check first item is newest - var newestItem = allLogs.Items.First(); - DateTimeOffset newDate; - DateTimeOffset.TryParse("2018-11-12T08:39:18.1971147Z", out newDate); + // Check first item is newest + LogMessage newestItem = allLogs.Items.First(); + DateTimeOffset.TryParse("2018-11-12T08:39:18.1971147Z", out DateTimeOffset newDate); Assert.AreEqual(newDate, newestItem.Timestamp); - - //Check we call method again with a smaller set of results & in ascending - var smallQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); + // Check we call method again with a smaller set of results & in ascending + PagedResult smallQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, pageSize: 10, orderDirection: Direction.Ascending); Assert.AreEqual(10, smallQuery.Items.Count()); Assert.AreEqual(11, smallQuery.TotalPages); - //Check first item is oldest - var oldestItem = smallQuery.Items.First(); - DateTimeOffset oldDate; - DateTimeOffset.TryParse("2018-11-12T08:34:45.8371142Z", out oldDate); + // Check first item is oldest + LogMessage oldestItem = smallQuery.Items.First(); + DateTimeOffset.TryParse("2018-11-12T08:34:45.8371142Z", out DateTimeOffset oldDate); Assert.AreEqual(oldDate, oldestItem.Timestamp); - - //Check invalid log levels - //Rather than expect 0 items - get all items back & ignore the invalid levels + // Check invalid log levels + // Rather than expect 0 items - get all items back & ignore the invalid levels string[] invalidLogLevels = { "Invalid", "NotALevel" }; - var queryWithInvalidLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: invalidLogLevels); + PagedResult queryWithInvalidLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: invalidLogLevels); Assert.AreEqual(102, queryWithInvalidLevels.TotalItems); - //Check we can call method with an array of logLevel (error & warning) - string [] logLevels = { "Warning", "Error" }; - var queryWithLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: logLevels); + // Check we can call method with an array of logLevel (error & warning) + string[] logLevels = { "Warning", "Error" }; + PagedResult queryWithLevels = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, logLevels: logLevels); Assert.AreEqual(7, queryWithLevels.TotalItems); - //Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) + // Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) string[] logLevelMismatch = { "Debug", "Information" }; - var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); + PagedResult filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); Assert.AreEqual(0, filterLevelQuery.TotalItems); } @@ -191,17 +202,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging [Test] public void Logs_Can_Query_With_Expressions(string queryToVerify, int expectedCount) { - var testQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: queryToVerify); + PagedResult testQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: queryToVerify); Assert.AreEqual(expectedCount, testQuery.TotalItems); } [Test] public void Log_Search_Can_Persist() { - //Add a new search + // Add a new search _logViewer.AddSavedSearch("Unit Test Example", "Has(UnitTest)"); - var searches = _logViewer.GetSavedSearches(); + IReadOnlyList searches = _logViewer.GetSavedSearches(); var savedSearch = new SavedLogSearch { @@ -209,17 +220,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging Query = "Has(UnitTest)" }; - //Check if we can find the newly added item from the results we get back - var findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)"); + // Check if we can find the newly added item from the results we get back + IEnumerable findItem = searches.Where(x => x.Name == "Unit Test Example" && x.Query == "Has(UnitTest)"); Assert.IsNotNull(findItem, "We should have found the saved search, but get no results"); Assert.AreEqual(1, findItem.Count(), "Our list of searches should only contain one result"); // TODO: Need someone to help me find out why these don't work - //CollectionAssert.Contains(searches, savedSearch, "Can not find the new search that was saved"); - //Assert.That(searches, Contains.Item(savedSearch)); + // CollectionAssert.Contains(searches, savedSearch, "Can not find the new search that was saved"); + // Assert.That(searches, Contains.Item(savedSearch)); - //Remove the search from above & ensure it no longer exists + // Remove the search from above & ensure it no longer exists _logViewer.DeleteSavedSearch("Unit Test Example", "Has(UnitTest)"); searches = _logViewer.GetSavedSearches(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Macros/MacroParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Macros/MacroParserTests.cs deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Macros/MacroParserTests.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestContentAppTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestContentAppTests.cs deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestContentAppTests.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestParserTests.cs deleted file mode 100644 index 5f282702bb..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Manifest/ManifestParserTests.cs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs index ba982cd593..ff443cd944 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Mapping/MappingTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -22,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping var mapper = new UmbracoMapper(definitions); var thing1 = new Thing1 { Value = "value" }; - var thing2 = mapper.Map(thing1); + Thing2 thing2 = mapper.Map(thing1); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); @@ -48,7 +51,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping var thing1A = new Thing1 { Value = "valueA" }; var thing1B = new Thing1 { Value = "valueB" }; - var thing1 = new[] { thing1A, thing1B }; + Thing1[] thing1 = new[] { thing1A, thing1B }; var thing2 = mapper.Map, IEnumerable>(thing1).ToList(); Assert.IsNotNull(thing2); @@ -81,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping var mapper = new UmbracoMapper(definitions); var thing3 = new Thing3 { Value = "value" }; - var thing2 = mapper.Map(thing3); + Thing2 thing2 = mapper.Map(thing3); Assert.IsNotNull(thing2); Assert.AreEqual("value", thing2.Value); @@ -107,7 +110,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping // can map a PropertyCollection var source = new PropertyCollection(); - var target = mapper.Map>(source); + IEnumerable target = mapper.Map>(source); } [Test] @@ -129,7 +132,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping // if timing is good, and mapper does have non-concurrent dictionaries, it fails // practically, to reproduce, one needs to add a 1s sleep in the mapper's loop // hence, this test is explicit - var thing3 = new Thing3 { Value = "value" }; var thing4 = new Thing4(); Exception caught = null; @@ -138,6 +140,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping { // keep failing at mapping - and looping through the maps for (var i = 0; i < 10; i++) + { try { mapper.Map(thing4); @@ -147,6 +150,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping caught = e; Console.WriteLine($"{e.GetType().Name} {e.Message}"); } + } Console.WriteLine("done"); } @@ -158,7 +162,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping try { Console.WriteLine($"{DateTime.Now:O} mapping"); - var thing2 = mapper.Map(thing3); + Thing2 thing2 = mapper.Map(thing3); Console.WriteLine($"{DateTime.Now:O} mapped"); Assert.IsNotNull(thing2); @@ -186,7 +190,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping Fruit3 = Thing5Enum.Cherry }; - var thing6 = mapper.Map(thing5); + Thing6 thing6 = mapper.Map(thing5); Assert.IsNotNull(thing6); Assert.AreEqual(Thing6Enum.Apple, thing6.Fruit1); @@ -205,20 +209,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping var thing7 = new Thing7(); - var thing8 = mapper.Map(thing7); + Thing8 thing8 = mapper.Map(thing7); Assert.IsNotNull(thing8); Assert.IsNull(thing8.Things); } - private class Thing1 { public string Value { get; set; } } private class Thing3 : Thing1 - { } + { + } private class Thing2 { @@ -226,12 +230,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping } private class Thing4 - { } + { + } private class Thing5 { public Thing5Enum Fruit1 { get; set; } + public Thing5Enum Fruit2 { get; set; } + public Thing5Enum Fruit3 { get; set; } } @@ -245,7 +252,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping private class Thing6 { public Thing6Enum Fruit1 { get; set; } + public Thing6Enum Fruit2 { get; set; } + public Thing6Enum Fruit3 { get; set; } } @@ -268,26 +277,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping private class MapperDefinition1 : IMapDefinition { - public void DefineMaps(UmbracoMapper mapper) - { - mapper.Define((source, context) => new Thing2(), Map); - } + public void DefineMaps(UmbracoMapper mapper) => mapper.Define((source, context) => new Thing2(), Map); - private void Map(Thing1 source, Thing2 target, MapperContext context) - { - target.Value = source.Value; - } + private void Map(Thing1 source, Thing2 target, MapperContext context) => target.Value = source.Value; } private class MapperDefinition2 : IMapDefinition { - public void DefineMaps(UmbracoMapper mapper) - { + public void DefineMaps(UmbracoMapper mapper) => mapper.Define((source, context) => new ContentPropertyDto(), Map); - } private static void Map(IProperty source, ContentPropertyDto target, MapperContext context) - { } + { + } } private class MapperDefinition3 : IMapDefinition @@ -328,15 +330,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Mapping mapper.Define((source, context) => new Thing8(), Map2); } - private void Map1(Thing1 source, Thing2 target, MapperContext context) - { + private void Map1(Thing1 source, Thing2 target, MapperContext context) => target.Value = source.Value; - } - private void Map2(Thing7 source, Thing8 target, MapperContext context) - { + private void Map2(Thing7 source, Thing8 target, MapperContext context) => target.Things = context.Map>(source.Things); - } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs index febb36145b..1ced792520 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Media/ImageSharpImageUrlGeneratorTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Infrastructure.Media; using Umbraco.Web.Models; @@ -9,36 +12,36 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media public class ImageSharpImageUrlGeneratorTests { private const string MediaPath = "/media/1005/img_0671.jpg"; - private static readonly ImageUrlGenerationOptions.CropCoordinates Crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); - private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m); - private static readonly ImageUrlGenerationOptions.FocalPointPosition Focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m); - private static readonly ImageSharpImageUrlGenerator Generator = new ImageSharpImageUrlGenerator(); + private static readonly ImageUrlGenerationOptions.CropCoordinates s_crop = new ImageUrlGenerationOptions.CropCoordinates(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new ImageUrlGenerationOptions.FocalPointPosition(0.80827067669172936m, 0.96m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new ImageUrlGenerationOptions.FocalPointPosition(0.41m, 0.4275m); + private static readonly ImageSharpImageUrlGenerator s_generator = new ImageSharpImageUrlGenerator(); [Test] public void GetCropUrl_CropAliasTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, Height = 100 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 }); Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } [Test] public void GetCropUrl_WidthHeightTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 200, Height = 300 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300 }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300", urlString); } [Test] public void GetCropUrl_FocalPointTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 100, Height = 100 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 100, Height = 100 }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=100&height=100", urlString); } [Test] public void GetCropUrlFurtherOptionsTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300, FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff" }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&width=200&height=300&filter=comic&roundedcorners=radius-26|bgcolor-fff", urlString); } @@ -48,7 +51,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrlNullTest() { - var urlString = Generator.GetImageUrl(null); + var urlString = s_generator.GetImageUrl(null); Assert.AreEqual(null, urlString); } @@ -58,7 +61,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrlEmptyTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null)); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null)); Assert.AreEqual("?mode=crop", urlString); } @@ -68,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetBaseCropUrlFromModelTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = Crop, Width = 100, Height = 100 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null) { Crop = s_crop, Width = 100, Height = 100 }); Assert.AreEqual("?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&width=100&height=100", urlString); } @@ -78,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = Crop, Width = 100, HeightRatio = 1 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, HeightRatio = 1 }); Assert.AreEqual(MediaPath + "?crop=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&cropmode=percentage&heightratio=1&width=100", urlString); } @@ -88,7 +91,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Width = 300, HeightRatio = 0.5m }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 300, HeightRatio = 0.5m }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&heightratio=0.5&width=300", urlString); } @@ -98,7 +101,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_HeightWidthRatioModeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus1, Height = 150, WidthRatio = 2 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Height = 150, WidthRatio = 2 }); Assert.AreEqual(MediaPath + "?center=0.80827067669172936,0.96&mode=crop&widthratio=2&height=150", urlString); } @@ -108,11 +111,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_SpecifiedCropModeTest() { - var urlStringMin = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Min, Width = 300, Height = 150 }); - var urlStringBoxPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.BoxPad, Width = 300, Height = 150 }); - var urlStringPad = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 300, Height = 150 }); - var urlStringMax = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Max, Width = 300, Height = 150 }); - var urlStringStretch = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Stretch, Width = 300, Height = 150 }); + var urlStringMin = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Min, Width = 300, Height = 150 }); + var urlStringBoxPad = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.BoxPad, Width = 300, Height = 150 }); + var urlStringPad = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 300, Height = 150 }); + var urlStringMax = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Max, Width = 300, Height = 150 }); + var urlStringStretch = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Stretch, Width = 300, Height = 150 }); Assert.AreEqual(MediaPath + "?mode=min&width=300&height=150", urlStringMin); Assert.AreEqual(MediaPath + "?mode=boxpad&width=300&height=150", urlStringBoxPad); @@ -127,7 +130,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_UploadTypeTest() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Crop, ImageCropAnchor = ImageCropAnchor.Center, Width = 100, Height = 270 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Crop, ImageCropAnchor = ImageCropAnchor.Center, Width = 100, Height = 270 }); Assert.AreEqual(MediaPath + "?mode=crop&anchor=center&width=100&height=270", urlString); } @@ -137,7 +140,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreferFocalPointCenter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 300, Height = 150 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=300&height=150", urlString); } @@ -147,7 +150,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidth() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -157,7 +160,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPoint() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 200, HeightRatio = 0.5962962962962962962962962963m }); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&heightratio=0.5962962962962962962962962963&width=200", urlString); } @@ -167,7 +170,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = Focus2, Width = 270, Height = 161 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 270, Height = 161 }); Assert.AreEqual(MediaPath + "?center=0.41,0.4275&mode=crop&width=270&height=161", urlString); } @@ -177,7 +180,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_PreDefinedCropNoCoordinatesWithHeight() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200, WidthRatio = 1.6770186335403726708074534161m }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&widthratio=1.6770186335403726708074534161&height=200", urlString); } @@ -187,7 +190,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_WidthOnlyParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Width = 200 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&width=200", urlString); } @@ -197,7 +200,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_HeightOnlyParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { DefaultCrop = true, Height = 200 }); Assert.AreEqual(MediaPath + "?anchor=center&mode=crop&height=200", urlString); } @@ -207,7 +210,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Media [Test] public void GetCropUrl_BackgroundColorParameter() { - var urlString = Generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); + var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { ImageCropMode = ImageCropMode.Pad, Width = 400, Height = 400, FurtherOptions = "&bgcolor=fff" }); Assert.AreEqual(MediaPath + "?mode=pad&width=400&height=400&bgcolor=fff", urlString); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs index 23ee3e143a..c09b354bef 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/AlterMigrationTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; using Microsoft.Extensions.Logging; @@ -15,7 +18,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations { private readonly ILogger _logger = Mock.Of>(); - [Test] public void Drop_Foreign_Key() { @@ -34,7 +36,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations // Assert Assert.That(database.Operations.Count, Is.EqualTo(1)); - Assert.That(database.Operations[0].Sql, + Assert.That( + database.Operations[0].Sql, Is.EqualTo("ALTER TABLE [umbracoUser2app] DROP CONSTRAINT [FK_umbracoUser2app_umbracoUser_id]")); } @@ -53,7 +56,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations } Assert.That(database.Operations.Count, Is.EqualTo(1)); - Assert.That(database.Operations[0].Sql, + Assert.That( + database.Operations[0].Sql, Is.EqualTo("ALTER TABLE [bar] ADD [foo] UniqueIdentifier NOT NULL")); } @@ -82,7 +86,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations } Assert.That(database.Operations.Count, Is.EqualTo(1)); - Assert.That(database.Operations[0].Sql, + Assert.That( + database.Operations[0].Sql, Is.EqualTo("ALTER TABLE [bar] ALTER COLUMN [foo] UniqueIdentifier NOT NULL")); } @@ -94,8 +99,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations } public override void Migrate() => + // bad/good syntax... - //Alter.Column("foo").OnTable("bar").AsGuid().NotNullable(); + //// Alter.Column("foo").OnTable("bar").AsGuid().NotNullable(); Alter.Table("bar").AlterColumn("foo").AsGuid().NotNullable().Do(); } @@ -114,9 +120,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations // Assert Assert.That(database.Operations.Any(), Is.True); - //Console output + // Console output Debug.Print("Number of expressions in context: {0}", database.Operations.Count); - Debug.Print(""); + Debug.Print(string.Empty); foreach (TestDatabase.Operation expression in database.Operations) { Debug.Print(expression.ToString()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs index 0511ee6290..a0d0966b99 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; @@ -32,8 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations .Setup(x => x.Database) .Returns(database); - var sqlContext = new SqlContext(new SqlServerSyntaxProvider(), DatabaseType.SQLCe, - Mock.Of()); + var sqlContext = new SqlContext(new SqlServerSyntaxProvider(), DatabaseType.SQLCe, Mock.Of()); var scopeProvider = new MigrationTests.TestScopeProvider(scope) { SqlContext = sqlContext }; IMigrationBuilder migrationBuilder = Mock.Of(); @@ -68,8 +70,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations var sourceState = kvs.GetValue("Umbraco.Tests.MigrationPlan") ?? string.Empty; // execute plan - state = plan.Execute(s, sourceState, migrationBuilder, loggerFactory.CreateLogger(), - loggerFactory); + state = plan.Execute(s, sourceState, migrationBuilder, loggerFactory.CreateLogger(), loggerFactory); // save new state kvs.SetValue("Umbraco.Tests.MigrationPlan", sourceState, state); @@ -97,10 +98,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations public void CannotTransitionToSameState() { var plan = new MigrationPlan("default"); - Assert.Throws(() => - { - plan.From("aaa").To("aaa"); - }); + Assert.Throws(() => plan.From("aaa").To("aaa")); } [Test] @@ -108,10 +106,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations { var plan = new MigrationPlan("default"); plan.From("aaa").To("bbb"); - Assert.Throws(() => - { - plan.From("aaa").To("ccc"); - }); + Assert.Throws(() => plan.From("aaa").To("ccc")); } [Test] @@ -191,7 +186,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations WritePlanToConsole(plan); plan.Validate(); - AssertList(plan.FollowPath(), "", "aaa", "bbb", "ccc", "*", "*", "fff", "ggg"); + AssertList(plan.FollowPath(), string.Empty, "aaa", "bbb", "ccc", "*", "*", "fff", "ggg"); AssertList(plan.FollowPath("ccc"), "ccc", "*", "*", "fff", "ggg"); AssertList(plan.FollowPath("eee"), "eee", "*", "*", "fff", "ggg"); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs index 34093d0bce..b37338c08b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Data; using Microsoft.Extensions.Logging; using Moq; @@ -37,7 +40,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations public IScope DetachScope() => throw new NotImplementedException(); - public IScopeContext Context { get; set; } public ISqlContext SqlContext { get; set; } @@ -85,7 +87,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Migrations { } - public override void Migrate() => Execute.Sql("").Do(); + public override void Migrate() => Execute.Sql(string.Empty).Do(); } public class BadMigration1 : MigrationBase diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs index 2b3e626d03..d1df3a8098 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/PostMigrationTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Moq; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/AlterUserTableMigrationStub.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/AlterUserTableMigrationStub.cs index ee212fa96f..a5c119f995 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/AlterUserTableMigrationStub.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/AlterUserTableMigrationStub.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,7 +9,8 @@ namespace Umbraco.Tests.Migrations.Stubs { public AlterUserTableMigrationStub(IMigrationContext context) : base(context) - { } + { + } public override void Migrate() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/DropForeignKeyMigrationStub.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/DropForeignKeyMigrationStub.cs index 02561fb563..cf3a26c7d3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/DropForeignKeyMigrationStub.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/DropForeignKeyMigrationStub.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,7 +9,8 @@ namespace Umbraco.Tests.Migrations.Stubs { public DropForeignKeyMigrationStub(IMigrationContext context) : base(context) - { } + { + } public override void Migrate() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/Dummy.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/Dummy.cs index 1a81120a14..f998c4b8a4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/Dummy.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/Dummy.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FiveZeroMigration.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FiveZeroMigration.cs index f69862610f..d870ef82db 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FiveZeroMigration.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FiveZeroMigration.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,9 +9,11 @@ namespace Umbraco.Tests.Migrations.Stubs { public FiveZeroMigration(IMigrationContext context) : base(context) - { } + { + } public override void Migrate() - { } + { + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FourElevenMigration.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FourElevenMigration.cs index fb1d905c0b..c168fa1c8f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FourElevenMigration.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/FourElevenMigration.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,8 +9,8 @@ namespace Umbraco.Tests.Migrations.Stubs { public FourElevenMigration(IMigrationContext context) : base(context) - { } - + { + } public override void Migrate() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration1.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration1.cs index d3bdc48cbf..1b5c580d5f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration1.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration1.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,8 +9,8 @@ namespace Umbraco.Tests.Migrations.Stubs { public SixZeroMigration1(IMigrationContext context) : base(context) - { } - + { + } public override void Migrate() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration2.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration2.cs index ed3f7b29ea..689e770a98 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration2.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/Stubs/SixZeroMigration2.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Migrations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Umbraco.Core.Migrations; namespace Umbraco.Tests.Migrations.Stubs { @@ -6,8 +9,8 @@ namespace Umbraco.Tests.Migrations.Stubs { public SixZeroMigration2(IMigrationContext context) : base(context) - { } - + { + } public override void Migrate() { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index b94effb907..71087917b0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -1,4 +1,8 @@ -using Newtonsoft.Json; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Reflection; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; @@ -12,19 +16,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private DataTypeBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new DataTypeBuilder(); - } + public void SetUp() => _builder = new DataTypeBuilder(); [Test] public void Can_Deep_Clone() { - var dtd = _builder - .WithId(3123) - .Build(); + DataType dtd = _builder + .WithId(3123) + .Build(); - var clone = (DataType) dtd.DeepClone(); + var clone = (DataType)dtd.DeepClone(); Assert.AreNotSame(clone, dtd); Assert.AreEqual(clone, dtd); @@ -41,9 +42,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models Assert.AreEqual(clone.Trashed, dtd.Trashed); Assert.AreEqual(clone.UpdateDate, dtd.UpdateDate); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + // This double verifies by reflection + PropertyInfo[] allProps = clone.GetType().GetProperties(); + foreach (PropertyInfo propertyInfo in allProps) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(dtd, null)); } @@ -52,10 +53,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [Test] public void Can_Serialize_Without_Error() { - var item = _builder.Build(); + DataType item = _builder.Build(); Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/PathValidationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/PathValidationTests.cs index 117b9d87cc..8a341687f5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/PathValidationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/PathValidationTests.cs @@ -1,7 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Microsoft.Extensions.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders; @@ -15,114 +18,111 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private EntitySlimBuilder _builder; [SetUp] - public void SetUp() - { - _builder = new EntitySlimBuilder(); - } + public void SetUp() => _builder = new EntitySlimBuilder(); [Test] public void Validate_Path() { - var entity = _builder + EntitySlim entity = _builder .WithoutIdentity() .Build(); - //it's empty with no id so we need to allow it + // it's empty with no id so we need to allow it Assert.IsTrue(entity.ValidatePath()); entity.Id = 1234; - //it has an id but no path, so we can't allow it + // it has an id but no path, so we can't allow it Assert.IsFalse(entity.ValidatePath()); entity.Path = "-1"; - //invalid path + // invalid path Assert.IsFalse(entity.ValidatePath()); entity.Path = string.Concat("-1,", entity.Id); - //valid path + // valid path Assert.IsTrue(entity.ValidatePath()); } [Test] public void Ensure_Path_Throws_Without_Id() { - var entity = _builder + EntitySlim entity = _builder .WithoutIdentity() .Build(); - //no id assigned + // no id assigned Assert.Throws(() => entity.EnsureValidPath(Mock.Of>(), umbracoEntity => new EntitySlim(), umbracoEntity => { })); } [Test] public void Ensure_Path_Throws_Without_Parent() { - var entity = _builder + EntitySlim entity = _builder .WithId(1234) .WithNoParentId() .Build(); - //no parent found + // no parent found Assert.Throws(() => entity.EnsureValidPath(Mock.Of>(), umbracoEntity => null, umbracoEntity => { })); } [Test] public void Ensure_Path_Entity_At_Root() { - var entity = _builder + EntitySlim entity = _builder .WithId(1234) .Build(); entity.EnsureValidPath(Mock.Of>(), umbracoEntity => null, umbracoEntity => { }); - //works because it's under the root + // works because it's under the root Assert.AreEqual("-1,1234", entity.Path); } [Test] public void Ensure_Path_Entity_Valid_Parent() { - var entity = _builder + EntitySlim entity = _builder .WithId(1234) .WithParentId(888) .Build(); entity.EnsureValidPath(Mock.Of>(), umbracoEntity => umbracoEntity.ParentId == 888 ? new EntitySlim { Id = 888, Path = "-1,888" } : null, umbracoEntity => { }); - //works because the parent was found + // works because the parent was found Assert.AreEqual("-1,888,1234", entity.Path); } [Test] public void Ensure_Path_Entity_Valid_Recursive_Parent() { - var parentA = _builder + EntitySlim parentA = _builder .WithId(999) .Build(); // Re-creating the class-level builder as we need to reset before usage when creating multiple entities. _builder = new EntitySlimBuilder(); - var parentB = _builder + EntitySlim parentB = _builder .WithId(888) .WithParentId(999) .Build(); _builder = new EntitySlimBuilder(); - var parentC = _builder + EntitySlim parentC = _builder .WithId(777) .WithParentId(888) .Build(); _builder = new EntitySlimBuilder(); - var entity = _builder + EntitySlim entity = _builder .WithId(1234) .WithParentId(777) .Build(); - Func getParent = umbracoEntity => + IUmbracoEntity GetParent(IUmbracoEntity umbracoEntity) { switch (umbracoEntity.ParentId) { @@ -137,10 +137,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models default: return null; } - }; + } - //this will recursively fix all paths - entity.EnsureValidPath(Mock.Of>(), getParent, umbracoEntity => { }); + // this will recursively fix all paths + entity.EnsureValidPath(Mock.Of>(), GetParent, umbracoEntity => { }); Assert.AreEqual("-1,999", parentA.Path); Assert.AreEqual("-1,999,888", parentB.Path); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs index 10926ef20a..e01f071f3c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/BulkDataReaderTests.cs @@ -1,8 +1,12 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Common; +using System.Data.SqlClient; using NUnit.Framework; using Umbraco.Core.Persistence; @@ -16,11 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// See: https://blogs.msdn.microsoft.com/anthonybloesch/2013/01/23/bulk-loading-data-with-idatareader-and-sqlbulkcopy/ /// [TestFixture] - public class BulkDataReaderTest + public class BulkDataReaderTests { - - #region Test constants - /// /// The schema name. /// @@ -56,10 +57,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// private const string TestXmlSchemaCollectionName = "Xml"; - #endregion - - #region Schema tests - /// /// Test that is functioning correctly. /// @@ -69,12 +66,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { using (var testReader = new BulkDataReaderSubclass()) { - var columnMappings = testReader.ColumnMappings; + ReadOnlyCollection columnMappings = testReader.ColumnMappings; Assert.IsTrue(columnMappings.Count > 0); Assert.AreEqual(columnMappings.Count, testReader.FieldCount); - foreach (var columnMapping in columnMappings) + foreach (SqlBulkCopyColumnMapping columnMapping in columnMappings) { Assert.AreEqual(columnMapping.SourceColumn, columnMapping.DestinationColumn); } @@ -82,9 +79,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test that is functioning correctly. + /// Test that is functioning correctly. /// - /// + /// [Test] public void GetDataTypeNameTest() { @@ -92,19 +89,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { Assert.IsTrue(testReader.FieldCount > 0); - for (var currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) { - var schemaTable = testReader.GetSchemaTable(); + DataTable schemaTable = testReader.GetSchemaTable(); Assert.IsNotNull(schemaTable); - Assert.AreEqual(testReader.GetDataTypeName(currentColumn), ((Type) schemaTable.Rows[currentColumn][SchemaTableColumn.DataType]).Name); + Assert.AreEqual(testReader.GetDataTypeName(currentColumn), ((Type)schemaTable.Rows[currentColumn][SchemaTableColumn.DataType]).Name); } } } /// - /// Test that is functioning correctly. + /// Test that is functioning correctly. /// - /// + /// [Test] public void GetFieldTypeTest() { @@ -112,9 +109,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { Assert.IsTrue(testReader.FieldCount > 0); - for (var currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) { - var schemaTable = testReader.GetSchemaTable(); + DataTable schemaTable = testReader.GetSchemaTable(); Assert.IsNotNull(schemaTable); Assert.AreEqual(testReader.GetFieldType(currentColumn), schemaTable.Rows[currentColumn][SchemaTableColumn.DataType]); } @@ -122,9 +119,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test that is functioning correctly. + /// Test that is functioning correctly. /// - /// + /// [Test] public void GetOrdinalTest() { @@ -132,7 +129,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { Assert.IsTrue(testReader.FieldCount > 0); - for (var currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) { Assert.AreEqual(testReader.GetOrdinal(testReader.GetName(currentColumn)), currentColumn); Assert.AreEqual(testReader.GetOrdinal(testReader.GetName(currentColumn).ToUpperInvariant()), currentColumn); @@ -152,7 +149,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { using (var testReader = new BulkDataReaderSubclass()) { - var schemaTable = testReader.GetSchemaTable(); + DataTable schemaTable = testReader.GetSchemaTable(); Assert.IsNotNull(schemaTable); Assert.IsTrue(schemaTable.Rows.Count > 0); @@ -161,13 +158,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test that + /// Test that /// throws a for null column names. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNullColumnNameTest() { @@ -189,19 +186,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty column names. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowEmptyColumnNameTest() { @@ -223,19 +220,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nonpositive column sizes. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNonpositiveColumnSizeTest() { @@ -257,19 +254,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nonpositive numeric precision. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNonpositiveNumericPrecisionTest() { @@ -291,19 +288,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for negative numeric scale. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNegativeNumericScaleTest() { @@ -325,19 +322,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for binary column without a column size. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowBinaryWithoutSizeTest() { @@ -359,19 +356,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for binary column with a column size that is too large (>8000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowBinaryWithTooLargeSizeTest() { @@ -393,19 +390,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for char column without a column size. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowCharWithoutSizeTest() { @@ -427,19 +424,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for char column with a column size that is too large (>8000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowCharWithTooLargeSizeTest() { @@ -461,19 +458,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for decimal column without a column precision. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDecimalWithoutPrecisionTest() { @@ -495,19 +492,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for decimal column without a column scale. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDecimalWithoutScaleTest() { @@ -529,19 +526,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for decimal column with a column precision that is too large (>38). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDecimalWithTooLargePrecisionTest() { @@ -563,19 +560,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for decimal column with a column scale that is larger than the column precision. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDecimalWithTooLargeScaleTest() { @@ -597,19 +594,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for datetime2 column with a column size that has a precision that is too large (>7). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDateTime2WithTooLargePrecisionTest() { @@ -631,19 +628,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for datetimeoffset column with a column size that has a precision that is too large (>7). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowDateTimeOffsetWithTooLargePrecisionTest() { @@ -665,19 +662,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nchar column without a precision. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowFloatWithoutPrecisionTest() { @@ -699,19 +696,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for float column with a column precision that is too large (>53). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowFloatWithTooLargePrecisionTest() { @@ -733,19 +730,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nchar column without a column size. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNCharWithoutSizeTest() { @@ -767,19 +764,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nchar column with a column size that is too large (>4000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNCharWithTooLargeSizeTest() { @@ -801,19 +798,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for nvarchar column with a column size that is too large (>4000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowNVarCharWithTooLargeSizeTest() { @@ -835,19 +832,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for time column with a column precision that is too large (>7). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowTimeWithTooLargePrecisionTest() { @@ -869,19 +866,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for missing UDT schema name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowUdtMissingSchemaNameTest() { @@ -903,19 +900,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty UDT schema name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowUdtEmptySchemaNameTest() { @@ -937,19 +934,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for missing UDT name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowUdtMissingNameTest() { @@ -971,19 +968,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty UDT name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowUdtEmptyNameTest() { @@ -1005,19 +1002,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for varbinary column with a column size that is too large (>8000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowVarBinaryWithTooLargeSizeTest() { @@ -1039,19 +1036,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for varchar column with a column size that is too large (>8000). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowVarCharWithTooLargeSizeTest() { @@ -1073,19 +1070,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for null xml collection name but with a name for the database. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowXmlNullNameWithDatabaseNameTest() { @@ -1107,19 +1104,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for null xml collection name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowXmlNullNameWithOwningSchemaNameTest() { @@ -1141,19 +1138,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty xml collection database name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowXmlEmptyDatabaseNameTest() { @@ -1175,19 +1172,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty xml collection owning schema name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowXmlEmptyOwningSchemaNameTest() { @@ -1209,19 +1206,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for empty xml collection name. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowXmlEmptyNameTest() { @@ -1243,19 +1240,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for a structured column (which is illegal). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowStructuredTypeTest() { @@ -1277,19 +1274,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws a for a timestamp column (which is illegal). /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowTimestampTypeTest() { @@ -1311,23 +1308,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); }); } } /// - /// Test that + /// Test that /// throws an for a column with an unallowed optional column set. /// /// /// Uses to test the illegal schema combination. /// - /// + /// [Test] public void AddSchemaTableRowUnallowedOptionalColumnTest() { - // Column size set using (var testReader = new BulkDataReaderSchemaTest()) { @@ -1344,17 +1340,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Bit, SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, - SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.Real, - SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, - SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, - SqlDbType.Variant, SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Bit, SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, + SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.Variant, SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1380,18 +1379,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, - SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, - SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.Udt, SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, - SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, + SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, + SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.Udt, SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, + SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1417,18 +1419,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, - SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, - SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, - SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, - SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, + SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1454,18 +1459,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, - SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, - SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, - SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, - SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Image, SqlDbType.Int, + SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, SqlDbType.Real, + SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, SqlDbType.Structured, SqlDbType.Text, + SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, SqlDbType.Udt, SqlDbType.UniqueIdentifier, + SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1491,18 +1499,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, - SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, - SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, - SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1528,18 +1539,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = null; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, - SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, - SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, - SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Xml + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1565,18 +1579,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = null; testReader.XmlSchemaCollectionName = "Name"; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, - SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, - SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, - SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1602,18 +1619,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = "Schema"; testReader.XmlSchemaCollectionName = "Name"; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, - SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, - SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, - SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1639,18 +1659,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence testReader.XmlSchemaCollectionOwningSchema = "Schema"; testReader.XmlSchemaCollectionName = "Name"; - foreach (var dbtype in new List { SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, - SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, - SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, - SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, - SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, - SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt }) + foreach (SqlDbType dbtype in new List + { + SqlDbType.BigInt, SqlDbType.Binary, SqlDbType.Bit, SqlDbType.Char, SqlDbType.Date, + SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.DateTimeOffset, SqlDbType.Decimal, SqlDbType.Float, + SqlDbType.Image, SqlDbType.Int, SqlDbType.Money, SqlDbType.NChar, SqlDbType.NText, + SqlDbType.NVarChar, SqlDbType.Real, SqlDbType.SmallDateTime, SqlDbType.SmallInt, SqlDbType.SmallMoney, + SqlDbType.Structured, SqlDbType.Text, SqlDbType.Time, SqlDbType.Timestamp, SqlDbType.TinyInt, + SqlDbType.UniqueIdentifier, SqlDbType.VarBinary, SqlDbType.VarChar, SqlDbType.Variant, SqlDbType.Udt + }) { testReader.ProviderType = dbtype; try { - var unused = testReader.GetSchemaTable(); + DataTable unused = testReader.GetSchemaTable(); Assert.Fail(); } @@ -1661,10 +1684,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } } - #endregion; - - #region Rowset tests - /// /// Test that is functioning correctly. /// @@ -1698,12 +1717,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test that is functioning correctly. + /// Test that is functioning correctly. /// /// /// Because nested row sets are not supported, this should always return null; /// - /// + /// [Test] public void GetDataTest() { @@ -1718,7 +1737,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test and related functions. + /// Test and related functions. /// /// /// Uses to test legal schema combinations. @@ -1731,13 +1750,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.IsTrue(testReader.Read()); // this[int] - for (var column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) + for (int column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) { Assert.AreEqual(testReader[column], BulkDataReaderSubclass.ExpectedResultSet[column]); } // this[string] - for (var column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) + for (int column = 0; column < BulkDataReaderSubclass.ExpectedResultSet.Count; column++) { Assert.AreEqual(testReader[testReader.GetName(column)], BulkDataReaderSubclass.ExpectedResultSet[column]); @@ -1746,8 +1765,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence // GetValues { - var values = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; - var expectedValues = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; + object[] values = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; + object[] expectedValues = new object[BulkDataReaderSubclass.ExpectedResultSet.Count]; Assert.AreEqual(testReader.GetValues(values), values.Length); @@ -1758,20 +1777,20 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence // Typed getters { - var currentColumn = 0; + int currentColumn = 0; Assert.AreEqual(testReader.GetInt64(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); currentColumn++; - { - var expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; - var expectedLength = expectedResult.Length; - var buffer = new byte[expectedLength]; + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); Assert.IsTrue(ArraysMatch(buffer, expectedResult)); } + currentColumn++; Assert.AreEqual(testReader.GetBoolean(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); @@ -1790,11 +1809,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence currentColumn++; Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); - { - var expectedResult = ((string)BulkDataReaderSubclass.ExpectedResultSet[currentColumn]).ToCharArray(); - var expectedLength = expectedResult.Length; - var buffer = new char[expectedLength]; + char[] expectedResult = ((string)BulkDataReaderSubclass.ExpectedResultSet[currentColumn]).ToCharArray(); + int expectedLength = expectedResult.Length; + char[] buffer = new char[expectedLength]; Assert.AreEqual(testReader.GetChars(currentColumn, 0, buffer, 0, expectedLength), expectedLength); @@ -1826,7 +1844,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.AreEqual(testReader.GetDouble(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); currentColumn++; - { byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; int expectedLength = expectedResult.Length; @@ -1836,6 +1853,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.IsTrue(ArraysMatch(buffer, expectedResult)); } + currentColumn++; Assert.AreEqual(testReader.GetInt32(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); @@ -1885,20 +1903,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.AreEqual(testReader.GetGuid(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); currentColumn++; - { - var expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; - var expectedLength = expectedResult.Length; - var buffer = new byte[expectedLength]; - - Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); - - Assert.IsTrue(ArraysMatch(buffer, expectedResult)); - } - currentColumn++; - - { - var expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; int expectedLength = expectedResult.Length; byte[] buffer = new byte[expectedLength]; @@ -1906,6 +1912,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.IsTrue(ArraysMatch(buffer, expectedResult)); } + + currentColumn++; + { + byte[] expectedResult = (byte[])BulkDataReaderSubclass.ExpectedResultSet[currentColumn]; + int expectedLength = expectedResult.Length; + byte[] buffer = new byte[expectedLength]; + + Assert.AreEqual(testReader.GetBytes(currentColumn, 0, buffer, 0, expectedLength), expectedLength); + + Assert.IsTrue(ArraysMatch(buffer, expectedResult)); + } + currentColumn++; Assert.AreEqual(testReader.GetString(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn]); @@ -1935,13 +1953,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } /// - /// Test throws a when + /// Test throws a when /// the index is too small. /// /// /// Uses to test the method. /// - /// + /// [Test] public void GetValueIndexTooSmallTest() { @@ -1951,19 +1969,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetValue(-1); + object unused = testReader.GetValue(-1); }); } } /// - /// Test throws a when + /// Test throws a when /// the index is too large. /// /// /// Uses to test the method. /// - /// + /// [Test] public void GetValueIndexTooLargeTest() { @@ -1973,19 +1991,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetValue(testReader.FieldCount); + object unused = testReader.GetValue(testReader.FieldCount); }); } } /// - /// Test throws a when + /// Test throws a when /// the index is too small. /// /// /// Uses to test the method. /// - /// + /// [Test] public void GetDataIndexTooSmallTest() { @@ -1995,13 +2013,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetData(-1); + IDataReader unused = testReader.GetData(-1); }); } } /// - /// Test throws a when + /// Test throws a when /// the index is too large. /// /// @@ -2016,21 +2034,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence Assert.Throws(() => { - var unused = testReader.GetData(testReader.FieldCount); + IDataReader unused = testReader.GetData(testReader.FieldCount); }); } } /// - /// Test that functions correctly. + /// Test that functions correctly. /// - /// + /// [Test] public void IsDbNullTest() { using (var testReader = new BulkDataReaderSubclass()) { - for (var currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) + for (int currentColumn = 0; currentColumn < testReader.FieldCount; currentColumn++) { Assert.AreEqual(testReader.IsDBNull(currentColumn), BulkDataReaderSubclass.ExpectedResultSet[currentColumn] == null); } @@ -2071,10 +2089,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } } - #endregion - - #region Test IDisposable - /// /// Test that the interface is functioning correctly. /// @@ -2104,27 +2118,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence } } - #endregion - - #region Utility - /// - /// Do the two arrays match exactly? + /// Do the two arrays match exactly? /// - /// + /// /// The type of the array elements. /// /// - /// The first array. + /// The first array. /// /// - /// The second array. + /// The second array. /// /// - /// True if the arrays have the same length and contents. + /// True if the arrays have the same length and contents. /// - private static bool ArraysMatch(ElementType[] left, - ElementType[] right) + private static bool ArraysMatch(TElementType[] left, TElementType[] right) { if (left == null) { @@ -2152,18 +2161,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence return result; } - #endregion - - #region Test stubs - /// /// A subclass of used for testing its utility functions. /// private class BulkDataReaderSubclass : BulkDataReader { - - #region Constructors - /// /// Constructor. /// @@ -2171,31 +2173,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { } - #endregion - - #region BulkDataReader - /// /// See . /// /// - /// Returns . + /// Returns . /// - protected override string SchemaName - { - get { return BulkDataReaderTest.TestSchemaName; } - } + protected override string SchemaName => BulkDataReaderTests.TestSchemaName; /// /// See . /// /// - /// Returns . + /// Returns . /// - protected override string TableName - { - get { return BulkDataReaderTest.TestTableName; } - } + protected override string TableName => BulkDataReaderTests.TestTableName; /// /// See @@ -2236,17 +2228,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence AddSchemaTableRow("Time", null, null, null, false, false, false, SqlDbType.Time, null, null, null, null, null); AddSchemaTableRow("Time_5", null, 5, null, false, false, false, SqlDbType.Time, null, null, null, null, null); AddSchemaTableRow("TinyInt", null, null, null, false, false, false, SqlDbType.TinyInt, null, null, null, null, null); - AddSchemaTableRow("Udt", null, null, null, false, false, false, SqlDbType.Udt, BulkDataReaderTest.TestUdtSchemaName, BulkDataReaderTest.TestUdtName, null, null, null); + AddSchemaTableRow("Udt", null, null, null, false, false, false, SqlDbType.Udt, BulkDataReaderTests.TestUdtSchemaName, BulkDataReaderTests.TestUdtName, null, null, null); AddSchemaTableRow("UniqueIdentifier", null, null, null, false, false, false, SqlDbType.UniqueIdentifier, null, null, null, null, null); AddSchemaTableRow("VarBinary_20", 20, null, null, false, false, false, SqlDbType.VarBinary, null, null, null, null, null); AddSchemaTableRow("VarBinary_Max", null, null, null, false, false, false, SqlDbType.VarBinary, null, null, null, null, null); AddSchemaTableRow("VarChar_20", 20, null, null, false, false, false, SqlDbType.VarChar, null, null, null, null, null); AddSchemaTableRow("VarChar_Max", null, null, null, false, false, false, SqlDbType.VarChar, null, null, null, null, null); AddSchemaTableRow("Variant", null, null, null, false, false, false, SqlDbType.Variant, null, null, null, null, null); - AddSchemaTableRow("Xml_Database", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTest.TestXmlSchemaCollectionDatabaseName, BulkDataReaderTest.TestXmlSchemaCollectionSchemaName, BulkDataReaderTest.TestXmlSchemaCollectionName); - AddSchemaTableRow("Xml_Database_XML", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTest.TestXmlSchemaCollectionDatabaseName, BulkDataReaderTest.TestXmlSchemaCollectionSchemaName, BulkDataReaderTest.TestXmlSchemaCollectionName); - AddSchemaTableRow("Xml_Schema", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, BulkDataReaderTest.TestXmlSchemaCollectionSchemaName, BulkDataReaderTest.TestXmlSchemaCollectionName); - AddSchemaTableRow("Xml_Xml", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, null, BulkDataReaderTest.TestXmlSchemaCollectionName); + AddSchemaTableRow("Xml_Database", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTests.TestXmlSchemaCollectionDatabaseName, BulkDataReaderTests.TestXmlSchemaCollectionSchemaName, BulkDataReaderTests.TestXmlSchemaCollectionName); + AddSchemaTableRow("Xml_Database_XML", null, null, null, false, false, false, SqlDbType.Xml, null, null, BulkDataReaderTests.TestXmlSchemaCollectionDatabaseName, BulkDataReaderTests.TestXmlSchemaCollectionSchemaName, BulkDataReaderTests.TestXmlSchemaCollectionName); + AddSchemaTableRow("Xml_Schema", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, BulkDataReaderTests.TestXmlSchemaCollectionSchemaName, BulkDataReaderTests.TestXmlSchemaCollectionName); + AddSchemaTableRow("Xml_Xml", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, null, BulkDataReaderTests.TestXmlSchemaCollectionName); AddSchemaTableRow("Xml", null, null, null, false, false, false, SqlDbType.Xml, null, null, null, null, null); } @@ -2255,7 +2247,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// public static readonly ReadOnlyCollection ExpectedResultSet = new ReadOnlyCollection(new List { - (long)10, + 10L, new byte[20], true, null, @@ -2269,19 +2261,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence DateTime.UtcNow, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, - (decimal)10.5, + 10.5M, (double)10.5, new byte[20], (int)10, - (decimal)10.5, + 10.5M, "nchar 20", "ntext", "nvarchar 20", "nvarchar max", - (float)10.5, + 10.5F, DateTime.UtcNow, (short)10, - (decimal)10.5, + 10.5M, "text", DateTime.UtcNow.TimeOfDay, DateTime.UtcNow.TimeOfDay, @@ -2301,7 +2293,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence }); /// - /// See + /// See /// /// /// The zero-based column ordinal. @@ -2309,16 +2301,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// /// The value of the column in . /// - /// - public override object GetValue(int i) - { - return BulkDataReaderSubclass.ExpectedResultSet[i]; - } + /// + public override object GetValue(int i) => BulkDataReaderSubclass.ExpectedResultSet[i]; /// /// The number of rows read. /// - private int readCount = 0; + private int _readCount = 0; /// /// See @@ -2327,89 +2316,76 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// True if there are more rows; otherwise, false. /// /// - public override bool Read() - { - return readCount++ < 1; - } - - #endregion - + public override bool Read() => _readCount++ < 1; } private class BulkDataReaderSchemaTest : BulkDataReader { - - #region Properties - /// - /// Is the column nullable (i.e. optional)? + /// Gets or sets a value indicating whether the column is nullable (i.e. optional). /// public bool AllowDBNull { get; set; } /// - /// The name of the column. + /// Gets or sets the name of the column. /// public string ColumnName { get; set; } /// - /// The size of the column which may be null if not applicable. + /// Gets or sets the size of the column which may be null if not applicable. /// public int? ColumnSize { get; set; } /// - /// Is the column part of the primary key? + /// Gets or sets a value indicating whether the column part of the primary key. /// public bool IsKey { get; set; } /// - /// Are the column values unique (i.e. never duplicated)? + /// Gets or sets a value indicating whether the column values are unique (i.e. never duplicated). /// public bool IsUnique { get; set; } /// - /// The precision of the column which may be null if not applicable. + /// Gets or sets the precision of the column which may be null if not applicable. /// public short? NumericPrecision { get; set; } /// - /// The scale of the column which may be null if not applicable. + /// Gets or sets the scale of the column which may be null if not applicable. /// public short? NumericScale { get; set; } /// - /// The corresponding . + /// Gets or sets the corresponding . /// public SqlDbType ProviderType { get; set; } /// - /// The schema name of the UDT. + /// Gets or sets the schema name of the UDT. /// public string UdtSchema { get; set; } /// - /// The type name of the UDT. + /// Gets or sets the type name of the UDT. /// public string UdtType { get; set; } /// - /// For XML columns the schema collection's database name. Otherwise, null. + /// Gets or sets the schema collection's database name for XML columns. Otherwise, null. /// public string XmlSchemaCollectionDatabase { get; set; } /// - /// For XML columns the schema collection's name. Otherwise, null. + /// Gets or sets the schema collection's name for XML columns. Otherwise, null. /// public string XmlSchemaCollectionName { get; set; } /// - /// For XML columns the schema collection's schema name. Otherwise, null. + /// Gets or sets the schema collection's scheme name for XML columns. Otherwise, null. /// public string XmlSchemaCollectionOwningSchema { get; set; } - #endregion - - #region Constructors - /// /// Constructor. /// @@ -2417,31 +2393,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence { } - #endregion - - #region BulkDataReader - /// /// See . /// /// - /// Returns . + /// Returns . /// - protected override string SchemaName - { - get { return BulkDataReaderTest.TestSchemaName; } - } + protected override string SchemaName => BulkDataReaderTests.TestSchemaName; /// /// See . /// /// - /// Returns . + /// Returns . /// - protected override string TableName - { - get { return BulkDataReaderTest.TestTableName; } - } + protected override string TableName => BulkDataReaderTests.TestTableName; /// /// See @@ -2449,25 +2415,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// /// Creates a schema row for the various values. /// - protected override void AddSchemaTableRows() - { - AddSchemaTableRow(this.ColumnName, - this.ColumnSize, - this.NumericPrecision, - this.NumericScale, - this.IsUnique, - this.IsKey, - this.AllowDBNull, - this.ProviderType, - this.UdtSchema, - this.UdtType, - this.XmlSchemaCollectionDatabase, - this.XmlSchemaCollectionOwningSchema, - this.XmlSchemaCollectionName); - } + protected override void AddSchemaTableRows() => + AddSchemaTableRow( + ColumnName, + ColumnSize, + NumericPrecision, + NumericScale, + IsUnique, + IsKey, + AllowDBNull, + ProviderType, + UdtSchema, + UdtType, + XmlSchemaCollectionDatabase, + XmlSchemaCollectionOwningSchema, + XmlSchemaCollectionName); /// - /// See + /// See /// /// /// The test stub is only for testing schema functionality and behaves as if it has no rows. @@ -2478,12 +2443,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// /// Never returns. /// - /// - public override object GetValue(int i) - { - throw new InvalidOperationException("No data."); - } - + /// + public override object GetValue(int i) => throw new InvalidOperationException("No data."); /// /// See @@ -2492,15 +2453,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence /// False. /// /// - public override bool Read() - { - return false; - } - - #endregion - + public override bool Read() => false; } - - #endregion } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentMapperTest.cs index 7c0ea0d642..5db6493858 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Mappers; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentTypeMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentTypeMapperTest.cs index 6666055ed7..923de28f13 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentTypeMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/ContentTypeMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -20,7 +23,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Name_Property() { - // Act string column = new ContentTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Name"); @@ -31,7 +33,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Thumbnail_Property() { - // Act string column = new ContentTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Thumbnail"); @@ -42,7 +43,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Description_Property() { - // Act string column = new ContentTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Description"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DataTypeMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DataTypeMapperTest.cs index 0725528254..7fdd2882c4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DataTypeMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DataTypeMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -21,7 +24,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Key_Property() { - // Act string column = new DataTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Key"); @@ -32,7 +34,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_DatabaseType_Property() { - // Act string column = new DataTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("DatabaseType"); @@ -43,7 +44,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_PropertyEditorAlias_Property() { - // Act string column = new DataTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("EditorAlias"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryMapperTest.cs index a27fc89dac..f31110f30a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -20,7 +23,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Key_Property() { - // Act string column = new DictionaryMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Key"); @@ -31,7 +33,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_ItemKey_Property() { - // Act string column = new DictionaryMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("ItemKey"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryTranslationMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryTranslationMapperTest.cs index c1fbc6ad61..ebe57e7746 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryTranslationMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/DictionaryTranslationMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -10,7 +13,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Key_Property() { - // Act string column = new DictionaryTranslationMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Key"); @@ -21,7 +23,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Language_Property() { - // Act string column = new DictionaryTranslationMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Language"); @@ -32,7 +33,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Value_Property() { - // Act string column = new DictionaryTranslationMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Value"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/LanguageMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/LanguageMapperTest.cs index 382d8c6826..20a446ad70 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/LanguageMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/LanguageMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -10,7 +13,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_Id_Property() { - // Act string column = new LanguageMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("Id"); @@ -21,7 +23,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_IsoCode_Property() { - // Act string column = new LanguageMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("IsoCode"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/MediaMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/MediaMapperTest.cs index cc7d528c3c..a94e4cade3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/MediaMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/MediaMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapperTest.cs index f5fa18e4ee..40c74f25a5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyGroupMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapperTest.cs index ba2d40feab..72223e952f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/PropertyTypeMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationMapperTest.cs index 4f1b4a7944..add7cd8b4b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapperTest.cs index e17d6905ac..310b12c1cf 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Mappers/RelationTypeMapperTest.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Persistence.Mappers; using Umbraco.Tests.TestHelpers; @@ -30,7 +33,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_ChildObjectType_Property() { - // Act string column = new RelationTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("ChildObjectType"); @@ -41,7 +43,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Mappers [Test] public void Can_Map_IsBidirectional_Property() { - // Act string column = new RelationTypeMapper(TestHelper.GetMockSqlContext(), TestHelper.CreateMaps()).Map("IsBidirectional"); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs index 9e9b148bb6..4ad1b89bf6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlExtensionsTests.cs @@ -1,9 +1,12 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using NPoco; using NUnit.Framework; -using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence.Querying; using Umbraco.Tests.TestHelpers; using static Umbraco.Core.Persistence.SqlExtensionsStatics; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void WhereTest() { - var sql = new Sql(SqlContext) + Sql sql = new Sql(SqlContext) .Select("*") .From() .Where(x => x.LanguageId == null); @@ -45,7 +48,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests // but the above comparison fails if @0 is null // what we want is something similar to: - sql = new Sql(SqlContext) .Select("*") .From() @@ -56,15 +58,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests // 'course it would be nicer if '==' could do it // see note in ExpressionVisitorBase for SqlNullableEquals - //sql = new Sql(SqlContext) + // sql = new Sql(SqlContext) // .Select("*") // .From() // .Where(x => x.LanguageId.SqlNullableEquals(nid)); - //Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@0 is not null) AND ([umbracoPropertyData].[languageId] = @0))))", sql.SQL, sql.SQL); + // Assert.AreEqual("SELECT *\nFROM [umbracoPropertyData]\nWHERE ((((@0 is null) AND ([umbracoPropertyData].[languageId] is null)) OR ((@0 is not null) AND ([umbracoPropertyData].[languageId] = @0))))", sql.SQL, sql.SQL); // but, the expression above fails with SQL CE, 'specified argument for the function is not valid' in 'isnull' function // so... compare with fallback values - sql = new Sql(SqlContext) .Select("*") .From() @@ -89,7 +90,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void WhereInValueFieldTest() { - var sql = new Sql(SqlContext) + Sql sql = new Sql(SqlContext) .Select("*") .From() .WhereIn(x => x.NodeId, new[] { 1, 2, 3 }); @@ -101,8 +102,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests { // this test used to fail because x => x.Text was evaluated as a lambda // and returned "[umbracoNode].[text] = @0"... had to fix WhereIn. - - var sql = new Sql(SqlContext) + Sql sql = new Sql(SqlContext) .Select("*") .From() .WhereIn(x => x.Text, new[] { "a", "b", "c" }); @@ -113,7 +113,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests public void SelectTests() { // select the whole DTO - var sql = Sql() + Sql sql = Sql() .Select() .From(); Assert.AreEqual("SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] FROM [dto1]", sql.SQL.NoCrLf()); @@ -135,7 +135,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests .Select(r => r.Select(x => x.Dto2)) .From() .InnerJoin().On(left => left.Id, right => right.Dto1Id); - Assert.AreEqual(@"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] + Assert.AreEqual( + @"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] , [dto2].[id] AS [Dto2__Id], [dto2].[dto1id] AS [Dto2__Dto1Id], [dto2].[name] AS [Dto2__Name] FROM [dto1] INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf(), sql.SQL); @@ -146,7 +147,8 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf(), .From() .InnerJoin().On(left => left.Id, right => right.Dto1Id) .InnerJoin().On(left => left.Id, right => right.Dto2Id); - Assert.AreEqual(@"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] + Assert.AreEqual( + @"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] , [dto2].[id] AS [Dto2__Id], [dto2].[dto1id] AS [Dto2__Dto1Id], [dto2].[name] AS [Dto2__Name] , [dto3].[id] AS [Dto2__Dto3__Id], [dto3].[dto2id] AS [Dto2__Dto3__Dto2Id], [dto3].[name] AS [Dto2__Dto3__Name] FROM [dto1] @@ -158,7 +160,8 @@ INNER JOIN [dto3] ON [dto2].[id] = [dto3].[dto2id]".NoCrLf(), sql.SQL.NoCrLf()); .Select(r => r.Select(x => x.Dto2s)) .From() .InnerJoin().On(left => left.Id, right => right.Dto1Id); - Assert.AreEqual(@"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] + Assert.AreEqual( + @"SELECT [dto1].[id] AS [Id], [dto1].[name] AS [Name], [dto1].[value] AS [Value] , [dto2].[id] AS [Dto2s__Id], [dto2].[dto1id] AS [Dto2s__Dto1Id], [dto2].[name] AS [Dto2s__Name] FROM [dto1] INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf()); @@ -189,7 +192,7 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf()); [Test] public void UpdateTests() { - var sql = Sql() + Sql sql = Sql() .Update(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker")) .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias"); } @@ -201,12 +204,16 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf()); { [Column("id")] public int Id { get; set; } + [Column("name")] public string Name { get; set; } + [Column("value")] public int Value { get; set; } + [Reference] public Dto2 Dto2 { get; set; } + [Reference] public List Dto2s { get; set; } } @@ -218,10 +225,13 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf()); { [Column("id")] public int Id { get; set; } + [Column("dto1id")] public int Dto1Id { get; set; } + [Column("name")] public string Name { get; set; } + [Reference] public Dto3 Dto3 { get; set; } } @@ -233,8 +243,10 @@ INNER JOIN [dto2] ON [dto1].[id] = [dto2].[dto1id]".NoCrLf(), sql.SQL.NoCrLf()); { [Column("id")] public int Id { get; set; } + [Column("dto2id")] public int Dto2Id { get; set; } + [Column("name")] public string Name { get; set; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs index 0367571d61..54c9814909 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTemplateTests.cs @@ -1,12 +1,14 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Moq; using NPoco; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Mappers; -using Umbraco.Core; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests { @@ -23,14 +25,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests // want to cache as a (static) template for ever, and ever - note // that using a MemoryCache would allow us to set a size limit, or // something equivalent, to reduce risk of memory explosion - var sql = sqlTemplates.Get("xxx", s => s + Sql sql = sqlTemplates.Get("xxx", s => s .SelectAll() .From("zbThing1") .Where("id=@id", new { id = SqlTemplate.Arg("id") })).Sql(new { id = 1 }); - var sql2 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(1); + Sql sql2 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(1); - var sql3 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(new { id = 1 }); + Sql sql3 = sqlTemplates.Get("xxx", x => throw new InvalidOperationException("Should be cached.")).Sql(new { id = 1 }); } [Test] @@ -44,10 +46,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests const string sqlBase = "SELECT [zbThing1].[id] AS [Id], [zbThing1].[name] AS [Name] FROM [zbThing1] WHERE "; - var template = sqlTemplates.Get("sql1", s => s.Select().From() + SqlTemplate template = sqlTemplates.Get("sql1", s => s.Select().From() .Where(x => x.Name == SqlTemplate.Arg("value"))); - var sql = template.Sql("foo"); + Sql sql = template.Sql("foo"); Assert.AreEqual(sqlBase + "(([zbThing1].[name] = @0))", sql.SQL.NoCrLf()); Assert.AreEqual(1, sql.Arguments.Length); Assert.AreEqual("foo", sql.Arguments[0]); @@ -90,7 +92,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests // but we cannot name them, because the arg name is the value of "i" // so we have to explicitely create the argument - template = sqlTemplates.Get("sql4", s => s.Select().From() .Where(x => x.Id == SqlTemplate.Arg("i"))); @@ -105,7 +106,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests Assert.AreEqual(123, sql.Arguments[0]); // and thanks to a patched visitor, this now works - sql = template.Sql(new { i = "foo" }); Assert.AreEqual(sqlBase + "(([zbThing1].[id] = @0))", sql.SQL.NoCrLf()); Assert.AreEqual(1, sql.Arguments.Length); @@ -120,7 +120,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests Assert.Throws(() => template.Sql(new { i = 123, j = 456 })); // now with more arguments - template = sqlTemplates.Get("sql4a", s => s.Select().From() .Where(x => x.Id == SqlTemplate.Arg("i") && x.Name == SqlTemplate.Arg("name"))); sql = template.Sql(0, 1); @@ -139,7 +138,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests Assert.AreEqual(1, sql.Arguments[1]); // works, magic - template = sqlTemplates.Get("sql5", s => s.Select().From() .WhereIn(x => x.Id, SqlTemplate.ArgIn("i"))); @@ -178,20 +176,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests // idea to just add the WhereIn to a templated, immutable SQL template // more fun... - template = sqlTemplates.Get("sql6", s => s.Select().From() + // do NOT do this, this is NOT a visited expression - //.Append(" AND whatever=@0", SqlTemplate.Arg("j")) + //// .Append(" AND whatever=@0", SqlTemplate.Arg("j")) // does not work anymore - due to proper TemplateArg //// instead, directly name the argument - //.Append("AND whatever=@0", "j") - //.Append("AND whatever=@0", "k") + ////.Append("AND whatever=@0", "j") + ////.Append("AND whatever=@0", "k") // instead, explicitely create the argument .Append("AND whatever=@0", SqlTemplate.Arg("j")) - .Append("AND whatever=@0", SqlTemplate.Arg("k")) - ); + .Append("AND whatever=@0", SqlTemplate.Arg("k"))); sql = template.Sql(new { j = new[] { 1, 2, 3 }, k = "oops" }); Assert.AreEqual(sqlBase.TrimEnd("WHERE ") + "AND whatever=@0,@1,@2 AND whatever=@3", sql.SQL.NoCrLf()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs index 80408bb211..a3fd28d952 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/NPocoTests/NPocoSqlTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using NPoco; using NUnit.Framework; @@ -7,7 +10,6 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests { @@ -18,7 +20,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests public void Where_Clause_With_Starts_With_Additional_Parameters() { var content = new NodeDto() { NodeId = 123, Path = "-1,123" }; - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); @@ -29,8 +31,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_Starts_With_By_Variable() { - var content = new NodeDto() {NodeId = 123, Path = "-1,123"}; - var sql = Sql().SelectAll().From() + var content = new NodeDto() { NodeId = 123, Path = "-1,123" }; + Sql sql = Sql().SelectAll().From() .Where(x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[path]) LIKE upper(@0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); @@ -43,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests public void Where_Clause_With_Not_Starts_With() { const int level = 1; - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Level == level && !x.Path.StartsWith("-20")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); @@ -56,7 +58,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests public void Where_Clause_With_EqualsFalse_Starts_With() { const int level = 1; - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Level == level && x.Path.StartsWith("-20") == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[level] = @0) AND NOT (upper([umbracoNode].[path]) LIKE upper(@1))))", sql.SQL.Replace("\n", " ")); @@ -68,7 +70,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_Equals_Clause() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Text.Equals("Hello@world.com")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " ")); @@ -79,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_False_Boolean() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Trashed == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); @@ -90,7 +92,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_EqualsFalse_Boolean() { - var sql = Sql().SelectAll().From().Where(x => x.Trashed == false); + Sql sql = Sql().SelectAll().From().Where(x => x.Trashed == false); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " ")); Assert.AreEqual(1, sql.Arguments.Length); @@ -100,7 +102,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_Boolean() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Trashed); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " ")); @@ -111,7 +113,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_ToUpper() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Text.ToUpper() == "hello".ToUpper()); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((upper([umbracoNode].[text]) = upper(@0)))", sql.SQL.Replace("\n", " ")); @@ -122,7 +124,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_ToString() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Text == 1.ToString()); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[text] = @0))", sql.SQL.Replace("\n", " ")); @@ -133,7 +135,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_With_Wildcard() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Text.StartsWith("D")); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) LIKE upper(@0))", sql.SQL.Replace("\n", " ")); @@ -144,7 +146,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_Single_Constant() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.NodeId == 2); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[id] = @0))", sql.SQL.Replace("\n", " ")); @@ -155,7 +157,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_And_Constant() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.NodeId != 2 && x.NodeId != 3); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[id] <> @0) AND ([umbracoNode].[id] <> @1)))", sql.SQL.Replace("\n", " ")); @@ -167,7 +169,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Clause_Or_Constant() { - var sql = Sql().SelectAll().From() + Sql sql = Sql().SelectAll().From() .Where(x => x.Text == "hello" || x.NodeId == 3); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ((([umbracoNode].[text] = @0) OR ([umbracoNode].[id] = @1)))", sql.SQL.Replace("\n", " ")); @@ -179,21 +181,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Where_Null() { - var sql = Sql().SelectAll().From().WhereNull(x => x.NodeId); + Sql sql = Sql().SelectAll().From().WhereNull(x => x.NodeId); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[id] IS NULL))", sql.SQL.Replace("\n", " ")); } [Test] public void Where_Not_Null() { - var sql = Sql().SelectAll().From().WhereNotNull(x => x.NodeId); + Sql sql = Sql().SelectAll().From().WhereNotNull(x => x.NodeId); Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (([umbracoNode].[id] IS NOT NULL))", sql.SQL.Replace("\n", " ")); } [Test] public void Where_Any() { - var sql = Sql().SelectAll().From().WhereAny( + Sql sql = Sql().SelectAll().From().WhereAny( s => s.Where(x => x.NodeId == 1), s => s.Where(x => x.NodeId == 2)); @@ -203,10 +205,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_Select_From_With_Type() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll().From($"[{Constants.DatabaseSchema.Tables.Content}]"); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll().From(); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -217,13 +219,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_InnerJoin_With_Types() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll() .From($"[{Constants.DatabaseSchema.Tables.DocumentVersion}]") .InnerJoin($"[{Constants.DatabaseSchema.Tables.ContentVersion}]") .On($"[{Constants.DatabaseSchema.Tables.DocumentVersion}].[id] = [{Constants.DatabaseSchema.Tables.ContentVersion}].[id]"); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll().From() .InnerJoin() .On(left => left.Id, right => right.Id); @@ -236,10 +238,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_OrderBy_With_Type() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll().From($"[{Constants.DatabaseSchema.Tables.Content}]").OrderBy($"([{Constants.DatabaseSchema.Tables.Content}].[contentTypeId])"); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll().From().OrderBy(x => x.ContentTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -250,10 +252,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_GroupBy_With_Type() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll().From($"[{Constants.DatabaseSchema.Tables.Content}]").GroupBy($"[{Constants.DatabaseSchema.Tables.Content}].[contentTypeId]"); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll().From().GroupBy(x => x.ContentTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -264,10 +266,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_Use_Where_Predicate() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll().From($"[{Constants.DatabaseSchema.Tables.Content}]").Where($"([{Constants.DatabaseSchema.Tables.Content}].[nodeId] = @0)", 1045); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll().From().Where(x => x.NodeId == 1045); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); @@ -278,13 +280,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests [Test] public void Can_Use_Where_And_Predicate() { - var expected = Sql(); + Sql expected = Sql(); expected.SelectAll() .From($"[{Constants.DatabaseSchema.Tables.Content}]") .Where($"([{Constants.DatabaseSchema.Tables.Content}].[nodeId] = @0)", 1045) .Where($"([{Constants.DatabaseSchema.Tables.Content}].[contentTypeId] = @0)", 1050); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll() .From() .Where(x => x.NodeId == 1045) @@ -300,7 +302,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.NPocoTests { var sessionId = Guid.NewGuid(); - var sql = Sql() + Sql sql = Sql() .SelectAll() .From() .Where(x => x.SessionId == sessionId); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs index 66253de575..dfd8e5ed12 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ContentTypeRepositorySqlClausesTest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using NUnit.Framework; using Umbraco.Core; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs index 708e7cac70..b6a389d83d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/DataTypeDefinitionRepositorySqlClausesTest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using NPoco; using NUnit.Framework; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Verify_Base_Clause() { - var NodeObjectTypeId = Constants.ObjectTypes.DataType; + var nodeObjectTypeId = Constants.ObjectTypes.DataType; var expected = new Sql(); expected.Select("*") @@ -28,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying .From() .InnerJoin() .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == nodeObjectTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs index 5d90d85f81..eed6fcdf59 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/ExpressionTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; @@ -25,8 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Equals_Claus_With_Two_Entity_Values() { - - var dataType = new DataType(new VoidEditor(NullLoggerFactory.Instance, Mock.Of(), Mock.Of(),Mock.Of(), Mock.Of(), new JsonNetSerializer()), new ConfigurationEditorJsonSerializer()) + var dataType = new DataType(new VoidEditor(NullLoggerFactory.Instance, Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new JsonNetSerializer()), new ConfigurationEditorJsonSerializer()) { Id = 12345 }; @@ -43,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Query_With_Content_Type_Alias() { - //Arrange + // Arrange Expression> predicate = content => content.ContentType.Alias == "Test"; var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); var result = modelToSqlExpressionHelper.Visit(predicate); @@ -57,7 +59,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Query_With_Content_Type_Aliases_IEnumerable() { - //Arrange - Contains is IEnumerable.Contains extension method + // Arrange - Contains is IEnumerable.Contains extension method var aliases = new[] { "Test1", "Test2" }; Expression> predicate = content => aliases.Contains(content.ContentType.Alias); var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); @@ -73,7 +75,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Query_With_Content_Type_Aliases_List() { - //Arrange - Contains is List.Contains instance method + // Arrange - Contains is List.Contains instance method var aliases = new System.Collections.Generic.List { "Test1", "Test2" }; Expression> predicate = content => aliases.Contains(content.ContentType.Alias); var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); @@ -89,9 +91,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result() { - //Arrange + // Arrange - //use a single cached expression for multiple expressions and ensure the correct output + // use a single cached expression for multiple expressions and ensure the correct output // is done for both of them. var cachedExpression = new CachedExpression(); @@ -108,13 +110,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression); Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2); Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]); - } [Test] public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result() { - //Arrange + // Arrange Expression> predicate = content => content.Path.StartsWith("-1"); var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); var result = modelToSqlExpressionHelper.Visit(predicate); @@ -126,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Verify_ParentId_StartsWith_Predicate_In_Same_Result() { - //Arrange + // Arrange Expression> predicate = content => content.ParentId == -1; var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor(SqlContext.SqlSyntax, Mappers); var result = modelToSqlExpressionHelper.Visit(predicate); @@ -195,10 +196,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying Assert.AreEqual("blah@blah.com", modelToSqlExpressionHelper.GetSqlParameters()[2]); } - private string GetSomeValue(string s) - { - return "xx" + s + "xx"; - } + private string GetSomeValue(string s) => "xx" + s + "xx"; private class Foo { @@ -242,12 +240,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying // and how would we get the comma there? we'd have to parse .StartsWith(group.Name + ',') // which is going to be quite complicated => no - //// how can we do user.Login.StartsWith(other.Value) ?? to use in WHERE or JOIN.ON clauses ?? - //Expression> predicate2 = (user, group) => user.Login.StartsWith(group.Name); - //var modelToSqlExpressionHelper2 = new PocoToSqlExpressionVisitor(SqlContext, null, null); - //var result2 = modelToSqlExpressionHelper2.Visit(predicate2); // fails, for now + // how can we do user.Login.StartsWith(other.Value) ?? to use in WHERE or JOIN.ON clauses ?? + //// Expression> predicate2 = (user, group) => user.Login.StartsWith(group.Name); + //// var modelToSqlExpressionHelper2 = new PocoToSqlExpressionVisitor(SqlContext, null, null); + //// var result2 = modelToSqlExpressionHelper2.Visit(predicate2); // fails, for now - //Console.WriteLine(result2); + //// Console.WriteLine(result2); Expression> predicate3 = (user, group) => SqlExtensionsStatics.SqlText(user.Login, group.Name, (n1, n2) => $"({n1} LIKE concat({n2}, ',%'))"); var modelToSqlExpressionHelper3 = new PocoToSqlExpressionVisitor(SqlContext, null, null); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaRepositorySqlClausesTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaRepositorySqlClausesTest.cs index 8c26f6a06b..09958a73b0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaRepositorySqlClausesTest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using NPoco; using NUnit.Framework; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Verify_Base_Clause() { - var nodeObjectTypeId = Constants.ObjectTypes.Media; + Guid nodeObjectTypeId = Constants.ObjectTypes.Media; var expected = new Sql(); expected.Select("*") @@ -24,7 +27,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying .InnerJoin("[umbracoNode]").On($"[{Constants.DatabaseSchema.Tables.Content}].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c")); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll() .From() .InnerJoin() diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs index f28540454d..4187ecf1df 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/MediaTypeRepositorySqlClausesTest.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Diagnostics; using NPoco; using NUnit.Framework; @@ -15,7 +18,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying [Test] public void Can_Verify_Base_Clause() { - var NodeObjectTypeId = Constants.ObjectTypes.MediaType; + Guid nodeObjectTypeId = Constants.ObjectTypes.MediaType; var expected = new Sql(); expected.Select("*") @@ -23,12 +26,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying .InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]") .Where("([umbracoNode].[nodeObjectType] = @0)", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e")); - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll() .From() .InnerJoin() .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .Where(x => x.NodeObjectType == nodeObjectTypeId); Assert.That(sql.SQL, Is.EqualTo(expected.SQL)); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/QueryBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/QueryBuilderTests.cs index f4b8c2b250..00500c9094 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/QueryBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Persistence/Querying/QueryBuilderTests.cs @@ -1,6 +1,9 @@ -using System.Diagnostics; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Diagnostics; +using NPoco; using NUnit.Framework; -using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; @@ -16,16 +19,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying public void Can_Build_StartsWith_Query_For_IContent() { // Arrange - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlContext).Where(x => x.Path.StartsWith("-1")); + IQuery query = new Query(SqlContext).Where(x => x.Path.StartsWith("-1")); // Act var translator = new SqlTranslator(sql, query); - var result = translator.Translate(); - var strResult = result.SQL; + Sql result = translator.Translate(); + string strResult = result.SQL; string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) LIKE upper(@0))"; @@ -43,16 +46,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying public void Can_Build_ParentId_Query_For_IContent() { // Arrange - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlContext).Where(x => x.ParentId == -1); + IQuery query = new Query(SqlContext).Where(x => x.ParentId == -1); // Act var translator = new SqlTranslator(sql, query); - var result = translator.Translate(); - var strResult = result.SQL; + Sql result = translator.Translate(); + string strResult = result.SQL; string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (([umbracoNode].[parentId] = @0))"; @@ -70,16 +73,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying public void Can_Build_ContentTypeAlias_Query_For_IContentType() { // Arrange - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll(); sql.From("umbracoNode"); - var query = new Query(SqlContext).Where(x => x.Alias == "umbTextpage"); + IQuery query = new Query(SqlContext).Where(x => x.Alias == "umbTextpage"); // Act var translator = new SqlTranslator(sql, query); - var result = translator.Translate(); - var strResult = result.SQL; + Sql result = translator.Translate(); + string strResult = result.SQL; string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (([cmsContentType].[alias] = @0))"; @@ -98,14 +101,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Persistence.Querying const string path = "-1,1046,1076,1089"; const int id = 1046; - var sql = Sql(); + Sql sql = Sql(); sql.SelectAll() .From(); // the actual SELECT really does not matter - var query = SqlContext.Query().Where(x => x.Path.StartsWith(path) && x.Id != id && x.Published && x.Trashed == false); + IQuery query = SqlContext.Query().Where(x => x.Path.StartsWith(path) && x.Id != id && x.Published && x.Trashed == false); var translator = new SqlTranslator(sql, query); - var result = translator.Translate(); + Sql result = translator.Translate(); Assert.AreEqual("-1,1046,1076,1089%", result.Arguments[0]); Assert.AreEqual(1046, result.Arguments[1]); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonNetSerializerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonNetSerializerTests.cs index 40a932181c..cbf3e7daaa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonNetSerializerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonNetSerializerTests.cs @@ -1,4 +1,7 @@ -using Newtonsoft.Json; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Serialization; @@ -49,7 +52,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Serialization [Test] public void DeserializeSubset__Subset_value_as_object() { - var expected = new MyStruct() { Key = "Test" @@ -65,7 +67,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Serialization [Test] public void DeserializeSubset__Subset_value_as_array() { - var expected = new []{"test"}; + var expected = new[] { "test" }; var key = "text"; var full = $"{{\"{key}\": {JsonConvert.SerializeObject(expected)}"; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs index 6eebf5d237..63331762b6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/AmbiguousEventTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Reflection; using System.Text; using NUnit.Framework; @@ -23,7 +26,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services string TypeName(Type type) { if (!type.IsGenericType) + { return type.Name; + } + var sb = new StringBuilder(); TypeNameSb(type, sb); return sb.ToString(); @@ -36,24 +42,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services name = pos > 0 ? name.Substring(0, pos) : name; sb.Append(name); if (!type.IsGenericType) + { return; + } + sb.Append("<"); var first = true; foreach (var arg in type.GetGenericArguments()) { - if (first) first = false; - else sb.Append(", "); + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + TypeNameSb(arg, sb); } + sb.Append(">"); } foreach (var e in events) { // only continue if this is a TypedEventHandler - if (!e.EventHandlerType.IsGenericType) continue; + if (!e.EventHandlerType.IsGenericType) + { + continue; + } + var typeDef = e.EventHandlerType.GetGenericTypeDefinition(); - if (typedEventHandler != typeDef) continue; + if (typedEventHandler != typeDef) + { + continue; + } // get the event args type var eventArgsType = e.EventHandlerType.GenericTypeArguments[1]; @@ -62,7 +86,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services // exclude -ing (eg Saving) events, we don't deal with them in EventDefinitionBase (they always trigger) var found = EventNameExtractor.FindEvents(serviceType, eventArgsType, EventNameExtractor.MatchIngNames); - if (found.Length == 1) continue; + if (found.Length == 1) + { + continue; + } if (found.Length == 0) { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs index dc857d2d5d..6bfc252574 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/LocalizedTextServiceTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -13,7 +16,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [TestFixture] public class LocalizedTextServiceTests { - private static ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; + private static readonly ILoggerFactory s_loggerFactory = NullLoggerFactory.Instance; + [Test] public void Using_Dictionary_Gets_All_Stored_Values() { @@ -27,22 +31,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea1", new Dictionary { - {"testKey1", "testValue1"}, - {"testKey2", "testValue2"} + { "testKey1", "testValue1" }, + { "testKey2", "testValue2" } } }, { "testArea2", new Dictionary { - {"blah1", "blahValue1"}, - {"blah2", "blahValue2"} + { "blah1", "blahValue1" }, + { "blah2", "blahValue2" } } }, } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.GetAllStoredValues(culture); + IDictionary result = txtService.GetAllStoredValues(culture); Assert.AreEqual(4, result.Count); Assert.AreEqual("testArea1/testKey1", result.ElementAt(0).Key); @@ -53,18 +57,19 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services Assert.AreEqual("testValue2", result["testArea1/testKey2"]); Assert.AreEqual("blahValue1", result["testArea2/blah1"]); Assert.AreEqual("blahValue2", result["testArea2/blah2"]); - } [Test] public void Using_XDocument_Gets_All_Stored_Values() { var culture = CultureInfo.GetCultureInfo("en-US"); - var txtService = new LocalizedTextService(new Dictionary> + var txtService = new LocalizedTextService( + new Dictionary> { { culture, new Lazy(() => new XDocument( - new XElement("language", + new XElement( + "language", new XElement("area", new XAttribute("alias", "testArea1"), new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"), new XElement("key", new XAttribute("alias", "testKey2"), "testValue2")), @@ -72,9 +77,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "blah1"), "blahValue1"), new XElement("key", new XAttribute("alias", "blah2"), "blahValue2"))))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.GetAllStoredValues(culture); + IDictionary result = txtService.GetAllStoredValues(culture); Assert.AreEqual(4, result.Count()); Assert.AreEqual("testArea1/testKey1", result.ElementAt(0).Key); @@ -85,10 +90,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services Assert.AreEqual("testValue2", result["testArea1/testKey2"]); Assert.AreEqual("blahValue1", result["testArea2/blah1"]); Assert.AreEqual("blahValue2", result["testArea2/blah2"]); - } - [Test] public void Using_XDocument_Gets_All_Stored_Values_With_Duplicates() { @@ -98,17 +101,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { { culture, new Lazy(() => new XDocument( - new XElement("language", + new XElement( + "language", new XElement("area", new XAttribute("alias", "testArea1"), new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"), new XElement("key", new XAttribute("alias", "testKey1"), "testValue1"))))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.GetAllStoredValues(culture); + IDictionary result = txtService.GetAllStoredValues(culture); Assert.AreEqual(1, result.Count()); - } [Test] @@ -124,14 +127,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "testValue"} + { "testKey", "testValue" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testArea/testKey", culture); + string result = txtService.Localize("testArea/testKey", culture); Assert.AreEqual("testValue", result); } @@ -149,14 +152,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "testValue"} + { "testKey", "testValue" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testKey", culture); + string result = txtService.Localize("testKey", culture); Assert.AreEqual("testValue", result); } @@ -174,16 +177,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "testValue"} + { "testKey", "testValue" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testArea/doNotFind", culture); + string result = txtService.Localize("testArea/doNotFind", culture); - //NOTE: Based on how legacy works, the default text does not contain the area, just the key + // NOTE: Based on how legacy works, the default text does not contain the area, just the key Assert.AreEqual("[doNotFind]", result); } @@ -200,14 +203,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "testValue"} + { "testKey", "testValue" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("doNotFind", culture); + string result = txtService.Localize("doNotFind", culture); Assert.AreEqual("[doNotFind]", result); } @@ -225,14 +228,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "Hello %0%, you are such a %1% %2%"} + { "testKey", "Hello %0%, you are such a %1% %2%" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testKey", culture, + string result = txtService.Localize( + "testKey", + culture, new Dictionary { { "0", "world" }, { "1", "great" }, { "2", "planet" } }); Assert.AreEqual("Hello world, you are such a great planet", result); @@ -251,9 +256,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "testKey"), "testValue")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testArea/testKey", culture); + string result = txtService.Localize("testArea/testKey", culture); Assert.AreEqual("testValue", result); } @@ -271,9 +276,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "testKey"), "testValue")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testKey", culture); + string result = txtService.Localize("testKey", culture); Assert.AreEqual("testValue", result); } @@ -291,11 +296,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "testKey"), "testValue")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testArea/doNotFind", culture); + string result = txtService.Localize("testArea/doNotFind", culture); - //NOTE: Based on how legacy works, the default text does not contain the area, just the key + // NOTE: Based on how legacy works, the default text does not contain the area, just the key Assert.AreEqual("[doNotFind]", result); } @@ -312,9 +317,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "testKey"), "testValue")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("doNotFind", culture); + string result = txtService.Localize("doNotFind", culture); Assert.AreEqual("[doNotFind]", result); } @@ -332,9 +337,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services new XElement("key", new XAttribute("alias", "testKey"), "Hello %0%, you are such a %1% %2%")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); - var result = txtService.Localize("testKey", culture, + string result = txtService.Localize("testKey", culture, new Dictionary { { "0", "world" }, { "1", "great" }, { "2", "planet" } }); Assert.AreEqual("Hello world, you are such a great planet", result); @@ -353,12 +358,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { "testArea", new Dictionary { - {"testKey", "testValue"} + { "testKey", "testValue" } } } } } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); Assert.AreEqual("[testKey]", txtService.Localize("testArea/testKey", CultureInfo.GetCultureInfo("en-AU"))); } @@ -373,10 +378,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services { culture, new Lazy(() => new XDocument( new XElement("area", new XAttribute("alias", "testArea"), - new XElement("key", new XAttribute("alias", "testKey"), - "testValue")))) + new XElement("key", new XAttribute("alias", "testKey"), "testValue")))) } - }, _loggerFactory.CreateLogger()); + }, s_loggerFactory.CreateLogger()); Assert.AreEqual("[testKey]", txtService.Localize("testArea/testKey", CultureInfo.GetCultureInfo("en-AU"))); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs index abc1bb0c9d..2f26b041c6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs @@ -1,4 +1,7 @@ -using System.Threading; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -23,15 +26,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services textService.Setup(x => x.Localize(It.IsAny(), Thread.CurrentThread.CurrentCulture, null)).Returns("Localized text"); var dataTypeService = new Mock(); - var dataType = Mock.Of( - x => x.Configuration == (object)string.Empty //irrelevant but needs a value + IDataType dataType = Mock.Of( + x => x.Configuration == (object)string.Empty // irrelevant but needs a value && x.DatabaseType == ValueStorageType.Nvarchar && x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox); dataTypeService.Setup(x => x.GetDataType(It.IsAny())).Returns(() => dataType); dt = dataType; - //new data editor that returns a TextOnlyValueEditor which will do the validation for the properties - var dataEditor = Mock.Of( + // new data editor that returns a TextOnlyValueEditor which will do the validation for the properties + IDataEditor dataEditor = Mock.Of( x => x.Type == EditorType.PropertyValue && x.Alias == Constants.PropertyEditors.Aliases.TextBox); Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny())) @@ -45,22 +48,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [Test] public void Validate_Invariant_Properties_On_Variant_Default_Culture() { - MockObjects(out var validationService, out var dataType); + MockObjects(out PropertyValidationService validationService, out IDataType dataType); - var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture}); + var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); p1.SetValue("Hello", "en-US"); var p2 = new Property(new PropertyType(ShortStringHelper, dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); p2.SetValue("Hello", null); var p3 = new Property(new PropertyType(ShortStringHelper, dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); - p3.SetValue(null, "en-US"); //invalid + p3.SetValue(null, "en-US"); // invalid var p4 = new Property(new PropertyType(ShortStringHelper, dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); - p4.SetValue(null, null); //invalid + p4.SetValue(null, null); // invalid - var content = Mock.Of( - x => x.Published == true //set to published, the default culture will validate invariant anyways + IContent content = Mock.Of( + x => x.Published == true // set to published, the default culture will validate invariant anyways && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); - var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", true)); + bool result = validationService.IsPropertyDataValid(content, out IProperty[] invalid, CultureImpact.Explicit("en-US", true)); Assert.IsFalse(result); Assert.AreEqual(2, invalid.Length); @@ -69,22 +72,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [Test] public void Validate_Invariant_Properties_On_Variant_Non_Default_Culture() { - MockObjects(out var validationService, out var dataType); + MockObjects(out PropertyValidationService validationService, out IDataType dataType); var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); p1.SetValue("Hello", "en-US"); var p2 = new Property(new PropertyType(ShortStringHelper, dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); p2.SetValue("Hello", null); var p3 = new Property(new PropertyType(ShortStringHelper, dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); - p3.SetValue(null, "en-US"); //invalid + p3.SetValue(null, "en-US"); // invalid var p4 = new Property(new PropertyType(ShortStringHelper, dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); - p4.SetValue(null, null); //invalid + p4.SetValue(null, null); // invalid - var content = Mock.Of( - x => x.Published == false //set to not published, the non default culture will need to validate invariant too + IContent content = Mock.Of( + x => x.Published == false // set to not published, the non default culture will need to validate invariant too && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); - var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false)); + bool result = validationService.IsPropertyDataValid(content, out IProperty[] invalid, CultureImpact.Explicit("en-US", false)); Assert.IsFalse(result); Assert.AreEqual(2, invalid.Length); @@ -93,22 +96,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [Test] public void Validate_Variant_Properties_On_Variant() { - MockObjects(out var validationService, out var dataType); + MockObjects(out PropertyValidationService validationService, out IDataType dataType); var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); - p1.SetValue(null, "en-US"); //invalid + p1.SetValue(null, "en-US"); // invalid var p2 = new Property(new PropertyType(ShortStringHelper, dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); - p2.SetValue(null, null); //invalid + p2.SetValue(null, null); // invalid var p3 = new Property(new PropertyType(ShortStringHelper, dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); - p3.SetValue(null, "en-US"); //ignored because the impact isn't the default lang + the content is published + p3.SetValue(null, "en-US"); // ignored because the impact isn't the default lang + the content is published var p4 = new Property(new PropertyType(ShortStringHelper, dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); - p4.SetValue(null, null); //ignored because the impact isn't the default lang + the content is published + p4.SetValue(null, null); // ignored because the impact isn't the default lang + the content is published - var content = Mock.Of( - x => x.Published == true //set to published + IContent content = Mock.Of( + x => x.Published == true // set to published && x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); - var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Explicit("en-US", false)); + bool result = validationService.IsPropertyDataValid(content, out IProperty[] invalid, CultureImpact.Explicit("en-US", false)); Assert.IsFalse(result); Assert.AreEqual(2, invalid.Length); @@ -117,21 +120,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [Test] public void Validate_Invariant_Properties_On_Invariant() { - MockObjects(out var validationService, out var dataType); + MockObjects(out PropertyValidationService validationService, out IDataType dataType); var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); - p1.SetValue(null, "en-US"); //ignored since this is variant + p1.SetValue(null, "en-US"); // ignored since this is variant var p2 = new Property(new PropertyType(ShortStringHelper, dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); - p2.SetValue(null, null); //invalid + p2.SetValue(null, null); // invalid var p3 = new Property(new PropertyType(ShortStringHelper, dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); - p3.SetValue("Hello", "en-US"); //ignored since this is variant + p3.SetValue("Hello", "en-US"); // ignored since this is variant var p4 = new Property(new PropertyType(ShortStringHelper, dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); - p4.SetValue(null, null); //invalid + p4.SetValue(null, null); // invalid - var content = Mock.Of( + IContent content = Mock.Of( x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); - var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.Invariant); + bool result = validationService.IsPropertyDataValid(content, out IProperty[] invalid, CultureImpact.Invariant); Assert.IsFalse(result); Assert.AreEqual(2, invalid.Length); @@ -140,28 +143,27 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services [Test] public void Validate_Properties_On_All() { - MockObjects(out var validationService, out var dataType); + MockObjects(out PropertyValidationService validationService, out IDataType dataType); var p1 = new Property(new PropertyType(ShortStringHelper, dataType, "test1") { Mandatory = true, Variations = ContentVariation.Culture }); - p1.SetValue(null, "en-US"); //invalid + p1.SetValue(null, "en-US"); // invalid var p2 = new Property(new PropertyType(ShortStringHelper, dataType, "test2") { Mandatory = true, Variations = ContentVariation.Nothing }); - p2.SetValue(null, null); //invalid + p2.SetValue(null, null); // invalid var p3 = new Property(new PropertyType(ShortStringHelper, dataType, "test3") { Mandatory = true, Variations = ContentVariation.Culture }); - p3.SetValue(null, "en-US"); //invalid + p3.SetValue(null, "en-US"); // invalid var p4 = new Property(new PropertyType(ShortStringHelper, dataType, "test4") { Mandatory = true, Variations = ContentVariation.Nothing }); - p4.SetValue(null, null); //invalid + p4.SetValue(null, null); // invalid - var content = Mock.Of( + IContent content = Mock.Of( x => x.Properties == new PropertyCollection(new[] { p1, p2, p3, p4 })); - var result = validationService.IsPropertyDataValid(content, out var invalid, CultureImpact.All); + bool result = validationService.IsPropertyDataValid(content, out IProperty[] invalid, CultureImpact.All); Assert.IsFalse(result); Assert.AreEqual(4, invalid.Length); } - - //used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed + // used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed // in to create the Requried and Regex validators so we aren't using singletons private class CustomTextOnlyValueEditor : TextOnlyValueEditor { @@ -174,10 +176,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Services ILocalizedTextService textService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer) - : base(dataTypeService, localizationService, attribute, textService, shortStringHelper, jsonSerializer) - { - _textService = textService; - } + : base(dataTypeService, localizationService, attribute, textService, shortStringHelper, jsonSerializer) => _textService = textService; public override IValueRequiredValidator RequiredValidator => new RequiredValidator(_textService); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs index 80c8b057ab..af77d0e570 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/BuilderTests.cs @@ -1,8 +1,12 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; using NUnit.Framework; +using Semver; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.ModelsBuilder.Embedded; @@ -13,12 +17,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded [TestFixture] public class BuilderTests { - [Test] public void GenerateSimpleType() { - // Umbraco returns nice, pascal-cased names - + // Umbraco returns nice, pascal-cased names. var type1 = new TypeModel { Id = 1, @@ -35,21 +37,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded ModelClrType = typeof(string), }); - var types = new[] { type1 }; - - var code = new Dictionary - { - }; + TypeModel[] types = new[] { type1 }; var modelsBuilderConfig = new ModelsBuilderSettings(); var builder = new TextBuilder(modelsBuilderConfig, types); - var btypes = builder.TypeModels; var sb = new StringBuilder(); builder.Generate(sb, builder.GetModelsToGenerate().First()); var gen = sb.ToString(); - var version = ApiVersion.Current.Version; + SemVersion version = ApiVersion.Current.Version; var expected = @"//------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -110,8 +107,7 @@ namespace Umbraco.Web.PublishedModels [Test] public void GenerateSimpleType_Ambiguous_Issue() { - // Umbraco returns nice, pascal-cased names - + // Umbraco returns nice, pascal-cased names. var type1 = new TypeModel { Id = 1, @@ -138,25 +134,13 @@ namespace Umbraco.Web.PublishedModels ItemType = TypeModel.ItemTypes.Element, }; - var types = new[] { type1, type2 }; - - var code = new Dictionary - { - { "code", @" -namespace Umbraco.Web.PublishedModels -{ - public partial class Foo - { - } -} -" } - }; + TypeModel[] types = new[] { type1, type2 }; var modelsBuilderConfig = new ModelsBuilderSettings(); - var builder = new TextBuilder(modelsBuilderConfig, types); - var btypes = builder.TypeModels; - - builder.ModelsNamespace = "Umbraco.Web.PublishedModels"; + var builder = new TextBuilder(modelsBuilderConfig, types) + { + ModelsNamespace = "Umbraco.Web.PublishedModels" + }; var sb1 = new StringBuilder(); builder.Generate(sb1, builder.GetModelsToGenerate().Skip(1).First()); @@ -167,7 +151,7 @@ namespace Umbraco.Web.PublishedModels builder.Generate(sb, builder.GetModelsToGenerate().First()); var gen = sb.ToString(); - var version = ApiVersion.Current.Version; + SemVersion version = ApiVersion.Current.Version; var expected = @"//------------------------------------------------------------------------------ // // This code was generated by a tool. @@ -228,8 +212,6 @@ namespace Umbraco.Web.PublishedModels [Test] public void GenerateAmbiguous() { - // NOTE: since - var type1 = new TypeModel { Id = 1, @@ -258,20 +240,20 @@ namespace Umbraco.Web.PublishedModels ClrName = "Prop3", ModelClrType = typeof(global::Umbraco.Core.Exceptions.BootFailedException), }); - var types = new[] { type1 }; - - var code = new Dictionary - { - }; + TypeModel[] types = new[] { type1 }; var modelsBuilderConfig = new ModelsBuilderSettings(); - var builder = new TextBuilder(modelsBuilderConfig, types); - builder.ModelsNamespace = "Umbraco.ModelsBuilder.Models"; // forces conflict with Umbraco.ModelsBuilder.Umbraco - var btypes = builder.TypeModels; + var builder = new TextBuilder(modelsBuilderConfig, types) + { + ModelsNamespace = "Umbraco.ModelsBuilder.Models" // forces conflict with Umbraco.ModelsBuilder.Umbraco + }; var sb = new StringBuilder(); - foreach (var model in builder.GetModelsToGenerate()) + foreach (TypeModel model in builder.GetModelsToGenerate()) + { builder.Generate(sb, model); + } + var gen = sb.ToString(); Console.WriteLine(gen); @@ -289,9 +271,10 @@ namespace Umbraco.Web.PublishedModels { // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - - var builder = new TextBuilder(); - builder.ModelsNamespaceForTests = "ModelsNamespace"; + var builder = new TextBuilder + { + ModelsNamespaceForTests = "ModelsNamespace" + }; var sb = new StringBuilder(); builder.WriteClrType(sb, input); Assert.AreEqual(expected, sb.ToString()); @@ -305,7 +288,6 @@ namespace Umbraco.Web.PublishedModels { // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - var builder = new TextBuilder(); builder.Using.Add("Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder"); builder.ModelsNamespaceForTests = "ModelsNamespace"; @@ -325,15 +307,16 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); } [Test] public void WriteClrTypeAnother_WithoutUsing() { - var builder = new TextBuilder(); - builder.ModelsNamespaceForTests = "Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Models"; + var builder = new TextBuilder + { + ModelsNamespaceForTests = "Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Models" + }; var sb = new StringBuilder(); builder.WriteClrType(sb, typeof(StringBuilder)); Assert.AreEqual("global::System.Text.StringBuilder", sb.ToString()); @@ -351,7 +334,6 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); } @@ -367,7 +349,6 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::System.Text.ASCIIEncoding", sb.ToString()); } @@ -383,7 +364,6 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded.ASCIIEncoding", sb.ToString()); } @@ -399,7 +379,6 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded.ASCIIEncoding", sb.ToString()); } @@ -415,21 +394,28 @@ namespace Umbraco.Web.PublishedModels // note - these assertions differ from the original tests in MB because in the embedded version, the result of Builder.IsAmbiguousSymbol is always true // which means global:: syntax will be applied to most things - Assert.AreEqual("global::Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded.ASCIIEncoding.Nested", sb.ToString()); } - public class Class1 { } + public class Class1 + { + } } // make it public to be ambiguous (see above) public class ASCIIEncoding { // can we handle nested types? - public class Nested { } + public class Nested + { + } } - class BuilderTestsClass1 { } + public class BuilderTestsClass1 + { + } - public class System { } + public class System + { + } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/StringExtensions.cs b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/StringExtensions.cs index 2afcd4f471..45065bbd53 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/StringExtensions.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/StringExtensions.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded { public static class StringExtensions { - public static string ClearLf(this string s) - { - return s.Replace("\r", ""); - } + public static string ClearLf(this string s) => s.Replace("\r", string.Empty); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/UmbracoApplicationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/UmbracoApplicationTests.cs index 25d1d9f1f9..0c75318c87 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/UmbracoApplicationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.ModelsBuilder.Embedded/UmbracoApplicationTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using NUnit.Framework; using Umbraco.ModelsBuilder.Embedded; @@ -9,23 +12,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.ModelsBuilder.Embedded [TestFixture] public class UmbracoApplicationTests { - //[Test] - //public void Test() - //{ - // // start and terminate - // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) - // { } + //// [Test] + //// public void Test() + //// { + //// // start and terminate + //// using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + //// { } - // // start and terminate - // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) - // { } + //// // start and terminate + //// using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + //// { } - // // start, use and terminate - // using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) - // { - // var types = app.GetContentTypes(); - // } - //} + //// // start, use and terminate + //// using (var app = Application.GetApplication(TestOptions.ConnectionString, TestOptions.DatabaseProvider)) + //// { + //// var types = app.GetContentTypes(); + //// } + //// } [Test] public void ThrowsOnDuplicateAliases() diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs index 05157d961a..7a60cf473c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.NuCache/SnapDictionaryTests.cs @@ -1,4 +1,6 @@ - +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Linq; using System.Threading.Tasks; @@ -47,7 +49,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s = d.CreateSnapshot(); + SnapDictionary.Snapshot s = d.CreateSnapshot(); Assert.AreEqual(1, s.Gen); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -67,7 +69,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache public void MissingReturnsNull() { var d = new SnapDictionary(); - var s = d.CreateSnapshot(); + SnapDictionary.Snapshot s = d.CreateSnapshot(); Assert.IsNull(s.Get(1)); } @@ -80,13 +82,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache // gen 1 d.Set(1, "one"); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual("one", s1.Get(1)); // gen 2 d.Clear(1); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.IsNull(s2.Get(1)); Assert.AreEqual("one", s1.Get(1)); @@ -110,7 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -125,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -140,7 +142,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var tv = d.Test.GetValues(1); + SnapDictionary.TestHelper.GenVal[] tv = d.Test.GetValues(1); Assert.AreEqual(3, tv[0].Gen); Assert.AreEqual(2, tv[1].Gen); Assert.AreEqual(1, tv[2].Gen); @@ -185,7 +187,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache var d = new SnapDictionary(); d.Test.CollectAuto = false; - for (var i = 0; i < 32; i++) + for (int i = 0; i < 32; i++) { d.Set(i, i.ToString()); d.CreateSnapshot().Dispose(); @@ -201,8 +203,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(0, d.SnapCount); Assert.AreEqual(32, d.Count); - for (var i = 0; i < 32; i++) + for (int i = 0; i < 32; i++) + { d.Set(i, null); + } d.CreateSnapshot().Dispose(); @@ -237,7 +241,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -252,7 +256,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -269,7 +273,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var tv = d.Test.GetValues(1); + SnapDictionary.TestHelper.GenVal[] tv = d.Test.GetValues(1); Assert.AreEqual(3, tv[0].Gen); Assert.AreEqual(2, tv[1].Gen); Assert.AreEqual(1, tv[2].Gen); @@ -334,11 +338,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsTrue(d.Test.NextGen); await d.CollectAsync(); - var tv = d.Test.GetValues(1); + SnapDictionary.TestHelper.GenVal[] tv = d.Test.GetValues(1); Assert.AreEqual(1, tv.Length); Assert.AreEqual(1, tv[0].Gen); - var s = d.CreateSnapshot(); + SnapDictionary.Snapshot s = d.CreateSnapshot(); Assert.AreEqual("one", s.Get(1)); Assert.AreEqual(1, d.Test.LiveGen); @@ -391,7 +395,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache // collect liveGen GC.Collect(); - Assert.IsTrue(d.Test.GenObjs.TryPeek(out var genObj)); + Assert.IsTrue(d.Test.GenObjs.TryPeek(out global::Umbraco.Web.PublishedCache.NuCache.Snap.GenObj genObj)); genObj = null; // in Release mode, it works, but in Debug mode, the weak reference is still alive @@ -426,7 +430,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -438,7 +442,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -450,7 +454,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(3, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -486,7 +490,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -498,7 +502,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -510,7 +514,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(3, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); @@ -539,14 +543,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache d.Set(1, "one"); d.Set(2, "two"); - var s1 = d.CreateSnapshot(); - var v1 = s1.Get(1); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); + string v1 = s1.Get(1); Assert.AreEqual("one", v1); d.Set(1, "uno"); - var s2 = d.CreateSnapshot(); - var v2 = s2.Get(1); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); + string v2 = s2.Get(1); Assert.AreEqual("uno", v2); v1 = s1.Get(1); @@ -586,14 +590,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache d.Set(1, "one"); d.Set(2, "two"); - var s1 = d.CreateSnapshot(); - var v1 = s1.Get(1); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); + string v1 = s1.Get(1); Assert.AreEqual("one", v1); d.Clear(1); - var s2 = d.CreateSnapshot(); - var v2 = s2.Get(1); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); + string v2 = s2.Get(1); Assert.AreEqual(null, v2); v1 = s1.Get(1); @@ -638,7 +642,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache using (d.GetScopedWriteLock(GetScopeProvider())) { - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(0, s1.Gen); Assert.AreEqual(1, d.Test.LiveGen); @@ -646,7 +650,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsNull(s1.Get(1)); } - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(1, s2.Gen); Assert.AreEqual(1, d.Test.LiveGen); @@ -667,7 +671,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, s1.Gen); Assert.AreEqual(1, d.Test.LiveGen); @@ -682,7 +686,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, s2.Gen); Assert.AreEqual(2, d.Test.LiveGen); @@ -699,7 +703,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -707,7 +711,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual("uno", s3.Get(1)); } - var s4 = d.CreateSnapshot(); + SnapDictionary.Snapshot s4 = d.CreateSnapshot(); Assert.AreEqual(3, s4.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -719,16 +723,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache public void NestedWriteLocking1() { var d = new SnapDictionary(); - var t = d.Test; + SnapDictionary.TestHelper t = d.Test; t.CollectAuto = false; Assert.AreEqual(0, d.CreateSnapshot().Gen); // no scope context: writers nest, last one to be disposed commits + IScopeProvider scopeProvider = GetScopeProvider(); - var scopeProvider = GetScopeProvider(); - - using (var w1 = d.GetScopedWriteLock(scopeProvider)) + using (IDisposable w1 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreEqual(1, t.LiveGen); Assert.IsTrue(t.IsLocked); @@ -736,7 +739,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.Throws(() => { - using (var w2 = d.GetScopedWriteLock(scopeProvider)) + using (IDisposable w2 = d.GetScopedWriteLock(scopeProvider)) { } }); @@ -764,16 +767,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(0, d.CreateSnapshot().Gen); // scope context: writers enlist - var scopeContext = new ScopeContext(); - var scopeProvider = GetScopeProvider(scopeContext); + IScopeProvider scopeProvider = GetScopeProvider(scopeContext); - using (var w1 = d.GetScopedWriteLock(scopeProvider)) + using (IDisposable w1 = d.GetScopedWriteLock(scopeProvider)) { // This one is interesting, although we don't allow recursive locks, since this is - // using the same ScopeContext/key, the lock acquisition is only done once - - using (var w2 = d.GetScopedWriteLock(scopeProvider)) + // using the same ScopeContext/key, the lock acquisition is only done once. + using (IDisposable w2 = d.GetScopedWriteLock(scopeProvider)) { Assert.AreSame(w1, w2); @@ -786,16 +787,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache public void NestedWriteLocking3() { var d = new SnapDictionary(); - var t = d.Test; + SnapDictionary.TestHelper t = d.Test; t.CollectAuto = false; Assert.AreEqual(0, d.CreateSnapshot().Gen); var scopeContext = new ScopeContext(); - var scopeProvider1 = GetScopeProvider(); - var scopeProvider2 = GetScopeProvider(scopeContext); + IScopeProvider scopeProvider1 = GetScopeProvider(); + IScopeProvider scopeProvider2 = GetScopeProvider(scopeContext); - using (var w1 = d.GetScopedWriteLock(scopeProvider1)) + using (IDisposable w1 = d.GetScopedWriteLock(scopeProvider1)) { Assert.AreEqual(1, t.LiveGen); Assert.IsTrue(t.IsLocked); @@ -803,11 +804,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.Throws(() => { - using (var w2 = d.GetScopedWriteLock(scopeProvider2)) + using (IDisposable w2 = d.GetScopedWriteLock(scopeProvider2)) { } }); - } } @@ -824,7 +824,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, s1.Gen); Assert.AreEqual(1, d.Test.LiveGen); @@ -839,15 +839,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(2, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, s2.Gen); Assert.AreEqual(2, d.Test.LiveGen); Assert.IsFalse(d.Test.NextGen); Assert.AreEqual("uno", s2.Get(1)); - - var scopeProvider = GetScopeProvider(); + IScopeProvider scopeProvider = GetScopeProvider(); using (d.GetScopedWriteLock(scopeProvider)) { // gen 3 @@ -858,7 +857,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -866,7 +865,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual("uno", s3.Get(1)); } - var s4 = d.CreateSnapshot(); + SnapDictionary.Snapshot s4 = d.CreateSnapshot(); Assert.AreEqual(3, s4.Gen); Assert.AreEqual(3, d.Test.LiveGen); @@ -880,35 +879,34 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache var d = new SnapDictionary(); d.Test.CollectAuto = false; - // gen 1 d.Set(1, "one"); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, s1.Gen); Assert.AreEqual("one", s1.Get(1)); d.Set(1, "uno"); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, s2.Gen); Assert.AreEqual("uno", s2.Get(1)); - var scopeProvider = GetScopeProvider(); + IScopeProvider scopeProvider = GetScopeProvider(); using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release d.SetLocked(1, "ein"); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); // but live snapshot contains changes - var ls = d.Test.LiveSnapshot; + SnapDictionary.Snapshot ls = d.Test.LiveSnapshot; Assert.AreEqual("ein", ls.Get(1)); Assert.AreEqual(3, ls.Gen); } - var s4 = d.CreateSnapshot(); + SnapDictionary.Snapshot s4 = d.CreateSnapshot(); Assert.AreEqual(3, s4.Gen); Assert.AreEqual("ein", s4.Get(1)); } @@ -921,39 +919,39 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache // gen 1 d.Set(1, "one"); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, s1.Gen); Assert.AreEqual("one", s1.Get(1)); d.Set(1, "uno"); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, s2.Gen); Assert.AreEqual("uno", s2.Get(1)); var scopeContext = new ScopeContext(); - var scopeProvider = GetScopeProvider(scopeContext); + IScopeProvider scopeProvider = GetScopeProvider(scopeContext); using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release d.SetLocked(1, "ein"); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); // but live snapshot contains changes - var ls = d.Test.LiveSnapshot; + SnapDictionary.Snapshot ls = d.Test.LiveSnapshot; Assert.AreEqual("ein", ls.Get(1)); Assert.AreEqual(3, ls.Gen); } - var s4 = d.CreateSnapshot(); + SnapDictionary.Snapshot s4 = d.CreateSnapshot(); Assert.AreEqual(2, s4.Gen); Assert.AreEqual("uno", s4.Get(1)); scopeContext.ScopeExit(true); - var s5 = d.CreateSnapshot(); + SnapDictionary.Snapshot s5 = d.CreateSnapshot(); Assert.AreEqual(3, s5.Gen); Assert.AreEqual("ein", s5.Get(1)); } @@ -962,17 +960,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache public void ScopeLocking2() { var d = new SnapDictionary(); - var t = d.Test; + SnapDictionary.TestHelper t = d.Test; t.CollectAuto = false; // gen 1 d.Set(1, "one"); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.AreEqual(1, s1.Gen); Assert.AreEqual("one", s1.Get(1)); d.Set(1, "uno"); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.AreEqual(2, s2.Gen); Assert.AreEqual("uno", s2.Get(1)); @@ -980,13 +978,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsFalse(t.NextGen); var scopeContext = new ScopeContext(); - var scopeProvider = GetScopeProvider(scopeContext); + IScopeProvider scopeProvider = GetScopeProvider(scopeContext); using (d.GetScopedWriteLock(scopeProvider)) { // creating a snapshot in a write-lock does NOT return the "current" content // it uses the previous snapshot, so new snapshot created only on release d.SetLocked(1, "ein"); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.AreEqual(2, s3.Gen); Assert.AreEqual("uno", s3.Get(1)); @@ -996,7 +994,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsTrue(t.IsLocked); // but live snapshot contains changes - var ls = t.LiveSnapshot; + SnapDictionary.Snapshot ls = t.LiveSnapshot; Assert.AreEqual("ein", ls.Get(1)); Assert.AreEqual(3, ls.Gen); } @@ -1007,7 +1005,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsTrue(t.IsLocked); // no changes until exit - var s4 = d.CreateSnapshot(); + SnapDictionary.Snapshot s4 = d.CreateSnapshot(); Assert.AreEqual(2, s4.Gen); Assert.AreEqual("uno", s4.Get(1)); @@ -1019,7 +1017,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.IsFalse(t.IsLocked); // no changes since not completed - var s5 = d.CreateSnapshot(); + SnapDictionary.Snapshot s5 = d.CreateSnapshot(); Assert.AreEqual(2, s5.Gen); Assert.AreEqual("uno", s5.Get(1)); } @@ -1037,14 +1035,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache d.Set(3, "three"); d.Set(4, "four"); - var s1 = d.CreateSnapshot(); - var all = s1.GetAll().ToArray(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); + string[] all = s1.GetAll().ToArray(); Assert.AreEqual(4, all.Length); Assert.AreEqual("one", all[0]); Assert.AreEqual("four", all[3]); d.Set(1, "uno"); - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); all = s1.GetAll().ToArray(); Assert.AreEqual(4, all.Length); @@ -1071,7 +1069,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.LiveGen); Assert.IsNull(d.Test.GenObj); - var s1 = d.CreateSnapshot(); + SnapDictionary.Snapshot s1 = d.CreateSnapshot(); Assert.IsFalse(d.Test.NextGen); Assert.AreEqual(1, d.Test.LiveGen); Assert.IsNotNull(d.Test.GenObj); @@ -1087,13 +1085,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(1, d.Test.GenObj.Gen); var scopeContext = new ScopeContext(); - var scopeProvider = GetScopeProvider(scopeContext); + IScopeProvider scopeProvider = GetScopeProvider(scopeContext); // scopeProvider.Context == scopeContext -> writer is scoped // writer is scope contextual and scoped // when disposed, nothing happens // when the context exists, the writer is released - using (d.GetScopedWriteLock(scopeProvider)) { d.SetLocked(1, "ein"); @@ -1113,7 +1110,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); // panic! - var s2 = d.CreateSnapshot(); + SnapDictionary.Snapshot s2 = d.CreateSnapshot(); Assert.IsTrue(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); @@ -1130,7 +1127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache Assert.AreEqual(3, d.Test.LiveGen); Assert.IsTrue(d.Test.NextGen); - var s3 = d.CreateSnapshot(); + SnapDictionary.Snapshot s3 = d.CreateSnapshot(); Assert.IsFalse(d.Test.IsLocked); Assert.IsNotNull(d.Test.GenObj); @@ -1141,7 +1138,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache private IScopeProvider GetScopeProvider(ScopeContext scopeContext = null) { - var scopeProvider = Mock.Of(); + IScopeProvider scopeProvider = Mock.Of(); Mock.Get(scopeProvider) .Setup(x => x.Context).Returns(scopeContext); return scopeProvider; @@ -1182,7 +1179,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Umbraco.PublishedCache private static IScopeProvider GetScopeProvider() { - var scopeProvider = Mock.Of(); + IScopeProvider scopeProvider = Mock.Of(); Mock.Get(scopeProvider) .Setup(x => x.Context).Returns(() => null); return scopeProvider; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/AllowedContentTypeDetail.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/AllowedContentTypeDetail.cs index 08ac69b1bb..b4c59de805 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/AllowedContentTypeDetail.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/AllowedContentTypeDetail.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders { public class AllowedContentTypeDetail { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/ContentTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/ContentTypeBuilderTests.cs index 98d887d984..157bf67fea 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/ContentTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/ContentTypeBuilderTests.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -23,8 +25,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testPropertyGroupName = "Content"; const int testParentId = 98; const int testCreatorId = 22; - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testLevel = 3; const string testPath = "-1, 4, 10"; const int testSortOrder = 5; @@ -43,7 +45,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new ContentTypeBuilder(); // Act - var contentType = builder + IContentType contentType = builder .WithId(testId) .WithKey(testKey) .WithAlias(testAlias) @@ -119,7 +121,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.IsFalse(contentType.IsContainer); Assert.AreEqual(2, contentType.PropertyTypes.Count()); - var propertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); + IOrderedEnumerable propertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 1, propertyTypeIds.Min()); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 2, propertyTypeIds.Max()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs index ce47c69763..5e9ebccdb9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -16,12 +20,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new DataTypeBuilder(); // Act - var dtd = builder + DataType dataType = builder .WithId(testId) .Build(); // Assert - Assert.AreEqual(testId, dtd.Id); + Assert.AreEqual(testId, dataType.Id); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilderTests.cs index 0aa93be802..3f99904f61 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DocumentEntitySlimBuilderTests.cs @@ -1,6 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using NUnit.Framework; +using Umbraco.Core.Models.Entities; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -26,15 +30,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testContentTypeIcon = "icon"; const string testContentTypeThumbnail = "thumb"; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; var testAdditionalData1 = new KeyValuePair("test1", 123); var testAdditionalData2 = new KeyValuePair("test2", "hello"); var builder = new DocumentEntitySlimBuilder(); // Act - var item = builder + DocumentEntitySlim item = builder .WithId(testId) .WithKey(testKey) .WithCreatorId(testCreatorId) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/LanguageBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/LanguageBuilderTests.cs index 1efd9ddc01..a30ec6b0bf 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/LanguageBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/LanguageBuilderTests.cs @@ -1,5 +1,9 @@ -using System.Globalization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Globalization; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -17,7 +21,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var expected = CultureInfo.GetCultureInfo("en-GB"); // Act - var language = builder + ILanguage language = builder .WithCultureInfo(expected.Name) .Build(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MacroBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MacroBuilderTests.cs index bf5d899d6a..fd98331634 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MacroBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MacroBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -31,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new MacroBuilder(); // Act - var macro = builder + Macro macro = builder .WithId(id) .WithKey(key) .WithUseInEditor(useInEditor) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MediaTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MediaTypeBuilderTests.cs index 1c4af5da81..720c6dbb8c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MediaTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MediaTypeBuilderTests.cs @@ -1,5 +1,7 @@ -using System; -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Linq; using NUnit.Framework; using Umbraco.Core; @@ -23,8 +25,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testPropertyGroupName = "Additional Content"; const int testParentId = 98; const int testCreatorId = 22; - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testLevel = 3; const string testPath = "-1, 4, 10"; const int testSortOrder = 5; @@ -39,7 +41,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new MediaTypeBuilder(); // Act - var mediaType = builder + IMediaType mediaType = builder .WithId(testId) .WithKey(testKey) .WithAlias(testAlias) @@ -99,7 +101,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.IsFalse(mediaType.IsContainer); Assert.AreEqual(7, mediaType.PropertyTypes.Count()); // 5 from media properties group, 2 custom - var propertyTypeIds = mediaType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); + IOrderedEnumerable propertyTypeIds = mediaType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 1, propertyTypeIds.Min()); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 7, propertyTypeIds.Max()); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs index 4a85949826..16b1a98ec8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -33,12 +36,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const int testSortOrder = 5; const bool testTrashed = false; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testFailedPasswordAttempts = 22; - var testLastLockoutDate = DateTime.Now.AddHours(-2); - var testLastLoginDate = DateTime.Now.AddHours(-3); - var testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + DateTime testLastLockoutDate = DateTime.Now.AddHours(-2); + DateTime testLastLoginDate = DateTime.Now.AddHours(-3); + DateTime testLastPasswordChangeDate = DateTime.Now.AddHours(-4); var testPropertyType1 = new PropertyTypeDetail { Alias = "title", Name = "Title", SortOrder = 1, DataTypeId = -88 }; var testPropertyType2 = new PropertyTypeDetail { Alias = "bodyText", Name = "Body Text", SortOrder = 2, DataTypeId = -87 }; var testPropertyType3 = new PropertyTypeDetail { Alias = "author", Name = "Author", Description = "Writer of the article", SortOrder = 1, DataTypeId = -88 }; @@ -53,7 +56,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new MemberBuilder(); // Act - var member = builder + Member member = builder .AddMemberType() .WithId(testMemberTypeId) .WithAlias(testMemberTypeAlias) @@ -140,7 +143,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.AreEqual(testPropertyData2.Value, member.GetValue(testPropertyData2.Key)); Assert.AreEqual(testPropertyData3.Value, member.GetValue(testPropertyData3.Key)); - var propertyIds = member.Properties.Select(x => x.Id).OrderBy(x => x); + IOrderedEnumerable propertyIds = member.Properties.Select(x => x.Id).OrderBy(x => x); Assert.AreEqual(testPropertyIdsIncrementingFrom + 1, propertyIds.Min()); Assert.AreEqual(testPropertyIdsIncrementingFrom + 10, propertyIds.Max()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs index 4cb47de052..afa7de5aac 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs @@ -1,6 +1,10 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -17,15 +21,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testName = "Test Group"; const int testCreatorId = 4; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; var testAdditionalData1 = new KeyValuePair("test1", 123); var testAdditionalData2 = new KeyValuePair("test2", "hello"); var builder = new MemberGroupBuilder(); // Act - var group = builder + MemberGroup group = builder .WithId(testId) .WithKey(testKey) .WithName(testName) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberTypeBuilderTests.cs index e42ad306f8..b55d6467f1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberTypeBuilderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -23,8 +26,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testPropertyGroupName = "Content"; const int testParentId = 98; const int testCreatorId = 22; - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testLevel = 3; const string testPath = "-1, 4, 10"; const int testSortOrder = 5; @@ -40,7 +43,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new MemberTypeBuilder(); // Act - var memberType = builder + IMemberType memberType = builder .WithId(testId) .WithKey(testKey) .WithAlias(testAlias) @@ -99,7 +102,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders Assert.IsFalse(memberType.IsContainer); Assert.AreEqual(9, memberType.PropertyTypes.Count()); // 7 from membership properties group, 2 custom - var propertyTypeIds = memberType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); + IOrderedEnumerable propertyTypeIds = memberType.PropertyTypes.Select(x => x.Id).OrderBy(x => x); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 1, propertyTypeIds.Min()); Assert.AreEqual(testPropertyTypeIdsIncrementingFrom + 9, propertyTypeIds.Max()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyBuilderTests.cs index 7010132091..8ce3b89bd3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -14,14 +18,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders // Arrange const int testId = 4; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; var testPropertyTypeId = 3; var builder = new PropertyBuilder(); // Act - var property = builder + IProperty property = builder .WithId(testId) .WithKey(testKey) .WithCreateDate(testCreateDate) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyGroupBuilderTests.cs index aa68e02388..adc460732b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyGroupBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyGroupBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -16,14 +20,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var testKey = Guid.NewGuid(); const string testName = "Group1"; const int testSortOrder = 555; - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testPropertyTypeId = 3; var builder = new PropertyGroupBuilder(); // Act - var propertyGroup = builder + PropertyGroup propertyGroup = builder .WithId(testId) .WithCreateDate(testCreateDate) .WithName(testName) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeBuilderTests.cs index 6bcb22ce4d..a774e924f8 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeBuilderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; @@ -21,8 +24,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testName = "Test"; const int testSortOrder = 9; const int testDataTypeId = 5; - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const string testDescription = "testing"; const int testPropertyGroupId = 11; const bool testMandatory = true; @@ -33,7 +36,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new PropertyTypeBuilder(); // Act - var propertyType = builder + PropertyType propertyType = builder .WithId(testId) .WithPropertyEditorAlias(testPropertyEditorAlias) .WithValueStorageType(testValueStorageType) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeDetail.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeDetail.cs index 1b1a1f013b..d074e6443d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeDetail.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/PropertyTypeDetail.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders { public class PropertyTypeDetail { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs index b59ae261e0..09d9121295 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -16,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const int childId = 8; const int id = 4; var key = Guid.NewGuid(); - var createDate = DateTime.Now.AddHours(-1); - var updateDate = DateTime.Now; + DateTime createDate = DateTime.Now.AddHours(-1); + DateTime updateDate = DateTime.Now; const string comment = "test comment"; const int relationTypeId = 66; const string relationTypeAlias = "test"; @@ -28,7 +32,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new RelationBuilder(); // Act - var relation = builder + Relation relation = builder .BetweenIds(parentId, childId) .WithId(id) .WithComment(comment) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs index 274ae7d878..e262d5ebbe 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/RelationTypeBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -16,9 +20,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string alias = "test"; const string name = "Test"; var key = Guid.NewGuid(); - var createDate = DateTime.Now.AddHours(-2); - var updateDate = DateTime.Now.AddHours(-1); - var deleteDate = DateTime.Now; + DateTime createDate = DateTime.Now.AddHours(-2); + DateTime updateDate = DateTime.Now.AddHours(-1); + DateTime deleteDate = DateTime.Now; var parentObjectType = Guid.NewGuid(); var childObjectType = Guid.NewGuid(); const bool isBidirectional = true; @@ -26,7 +30,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new RelationTypeBuilder(); // Act - var relationType = builder + IRelationType relationType = builder .WithId(id) .WithAlias(alias) .WithName(name) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/StylesheetBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/StylesheetBuilderTests.cs index c770860b9c..9c8e5fbc28 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/StylesheetBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/StylesheetBuilderTests.cs @@ -1,6 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.IO; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Core.Routing; using Umbraco.Tests.Common.Builders; @@ -19,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new StylesheetBuilder(); // Act - var stylesheet = builder + Stylesheet stylesheet = builder .WithPath(testPath) .WithContent(testContent) .Build(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateBuilderTests.cs index 717c937f7d..e0ec812d4e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateBuilderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; @@ -17,8 +20,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const string testAlias = "test"; const string testName = "Test"; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const string testPath = "-1,3"; const string testContent = "blah"; const string testMasterTemplateAlias = "master"; @@ -27,7 +30,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new TemplateBuilder(); // Act - var template = builder + ITemplate template = builder .WithId(3) .WithAlias(testAlias) .WithName(testName) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateDetail.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateDetail.cs index 2a085931b8..f612fcf0a3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateDetail.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/TemplateDetail.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders { public class TemplateDetail { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserBuilderTests.cs index 5b3b0595a3..1063f7cf96 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserBuilderTests.cs @@ -1,5 +1,9 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; +using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -20,12 +24,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders const bool testIsApproved = true; const bool testIsLockedOut = true; var testKey = Guid.NewGuid(); - var testCreateDate = DateTime.Now.AddHours(-1); - var testUpdateDate = DateTime.Now; + DateTime testCreateDate = DateTime.Now.AddHours(-1); + DateTime testUpdateDate = DateTime.Now; const int testFailedPasswordAttempts = 22; - var testLastLockoutDate = DateTime.Now.AddHours(-2); - var testLastLoginDate = DateTime.Now.AddHours(-3); - var testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + DateTime testLastLockoutDate = DateTime.Now.AddHours(-2); + DateTime testLastLoginDate = DateTime.Now.AddHours(-3); + DateTime testLastPasswordChangeDate = DateTime.Now.AddHours(-4); var testComments = "comments"; var testSessionTimeout = 5; var testStartContentIds = new[] { 3 }; @@ -34,7 +38,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new UserBuilder(); // Act - var user = builder + User user = builder .WithId(testId) .WithKey(testKey) .WithName(testName) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserGroupBuilderTests.cs index 0cfa0649ff..2751ea0e15 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserGroupBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/UserGroupBuilderTests.cs @@ -1,5 +1,9 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using NUnit.Framework; +using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; @@ -24,7 +28,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new UserGroupBuilder(); // Act - var userGroup = builder + IUserGroup userGroup = builder .WithId(testId) .WithAlias(testAlias) .WithName(testName) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs index 64f305edd8..028b2842d1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/XmlDocumentBuilderTests.cs @@ -1,4 +1,8 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Xml; +using NUnit.Framework; using Umbraco.Tests.Common.Builders; namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders @@ -16,7 +20,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders var builder = new XmlDocumentBuilder(); // Act - var xml = builder + XmlDocument xml = builder .WithContent(content) .Build(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs similarity index 86% rename from src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs index 4f4db85e5e..d8e5a04c59 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/UsersControllerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using AutoFixture.NUnit3; using Moq; using NUnit.Framework; @@ -9,9 +12,10 @@ using Umbraco.Web.Common.Exceptions; namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers { [TestFixture] - public class UsersControllerUnitTests + public class UsersControllerTests { - [Test,AutoMoqData] + [Test] + [AutoMoqData] public void PostUnlockUsers_When_User_Lockout_Update_Fails_Expect_Failure_Response( [Frozen] IBackOfficeUserManager backOfficeUserManager, UsersController sut, @@ -25,6 +29,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers Assert.ThrowsAsync(() => sut.PostUnlockUsers(userIds)); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Extensions/ModelStateExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Extensions/ModelStateExtensionsTests.cs index 01dad1eebf..ce182c7e7c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Extensions/ModelStateExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Extensions/ModelStateExtensionsTests.cs @@ -1,4 +1,8 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNetCore.Mvc.ModelBinding; using Moq; @@ -11,7 +15,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions [TestFixture] public class ModelStateExtensionsTests { - [Test] public void Get_Cultures_With_Errors() { @@ -19,12 +22,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property - ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); // invariant property + ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); // variant property - var result = ms.GetVariantsWithErrors("en-US"); + IReadOnlyList<(string culture, string segment)> result = ms.GetVariantsWithErrors("en-US"); - //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property + // even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property Assert.AreEqual(1, result.Count); Assert.AreEqual("en-US", result[0].culture); @@ -44,12 +47,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property - ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); // invariant property + ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); // variant property - var result = ms.GetVariantsWithPropertyErrors("en-US"); + IReadOnlyList<(string culture, string segment)> result = ms.GetVariantsWithPropertyErrors("en-US"); - //even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property + // even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property Assert.AreEqual(1, result.Count); Assert.AreEqual("en-US", result[0].culture); } @@ -61,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); // invariant property Assert.AreEqual("_Properties.headerImage.invariant.null", ms.Keys.First()); } @@ -73,7 +76,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //variant property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); // variant property Assert.AreEqual("_Properties.headerImage.en-US.null", ms.Keys.First()); } @@ -85,7 +88,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); //invariant/segment property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); // invariant/segment property Assert.AreEqual("_Properties.headerImage.invariant.mySegment", ms.Keys.First()); } @@ -97,7 +100,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); //variant/segment property + ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); // variant/segment property Assert.AreEqual("_Properties.headerImage.en-US.mySegment", ms.Keys.First()); } @@ -109,7 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); //invariant/segment property + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); // invariant/segment property Assert.AreEqual("_Properties.headerImage.invariant.mySegment.myField", ms.Keys.First()); } @@ -121,7 +124,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Extensions var localizationService = new Mock(); localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US"); - ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); //variant/segment property + ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); // variant/segment property Assert.AreEqual("_Properties.headerImage.en-US.mySegment.myField", ms.Keys.First()); } 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 e18f5cb3f4..fe442a023b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs @@ -1,10 +1,14 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Primitives; using Moq; using NUnit.Framework; using Umbraco.Core.Models.Membership; @@ -20,14 +24,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Appends_Header_When_No_User_Parameter_Provider() { // Arrange - var context = CreateContext(); + ActionExecutingContext context = CreateContext(); var attribute = new AppendUserModifiedHeaderAttribute(); // Act attribute.OnActionExecuting(context); // Assert - context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out StringValues headerValue); Assert.AreEqual("1", headerValue[0]); } @@ -35,14 +39,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Append_Header_If_Already_Exists() { // Arrange - var context = CreateContext(headerValue: "0"); + ActionExecutingContext context = CreateContext(headerValue: "0"); var attribute = new AppendUserModifiedHeaderAttribute(); // Act attribute.OnActionExecuting(context); // Assert - context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out StringValues headerValue); Assert.AreEqual("0", headerValue[0]); } @@ -50,7 +54,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Append_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User() { // Arrange - var context = CreateContext(actionArgument: new KeyValuePair("UserId", 99)); + ActionExecutingContext context = CreateContext(actionArgument: new KeyValuePair("UserId", 99)); var userIdParameter = "UserId"; var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter); @@ -65,7 +69,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Appends_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User() { // Arrange - var context = CreateContext(actionArgument: new KeyValuePair("UserId", 100)); + ActionExecutingContext context = CreateContext(actionArgument: new KeyValuePair("UserId", 100)); var userIdParameter = "UserId"; var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter); @@ -73,7 +77,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters attribute.OnActionExecuting(context); // Assert - context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue); + context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out StringValues headerValue); Assert.AreEqual("1", headerValue[0]); } @@ -95,7 +99,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters .SetupGet(x => x.CurrentUser) .Returns(currentUserMock.Object); - var serviceProviderMock = new Mock(); serviceProviderMock .Setup(x => x.GetService(typeof(IBackOfficeSecurity))) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ContentModelValidatorTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ContentModelValidatorTests.cs index 295ece927d..97f819a402 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ContentModelValidatorTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ContentModelValidatorTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -11,7 +14,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters [TestFixture] public class ContentModelValidatorTests { - [Test] public void Test_Serializer() { @@ -52,7 +54,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters var outerError = new ComplexEditorValidationResult(); var id4 = Guid.NewGuid(); var addressBookCollectionElementTypeResult = new ComplexEditorElementTypeValidationResult("addressBookCollection", id4); - var booksPropertyTypeResult= new ComplexEditorPropertyTypeValidationResult("books"); + var booksPropertyTypeResult = new ComplexEditorPropertyTypeValidationResult("books"); booksPropertyTypeResult.AddValidationResult(nestedLevel1); // books is the outer most validation result addressBookCollectionElementTypeResult.ValidationResults.Add(booksPropertyTypeResult); outerError.ValidationResults.Add(addressBookCollectionElementTypeResult); @@ -60,7 +62,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters var serialized = JsonConvert.SerializeObject(outerError, Formatting.Indented, new ValidationResultConverter()); Console.WriteLine(serialized); - var jsonError = JsonConvert.DeserializeObject(serialized); + JArray jsonError = JsonConvert.DeserializeObject(serialized); Assert.IsNotNull(jsonError.SelectToken("$[0]")); Assert.AreEqual(id4.ToString(), jsonError.SelectToken("$[0].$id").Value()); @@ -105,6 +107,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters Assert.IsNotNull(error5); Assert.AreEqual(2, error5.Count); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttributeTests.cs index 6126ee9419..fcd0dd0aea 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttributeTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; using Moq; @@ -23,16 +26,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters [Test] public void GetValueFromResponse_Already_EnumerableContent() { - var expected = new List() {new ContentItemBasic()}; + var expected = new List() { new ContentItemBasic() }; - var att = new FilterAllowedOutgoingContentFilter(expected.GetType(), + var att = new FilterAllowedOutgoingContentFilter( + expected.GetType(), null, ActionBrowse.ActionLetter, Mock.Of(), Mock.Of(), - Mock.Of() ); + Mock.Of()); - var result = att.GetValueFromResponse(new ObjectResult(expected)); + dynamic result = att.GetValueFromResponse(new ObjectResult(expected)); Assert.AreEqual(expected, result); } @@ -41,16 +45,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void GetValueFromResponse_From_Property() { var expected = new List() { new ContentItemBasic() }; - var container = new MyTestClass() {MyList = expected}; + var container = new MyTestClass() { MyList = expected }; - var att = new FilterAllowedOutgoingContentFilter(expected.GetType(), + var att = new FilterAllowedOutgoingContentFilter( + expected.GetType(), nameof(MyTestClass.MyList), ActionBrowse.ActionLetter, Mock.Of(), Mock.Of(), - Mock.Of() ); + Mock.Of()); - var result = att.GetValueFromResponse(new ObjectResult(container)); + dynamic result = att.GetValueFromResponse(new ObjectResult(container)); Assert.AreEqual(expected, result); } @@ -61,45 +66,47 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters var expected = new List() { new ContentItemBasic() }; var container = new MyTestClass() { MyList = expected }; - var att = new FilterAllowedOutgoingContentFilter(expected.GetType(), + var att = new FilterAllowedOutgoingContentFilter( + expected.GetType(), "DontFind", ActionBrowse.ActionLetter, Mock.Of(), Mock.Of(), - Mock.Of() ); + Mock.Of()); - var actual = att.GetValueFromResponse(new ObjectResult(container)); + dynamic actual = att.GetValueFromResponse(new ObjectResult(container)); Assert.IsNull(actual); - } [Test] public void Filter_On_Start_Node() { - var user = CreateUser(id: 9, startContentId: 5); + IUser user = CreateUser(id: 9, startContentId: 5); var userServiceMock = new Mock(); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; var entityServiceMock = new Mock(); entityServiceMock.Setup(x => x.GetAllPaths(It.IsAny(), It.IsAny())) .Returns(new[] { Mock.Of(entity => entity.Id == 5 && entity.Path == "-1,5") }); - var entityService = entityServiceMock.Object; + IEntityService entityService = entityServiceMock.Object; var list = new List(); - var att = new FilterAllowedOutgoingContentFilter(list.GetType(), + var att = new FilterAllowedOutgoingContentFilter( + list.GetType(), null, ActionBrowse.ActionLetter, userService, entityService, - Mock.Of() ); + Mock.Of()); - var path = ""; + var path = string.Empty; for (var i = 0; i < 10; i++) { if (i > 0 && path.EndsWith(",") == false) { path += ","; } + path += i.ToInvariantString(); list.Add(new ContentItemBasic { Id = i, Name = "Test" + i, ParentId = i, Path = path }); } @@ -107,7 +114,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters att.FilterBasedOnStartNode(list, user); Assert.AreEqual(5, list.Count); - } [Test] @@ -116,30 +122,33 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters var list = new List(); for (var i = 0; i < 10; i++) { - list.Add(new ContentItemBasic{Id = i, Name = "Test" + i, ParentId = -1}); + list.Add(new ContentItemBasic { Id = i, Name = "Test" + i, ParentId = -1 }); } + var ids = list.Select(x => (int)x.Id).ToArray(); - var user = CreateUser(id: 9, startContentId: 0); + IUser user = CreateUser(id: 9, startContentId: 0); var userServiceMock = new Mock(); - //we're only assigning 3 nodes browse permissions so that is what we expect as a result + + // We're only assigning 3 nodes browse permissions so that is what we expect as a result var permissions = new EntityPermissionCollection { - new EntityPermission(9876, 1, new string[]{ ActionBrowse.ActionLetter.ToString() }), - new EntityPermission(9876, 2, new string[]{ ActionBrowse.ActionLetter.ToString() }), - new EntityPermission(9876, 3, new string[]{ ActionBrowse.ActionLetter.ToString() }), - new EntityPermission(9876, 4, new string[]{ ActionUpdate.ActionLetter.ToString() }) + new EntityPermission(9876, 1, new string[] { ActionBrowse.ActionLetter.ToString() }), + new EntityPermission(9876, 2, new string[] { ActionBrowse.ActionLetter.ToString() }), + new EntityPermission(9876, 3, new string[] { ActionBrowse.ActionLetter.ToString() }), + new EntityPermission(9876, 4, new string[] { ActionUpdate.ActionLetter.ToString() }) }; userServiceMock.Setup(x => x.GetPermissions(user, ids)).Returns(permissions); - var userService = userServiceMock.Object; + IUserService userService = userServiceMock.Object; - var att = new FilterAllowedOutgoingContentFilter(list.GetType(), + var att = new FilterAllowedOutgoingContentFilter( + list.GetType(), null, ActionBrowse.ActionLetter, userService, Mock.Of(), - Mock.Of() ); + Mock.Of()); att.FilterBasedOnPermissions(list, user); Assert.AreEqual(3, list.Count); @@ -148,13 +157,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters Assert.AreEqual(3, list.ElementAt(2).Id); } - private IUser CreateUser(int id = 0, int? startContentId = null) - { - return new UserBuilder() + private IUser CreateUser(int id = 0, int? startContentId = null) => + new UserBuilder() .WithId(id) .WithStartContentIds(startContentId.HasValue ? new[] { startContentId.Value } : new int[0]) .Build(); - } private class MyTestClass { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs index 7d1fbdddf1..f616216974 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -18,7 +21,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Set_Result_When_No_Remote_Address() { // Arrange - var context = CreateContext(); + ActionExecutingContext context = CreateContext(); var attribute = new OnlyLocalRequestsAttribute(); // Act @@ -32,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Set_Result_When_Remote_Address_Is_Null_Ip_Address() { // Arrange - var context = CreateContext(remoteIpAddress: "::1"); + ActionExecutingContext context = CreateContext(remoteIpAddress: "::1"); var attribute = new OnlyLocalRequestsAttribute(); // Act @@ -46,7 +49,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Set_Result_When_Remote_Address_Matches_Local_Address() { // Arrange - var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.3"); + ActionExecutingContext context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.3"); var attribute = new OnlyLocalRequestsAttribute(); // Act @@ -60,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_Local_Address() { // Arrange - var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.2"); + ActionExecutingContext context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.2"); var attribute = new OnlyLocalRequestsAttribute(); // Act @@ -75,7 +78,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Set_Result_When_Remote_Address_Matches_LoopBack_Address() { // Arrange - var context = CreateContext(remoteIpAddress: "127.0.0.1", localIpAddress: "::1"); + ActionExecutingContext context = CreateContext(remoteIpAddress: "127.0.0.1", localIpAddress: "::1"); var attribute = new OnlyLocalRequestsAttribute(); // Act @@ -89,7 +92,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_LoopBack_Address() { // Arrange - var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "::1"); + ActionExecutingContext context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "::1"); var attribute = new OnlyLocalRequestsAttribute(); // Act diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs index 51521c48fa..a14084c923 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/ValidationFilterAttributeTests.cs @@ -1,12 +1,12 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; -using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Moq; using NUnit.Framework; @@ -21,7 +21,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Does_Not_Set_Result_When_No_Errors_In_Model_State() { // Arrange - var context = CreateContext(); + ActionExecutingContext context = CreateContext(); var attribute = new ValidationFilterAttribute(); // Act @@ -35,7 +35,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters public void Returns_Bad_Request_When_Errors_In_Model_State() { // Arrange - var context = CreateContext(withError: true); + ActionExecutingContext context = CreateContext(withError: true); var attribute = new ValidationFilterAttribute(); // Act diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs index 7899ef39c2..568318006e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeAntiforgeryTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Linq; using System.Security.Claims; @@ -43,12 +46,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security var container = new ServiceCollection(); container.AddLogging(); container.AddAntiforgery(); - var services = container.BuildServiceProvider(); + ServiceProvider services = container.BuildServiceProvider(); - var antiforgery = services.GetRequiredService(); - var options = services.GetRequiredService>(); + IAntiforgery antiforgery = services.GetRequiredService(); + IOptions options = services.GetRequiredService>(); - var httpContext = GetHttpContext(); + HttpContext httpContext = GetHttpContext(); var backofficeAntiforgery = new BackOfficeAntiforgery(antiforgery, options); backofficeAntiforgery.GetTokens(httpContext, out var cookieToken, out var headerToken); @@ -58,7 +61,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Security // the same context cannot validate since it's already created tokens httpContext = GetHttpContext(); - var result = await backofficeAntiforgery.ValidateRequestAsync(httpContext); + Attempt result = await backofficeAntiforgery.ValidateRequestAsync(httpContext); Assert.IsFalse(result.Success); // missing token 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 80432cdce2..b677f11f2c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -1,10 +1,10 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Extensions; @@ -22,7 +22,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security { var globalSettings = new GlobalSettings(); - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Install); + IRuntimeState runtime = Mock.Of(x => x.Level == RuntimeLevel.Install); var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, @@ -39,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security { var globalSettings = new GlobalSettings(); - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); + IRuntimeState runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, @@ -56,7 +56,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security { var globalSettings = new GlobalSettings(); - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); + IRuntimeState runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); GenerateAuthPaths(out var remainingTimeoutSecondsPath, out var isAuthPath); @@ -73,13 +73,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security Assert.IsTrue(result); } - [Test] public void ShouldAuthenticateRequest_Not_Back_Office() { var globalSettings = new GlobalSettings(); - var runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); + IRuntimeState runtime = Mock.Of(x => x.Level == RuntimeLevel.Run); var mgr = new BackOfficeCookieManager( Mock.Of(), @@ -104,7 +103,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security // this is on the same controller but is considered a back office request var aPath = isAuthPath = $"/umbraco/{Constants.Web.Mvc.BackOfficePathSegment}/{Constants.Web.Mvc.BackOfficeApiArea}/{controllerName}/{nameof(AuthenticationController.IsAuthenticated)}".ToLower(); - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ContentModelSerializationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ContentModelSerializationTests.cs index eb8b2f8a23..79a971d9f4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ContentModelSerializationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ContentModelSerializationTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -14,7 +17,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration [Test] public void Content_Display_To_Json() { - //create 3 tabs with 3 properties each + // create 3 tabs with 3 properties each var tabs = new List>(); for (var tabIndex = 0; tabIndex < 3; tabIndex++) { @@ -33,6 +36,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration HideLabel = false }); } + tabs.Add(new Tab() { Alias = "Tab" + tabIndex, @@ -60,7 +64,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration Assert.AreEqual("1234", jObject["id"].ToString()); Assert.AreEqual("Test", jObject["variants"][0]["name"].ToString()); - var jsonTabs = jObject["variants"][0]["tabs"]; + + JToken jsonTabs = jObject["variants"][0]["tabs"]; Assert.AreEqual(3, jsonTabs.Count()); for (var tab = 0; tab < jsonTabs.Count(); tab++) { @@ -79,6 +84,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration } } } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/JsInitializationTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/JsInitializationTests.cs index f082ba5d76..c29f5f16b7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/JsInitializationTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/JsInitializationTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.WebAssets; @@ -7,13 +10,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration [TestFixture] public class JsInitializationTests { - [Test] public void Parse_Main() { var result = BackOfficeJavaScriptInitializer.WriteScript("[World]", "Hello", "Blah"); - Assert.AreEqual(@"LazyLoad.js([World], function () { + Assert.AreEqual( + @"LazyLoad.js([World], function () { //we need to set the legacy UmbClientMgr path if ((typeof UmbClientMgr) !== ""undefined"") { UmbClientMgr.setUmbracoPath('Hello'); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs index 0663c423d8..fd9f28946f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/AngularIntegration/ServerVariablesParserTests.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using NUnit.Framework; using Umbraco.Core; using Umbraco.Web.WebAssets; @@ -11,12 +14,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.AngularIntegration [Test] public void Parse() { - var d = new Dictionary(); - d.Add("test1", "Test 1"); - d.Add("test2", "Test 2"); - d.Add("test3", "Test 3"); - d.Add("test4", "Test 4"); - d.Add("test5", "Test 5"); + var d = new Dictionary + { + { "test1", "Test 1" }, + { "test2", "Test 2" }, + { "test3", "Test 3" }, + { "test4", "Test 4" }, + { "test5", "Test 5" } + }; var output = ServerVariablesParser.Parse(d).StripWhitespace(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/HtmlHelperExtensionMethodsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/HtmlHelperExtensionMethodsTests.cs index abb44a5dfb..19433769fa 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/HtmlHelperExtensionMethodsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Extensions/HtmlHelperExtensionMethodsTests.cs @@ -1,4 +1,7 @@ -using System.Text.Encodings.Web; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -10,7 +13,6 @@ using Umbraco.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Filters { - [TestFixture] public class HtmlHelperExtensionMethodsTests { @@ -18,16 +20,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Filters private const string SampleWithBoldAndAnchorElements = "Hello world, this is some text with a link"; [SetUp] - public virtual void Initialize() - { - //create an empty htmlHelper - _htmlHelper = new HtmlHelper(Mock.Of(), + public virtual void Initialize() => + + // Create an empty HtmlHelper. + _htmlHelper = new HtmlHelper( + Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new HtmlTestEncoder(), UrlEncoder.Default); - } private HtmlHelper _htmlHelper; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/FileNameTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/FileNameTests.cs index bee0b8bf15..5fc5987354 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/FileNameTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/FileNameTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Collections.Generic; using System.IO; using System.Linq; @@ -8,7 +11,6 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Tests.UnitTests.AutoFixture; @@ -48,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common var viewResult = await sut.Index() as ViewResult; var fileName = GetViewName(viewResult, Path.DirectorySeparatorChar.ToString()); - var views = GetUiFiles(new[] { "umbraco", "UmbracoInstall" }); + IEnumerable views = GetUiFiles(new[] { "umbraco", "UmbracoInstall" }); Assert.True(views.Contains(fileName), $"Expected {fileName} to exist, but it didn't"); } @@ -63,7 +65,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common var viewResult = sut.Index() as ViewResult; var fileName = GetViewName(viewResult); - var views = GetUiFiles(new[] { "umbraco", "UmbracoBackOffice" }); + IEnumerable views = GetUiFiles(new[] { "umbraco", "UmbracoBackOffice" }); Assert.True(views.Contains(fileName), $"Expected {fileName} to exist, but it didn't"); } @@ -80,24 +82,24 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common Mock.Get(hostingEnvironment).Setup(x => x.ToAbsolute("/")).Returns("http://localhost/"); Mock.Get(hostingEnvironment).SetupGet(x => x.ApplicationVirtualPath).Returns("/"); - sut.TempData = tempDataDictionary; var viewResult = await sut.Default() as ViewResult; var fileName = GetViewName(viewResult); - var views = GetUiFiles(new[] { "umbraco", "UmbracoBackOffice" }); + IEnumerable views = GetUiFiles(new[] { "umbraco", "UmbracoBackOffice" }); Assert.True(views.Contains(fileName), $"Expected {fileName} to exist, but it didn't"); } - [Test] public void LanguageFilesAreLowercase() { - var files = GetUiFiles(new[] { "umbraco", "config", "lang" }); + IEnumerable files = GetUiFiles(new[] { "umbraco", "config", "lang" }); foreach (var fileName in files) { - Assert.AreEqual(fileName.ToLower(), fileName, + Assert.AreEqual( + fileName.ToLower(), + fileName, $"Language files must be all lowercase but {fileName} is not lowercase."); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringFilterTests.cs index 3ed4a28b06..03bab2e02b 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Filters/ValidateUmbracoFormRouteStringFilterTests.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.DataProtection; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.DataProtection; using NUnit.Framework; using Umbraco.Web.Common.Exceptions; using Umbraco.Web.Common.Filters; @@ -36,6 +39,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Filters Assert.DoesNotThrow(() => filter.ValidateRouteString(validUfprt, ControllerName, ControllerAction, Area)); Assert.DoesNotThrow(() => filter.ValidateRouteString(validUfprt, ControllerName.ToLowerInvariant(), ControllerAction.ToLowerInvariant(), Area.ToLowerInvariant())); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributesResolverUnitTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributesResolverUnitTests.cs index 94133c9e27..ab087b325a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributesResolverUnitTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Formatters/IgnoreRequiredAttributesResolverUnitTests.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Runtime.Serialization; using Newtonsoft.Json; using NUnit.Framework; @@ -9,7 +11,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Formatters [TestFixture] public class IgnoreRequiredAttributesResolverUnitTests { - [Test] public void Test() { @@ -17,14 +18,16 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Formatters Assert.Multiple(() => { - //Ensure the deserialization throws if using default settings + // Ensure the deserialization throws if using default settings Assert.Throws(() => JsonConvert.DeserializeObject(emptyJsonObject)); - var actual = JsonConvert.DeserializeObject(emptyJsonObject,new JsonSerializerSettings - { - ContractResolver = new IgnoreRequiredAttributesResolver() - }); + ObjectWithRequiredProperty actual = JsonConvert.DeserializeObject( + emptyJsonObject, + new JsonSerializerSettings + { + ContractResolver = new IgnoreRequiredAttributesResolver() + }); Assert.IsNotNull(actual); Assert.IsNull(actual.Property); @@ -35,7 +38,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Formatters private class ObjectWithRequiredProperty { [DataMember(Name = "property", IsRequired = true)] - public string Property{ get; set; } + public string Property { get; set; } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs index 417880f476..2ec4c40714 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs @@ -1,19 +1,21 @@ -using System.Globalization; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; +using System.Globalization; +using System.Text; using Newtonsoft.Json; -using NUnit.Framework; using Newtonsoft.Json.Linq; +using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Media; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Models; -using System.Text; -using Umbraco.Core.Media; using Umbraco.Extensions; -using System.Collections.Generic; +using Umbraco.Web.Models; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common { - [TestFixture] public class ImageCropperTest { @@ -25,9 +27,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void CanConvertImageCropperDataSetSrcToString() { - //cropperJson3 - has not crops - var cropperValue = CropperJson3.DeserializeImageCropperValue(); - var serialized = cropperValue.TryConvertTo(); + // cropperJson3 - has not crops + ImageCropperValue cropperValue = CropperJson3.DeserializeImageCropperValue(); + Attempt serialized = cropperValue.TryConvertTo(); Assert.IsTrue(serialized.Success); Assert.AreEqual("/media/1005/img_0672.jpg", serialized.Result); } @@ -35,9 +37,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void CanConvertImageCropperDataSetJObject() { - //cropperJson3 - has not crops - var cropperValue = CropperJson3.DeserializeImageCropperValue(); - var serialized = cropperValue.TryConvertTo(); + // cropperJson3 - has not crops + ImageCropperValue cropperValue = CropperJson3.DeserializeImageCropperValue(); + Attempt serialized = cropperValue.TryConvertTo(); Assert.IsTrue(serialized.Success); Assert.AreEqual(cropperValue, serialized.Result.ToObject()); } @@ -45,56 +47,56 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void CanConvertImageCropperDataSetJsonToString() { - var cropperValue = CropperJson1.DeserializeImageCropperValue(); - var serialized = cropperValue.TryConvertTo(); + ImageCropperValue cropperValue = CropperJson1.DeserializeImageCropperValue(); + Attempt serialized = cropperValue.TryConvertTo(); Assert.IsTrue(serialized.Success); Assert.IsTrue(serialized.Result.DetectIsJson()); - var obj = JsonConvert.DeserializeObject(CropperJson1, new JsonSerializerSettings {Culture = CultureInfo.InvariantCulture, FloatParseHandling = FloatParseHandling.Decimal}); + ImageCropperValue obj = JsonConvert.DeserializeObject(CropperJson1, new JsonSerializerSettings { Culture = CultureInfo.InvariantCulture, FloatParseHandling = FloatParseHandling.Decimal }); Assert.AreEqual(cropperValue, obj); } - // [TestCase(CropperJson1, CropperJson1, true)] - // [TestCase(CropperJson1, CropperJson2, false)] - // public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected) - // { - // try - // { - // var container = TestHelper.GetRegister(); - // var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); - // - // composition.WithCollectionBuilder(); - // - // Current.Factory = composition.CreateFactory(); - // - // var logger = Mock.Of(); - // var scheme = Mock.Of(); - // var shortStringHelper = Mock.Of(); - // - // var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, logger, shortStringHelper); - // - // var dataTypeService = new TestObjects.TestDataTypeService( - // new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of(), Mock.Of(), Mock.Of(), TestHelper.IOHelper, TestHelper.ShortStringHelper, Mock.Of())) { Id = 1 }); - // - // var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); - // - // var converter = new ImageCropperValueConverter(); - // var result = converter.ConvertSourceToIntermediate(null, factory.CreatePropertyType("test", 1), val1, false); // does not use type for conversion - // - // var resultShouldMatch = val2.DeserializeImageCropperValue(); - // if (expected) - // { - // Assert.AreEqual(resultShouldMatch, result); - // } - // else - // { - // Assert.AreNotEqual(resultShouldMatch, result); - // } - // } - // finally - // { - // Current.Reset(); - // } - // } + //// [TestCase(CropperJson1, CropperJson1, true)] + //// [TestCase(CropperJson1, CropperJson2, false)] + //// public void CanConvertImageCropperPropertyEditor(string val1, string val2, bool expected) + //// { + //// try + //// { + //// var container = TestHelper.GetRegister(); + //// var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); + //// + //// composition.WithCollectionBuilder(); + //// + //// Current.Factory = composition.CreateFactory(); + //// + //// var logger = Mock.Of(); + //// var scheme = Mock.Of(); + //// var shortStringHelper = Mock.Of(); + //// + //// var mediaFileSystem = new MediaFileSystem(Mock.Of(), scheme, logger, shortStringHelper); + //// + //// var dataTypeService = new TestObjects.TestDataTypeService( + //// new DataType(new ImageCropperPropertyEditor(Mock.Of(), mediaFileSystem, Mock.Of(), Mock.Of(), Mock.Of(), TestHelper.IOHelper, TestHelper.ShortStringHelper, Mock.Of())) { Id = 1 }); + //// + //// var factory = new PublishedContentTypeFactory(Mock.Of(), new PropertyValueConverterCollection(Array.Empty()), dataTypeService); + //// + //// var converter = new ImageCropperValueConverter(); + //// var result = converter.ConvertSourceToIntermediate(null, factory.CreatePropertyType("test", 1), val1, false); // does not use type for conversion + //// + //// var resultShouldMatch = val2.DeserializeImageCropperValue(); + //// if (expected) + //// { + //// Assert.AreEqual(resultShouldMatch, result); + //// } + //// else + //// { + //// Assert.AreNotEqual(resultShouldMatch, result); + //// } + //// } + //// finally + //// { + //// Current.Reset(); + //// } + //// } [Test] public void GetCropUrl_CropAliasTest() @@ -150,7 +152,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetBaseCropUrlFromModelTest() { - var cropDataSet = CropperJson1.DeserializeImageCropperValue(); + ImageCropperValue cropDataSet = CropperJson1.DeserializeImageCropperValue(); var urlString = cropDataSet.GetCropUrl("thumb", new TestImageUrlGenerator()); Assert.AreEqual("?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&w=100&h=100", urlString); } @@ -161,7 +163,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrl_CropAliasHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode:ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, cropAlias: "Thumb", useCropDimensions: true, ratioMode: ImageCropRatioMode.Height); Assert.AreEqual(MediaPath + "?c=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&hr=1&w=100", urlString); } @@ -171,7 +173,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common [Test] public void GetCropUrl_WidthHeightRatioModeTest() { - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode:ImageCropRatioMode.Height); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, ratioMode: ImageCropRatioMode.Height); Assert.AreEqual(MediaPath + "?f=0.80827067669172936x0.96&hr=0.5&w=300", urlString); } @@ -194,7 +196,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common var urlStringMin = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Min); var urlStringBoxPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.BoxPad); var urlStringPad = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Pad); - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode:ImageCropMode.Max); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Max); var urlStringStretch = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: CropperJson1, width: 300, height: 150, imageCropMode: ImageCropMode.Stretch); Assert.AreEqual(MediaPath + "?m=min&w=300&h=150", urlStringMin); @@ -222,7 +224,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common { const string cropperJson = "{\"focalPoint\": {\"left\": 0.5,\"top\": 0.5},\"src\": \"/media/1005/img_0671.jpg\",\"crops\": [{\"alias\":\"thumb\",\"width\": 100,\"height\": 100,\"coordinates\": {\"x1\": 0.58729977382575338,\"y1\": 0.055768992440203169,\"x2\": 0,\"y2\": 0.32457553600198386}}]}"; - var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint:true); + var urlString = MediaPath.GetCropUrl(new TestImageUrlGenerator(), imageCropperValue: cropperJson, width: 300, height: 150, preferFocalPoint: true); Assert.AreEqual(MediaPath + "?m=defaultcrop&w=300&h=150", urlString); } @@ -340,20 +342,62 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common else { imageProcessorUrl.Append("?m=" + options.ImageCropMode.ToString().ToLower()); - if (options.ImageCropAnchor != null)imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); + if (options.ImageCropAnchor != null) + { + imageProcessorUrl.Append("&a=" + options.ImageCropAnchor.ToString().ToLower()); + } } var hasFormat = options.FurtherOptions != null && options.FurtherOptions.InvariantContains("&f="); - if (options.Quality != null && hasFormat == false) imageProcessorUrl.Append("&q=" + options.Quality); - if (options.HeightRatio != null) imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.WidthRatio != null) imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); - if (options.Width != null) imageProcessorUrl.Append("&w=" + options.Width); - if (options.Height != null) imageProcessorUrl.Append("&h=" + options.Height); - if (options.UpScale == false) imageProcessorUrl.Append("&u=no"); - if (options.AnimationProcessMode != null) imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode); - if (options.FurtherOptions != null) imageProcessorUrl.Append(options.FurtherOptions); - if (options.Quality != null && hasFormat) imageProcessorUrl.Append("&q=" + options.Quality); - if (options.CacheBusterValue != null) imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue); + if (options.Quality != null && hasFormat == false) + { + imageProcessorUrl.Append("&q=" + options.Quality); + } + + if (options.HeightRatio != null) + { + imageProcessorUrl.Append("&hr=" + options.HeightRatio.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (options.WidthRatio != null) + { + imageProcessorUrl.Append("&wr=" + options.WidthRatio.Value.ToString(CultureInfo.InvariantCulture)); + } + + if (options.Width != null) + { + imageProcessorUrl.Append("&w=" + options.Width); + } + + if (options.Height != null) + { + imageProcessorUrl.Append("&h=" + options.Height); + } + + if (options.UpScale == false) + { + imageProcessorUrl.Append("&u=no"); + } + + if (options.AnimationProcessMode != null) + { + imageProcessorUrl.Append("&apm=" + options.AnimationProcessMode); + } + + if (options.FurtherOptions != null) + { + imageProcessorUrl.Append(options.FurtherOptions); + } + + if (options.Quality != null && hasFormat) + { + imageProcessorUrl.Append("&q=" + options.Quality); + } + + if (options.CacheBusterValue != null) + { + imageProcessorUrl.Append("&r=").Append(options.CacheBusterValue); + } return imageProcessorUrl.ToString(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroParserTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroParserTests.cs index 80a25d7fc5..e8cfa0501f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroParserTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroParserTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using NUnit.Framework; using Umbraco.Web.Macros; @@ -11,155 +14,129 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros [Test] public void Format_RTE_Data_For_Editor_With_No_Macros() { - var content = @"

hello world

"; - var result = MacroTagParser.FormatRichTextContentForPersistence(content); + string content = @"

hello world

"; + string result = MacroTagParser.FormatRichTextContentForPersistence(content); Assert.AreEqual(@"

hello world

", content); } [Test] public void Format_RTE_Data_For_Editor_With_Non_AlphaNumeric_Char_In_Alias() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: My.Map.isCool eh[boy!]
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary(){{"test1", "value1"},{"test2", "value2"}}); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_Closing_Tag() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params_When_MacroAlias_Not_First() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params_When_MacroAlias_Is_First() { - - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); - // Assert.AreEqual(@"

asdfasdf

- //

asdfsadf

- //
- // - //Macro alias: Map
- //

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params_When_Multiple_Macros() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfsadf

@@ -167,24 +144,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); - // Assert.AreEqual(@"

asdfasdf

- //

asdfsadf

- //
- // - //Macro alias: Map
- //

asdfsadf

- //
- // - //Macro alias: Map
- //

asdfsadf

- //
- // - //Macro alias: Map
- //

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

@@ -197,21 +160,23 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros
Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Multiple_Macros() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

 

 

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary()); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary()); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

Macro alias: Breadcrumb
@@ -220,13 +185,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros
Macro alias: login
-

 

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

 

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Persistence_Multiline_Parameters() { - var content = @" + string content = @"

asdfasdf

@@ -243,9 +209,10 @@ asdfsdf
"; - var result = MacroTagParser.FormatRichTextContentForPersistence(content); + string result = MacroTagParser.FormatRichTextContentForPersistence(content); - Assert.AreEqual(@" + Assert.AreEqual( + @"

asdfasdf

asdfsdf
-".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Editor_With_Params_Closing_Tag_And_Content() { - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2" } }); -// Assert.AreEqual(@"

asdfasdf

-//

asdfsadf

-//
-// -//Macro alias: Map
-//

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +

asdfasdf

".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } - [Test] public void Format_RTE_Data_For_Editor_With_Multiline_Parameters() { - - var content = @"

asdfasdf

+ string content = @"

asdfasdf

asdfsadf

asdfasdf

"; - var result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2\r\ntest" } }); + string result = MacroTagParser.FormatRichTextPersistedDataForEditor(content, new Dictionary() { { "test1", "value1" }, { "test2", "value2\r\ntest" } }); - Assert.AreEqual(@"

asdfasdf

+ Assert.AreEqual( + @"

asdfasdf

asdfsadf

Macro alias: Map
-

asdfasdf

".NoCrLf(), result.NoCrLf()); +

asdfasdf

".NoCrLf(), + result.NoCrLf()); } [Test] public void Format_RTE_Data_For_Persistence() { -// var content = @" -// -//

asdfasdf

-//
-// -//asdfasdf -//asdfas -//asdfasdfasdf -//

asdfasdf

-//
-//asdfdasf -//
-//asdfsdf -//
-// -//"; - var content = @" + string content = @"

asdfasdf

@@ -361,9 +305,10 @@ asdfsdf
"; - var result = MacroTagParser.FormatRichTextContentForPersistence(content); + string result = MacroTagParser.FormatRichTextContentForPersistence(content); - Assert.AreEqual(@" + Assert.AreEqual( + @"

asdfasdf

@@ -372,13 +317,14 @@ asdfsdf asdfsdf -".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Persistence_No_Class() { - var content = @" + string content = @"

asdfasdf

@@ -394,9 +340,10 @@ asdfsdf
"; - var result = MacroTagParser.FormatRichTextContentForPersistence(content); + string result = MacroTagParser.FormatRichTextContentForPersistence(content); - Assert.AreEqual(@" + Assert.AreEqual( + @"

asdfasdf

@@ -405,21 +352,14 @@ asdfsdf asdfsdf -".Replace(Environment.NewLine, string.Empty), result.Replace(Environment.NewLine, string.Empty)); +".Replace(Environment.NewLine, string.Empty), + result.Replace(Environment.NewLine, string.Empty)); } [Test] public void Format_RTE_Data_For_Persistence_Custom_Single_Entry() { -// var content = @"
-//
-//
-//

null

-//
-//
1089
-//
-//
"; - var content = @"
+ string content = @"

null

@@ -427,10 +367,9 @@ asdfsdf
1089
"; - var result = MacroTagParser.FormatRichTextContentForPersistence(content); + string result = MacroTagParser.FormatRichTextContentForPersistence(content); Assert.AreEqual(@"", result); } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroTests.cs index 9bb4e6e54a..d789156eb5 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Macros/MacroTests.cs @@ -1,4 +1,7 @@ -using NUnit.Framework; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Web.Macros; @@ -7,11 +10,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros [TestFixture] public class MacroTests { - [SetUp] public void Setup() { - //we DO want cache enabled for these tests + // We DO want cache enabled for these tests var cacheHelper = new AppCaches( new ObjectCacheAppCache(), NoAppCache.Instance, @@ -28,23 +30,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Macros }; var filename = MacroRenderer.GetMacroFileName(model); if (expectedNonNull) + { Assert.IsNotNull(filename); + } else + { Assert.IsNull(filename); - } - - //[TestCase(-5, true)] //the cache DateTime will be older than the file date - //[TestCase(5, false)] //the cache DateTime will be newer than the file date - public void Macro_Needs_Removing_Based_On_Macro_File(int minutesToNow, bool expectedNull) - { - // macro has been refactored, and macro.GetMacroContentFromCache() will - // take care of the macro file, if any. It requires a web environment, - // so we cannot really test this anymore. - } - - public void Get_Macro_Cache_Identifier() - { - //var asdf = macro.GetCacheIdentifier() + } } } } 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 b414e49e95..a27f259de4 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -20,7 +23,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_UmbracoDataToken_Not_In_Route_Data() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), withUmbracoDataToken: false); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), withUmbracoDataToken: false); var binder = new ContentModelBinder(); // Act @@ -34,7 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Does_Not_Bind_Model_When_Source_Not_Of_Expected_Type() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new NonContentModel()); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), source: new NonContentModel()); var binder = new ContentModelBinder(); // Act @@ -49,7 +52,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { // Arrange var content = new ContentModel(CreatePublishedContent()); - var bindingContext = CreateBindingContext(typeof(ContentModel), source: content); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), source: content); var binder = new ContentModelBinder(); // Act @@ -63,7 +66,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: CreatePublishedContent()); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), source: CreatePublishedContent()); var binder = new ContentModelBinder(); // Act @@ -77,7 +80,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_From_IPublishedContent_To_Content_Model_Of_T() { // Arrange - var bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(new ContentType2(CreatePublishedContent()))); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), source: new ContentModel(new ContentType2(CreatePublishedContent()))); var binder = new ContentModelBinder(); // Act @@ -92,7 +95,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) + { routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); @@ -111,19 +116,22 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { } - private IPublishedContent CreatePublishedContent() - { - return new ContentType2(new Mock().Object); - } + private IPublishedContent CreatePublishedContent() => new ContentType2(new Mock().Object); public class ContentType1 : PublishedContentWrapped { - public ContentType1(IPublishedContent content) : base(content) { } + public ContentType1(IPublishedContent content) + : base(content) + { + } } public class ContentType2 : ContentType1 { - public ContentType2(IPublishedContent content) : base(content) { } + public ContentType2(IPublishedContent content) + : base(content) + { + } } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs index 6dd3b024b3..6e06adf727 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/HttpQueryStringModelBinderTests.cs @@ -1,4 +1,8 @@ -using Microsoft.AspNetCore.Http; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -16,7 +20,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Binds_Query_To_FormCollection() { // Arrange - var bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); + ModelBindingContext bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); var binder = new HttpQueryStringModelBinder(); // Act @@ -35,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Sets_Culture_Form_Value_From_Query_If_Provided() { // Arrange - var bindingContext = CreateBindingContext("?foo=bar&baz=buzz&culture=en-gb"); + ModelBindingContext bindingContext = CreateBindingContext("?foo=bar&baz=buzz&culture=en-gb"); var binder = new HttpQueryStringModelBinder(); // Act @@ -53,7 +57,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void Sets_Culture_Form_Value_From_Header_If_Not_Provided_In_Query() { // Arrange - var bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); + ModelBindingContext bindingContext = CreateBindingContext("?foo=bar&baz=buzz"); var binder = new HttpQueryStringModelBinder(); // Act @@ -77,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var metadataProvider = new EmptyModelMetadataProvider(); var routeValueDictionary = new RouteValueDictionary(); var valueProvider = new RouteValueProvider(BindingSource.Path, routeValueDictionary); - var modelType = typeof(FormCollection); + Type modelType = typeof(FormCollection); return new DefaultModelBindingContext { ActionContext = actionContext, diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs index 501c10551d..6bd2e3f315 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -17,11 +20,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public class RenderModelBinderTests { private ContentModelBinder _contentModelBinder; + [SetUp] - public void SetUp() - { - _contentModelBinder = new ContentModelBinder(); - } + public void SetUp() => _contentModelBinder = new ContentModelBinder(); [Test] [TestCase(typeof(IPublishedContent), false)] @@ -37,7 +38,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var contextMock = new Mock(); contextMock.Setup(x => x.Metadata).Returns(new EmptyModelMetadataProvider().GetMetadataForType(testType)); - var found = binderProvider.GetBinder(contextMock.Object); + IModelBinder found = binderProvider.GetBinder(contextMock.Object); if (expectNull) { Assert.IsNull(found); @@ -86,8 +87,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var bindingContext = new DefaultModelBindingContext(); _contentModelBinder.BindModelAsync(bindingContext, content, typeof(ContentModel)); - var bound = (IContentModel) bindingContext.Result.Model; + var bound = (IContentModel)bindingContext.Result.Model; Assert.AreSame(content, bound.Content); } @@ -98,8 +99,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var bindingContext = new DefaultModelBindingContext(); _contentModelBinder.BindModelAsync(bindingContext, content, typeof(ContentModel)); - var bound = (IContentModel) bindingContext.Result.Model; + var bound = (IContentModel)bindingContext.Result.Model; Assert.AreSame(content, bound.Content); } @@ -107,7 +108,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void No_DataToken_Returns_Null() { var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(ContentModel), false, content); + ModelBindingContext bindingContext = CreateBindingContext(typeof(ContentModel), false, content); _contentModelBinder.BindModelAsync(bindingContext); @@ -117,7 +118,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders [Test] public void Invalid_DataToken_Model_Type_Returns_Null() { - var bindingContext = CreateBindingContext(typeof(IPublishedContent), source: "Hello"); + ModelBindingContext bindingContext = CreateBindingContext(typeof(IPublishedContent), source: "Hello"); _contentModelBinder.BindModelAsync(bindingContext); Assert.IsNull(bindingContext.Result.Model); } @@ -126,7 +127,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders public void IPublishedContent_DataToken_Model_Type_Uses_DefaultImplementation() { var content = new MyContent(Mock.Of()); - var bindingContext = CreateBindingContext(typeof(MyContent), source: content); + ModelBindingContext bindingContext = CreateBindingContext(typeof(MyContent), source: content); _contentModelBinder.BindModelAsync(bindingContext); @@ -138,7 +139,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders var httpContext = new DefaultHttpContext(); var routeData = new RouteData(); if (withUmbracoDataToken) + { routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source); + } var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor()); var metadataProvider = new EmptyModelMetadataProvider(); @@ -157,17 +160,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders { public MyCustomContentModel(IPublishedContent content) : base(content) - { } + { + } } public class MyOtherContent { - } public class MyContent : PublishedContentWrapped { - public MyContent(IPublishedContent content) : base(content) + public MyContent(IPublishedContent content) + : base(content) { } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Mvc/HtmlStringUtilitiesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Mvc/HtmlStringUtilitiesTests.cs index 4b1675b7f0..b106d24ed6 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Mvc/HtmlStringUtilitiesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Mvc/HtmlStringUtilitiesTests.cs @@ -1,5 +1,7 @@ -using NUnit.Framework; -using Umbraco.Web; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; using Umbraco.Web.Common.Mvc; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Mvc @@ -10,16 +12,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Mvc private HtmlStringUtilities _htmlStringUtilities; [SetUp] - public virtual void Initialize() - { - - _htmlStringUtilities = new HtmlStringUtilities(); - } + public virtual void Initialize() => _htmlStringUtilities = new HtmlStringUtilities(); [Test] public void TruncateWithElipsis() { - var output = _htmlStringUtilities.Truncate("hello world", 5, true, false).ToString(); + string output = _htmlStringUtilities.Truncate("hello world", 5, true, false).ToString(); var expected = "hello…"; Assert.AreEqual(expected, output); } @@ -27,7 +25,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Mvc [Test] public void TruncateWithoutElipsis() { - var output = _htmlStringUtilities.Truncate("hello world", 5, false, false).ToString(); + string output = _htmlStringUtilities.Truncate("hello world", 5, false, false).ToString(); var expected = "hello"; Assert.AreEqual(expected, output); } @@ -35,7 +33,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Mvc [Test] public void TruncateShorterWordThanHellip() { - //http://issues.umbraco.org/issue/U4-10478 + // http://issues.umbraco.org/issue/U4-10478 var output = _htmlStringUtilities.Truncate("hi", 5, true, false).ToString(); var expected = "hi"; Assert.AreEqual(expected, output); @@ -48,7 +46,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Mvc var expected = "hello…"; Assert.AreEqual(expected, output); } - - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs index 40b7030ba8..e221e88dd1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; @@ -17,7 +20,6 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { - [TestFixture] public class BackOfficeAreaRoutesTests { @@ -27,7 +29,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase(RuntimeLevel.Boot)] public void RuntimeState_No_Routes(RuntimeLevel level) { - var routes = GetBackOfficeAreaRoutes(level); + BackOfficeAreaRoutes routes = GetBackOfficeAreaRoutes(level); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); @@ -37,12 +39,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [Test] public void RuntimeState_Upgrade() { - var routes = GetBackOfficeAreaRoutes(RuntimeLevel.Upgrade); + BackOfficeAreaRoutes routes = GetBackOfficeAreaRoutes(RuntimeLevel.Upgrade); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(1, endpoints.DataSources.Count); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); Assert.AreEqual(2, route.Endpoints.Count); AssertMinimalBackOfficeRoutes(route); } @@ -50,18 +52,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [Test] public void RuntimeState_Run() { - var routes = GetBackOfficeAreaRoutes(RuntimeLevel.Run); + BackOfficeAreaRoutes routes = GetBackOfficeAreaRoutes(RuntimeLevel.Run); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(1, endpoints.DataSources.Count); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); Assert.AreEqual(3, route.Endpoints.Count); AssertMinimalBackOfficeRoutes(route); var endpoint4 = (RouteEndpoint)route.Endpoints[2]; - var apiControllerName = ControllerExtensions.GetControllerName(); + string apiControllerName = ControllerExtensions.GetControllerName(); Assert.AreEqual($"umbraco/backoffice/api/{apiControllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint4.RoutePattern.RawText); Assert.IsFalse(endpoint4.RoutePattern.Defaults.ContainsKey("area")); Assert.IsFalse(endpoint4.RoutePattern.Defaults.ContainsKey("action")); @@ -78,7 +80,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing Assert.AreEqual(endpoint1.RoutePattern.Defaults["area"], typeof(BackOfficeController).GetCustomAttribute(false).RouteValue); var endpoint2 = (RouteEndpoint)route.Endpoints[1]; - var controllerName = ControllerExtensions.GetControllerName(); + string controllerName = ControllerExtensions.GetControllerName(); Assert.AreEqual($"umbraco/backoffice/{Constants.Web.Mvc.BackOfficeApiArea.ToLowerInvariant()}/{controllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint2.RoutePattern.RawText); Assert.AreEqual(Constants.Web.Mvc.BackOfficeApiArea, endpoint2.RoutePattern.Defaults["area"]); Assert.IsFalse(endpoint2.RoutePattern.Defaults.ContainsKey("action")); @@ -101,7 +103,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [IsBackOffice] private class Testing1Controller : UmbracoApiController { - } } } 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 a5b7c6dae3..f5b491a8af 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs @@ -1,10 +1,10 @@ -using Microsoft.AspNetCore.Http.Features; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using NUnit.Framework; -using System; -using System.Linq; -using System.Text; using Umbraco.Core; using Umbraco.Extensions; using Umbraco.Web.Common.Routing; @@ -12,7 +12,6 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { - [TestFixture] public class EndpointRouteBuilderExtensionsTests { @@ -37,31 +36,45 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var endpoints = new TestRouteBuilder(); endpoints.MapUmbracoRoute(umbracoPath, area, prefix, defaultAction, includeControllerName); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); var endpoint = (RouteEndpoint)route.Endpoints[0]; - var controllerName = ControllerExtensions.GetControllerName(); - var controllerNamePattern = controllerName.ToLowerInvariant(); + string controllerName = ControllerExtensions.GetControllerName(); + string controllerNamePattern = controllerName.ToLowerInvariant(); if (includeControllerName) { if (prefix.IsNullOrWhiteSpace()) + { Assert.AreEqual($"{umbracoPath}/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } else + { Assert.AreEqual($"{umbracoPath}/{prefix}/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } } else { if (prefix.IsNullOrWhiteSpace()) + { Assert.AreEqual($"{umbracoPath}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } else + { Assert.AreEqual($"{umbracoPath}/{prefix}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } } if (!area.IsNullOrWhiteSpace()) + { Assert.AreEqual(area, endpoint.RoutePattern.Defaults["area"]); + } + if (!defaultAction.IsNullOrWhiteSpace()) - Assert.AreEqual(defaultAction, endpoint.RoutePattern.Defaults["action"]); + { + Assert.AreEqual(defaultAction, endpoint.RoutePattern.Defaults["action"]); + } + Assert.AreEqual(controllerName, endpoint.RoutePattern.Defaults["controller"]); } @@ -78,38 +91,51 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing var endpoints = new TestRouteBuilder(); endpoints.MapUmbracoApiRoute(umbracoPath, area, isBackOffice, defaultAction); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); var endpoint = (RouteEndpoint)route.Endpoints[0]; - var controllerName = ControllerExtensions.GetControllerName(); - var controllerNamePattern = controllerName.ToLowerInvariant(); - var areaPattern = area?.ToLowerInvariant(); + string controllerName = ControllerExtensions.GetControllerName(); + string controllerNamePattern = controllerName.ToLowerInvariant(); + string areaPattern = area?.ToLowerInvariant(); if (isBackOffice) { if (area.IsNullOrWhiteSpace()) + { Assert.AreEqual($"{umbracoPath}/backoffice/api/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } else + { Assert.AreEqual($"{umbracoPath}/backoffice/{areaPattern}/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } } else { if (area.IsNullOrWhiteSpace()) + { Assert.AreEqual($"{umbracoPath}/api/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } else + { Assert.AreEqual($"{umbracoPath}/{areaPattern}/{controllerNamePattern}/{{action}}/{{id?}}", endpoint.RoutePattern.RawText); + } } if (!area.IsNullOrWhiteSpace()) + { Assert.AreEqual(area, endpoint.RoutePattern.Defaults["area"]); + } + if (!defaultAction.IsNullOrWhiteSpace()) + { Assert.AreEqual(defaultAction, endpoint.RoutePattern.Defaults["action"]); + } + Assert.AreEqual(controllerName, endpoint.RoutePattern.Defaults["controller"]); } private class Testing1Controller : ControllerBase { - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs index 4035c4d2d0..74671f819a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs @@ -1,8 +1,11 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Moq; using NUnit.Framework; -using System.Linq; using Umbraco.Core; using Umbraco.Core.Hosting; using Umbraco.Extensions; @@ -18,7 +21,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase(RuntimeLevel.Boot)] public void RuntimeState_No_Routes(RuntimeLevel level) { - var routes = GetInstallAreaRoutes(level); + InstallAreaRoutes routes = GetInstallAreaRoutes(level); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); @@ -29,12 +32,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase(RuntimeLevel.Upgrade)] public void RuntimeState_Install(RuntimeLevel level) { - var routes = GetInstallAreaRoutes(level); + InstallAreaRoutes routes = GetInstallAreaRoutes(level); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(2, endpoints.DataSources.Count); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); Assert.AreEqual(2, route.Endpoints.Count); var endpoint1 = (RouteEndpoint)route.Endpoints[0]; @@ -51,7 +54,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing Assert.AreEqual(ControllerExtensions.GetControllerName(), endpoint2.RoutePattern.Defaults["controller"]); Assert.AreEqual(endpoint2.RoutePattern.Defaults["area"], typeof(InstallController).GetCustomAttribute(false).RouteValue); - var fallbackRoute = endpoints.DataSources.Last(); + EndpointDataSource fallbackRoute = endpoints.DataSources.Last(); Assert.AreEqual(1, fallbackRoute.Endpoints.Count); Assert.AreEqual("Fallback {*path:nonfile}", fallbackRoute.Endpoints[0].ToString()); @@ -60,26 +63,21 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [Test] public void RuntimeState_Run() { - var routes = GetInstallAreaRoutes(RuntimeLevel.Run); + InstallAreaRoutes routes = GetInstallAreaRoutes(RuntimeLevel.Run); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(1, endpoints.DataSources.Count); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); Assert.AreEqual(1, route.Endpoints.Count); Assert.AreEqual("install/{controller?}/{action?} HTTP: GET", route.Endpoints[0].ToString()); - } - private InstallAreaRoutes GetInstallAreaRoutes(RuntimeLevel level) - { - var routes = new InstallAreaRoutes( + private InstallAreaRoutes GetInstallAreaRoutes(RuntimeLevel level) => + new InstallAreaRoutes( Mock.Of(x => x.Level == level), Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/install" && x.ApplicationVirtualPath == string.Empty), Mock.Of()); - - return routes; - } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs index 1fc6b092c8..32d4f41f8a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/PreviewRoutesTests.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; @@ -10,15 +13,10 @@ using Umbraco.Core.Hosting; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.Routing; -using Umbraco.Web.BackOffice.SignalR; -using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Common.Controllers; -using Umbraco.Web.WebApi; using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { - [TestFixture] public class PreviewRoutesTests { @@ -29,31 +27,29 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing [TestCase(RuntimeLevel.Upgrade)] public void RuntimeState_No_Routes(RuntimeLevel level) { - var routes = GetRoutes(level); + PreviewRoutes routes = GetRoutes(level); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(0, endpoints.DataSources.Count); } - [Test] public void RuntimeState_Run() { - var routes = GetRoutes(RuntimeLevel.Run); + PreviewRoutes routes = GetRoutes(RuntimeLevel.Run); var endpoints = new TestRouteBuilder(); routes.CreateRoutes(endpoints); Assert.AreEqual(2, endpoints.DataSources.Count); - var route = endpoints.DataSources.First(); + EndpointDataSource route = endpoints.DataSources.First(); Assert.AreEqual(2, route.Endpoints.Count); - var endpoint0 = (RouteEndpoint) route.Endpoints[0]; + var endpoint0 = (RouteEndpoint)route.Endpoints[0]; Assert.AreEqual($"{routes.GetPreviewHubRoute()}/negotiate", endpoint0.RoutePattern.RawText); - var endpoint1 = (RouteEndpoint) route.Endpoints[1]; + var endpoint1 = (RouteEndpoint)route.Endpoints[1]; Assert.AreEqual($"{routes.GetPreviewHubRoute()}", endpoint1.RoutePattern.RawText); - var endpoint3 = (RouteEndpoint)endpoints.DataSources.Last().Endpoints[0]; var previewControllerName = ControllerExtensions.GetControllerName(); Assert.AreEqual($"umbraco/{previewControllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint3.RoutePattern.RawText); @@ -61,9 +57,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing Assert.AreEqual("Index", endpoint3.RoutePattern.Defaults["action"]); Assert.AreEqual(previewControllerName, endpoint3.RoutePattern.Defaults["controller"]); Assert.AreEqual(endpoint3.RoutePattern.Defaults["area"], typeof(PreviewController).GetCustomAttribute(false).RouteValue); - - } + private PreviewRoutes GetRoutes(RuntimeLevel level) { var globalSettings = new GlobalSettings(); @@ -72,7 +67,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), Mock.Of(x => x.Level == level)); - return routes; } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs index de21bc5129..c35c1a1d9e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs @@ -1,15 +1,16 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Logging; +using Moq; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { @@ -37,6 +38,5 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing mock.Setup(x => x.Build()).Returns(httpContext => Task.CompletedTask); return mock.Object; } - } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/EncryptionHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/EncryptionHelperTests.cs index db80e2cd74..0e54676f10 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/EncryptionHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Security/EncryptionHelperTests.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Collections.Generic; using Microsoft.AspNetCore.DataProtection; using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Web; using Umbraco.Web.Common.Security; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Security @@ -10,7 +11,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Security [TestFixture] public class EncryptionHelperTests { - private IDataProtectionProvider DataProtectionProvider { get; } = new EphemeralDataProtectionProvider(); [Test] @@ -28,9 +28,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Security DataProtectionProvider, "FormController", "FormAction", - "", - additionalRouteValues - ); + string.Empty, + additionalRouteValues); var result = EncryptionHelper.Decrypt(encryptedRouteString, DataProtectionProvider); @@ -44,19 +43,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Security { var additionalRouteValues = new Dictionary() { - {"key1", "value1"}, - {"key2", "value2"}, - {"Key3", "Value3"}, - {"keY4", "valuE4"} + { "key1", "value1" }, + { "key2", "value2" }, + { "Key3", "Value3" }, + { "keY4", "valuE4" } }; var encryptedRouteString = EncryptionHelper.CreateEncryptedRouteString( DataProtectionProvider, "FormController", "FormAction", - "", - additionalRouteValues - ); + string.Empty, + additionalRouteValues); var result = EncryptionHelper.Decrypt(encryptedRouteString, DataProtectionProvider); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs index 3b52d0701e..d67f507f0f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -13,14 +16,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views [TestFixture] public class UmbracoViewPageTests { - #region RenderModel To ... [Test] public void RenderModel_To_RenderModel() { var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelTestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); view.ViewData = viewData; Assert.AreSame(model, view.Model); @@ -31,7 +33,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { var content = new ContentType1(null); var view = new ContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); view.ViewData = viewData; @@ -44,7 +46,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType2(null); var view = new ContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); view.ViewData = viewData; Assert.IsInstanceOf(view.Model); @@ -56,7 +58,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new ContentType2TestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } @@ -67,7 +69,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary>(model); + ViewDataDictionary> viewData = GetViewDataDictionary>(model); view.ViewData = viewData; @@ -81,7 +83,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType2(null); var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary>(model); + ViewDataDictionary> viewData = GetViewDataDictionary>(model); view.ViewData = viewData; Assert.IsInstanceOf>(view.Model); @@ -94,22 +96,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelOfContentType2TestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } - #endregion - - #region RenderModelOf To ... - [Test] public void RenderModelOf_ContentType1_To_RenderModel() { var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelTestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); view.ViewData = viewData; @@ -122,7 +120,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new ContentType1TestPage(); - var viewData = GetViewDataDictionary>(model); + ViewDataDictionary> viewData = GetViewDataDictionary>(model); await view.SetViewDataAsyncX(viewData); @@ -135,9 +133,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType2(null); var model = new ContentModel(content); var view = new ContentType1TestPage(); - var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) { - Model = model + Model = model }; await view.SetViewDataAsyncX(viewData); @@ -148,11 +146,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views [Test] public async Task RenderModelOf_ContentType1_To_ContentType2() { - var content = new ContentType1(null); var model = new ContentModel(content); var view = new ContentType2TestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } @@ -163,7 +160,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary>(model); + ViewDataDictionary> viewData = GetViewDataDictionary>(model); view.ViewData = viewData; @@ -177,7 +174,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType2(null); var model = new ContentModel(content); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary>(model); + ViewDataDictionary> viewData = GetViewDataDictionary>(model); await view.SetViewDataAsyncX(viewData); @@ -191,22 +188,18 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var model = new ContentModel(content); var view = new RenderModelOfContentType2TestPage(); - var viewData = GetViewDataDictionary(model); + ViewDataDictionary viewData = GetViewDataDictionary(model); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } - #endregion - - #region ContentType To ... - [Test] public async Task ContentType1_To_RenderModel() { var content = new ContentType1(null); var view = new RenderModelTestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); await view.SetViewDataAsyncX(viewData); @@ -219,7 +212,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views var content = new ContentType1(null); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); await view.SetViewDataAsyncX(viewData); Assert.IsInstanceOf>(view.Model); @@ -232,7 +225,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views // Same as above but with ContentModel var content = new ContentType2(null); var view = new RenderModelOfContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); await view.SetViewDataAsyncX(viewData); @@ -245,7 +238,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { var content = new ContentType1(null); var view = new RenderModelOfContentType2TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } @@ -255,7 +248,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { var content = new ContentType1(null); var view = new ContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); await view.SetViewDataAsyncX(viewData); @@ -267,7 +260,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { var content = new ContentType1(null); var view = new ContentType2TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); Assert.ThrowsAsync(async () => await view.SetViewDataAsyncX(viewData)); } @@ -277,17 +270,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views { var content = new ContentType2(null); var view = new ContentType1TestPage(); - var viewData = GetViewDataDictionary(content); + ViewDataDictionary viewData = GetViewDataDictionary(content); await view.SetViewDataAsyncX(viewData); Assert.IsInstanceOf(view.Model); } - #endregion - - #region Test helpers methods - private ViewDataDictionary GetViewDataDictionary(object model) { var sourceViewDataDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); @@ -303,48 +292,47 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views }; } - #endregion - - #region Test elements - public class ContentType1 : PublishedContentWrapped { - public ContentType1(IPublishedContent content) : base(content) {} + public ContentType1(IPublishedContent content) + : base(content) + { + } } public class ContentType2 : ContentType1 { - public ContentType2(IPublishedContent content) : base(content) { } + public ContentType2(IPublishedContent content) + : base(content) + { + } } public class TestPage : UmbracoViewPage { - public override Task ExecuteAsync() - { - throw new NotImplementedException(); - } + public override Task ExecuteAsync() => throw new NotImplementedException(); - public async Task SetViewDataAsyncX(ViewDataDictionary viewData) - { - await SetViewDataAsync(viewData); - } + public async Task SetViewDataAsyncX(ViewDataDictionary viewData) => await SetViewDataAsync(viewData); } public class RenderModelTestPage : TestPage - { } + { + } public class ContentType1TestPage : TestPage - { } + { + } public class ContentType2TestPage : TestPage - { } + { + } public class RenderModelOfContentType1TestPage : TestPage> - { } + { + } public class RenderModelOfContentType2TestPage : TestPage> - { } - - #endregion + { + } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/AspNetCoreHostingEnvironmentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/AspNetCoreHostingEnvironmentTests.cs index 1e579cc7dd..8ecd05367a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/AspNetCoreHostingEnvironmentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/AspNetCoreHostingEnvironmentTests.cs @@ -1,11 +1,9 @@ -using System; -using AutoFixture.NUnit3; -using Moq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using NUnit.Framework; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Strings; -using Umbraco.Tests.TestHelpers; using Umbraco.Tests.UnitTests.AutoFixture; using Umbraco.Web.Common.AspNetCore; @@ -14,20 +12,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website [TestFixture] public class AspNetCoreHostingEnvironmentTests { - [InlineAutoMoqData("~/Scripts", "/Scripts", null)] [InlineAutoMoqData("/Scripts", "/Scripts", null)] [InlineAutoMoqData("../Scripts", "/Scripts", typeof(InvalidOperationException))] public void IOHelper_ResolveUrl(string input, string expected, Type expectedExceptionType, AspNetCoreHostingEnvironment sut) { - if (expectedExceptionType != null) { - Assert.Throws(expectedExceptionType, () =>sut.ToAbsolute(input)); + Assert.Throws(expectedExceptionType, () => sut.ToAbsolute(input)); } else { - var result = sut.ToAbsolute(input); Assert.AreEqual(expected, result); } @@ -36,7 +31,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website [Test] public void EnsurePathIsApplicationRootPrefixed() { - //Assert + // Assert Assert.AreEqual("~/Views/Template.cshtml", PathUtility.EnsurePathIsApplicationRootPrefixed("Views/Template.cshtml")); Assert.AreEqual("~/Views/Template.cshtml", PathUtility.EnsurePathIsApplicationRootPrefixed("/Views/Template.cshtml")); Assert.AreEqual("~/Views/Template.cshtml", PathUtility.EnsurePathIsApplicationRootPrefixed("~/Views/Template.cshtml")); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs index 3a987fb038..872679769f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -27,62 +30,78 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers { var sut = new RenderIndexActionSelectorAttribute(); - var actionDescriptor = + ControllerActionDescriptor actionDescriptor = GetRenderMvcControllerIndexMethodFromCurrentType(typeof(MatchesDefaultIndexController)).First(); var actionDescriptorCollectionProviderMock = new Mock(); actionDescriptorCollectionProviderMock.Setup(x => x.ActionDescriptors) .Returns(new ActionDescriptorCollection(Array.Empty(), 1)); - var routeContext = CreateRouteContext(actionDescriptorCollectionProviderMock.Object); + RouteContext routeContext = CreateRouteContext(actionDescriptorCollectionProviderMock.Object); // Call the method multiple times - for (var i = 0; i < 1; i++) + for (int i = 0; i < 1; i++) { sut.IsValidForRequest(routeContext, actionDescriptor); } - //Ensure the underlying ActionDescriptors is only called once. - actionDescriptorCollectionProviderMock.Verify(x=>x.ActionDescriptors, Times.Once); + // Ensure the underlying ActionDescriptors is only called once. + actionDescriptorCollectionProviderMock.Verify(x => x.ActionDescriptors, Times.Once); } [Test] - [TestCase(typeof(MatchesDefaultIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesOverriddenIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(IActionResult), ExpectedResult = true)] - [TestCase(typeof(MatchesCustomIndexController), - "Index", new[] { typeof(ContentModel), typeof(int) }, typeof(IActionResult), ExpectedResult = false)] - [TestCase(typeof(MatchesAsyncIndexController), - "Index", new[] { typeof(ContentModel) }, typeof(Task), ExpectedResult = false)] + [TestCase( + typeof(MatchesDefaultIndexController), + "Index", + new[] { typeof(ContentModel) }, + typeof(IActionResult), + ExpectedResult = true)] + [TestCase( + typeof(MatchesOverriddenIndexController), + "Index", + new[] { typeof(ContentModel) }, + typeof(IActionResult), + ExpectedResult = true)] + [TestCase( + typeof(MatchesCustomIndexController), + "Index", + new[] { typeof(ContentModel), typeof(int) }, + typeof(IActionResult), + ExpectedResult = false)] + [TestCase( + typeof(MatchesAsyncIndexController), + "Index", + new[] { typeof(ContentModel) }, + typeof(Task), + ExpectedResult = false)] public bool IsValidForRequest__must_return_the_expected_result(Type controllerType, string actionName, Type[] parameterTypes, Type returnType) { - //Fake all IActionDescriptor's that will be returned by IActionDescriptorCollectionProvider - var actionDescriptors = GetRenderMvcControllerIndexMethodFromCurrentType(controllerType); + // Fake all IActionDescriptor's that will be returned by IActionDescriptorCollectionProvider + ControllerActionDescriptor[] actionDescriptors = GetRenderMvcControllerIndexMethodFromCurrentType(controllerType); // Find the one that match the current request - var actualActionDescriptor = actionDescriptors.Single(x => x.ActionName == actionName + ControllerActionDescriptor actualActionDescriptor = actionDescriptors.Single(x => x.ActionName == actionName && x.ControllerTypeInfo.Name == controllerType.Name && x.MethodInfo.ReturnType == returnType && x.MethodInfo.GetParameters().Select(m => m.ParameterType).SequenceEqual(parameterTypes)); - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext + // Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext var sut = new RenderIndexActionSelectorAttribute(); - var routeContext = CreateRouteContext(new TestActionDescriptorCollectionProvider(actionDescriptors)); + RouteContext routeContext = CreateRouteContext(new TestActionDescriptorCollectionProvider(actionDescriptors)); - //Act - var result = sut.IsValidForRequest(routeContext, actualActionDescriptor); + // Act + bool result = sut.IsValidForRequest(routeContext, actualActionDescriptor); return result; } private ControllerActionDescriptor[] GetRenderMvcControllerIndexMethodFromCurrentType(Type controllerType) { - var actions = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + IEnumerable actions = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => !m.IsSpecialName && m.GetCustomAttribute() is null && m.Module.Name.Contains("Umbraco")); - var actionDescriptors = actions + ControllerActionDescriptor[] actionDescriptors = actions .Select(x => new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo(), @@ -95,7 +114,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers private static RouteContext CreateRouteContext(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) { - //Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext + // Fake the IActionDescriptorCollectionProvider and add it to the service collection on httpcontext var httpContext = new DefaultHttpContext(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(actionDescriptorCollectionProvider); @@ -110,57 +129,53 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers private class TestActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider { - public TestActionDescriptorCollectionProvider(IReadOnlyList items) - { - ActionDescriptors = new ActionDescriptorCollection(items, 1); - } + public TestActionDescriptorCollectionProvider(IReadOnlyList items) => ActionDescriptors = new ActionDescriptorCollection(items, 1); public ActionDescriptorCollection ActionDescriptors { get; } } private class MatchesDefaultIndexController : RenderMvcController { - public MatchesDefaultIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) + public MatchesDefaultIndexController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) { } } private class MatchesOverriddenIndexController : RenderMvcController { - public override IActionResult Index(ContentModel model) - { - return base.Index(model); - } + public override IActionResult Index(ContentModel model) => base.Index(model); - public MatchesOverriddenIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) + public MatchesOverriddenIndexController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) { } } private class MatchesCustomIndexController : RenderMvcController { - public IActionResult Index(ContentModel model, int page) - { - return base.Index(model); - } + public IActionResult Index(ContentModel model, int page) => Index(model); - public MatchesCustomIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) + public MatchesCustomIndexController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) { } } private class MatchesAsyncIndexController : RenderMvcController { - public new async Task Index(ContentModel model) - { - return await Task.FromResult(base.Index(model)); - } + public new async Task Index(ContentModel model) => await Task.FromResult(base.Index(model)); - public MatchesAsyncIndexController(ILogger logger, - ICompositeViewEngine compositeViewEngine) : base(logger, compositeViewEngine) + public MatchesAsyncIndexController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) { } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs index 182fb94a40..9320c87949 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderNoContentControllerTests.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; @@ -17,7 +20,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers [Test] public void Redirects_To_Root_When_Content_Published() { - var mockUmbracoContext = new Mock(); mockUmbracoContext.Setup(x => x.Content.HasContent()).Returns(true); var mockIOHelper = new Mock(); @@ -40,7 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers var mockIOHelper = new Mock(); mockIOHelper.Setup(x => x.ResolveUrl(It.Is(y => y == UmbracoPathSetting))).Returns(UmbracoPath); - var globalSettings = Options.Create(new GlobalSettings() + IOptions globalSettings = Options.Create(new GlobalSettings() { UmbracoPath = UmbracoPathSetting, NoNodesViewPath = ViewPath, 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 82bd6719d4..8b353488e9 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,7 +19,6 @@ using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Security; using Umbraco.Web.Website; using Umbraco.Web.Website.Controllers; using CoreConstants = Umbraco.Core.Constants; @@ -30,16 +32,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers private IUmbracoContextAccessor _umbracoContextAccessor; [SetUp] - public void SetUp() - { - _umbracoContextAccessor = new TestUmbracoContextAccessor(); - } + public void SetUp() => _umbracoContextAccessor = new TestUmbracoContextAccessor(); [Test] public void Can_Construct_And_Get_Result() { - var hostingEnvironment = Mock.Of(); - var backofficeSecurityAccessor = Mock.Of(); + IHostingEnvironment hostingEnvironment = Mock.Of(); + IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); var globalSettings = new GlobalSettings(); @@ -55,14 +54,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), backofficeSecurityAccessor); - var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); - var umbracoContext = umbracoContextReference.UmbracoContext; + UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); + IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of(), Mock.Of()); - var result = ctrl.Index(); + IActionResult result = ctrl.Index(); Assert.IsNotNull(result); } @@ -71,8 +70,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers public void Umbraco_Context_Not_Null() { var globalSettings = new GlobalSettings(); - var hostingEnvironment = Mock.Of(); - var backofficeSecurityAccessor = Mock.Of(); + IHostingEnvironment hostingEnvironment = Mock.Of(); + IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); var umbracoContextFactory = new UmbracoContextFactory( _umbracoContextAccessor, @@ -86,8 +85,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), backofficeSecurityAccessor); - var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); - var umbCtx = umbracoContextReference.UmbracoContext; + UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); + IUmbracoContext umbCtx = umbracoContextReference.UmbracoContext; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbCtx); @@ -103,10 +102,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers publishedSnapshot.Setup(x => x.Members).Returns(Mock.Of()); var content = new Mock(); content.Setup(x => x.Id).Returns(2); - var backofficeSecurityAccessor = Mock.Of(); + IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); var publishedSnapshotService = new Mock(); - var hostingEnvironment = Mock.Of(); + IHostingEnvironment hostingEnvironment = Mock.Of(); var globalSettings = new GlobalSettings(); var umbracoContextFactory = new UmbracoContextFactory( @@ -121,12 +120,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), backofficeSecurityAccessor); - var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); - var umbracoContext = umbracoContextReference.UmbracoContext; + UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); + IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var publishedContentQuery = Mock.Of(query => query.Content(2) == content.Object); + IPublishedContentQuery publishedContentQuery = Mock.Of(query => query.Content(2) == content.Object); var ctrl = new TestSurfaceController(umbracoContextAccessor, publishedContentQuery, Mock.Of()); var result = ctrl.GetContent(2) as PublishedContentResult; @@ -136,13 +135,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Assert.AreEqual(2, result.Content.Id); } - [Test] public void Mock_Current_Page() { var globalSettings = new GlobalSettings(); - var hostingEnvironment = Mock.Of(); - var backofficeSecurityAccessor = Mock.Of(); + IHostingEnvironment hostingEnvironment = Mock.Of(); + IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of(); Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of()); var umbracoContextFactory = new UmbracoContextFactory( _umbracoContextAccessor, @@ -156,17 +154,17 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), backofficeSecurityAccessor); - var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); - var umbracoContext = umbracoContextReference.UmbracoContext; + UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext(); + IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext; var umbracoContextAccessor = new TestUmbracoContextAccessor(umbracoContext); - var content = Mock.Of(publishedContent => publishedContent.Id == 12345); + IPublishedContent content = Mock.Of(publishedContent => publishedContent.Id == 12345); var publishedRequestMock = new Mock(); publishedRequestMock.Setup(x => x.PublishedContent).Returns(content); - var routeDefinition = new RouteDefinition + var routeDefinition = new RouteDefinition { PublishedRequest = publishedRequestMock.Object }; @@ -174,11 +172,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers var routeData = new RouteData(); routeData.DataTokens.Add(CoreConstants.Web.UmbracoRouteDefinitionDataToken, routeDefinition); - var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of(), Mock.Of()); - ctrl.ControllerContext = new ControllerContext() + var ctrl = new TestSurfaceController(umbracoContextAccessor, Mock.Of(), Mock.Of()) { - HttpContext = Mock.Of(), - RouteData = routeData + ControllerContext = new ControllerContext() + { + HttpContext = Mock.Of(), + RouteData = routeData + } }; var result = ctrl.GetContentFromCurrentPage() as PublishedContentResult; @@ -186,33 +186,29 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Assert.AreEqual(12345, result.Content.Id); } - public class TestSurfaceController : SurfaceController { private readonly IPublishedContentQuery _publishedContentQuery; public TestSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IPublishedContentQuery publishedContentQuery, IPublishedUrlProvider publishedUrlProvider) - : base(umbracoContextAccessor, null, ServiceContext.CreatePartial(), AppCaches.Disabled, null, publishedUrlProvider) - { + : base(umbracoContextAccessor, null, ServiceContext.CreatePartial(), AppCaches.Disabled, null, publishedUrlProvider) => _publishedContentQuery = publishedContentQuery; - } - public IActionResult Index() - { + public IActionResult Index() => + // ReSharper disable once Mvc.ViewNotResolved - return View(); - } + View(); public IActionResult GetContent(int id) { - var content = _publishedContentQuery.Content(id); + IPublishedContent content = _publishedContentQuery.Content(id); return new PublishedContentResult(content); } public IActionResult GetContentFromCurrentPage() { - var content = CurrentPage; + IPublishedContent content = CurrentPage; return new PublishedContentResult(content); } @@ -222,15 +218,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers { public IPublishedContent Content { get; set; } - public PublishedContentResult(IPublishedContent content) - { - Content = content; - } + public PublishedContentResult(IPublishedContent content) => Content = content; - public Task ExecuteResultAsync(ActionContext context) - { - return Task.CompletedTask; - } + public Task ExecuteResultAsync(ActionContext context) => Task.CompletedTask; } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs index d021d38c15..50ff362cdc 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs @@ -1,4 +1,7 @@ -using System.Linq; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Linq; using System.Security.Claims; using System.Security.Principal; using Microsoft.AspNetCore.Http; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs index a74431b705..ba791dd8f2 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/ViewEngines/ProfilingViewEngineWrapperMvcViewOptionsSetupTests.cs @@ -1,4 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using Microsoft.AspNetCore.Mvc; using Moq; using NUnit.Framework; using Umbraco.Core.Logging; @@ -11,6 +14,7 @@ namespace Umbraco.Tests.Runtimes { private ProfilingViewEngineWrapperMvcViewOptionsSetup Sut => new ProfilingViewEngineWrapperMvcViewOptionsSetup(Mock.Of()); + [Test] public void WrapViewEngines_HasEngines_WrapsAll() { @@ -67,9 +71,6 @@ namespace Umbraco.Tests.Runtimes } [Test] - public void WrapViewEngines_CollectionIsNull_DoesNotThrow() - { - Assert.DoesNotThrow(() => Sut.Configure(new MvcViewOptions())); - } + public void WrapViewEngines_CollectionIsNull_DoesNotThrow() => Assert.DoesNotThrow(() => Sut.Configure(new MvcViewOptions())); } } From 852aa9177e205c93c5443d82e10a0a4a57e51e13 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 22 Dec 2020 10:33:57 +0000 Subject: [PATCH 034/127] Add Dispose(bool) based on discussions on PR. --- src/Umbraco.Core/Manifest/ManifestWatcher.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs index 16e961652f..e74393a179 100644 --- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs +++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Manifest private readonly ILogger _logger; private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime; private readonly List _fws = new List(); + private bool _disposed; public ManifestWatcher(ILogger logger, IUmbracoApplicationLifetime umbracoApplicationLifetime) { @@ -64,12 +65,22 @@ namespace Umbraco.Core.Manifest } } - public void Dispose() + private void Dispose(bool disposing) { - foreach (var fw in _fws) + // ReSharper disable InvertIf + if (disposing && !_disposed) { - fw.Dispose(); + foreach (FileSystemWatcher fw in _fws) + { + fw.Dispose(); + } + + _disposed = true; } + + // ReSharper restore InvertIf } + + public void Dispose() => Dispose(true); } } From 50c15a42ed5321612340be5ba5994bdc2629f091 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Tue, 22 Dec 2020 10:43:07 +0000 Subject: [PATCH 035/127] Misc, async TearDown --- .../Testing/UmbracoIntegrationTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 414ad6ec43..be0bc576f3 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -69,7 +70,7 @@ namespace Umbraco.Tests.Integration.Testing } [TearDown] - public virtual void TearDown() + public async Task TearDownAsync() { if (_testTeardown != null) { @@ -85,7 +86,7 @@ namespace Umbraco.Tests.Integration.Testing // Ensure CoreRuntime stopped (now it's a HostedService) IHost host = Services.GetRequiredService(); - host.StopAsync().GetAwaiter().GetResult(); + await host.StopAsync(); host.Dispose(); } From 358a8ec2aff6276c30456a64b4855172cb86b7c3 Mon Sep 17 00:00:00 2001 From: Elitsa Marinovska Date: Tue, 22 Dec 2020 16:36:07 +0100 Subject: [PATCH 036/127] 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 0c645a99bdee5cfcd72374e4cb65ac4b7a57077a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 10:45:52 +1100 Subject: [PATCH 037/127] Makes PublishedSnapshotStatus status code more human readable, fixes ModelBinding ns --- .../PublishedSnapshotStatus.cs | 34 ++++++++++--------- .../UmbracoJsonModelBinderConvention.cs | 2 +- .../UmbracoJsonModelBinder.cs} | 6 ++-- .../Runtime/AspNetCoreComposer.cs | 2 +- 4 files changed, 23 insertions(+), 21 deletions(-) rename src/Umbraco.Web.Common/{ModelBinding/UmbracoJsonModelBinderProvider.cs => ModelBinders/UmbracoJsonModelBinder.cs} (96%) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs index c8975844ef..dff40275d8 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotStatus.cs @@ -1,4 +1,4 @@ -using Umbraco.Infrastructure.PublishedCache.Persistence; +using Umbraco.Infrastructure.PublishedCache.Persistence; namespace Umbraco.Web.PublishedCache.NuCache { @@ -26,26 +26,28 @@ namespace Umbraco.Web.PublishedCache.NuCache var dbCacheIsOk = _publishedContentService.VerifyContentDbCache() && _publishedContentService.VerifyMediaDbCache() - && _publishedContentService.VerifyMemberDbCache(); + && _publishedContentService.VerifyMemberDbCache() + ? "ok" + : "NOT ok (rebuild?)"; ContentStore contentStore = _service.GetContentStore(); ContentStore mediaStore = _service.GetMediaStore(); - var cg = contentStore.GenCount; - var mg = mediaStore.GenCount; - var cs = contentStore.SnapCount; - var ms = mediaStore.SnapCount; - var ce = contentStore.Count; - var me = mediaStore.Count; + var contentStoreGen = contentStore.GenCount; + var mediaStoreGen = mediaStore.GenCount; + var contentStoreSnap = contentStore.SnapCount; + var mediaStoreSnap = mediaStore.SnapCount; + var contentStoreCount = contentStore.Count; + var mediaStoreCount = mediaStore.Count; - return - " Database cache is " + (dbCacheIsOk ? "ok" : "NOT ok (rebuild?)") + "." + - " ContentStore contains " + ce + " item" + (ce > 1 ? "s" : "") + - " and has " + cg + " generation" + (cg > 1 ? "s" : "") + - " and " + cs + " snapshot" + (cs > 1 ? "s" : "") + "." + - " MediaStore contains " + me + " item" + (ce > 1 ? "s" : "") + - " and has " + mg + " generation" + (mg > 1 ? "s" : "") + - " and " + ms + " snapshot" + (ms > 1 ? "s" : "") + "."; + string contentStoreCountPlural = contentStoreCount > 1 ? "s" : string.Empty; + string contentStoreGenPlural = contentStoreGen > 1 ? "s" : string.Empty; + string contentStoreSnapPlural = contentStoreSnap > 1 ? "s" : string.Empty; + string mediaStoreCountPlural = mediaStoreCount > 1 ? "s" : string.Empty; + string mediaStoreGenPlural = mediaStoreGen > 1 ? "s" : string.Empty; + string mediaStoreSnapPlural = mediaStoreSnap > 1 ? "s" : string.Empty; + + return $" Database cache is {dbCacheIsOk}. ContentStore contains {contentStoreCount} item{contentStoreCountPlural} and has {contentStoreGen} generation{contentStoreGenPlural} and {contentStoreSnap} snapshot{contentStoreSnapPlural}. MediaStore contains {mediaStoreCount} item{mediaStoreCountPlural} and has {mediaStoreGen} generation{mediaStoreGenPlural} and {mediaStoreSnap} snapshot{mediaStoreSnapPlural}."; } } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs index 42d23b33b3..5a9e3ff90d 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs @@ -1,10 +1,10 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Umbraco.Web.Common.ModelBinding; using System.Linq; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Actions; using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.ApplicationModels { diff --git a/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs b/src/Umbraco.Web.Common/ModelBinders/UmbracoJsonModelBinder.cs similarity index 96% rename from src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs rename to src/Umbraco.Web.Common/ModelBinders/UmbracoJsonModelBinder.cs index 7ec61656fb..7069344bda 100644 --- a/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs +++ b/src/Umbraco.Web.Common/ModelBinders/UmbracoJsonModelBinder.cs @@ -1,14 +1,14 @@ -using Microsoft.AspNetCore.Mvc; +using System.Buffers; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; -using System.Buffers; using Umbraco.Web.Common.Formatters; -namespace Umbraco.Web.Common.ModelBinding +namespace Umbraco.Web.Common.ModelBinders { /// /// A custom body model binder that only uses a to bind body action parameters diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 508f4e987f..49dcf5a6db 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -16,7 +16,6 @@ using Umbraco.Web.Common.Install; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.Macros; using Umbraco.Web.Common.Middleware; -using Umbraco.Web.Common.ModelBinding; using Umbraco.Web.Common.Profiler; using Umbraco.Web.Common.Routing; using Umbraco.Web.Common.Security; @@ -25,6 +24,7 @@ using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Macros; using Umbraco.Web.Security; using Umbraco.Web.Templates; +using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.Runtime { From 674b61a7f926732bd672a3c9dde5e11657c3dac2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 11:00:37 +1100 Subject: [PATCH 038/127] small cleanup of DisposableObjectSlim --- src/Umbraco.Core/DisposableObjectSlim.cs | 51 +++++++++++++----------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/DisposableObjectSlim.cs b/src/Umbraco.Core/DisposableObjectSlim.cs index 4992f8bc0f..6874ad8001 100644 --- a/src/Umbraco.Core/DisposableObjectSlim.cs +++ b/src/Umbraco.Core/DisposableObjectSlim.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Umbraco.Core { @@ -6,8 +6,7 @@ namespace Umbraco.Core /// Abstract implementation of managed IDisposable. /// /// - /// This is for objects that do NOT have unmanaged resources. Use - /// for objects that DO have unmanaged resources and need to deal with them when disposing. + /// This is for objects that do NOT have unmanaged resources. /// /// Can also be used as a pattern for when inheriting is not possible. /// @@ -19,35 +18,39 @@ namespace Umbraco.Core /// public abstract class DisposableObjectSlim : IDisposable { - private readonly object _locko = new object(); - - // gets a value indicating whether this instance is disposed. - // for internal tests only (not thread safe) + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// + /// for internal tests only (not thread safe) + /// public bool Disposed { get; private set; } - // implements IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + /// + /// Disposes managed resources + /// + protected abstract void DisposeResources(); - private void Dispose(bool disposing) + /// + /// Disposes managed resources + /// + /// True if disposing via Dispose method and not a finalizer. Always true for this class. + protected virtual void Dispose(bool disposing) { - // can happen if the object construction failed - if (_locko == null) - return; - - lock (_locko) + if (!Disposed) { - if (Disposed) return; + if (disposing) + { + DisposeResources(); + } + Disposed = true; } - - if (disposing) - DisposeResources(); } - protected virtual void DisposeResources() { } + /// +#pragma warning disable CA1063 // Implement IDisposable Correctly + public void Dispose() => Dispose(disposing: true); // We do not use GC.SuppressFinalize because this has no finalizer +#pragma warning restore CA1063 // Implement IDisposable Correctly } } From ce5cdad3764f7421d89a6e691d187d6921fbe7d8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 11:02:50 +1100 Subject: [PATCH 039/127] separates classes to files --- src/Umbraco.Core/Events/IEventAggregator.cs | 23 ------------------ src/Umbraco.Core/Events/INotification.cs | 12 ++++++++++ .../Events/INotificationHandler.cs | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 src/Umbraco.Core/Events/INotification.cs create mode 100644 src/Umbraco.Core/Events/INotificationHandler.cs diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs index bd01ad0b57..d78aed36b7 100644 --- a/src/Umbraco.Core/Events/IEventAggregator.cs +++ b/src/Umbraco.Core/Events/IEventAggregator.cs @@ -22,27 +22,4 @@ namespace Umbraco.Core.Events Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification; } - - /// - /// A marker interface to represent a notification. - /// - public interface INotification - { - } - - /// - /// Defines a handler for a notification. - /// - /// The type of notification being handled. - public interface INotificationHandler - where TNotification : INotification - { - /// - /// Handles a notification - /// - /// The notification - /// The cancellation token. - /// A representing the asynchronous operation. - Task HandleAsync(TNotification notification, CancellationToken cancellationToken); - } } diff --git a/src/Umbraco.Core/Events/INotification.cs b/src/Umbraco.Core/Events/INotification.cs new file mode 100644 index 0000000000..3030b0836f --- /dev/null +++ b/src/Umbraco.Core/Events/INotification.cs @@ -0,0 +1,12 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +namespace Umbraco.Core.Events +{ + /// + /// A marker interface to represent a notification. + /// + public interface INotification + { + } +} diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs new file mode 100644 index 0000000000..dc5d83e0f7 --- /dev/null +++ b/src/Umbraco.Core/Events/INotificationHandler.cs @@ -0,0 +1,24 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Core.Events +{ + /// + /// Defines a handler for a notification. + /// + /// The type of notification being handled. + public interface INotificationHandler + where TNotification : INotification + { + /// + /// Handles a notification + /// + /// The notification + /// The cancellation token. + /// A representing the asynchronous operation. + Task HandleAsync(TNotification notification, CancellationToken cancellationToken); + } +} From 7115e0a11e0e7b22629aa1267034d8a5f04e8925 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 12:02:01 +1100 Subject: [PATCH 040/127] shuffles code with DependencyInjection namespace, forgot to commit changes to Startup --- src/Umbraco.Tests.Integration/RuntimeTests.cs | 3 +- .../UmbracoBuilderExtensions.cs | 4 +- .../UmbracoTestServerTestBase.cs | 3 +- .../Testing/UmbracoIntegrationTest.cs | 6 +- ...kOfficeServiceCollectionExtensionsTests.cs | 2 +- .../ServiceCollectionExtensions.cs} | 13 ++--- .../UmbracoBuilderExtensions.cs} | 36 +++++++++--- .../AuthenticationBuilderExtensions.cs | 16 ----- .../Extensions/CompositionExtensions.cs | 29 ---------- .../Extensions/IdentityBuilderExtensions.cs | 22 ++++--- .../Runtime/BackOfficeComposer.cs | 5 +- .../AspNetCore/UmbracoMvcConfigureOptions.cs | 24 ++++++++ .../ServiceCollectionExtensions.cs} | 49 ++-------------- .../UmbracoBuilderExtensions.cs | 58 ++++++++++++------- .../ApplicationBuilderExtensions.cs | 1 - .../Extensions/ServiceCollectionExtensions.cs | 25 -------- src/Umbraco.Web.UI.NetCore/Startup.cs | 12 +++- ...racoWebsiteApplicationBuilderExtensions.cs | 1 - .../WebsiteUmbracoBuilderExtensions.cs | 44 -------------- 19 files changed, 132 insertions(+), 221 deletions(-) rename src/Umbraco.Web.BackOffice/{Extensions/BackOfficeServiceCollectionExtensions.cs => DependencyInjection/ServiceCollectionExtensions.cs} (98%) rename src/Umbraco.Web.BackOffice/{Extensions/BackOfficeUmbracoBuilderExtensions.cs => DependencyInjection/UmbracoBuilderExtensions.cs} (72%) delete mode 100644 src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs delete mode 100644 src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs create mode 100644 src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs rename src/Umbraco.Web.Common/{Extensions/UmbracoWebServiceCollectionExtensions.cs => DependencyInjection/ServiceCollectionExtensions.cs} (67%) rename src/Umbraco.Web.Common/{Extensions => DependencyInjection}/UmbracoBuilderExtensions.cs (91%) delete mode 100644 src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs delete mode 100644 src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index a5239bf05e..9b09f7c562 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -12,7 +12,7 @@ using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; -using Umbraco.Web.Common.Extensions; +using Umbraco.Web.Common.DependencyInjection; namespace Umbraco.Tests.Integration { @@ -37,7 +37,6 @@ namespace Umbraco.Tests.Integration /// /// Calling AddUmbracoCore to configure the container and UseUmbracoCore to start the runtime /// - /// [Test] public async Task UseUmbracoCore() { diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs index cfab1e29c5..796f9a8669 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs @@ -4,7 +4,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Core.Runtime; using Umbraco.Tests.Integration.Implementations; -using Umbraco.Web.Common.Extensions; +using Umbraco.Web.Common.DependencyInjection; namespace Umbraco.Tests.Integration.TestServerTest { @@ -13,8 +13,6 @@ namespace Umbraco.Tests.Integration.TestServerTest /// /// Uses a test version of Umbraco Core with a test IRuntime /// - /// - /// public static IUmbracoBuilder AddTestCore(this IUmbracoBuilder builder, TestHelper testHelper) { builder.AddUmbracoCore(); diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index d2b7f1664d..416d0f5835 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -19,8 +19,9 @@ using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.Common.Controllers; -using Umbraco.Web.Common.Extensions; +using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Website.Controllers; namespace Umbraco.Tests.Integration.TestServerTest diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 27ddcc9fd1..3d50e72b9a 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -16,7 +16,6 @@ using NUnit.Framework; using Serilog; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; using Umbraco.Core.Configuration.Models; using Umbraco.Core.DependencyInjection; using Umbraco.Core.IO; @@ -31,7 +30,8 @@ using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; using Umbraco.Tests.Testing; using Umbraco.Web; -using Umbraco.Web.Common.Extensions; +using Umbraco.Web.BackOffice.DependencyInjection; +using Umbraco.Web.Common.DependencyInjection; namespace Umbraco.Tests.Integration.Testing { @@ -400,7 +400,7 @@ namespace Umbraco.Tests.Integration.Testing public IConfiguration Configuration { get; protected set; } - public TestHelper TestHelper = new TestHelper(); + public TestHelper TestHelper { get; } = new TestHelper(); protected virtual Action CustomTestSetup => services => { }; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index d9dee389ee..b942e57e46 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -3,8 +3,8 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using Umbraco.Core.Security; -using Umbraco.Extensions; using Umbraco.Tests.Integration.Testing; +using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.Common.Security; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs similarity index 98% rename from src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs rename to src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs index 9ad448a603..518152a543 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Core; using Umbraco.Core.Security; using Umbraco.Core.Serialization; +using Umbraco.Extensions; using Umbraco.Net; using Umbraco.Web.Actions; using Umbraco.Web.BackOffice.Authorization; @@ -13,15 +14,13 @@ using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Authorization; using Umbraco.Web.Common.Security; -namespace Umbraco.Extensions +namespace Umbraco.Web.BackOffice.DependencyInjection { - public static class BackOfficeServiceCollectionExtensions + public static class ServiceCollectionExtensions { - /// /// Adds the services required for using Umbraco back office Identity /// - /// public static void AddUmbracoBackOfficeIdentity(this IServiceCollection services) { services.AddDataProtection(); @@ -79,12 +78,10 @@ namespace Umbraco.Extensions /// /// Add authorization handlers and policies /// - /// public static void AddBackOfficeAuthorizationPolicies(this IServiceCollection services, string backOfficeAuthenticationScheme = Constants.Security.BackOfficeAuthenticationType) { // NOTE: Even though we are registering these handlers globally they will only actually execute their logic for - // any auth defining a matching requirement and scheme. - + // any auth defining a matching requirement and scheme. services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -247,7 +244,7 @@ namespace Umbraco.Extensions policy.Requirements.Add(new SectionRequirement(Constants.Applications.Settings)); }); - //We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered + // We will not allow the tree to render unless the user has access to any of the sections that the tree gets rendered // this is not ideal but until we change permissions to be tree based (not section) there's not much else we can do here. options.AddPolicy(AuthorizationPolicies.SectionAccessForContentTree, policy => { diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs similarity index 72% rename from src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs rename to src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 16afd976db..9c1d64aba8 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeUmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,16 +1,20 @@ using System; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; using Umbraco.Infrastructure.PublishedCache.Extensions; +using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Security; -using Umbraco.Web.Common.Extensions; +using Umbraco.Web.BackOffice.Trees; +using Umbraco.Web.Common.Authorization; +using Umbraco.Web.Common.DependencyInjection; -namespace Umbraco.Extensions +namespace Umbraco.Web.BackOffice.DependencyInjection { /// /// Extension methods for for the Umbraco back office /// - public static class BackOfficeUmbracoBuilderExtensions + public static partial class UmbracoBuilderExtensions { /// /// Adds all required components to run the Umbraco back office @@ -76,14 +80,15 @@ namespace Umbraco.Extensions /// /// Adds Umbraco back office authorization policies /// - public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder, string backOfficeAuthenticationScheme = Umbraco.Core.Constants.Security.BackOfficeAuthenticationType) + public static IUmbracoBuilder AddBackOfficeAuthorizationPolicies(this IUmbracoBuilder builder, string backOfficeAuthenticationScheme = Core.Constants.Security.BackOfficeAuthenticationType) { builder.Services.AddBackOfficeAuthorizationPolicies(backOfficeAuthenticationScheme); - // TODO: See other TODOs in things like UmbracoApiControllerBase ... AFAIK all of this is only used for the back office - // so IMO these controllers and the features auth policies should just be moved to the back office project and then this - // ext method can be removed. - builder.Services.AddUmbracoCommonAuthorizationPolicies(); + builder.Services.AddSingleton(); + + builder.Services.AddAuthorization(options + => options.AddPolicy(AuthorizationPolicies.UmbracoFeatureEnabled, policy + => policy.Requirements.Add(new FeatureAuthorizeRequirement()))); return builder; } @@ -97,5 +102,20 @@ namespace Umbraco.Extensions return builder; } + + /// + /// Adds support for external login providers in Umbraco + /// + public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) + { + builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); + return umbracoBuilder; + } + + /// + /// Gets the back office tree collection builder + /// + public static TreeCollectionBuilder Trees(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs deleted file mode 100644 index 8145cb4278..0000000000 --- a/src/Umbraco.Web.BackOffice/Extensions/AuthenticationBuilderExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Umbraco.Core.DependencyInjection; -using Umbraco.Web.BackOffice.Security; - -namespace Umbraco.Extensions -{ - public static class AuthenticationBuilderExtensions - { - public static IUmbracoBuilder AddBackOfficeExternalLogins(this IUmbracoBuilder umbracoBuilder, Action builder) - { - builder(new BackOfficeExternalLoginsBuilder(umbracoBuilder.Services)); - return umbracoBuilder; - } - } - -} diff --git a/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs deleted file mode 100644 index 2c14e33e2b..0000000000 --- a/src/Umbraco.Web.BackOffice/Extensions/CompositionExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Umbraco.Core.DependencyInjection; -using Umbraco.Core.Composing; -using Umbraco.Web.BackOffice.Trees; - -// the namespace here is intentional - although defined in Umbraco.Web assembly, -// this class should be visible when using Umbraco.Core.Components, alongside -// Umbraco.Core's own CompositionExtensions class - -// ReSharper disable once CheckNamespace -namespace Umbraco.Extensions -{ - /// - /// Provides extension methods to the class. - /// - public static class WebCompositionExtensions - { - #region Collection Builders - - /// - /// Gets the back office tree collection builder - /// - /// - /// - public static TreeCollectionBuilder Trees(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - #endregion - } -} diff --git a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs index e6385e6bf9..dcb0606ebb 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/IdentityBuilderExtensions.cs @@ -4,15 +4,20 @@ using Umbraco.Core.Security; namespace Umbraco.Extensions { + /// + /// Extension methods for + /// public static class IdentityBuilderExtensions { /// /// Adds a implementation for /// - /// The type of the user manager to add. - /// + /// The usermanager interface + /// The usermanager type + /// The /// The current instance. - public static IdentityBuilder AddUserManager(this IdentityBuilder identityBuilder) where TUserManager : UserManager, TInterface + public static IdentityBuilder AddUserManager(this IdentityBuilder identityBuilder) + where TUserManager : UserManager, TInterface { identityBuilder.AddUserManager(); identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager)); @@ -22,11 +27,12 @@ namespace Umbraco.Extensions /// /// Adds a implementation for /// - /// - /// - /// - /// - public static IdentityBuilder AddSignInManager(this IdentityBuilder identityBuilder) where TSignInManager : SignInManager, TInterface + /// The sign in manager interface + /// The sign in manager type + /// The + /// The current instance. + public static IdentityBuilder AddSignInManager(this IdentityBuilder identityBuilder) + where TSignInManager : SignInManager, TInterface { identityBuilder.AddSignInManager(); identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TSignInManager)); diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs index 44896016cd..e9bf69f2c0 100644 --- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs +++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs @@ -1,14 +1,15 @@ -using System.Linq; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.DependencyInjection; 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; diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs b/src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs new file mode 100644 index 0000000000..6c8420cd03 --- /dev/null +++ b/src/Umbraco.Web.Common/AspNetCore/UmbracoMvcConfigureOptions.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.ModelBinders; + +namespace Umbraco.Web.Common.AspNetCore +{ + /// + /// Options for globally configuring MVC for Umbraco + /// + /// + /// We generally don't want to change the global MVC settings since we want to be unobtrusive as possible but some + /// global mods are needed - so long as they don't interfere with normal user usages of MVC. + /// + public class UmbracoMvcConfigureOptions : IConfigureOptions + { + /// + public void Configure(MvcOptions options) + { + options.ModelBinderProviders.Insert(0, new ContentModelBinderProvider()); + options.Filters.Insert(0, new EnsurePartialViewMacroViewContextFilterAttribute()); + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs similarity index 67% rename from src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs rename to src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs index 2b077fe840..dd7eda895e 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,37 +1,22 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Web.Caching; using SixLabors.ImageSharp.Web.Commands; using SixLabors.ImageSharp.Web.DependencyInjection; using SixLabors.ImageSharp.Web.Processors; using SixLabors.ImageSharp.Web.Providers; -using Smidge; -using Smidge.Nuglify; -using Umbraco.Core.DependencyInjection; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Configuration.Models.Validation; -using Umbraco.Web.Common.ApplicationModels; -namespace Umbraco.Extensions +namespace Umbraco.Web.Common.DependencyInjection { - public static class UmbracoWebServiceCollectionExtensions + public static class ServiceCollectionExtensions { - - - /// /// Adds Image Sharp with Umbraco settings /// - /// - /// - /// public static IServiceCollection AddUmbracoImageSharp(this IServiceCollection services, IConfiguration configuration) { var imagingSettings = configuration.GetSection(Core.Constants.Configuration.ConfigImaging) @@ -50,7 +35,7 @@ namespace Umbraco.Extensions return Task.CompletedTask; }; - options.OnBeforeSaveAsync = _ => Task.CompletedTask; + options.OnBeforeSaveAsync = _ => Task.CompletedTask; options.OnProcessedAsync = _ => Task.CompletedTask; options.OnPrepareResponseAsync = _ => Task.CompletedTask; }) @@ -70,7 +55,6 @@ namespace Umbraco.Extensions return services; } - private static void RemoveIntParamenterIfValueGreatherThen(IDictionary commands, string parameter, int maxValue) { if (commands.TryGetValue(parameter, out var command)) @@ -84,30 +68,5 @@ namespace Umbraco.Extensions } } } - - /// - /// Options for globally configuring MVC for Umbraco - /// - /// - /// We generally don't want to change the global MVC settings since we want to be unobtrusive as possible but some - /// global mods are needed - so long as they don't interfere with normal user usages of MVC. - /// - public class UmbracoMvcConfigureOptions : IConfigureOptions - { - - // TODO: we can inject params with DI here - public UmbracoMvcConfigureOptions() - { - } - - // TODO: we can configure global mvc options here if we need to - public void Configure(MvcOptions options) - { - - } - } - - } - } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs similarity index 91% rename from src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs rename to src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 80185f30a3..c524cb064d 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; @@ -7,7 +8,6 @@ using System.Runtime.InteropServices; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; @@ -36,21 +36,19 @@ using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Infrastructure.Runtime; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; -using Umbraco.Web.Common.Extensions; -using Umbraco.Web.Common.Filters; -using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Common.Profiler; using Umbraco.Web.Telemetry; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; -namespace Umbraco.Web.Common.Extensions +namespace Umbraco.Web.Common.DependencyInjection { // TODO: We could add parameters to configure each of these for flexibility /// /// Extension methods for for the common Umbraco functionality /// - public static class UmbracoBuilderExtensions + public static partial class UmbracoBuilderExtensions { public static IUmbracoBuilder AddUmbraco( this IServiceCollection services, @@ -90,11 +88,15 @@ namespace Umbraco.Web.Common.Extensions return new UmbracoBuilder(services, config, typeLoader, loggerFactory); } - /// Composes Composers + /// + /// Adds core Umbraco services + /// public static IUmbracoBuilder AddUmbracoCore(this IUmbracoBuilder builder) { if (builder is null) + { throw new ArgumentNullException(nameof(builder)); + } builder.Services.AddLazySupport(); @@ -163,15 +165,21 @@ namespace Umbraco.Web.Common.Extensions return builder; } + /// + /// Adds Umbraco composers for plugins + /// public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) { - var composerTypes = builder.TypeLoader.GetTypes(); - var enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + 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. @@ -208,6 +216,9 @@ namespace Umbraco.Web.Common.Extensions return builder; } + /// + /// Add Umbraco hosted services + /// public static IUmbracoBuilder AddHostedServices(this IUmbracoBuilder builder) { builder.Services.AddHostedService(); @@ -221,40 +232,44 @@ namespace Umbraco.Web.Common.Extensions return builder; } + // TODO: Not sure this needs to exist and/or be public? public static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) { builder.Services.AddHttpClient(); return builder; } + /// + /// Adds mini profiler services for Umbraco + /// public static IUmbracoBuilder AddMiniProfiler(this IUmbracoBuilder builder) { builder.Services.AddMiniProfiler(options => - { - options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile - }); + + // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile + options.ShouldProfile = request => false); return builder; } - public static IUmbracoBuilder AddMvcAndRazor(this IUmbracoBuilder builder, Action mvcOptions = null, Action mvcBuilding = null) + public static IUmbracoBuilder AddMvcAndRazor(this IUmbracoBuilder builder, Action mvcBuilding = null) { // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important // this will directly affect developers who need to call that themselves. // We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. // But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. - var mvcBuilder = builder.Services.AddControllersWithViews(options => - { - options.ModelBinderProviders.Insert(0, new ContentModelBinderProvider()); + IMvcBuilder mvcBuilder = builder.Services + .AddControllersWithViews() + .AddRazorRuntimeCompilation(); - options.Filters.Insert(0, new EnsurePartialViewMacroViewContextFilterAttribute()); - mvcOptions?.Invoke(options); - }).AddRazorRuntimeCompilation(); mvcBuilding?.Invoke(mvcBuilder); return builder; } + /// + /// Add runtime minifier support for Umbraco + /// public static IUmbracoBuilder AddRuntimeMinifier(this IUmbracoBuilder builder) { builder.Services.AddSmidge(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification)); @@ -274,7 +289,7 @@ namespace Umbraco.Web.Common.Extensions options.Cookie.HttpOnly = true; }); - builder.Services.ConfigureOptions(); + builder.Services.ConfigureOptions(); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.AddUmbracoImageSharp(builder.Config); @@ -282,9 +297,10 @@ namespace Umbraco.Web.Common.Extensions return builder; } + // TODO: Does this need to exist and/or be public? public static IUmbracoBuilder AddWebServer(this IUmbracoBuilder builder) { - // TODO: We need to figure out why thsi is needed and fix those endpoints to not need them, we don't want to change global things + // TODO: We need to figure out why this is needed and fix those endpoints to not need them, we don't want to change global things // If using Kestrel: https://stackoverflow.com/a/55196057 builder.Services.Configure(options => { diff --git a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 0f2d1ffcdd..5c7c4ee713 100644 --- a/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Hosting; using Umbraco.Infrastructure.Logging.Serilog.Enrichers; using Umbraco.Web.Common.Middleware; using Umbraco.Web.Common.Plugins; -using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.Extensions { diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs deleted file mode 100644 index e8e3e2c329..0000000000 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Web.BackOffice.Authorization; -using Umbraco.Web.Common.Authorization; - -namespace Umbraco.Extensions -{ - public static class ServiceCollectionExtensions - { - public static void AddUmbracoCommonAuthorizationPolicies(this IServiceCollection services) - { - // TODO: Should this only exist in the back office project? These really are only ever used for the back office AFAIK - // If it is moved it should only target the back office scheme - - services.AddSingleton(); - - services.AddAuthorization(options => - { - options.AddPolicy(AuthorizationPolicies.UmbracoFeatureEnabled, policy => - policy.Requirements.Add(new FeatureAuthorizeRequirement())); - }); - } - } - -} diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index d16015101d..eb7c8f5c04 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -4,8 +4,12 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Umbraco.Core.DependencyInjection; +using Microsoft.Identity.Web; using Umbraco.Extensions; +using Umbraco.Web.BackOffice.DependencyInjection; +using Umbraco.Web.BackOffice.Security; +using Umbraco.Web.Common.DependencyInjection; +using Umbraco.Web.Website.DependencyInjection; namespace Umbraco.Web.UI.NetCore { @@ -29,6 +33,7 @@ namespace Umbraco.Web.UI.NetCore } + /// /// Configures the services /// @@ -40,10 +45,11 @@ namespace Umbraco.Web.UI.NetCore { #pragma warning disable IDE0022 // Use expression body for methods services.AddUmbraco(_env, _config) - .AddAllBackOfficeComponents() - .AddUmbracoWebsite() + .AddBackOffice() + .AddWebsite() .Build(); #pragma warning restore IDE0022 // Use expression body for methods + } /// diff --git a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs index 438ad154ed..5ceb5e523f 100644 --- a/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/UmbracoWebsiteApplicationBuilderExtensions.cs @@ -1,7 +1,6 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using SixLabors.ImageSharp.Web.DependencyInjection; using Umbraco.Web.Website.Routing; namespace Umbraco.Extensions diff --git a/src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs deleted file mode 100644 index 204f97ae30..0000000000 --- a/src/Umbraco.Web.Website/Extensions/WebsiteUmbracoBuilderExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Umbraco.Core.DependencyInjection; -using Umbraco.Infrastructure.PublishedCache.Extensions; -using Umbraco.Web.Website.Controllers; -using Umbraco.Web.Website.Routing; -using Umbraco.Web.Website.ViewEngines; - -namespace Umbraco.Extensions -{ - /// - /// extensions for umbraco front-end website - /// - public static class WebsiteUmbracoBuilderExtensions - { - /// - /// Add services for the umbraco front-end website - /// - public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) - { - // 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(); - - // Wraps all existing view engines in a ProfilerViewEngine - builder.Services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>(); - - // TODO figure out if we need more to work on load balanced setups - builder.Services.AddDataProtection(); - - builder.Services.AddScoped(); - builder.Services.AddSingleton(); - - builder.AddNuCache(); - - return builder; - } - - } -} From e62edc7bef567b99f939b08994b165fb04e30d3a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 12:02:23 +1100 Subject: [PATCH 041/127] missing file --- .../UmbracoBuilderExtensions.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs new file mode 100644 index 0000000000..6f34ca6136 --- /dev/null +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Umbraco.Core.DependencyInjection; +using Umbraco.Infrastructure.PublishedCache.Extensions; +using Umbraco.Web.Website.Controllers; +using Umbraco.Web.Website.Routing; +using Umbraco.Web.Website.ViewEngines; + +namespace Umbraco.Web.Website.DependencyInjection +{ + /// + /// extensions for umbraco front-end website + /// + public static partial class UmbracoBuilderExtensions + { + /// + /// Add services for the umbraco front-end website + /// + public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder) + { + // 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(); + + // Wraps all existing view engines in a ProfilerViewEngine + builder.Services.AddTransient, ProfilingViewEngineWrapperMvcViewOptionsSetup>(); + + // TODO figure out if we need more to work on load balanced setups + builder.Services.AddDataProtection(); + + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + + builder.AddNuCache(); + + return builder; + } + + } +} From 696dd0b8dc9e2046bd52b4ba20c2d151b0b55bba Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 12:11:38 +1100 Subject: [PATCH 042/127] removes partial keyword --- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index 9c1d64aba8..dc85644239 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -14,7 +14,7 @@ namespace Umbraco.Web.BackOffice.DependencyInjection /// /// Extension methods for for the Umbraco back office /// - public static partial class UmbracoBuilderExtensions + public static class UmbracoBuilderExtensions { /// /// Adds all required components to run the Umbraco back office diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index c524cb064d..39a3eb3dce 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.Common.DependencyInjection /// /// Extension methods for for the common Umbraco functionality /// - public static partial class UmbracoBuilderExtensions + public static class UmbracoBuilderExtensions { public static IUmbracoBuilder AddUmbraco( this IServiceCollection services, diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 6f34ca6136..579950c23a 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Website.DependencyInjection /// /// extensions for umbraco front-end website /// - public static partial class UmbracoBuilderExtensions + public static class UmbracoBuilderExtensions { /// /// Add services for the umbraco front-end website From e8379d6c779c136167824e8e24322be3960ae55d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 12:27:47 +1100 Subject: [PATCH 043/127] oops, trying to fix build --- src/Umbraco.Web.UI.NetCore/Startup.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index eb7c8f5c04..75212e3077 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Identity.Web; using Umbraco.Extensions; using Umbraco.Web.BackOffice.DependencyInjection; using Umbraco.Web.BackOffice.Security; From d5a19530f38ad2016efe00fac1a230d8667fea50 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 13:06:22 +1100 Subject: [PATCH 044/127] Fixing tests, more DependencyInjection namespace --- .../UmbracoBuilderExtensions.cs} | 4 ++-- .../Controllers/BackOfficeAssetsControllerTests.cs | 4 ++-- .../TestServerTest/UmbracoTestServerTestBase.cs | 2 ++ .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- .../DependencyInjection/UmbracoBuilderExtensions.cs | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) rename src/Umbraco.PublishedCache.NuCache/{Extensions/NuCacheUmbracoBuilderExtensions.cs => DependencyInjection/UmbracoBuilderExtensions.cs} (95%) diff --git a/src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs similarity index 95% rename from src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs rename to src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs index ee0b0e2630..719d014296 100644 --- a/src/Umbraco.PublishedCache.NuCache/Extensions/NuCacheUmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -9,12 +9,12 @@ using Umbraco.Infrastructure.PublishedCache.Persistence; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; -namespace Umbraco.Infrastructure.PublishedCache.Extensions +namespace Umbraco.Infrastructure.PublishedCache.DependencyInjection { /// /// Extension methods for for the Umbraco's NuCache /// - public static class NuCacheUmbracoBuilderExtensions + public static class UmbracoBuilderExtensions { /// /// Adds Umbraco NuCache dependencies diff --git a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/BackOfficeAssetsControllerTests.cs b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/BackOfficeAssetsControllerTests.cs index 7c9ab2daf3..9d49ec22fa 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/Controllers/BackOfficeAssetsControllerTests.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/Controllers/BackOfficeAssetsControllerTests.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Tests.Testing; @@ -7,7 +7,7 @@ using Umbraco.Web.BackOffice.Controllers; namespace Umbraco.Tests.Integration.TestServerTest.Controllers { [TestFixture] - public class BackOfficeAssetsControllerTests: UmbracoTestServerTestBase + public class BackOfficeAssetsControllerTests : UmbracoTestServerTestBase { [Test] public async Task EnsureSuccessStatusCode() diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index 416d0f5835..ef3735032e 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -15,6 +15,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.DependencyInjection; using Umbraco.Extensions; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web; @@ -153,6 +154,7 @@ namespace Umbraco.Tests.Integration.TestServerTest mvcBuilder.AddApplicationPart(typeof(SurfaceController).Assembly); }) .AddWebServer() + .AddNuCache() .Build(); } diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs index dc85644239..e8c6171f01 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,7 +2,7 @@ using System; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core.DependencyInjection; -using Umbraco.Infrastructure.PublishedCache.Extensions; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Web.BackOffice.Authorization; using Umbraco.Web.BackOffice.Security; using Umbraco.Web.BackOffice.Trees; diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs index 579950c23a..f495c09d3c 100644 --- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Core.DependencyInjection; -using Umbraco.Infrastructure.PublishedCache.Extensions; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; using Umbraco.Web.Website.ViewEngines; From 534b74dda5bcc3bfa7073221303b6536edb3a618 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 13:57:41 +1100 Subject: [PATCH 045/127] Changes CustomTestSetup to just be a normal method, not sure why it was an action, fixes ContentEventsTests which requires NuCache --- .../Testing/UmbracoTestAttribute.cs | 3 +-- .../Testing/UmbracoIntegrationTest.cs | 5 ++-- .../Scoping/ScopedRepositoryTests.cs | 7 ++--- .../Services/ContentEventsTests.cs | 26 ++++++------------- ...kOfficeServiceCollectionExtensionsTests.cs | 7 +++-- 5 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs b/src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs index f5983ddca6..4c1101d6f1 100644 --- a/src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs +++ b/src/Umbraco.Tests.Common/Testing/UmbracoTestAttribute.cs @@ -24,9 +24,8 @@ namespace Umbraco.Tests.Testing public bool Mapper { get => _mapper.ValueOrDefault(WithApplication); set => _mapper.Set(value); } private readonly Settable _mapper = new Settable(); - // FIXME: to be completed /// - /// Gets or sets a value indicating ... + /// Gets or sets a value indicating whether the LEGACY XML Cache used in tests should bind to repository events /// public bool PublishedRepositoryEvents { get => _publishedRepositoryEvents.ValueOrDefault(false); set => _publishedRepositoryEvents.Set(value); } private readonly Settable _publishedRepositoryEvents = new Settable(); diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 3d50e72b9a..d8f27d27a7 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.PublishedCache.DependencyInjection; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.Extensions; using Umbraco.Tests.Integration.Implementations; @@ -232,7 +233,7 @@ namespace Umbraco.Tests.Integration.Testing builder.Build(); - CustomTestSetup(services); + CustomTestSetup(builder); } protected virtual AppCaches GetAppCaches() @@ -402,7 +403,7 @@ namespace Umbraco.Tests.Integration.Testing public TestHelper TestHelper { get; } = new TestHelper(); - protected virtual Action CustomTestSetup => services => { }; + protected virtual void CustomTestSetup(IUmbracoBuilder builder) { } /// /// Returns the DI container diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index 5542f926e1..f5301fb7f6 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; @@ -6,6 +6,7 @@ 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; @@ -31,8 +32,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Scoping private IServerMessenger ServerMessenger => GetRequiredService(); private CacheRefresherCollection CacheRefresherCollection => GetRequiredService(); - protected override Action CustomTestSetup => (services) - => services.Replace(ServiceDescriptor.Singleton(typeof(IServerMessenger), typeof(LocalServerMessenger))); + protected override void CustomTestSetup(IUmbracoBuilder builder) + => builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IServerMessenger), typeof(LocalServerMessenger))); 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 d5f9f41cbb..80bf464c31 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEventsTests.cs @@ -1,17 +1,20 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; -using Microsoft.Extensions.Logging; +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 @@ -21,14 +24,13 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services public class ContentEventsTests : UmbracoIntegrationTestWithContent { private CacheRefresherCollection CacheRefresherCollection => GetRequiredService(); + private IUmbracoContextFactory UmbracoContextFactory => GetRequiredService(); + private ILogger Logger => GetRequiredService>(); #region Setup - // trace ContentRepository unit-of-work events (refresh, remove), and ContentCacheRefresher CacheUpdated event - // - [SetUp] public void SetUp() { @@ -52,19 +54,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services ContentTypeService.Save(_contentType); } - // protected override void Compose() - // { - // base.Compose(); - // - // Composition.Register(_ => new TestServerRegistrar()); // localhost-only - // composition.Services.AddUnique(); - // - // Composition.WithCollectionBuilder() - // .Add() - // .Add() - // .Add(); - // } - + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddNuCache(); [TearDown] public void TearDownTest() diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs index b942e57e46..37863da472 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/UmbracoBackOfficeServiceCollectionExtensionsTests.cs @@ -1,17 +1,18 @@ -using System; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Security; using Umbraco.Tests.Integration.Testing; using Umbraco.Web.BackOffice.DependencyInjection; -using Umbraco.Web.Common.Security; namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice { [TestFixture] public class UmbracoBackOfficeServiceCollectionExtensionsTests : UmbracoIntegrationTest { + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.Services.AddUmbracoBackOfficeIdentity(); + [Test] public void AddUmbracoBackOfficeIdentity_ExpectBackOfficeUserStoreResolvable() { @@ -37,7 +38,5 @@ namespace Umbraco.Tests.Integration.Umbraco.Web.BackOffice Assert.NotNull(userManager); } - - protected override Action CustomTestSetup => (services) => services.AddUmbracoBackOfficeIdentity(); } } From 544753112ea0a12b9fd34ab2bbf9b8baa37f4c4d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 14:02:05 +1100 Subject: [PATCH 046/127] fixing more tests by enabling nucache explicitly where required --- .../Services/ContentTypeServiceVariantsTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs index 5e50f570ab..010f680ad1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs @@ -7,11 +7,13 @@ using Microsoft.Extensions.Hosting; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; +using Umbraco.Core.DependencyInjection; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Services; using Umbraco.Core.Sync; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; using Umbraco.Net; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.Testing; @@ -36,6 +38,8 @@ 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); From a70951b382cde5a11290ca1545fbbc6c31608f65 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 23 Dec 2020 14:28:03 +1100 Subject: [PATCH 047/127] Fixes (hopefully last) tests --- .../UmbracoIntegrationTestWithContent.cs | 10 +-- .../Scoping/ScopedRepositoryTests.cs | 6 +- .../Services/ContentServiceTests.cs | 77 ++++--------------- .../Services/TrackRelationsTests.cs | 74 ++++++++++++++++++ 4 files changed, 101 insertions(+), 66 deletions(-) create mode 100644 src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs index ecfc829427..2feccbcccb 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTestWithContent.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -10,17 +10,17 @@ namespace Umbraco.Tests.Integration.Testing public abstract class UmbracoIntegrationTestWithContent : UmbracoIntegrationTest { protected IContentTypeService ContentTypeService => GetRequiredService(); + protected IFileService FileService => GetRequiredService(); + protected ContentService ContentService => (ContentService)GetRequiredService(); [SetUp] - public void Setup(){ - CreateTestData(); - } + public void Setup() => CreateTestData(); public virtual void CreateTestData() { - //NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested. + // NOTE Maybe not the best way to create/save test data as we are using the services, which are being tested. var template = TemplateBuilder.CreateTextPageTemplate(); FileService.SaveTemplate(template); diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs index f5301fb7f6..11f4095f53 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/ScopedRepositoryTests.cs @@ -14,6 +14,7 @@ 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; @@ -33,7 +34,10 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Scoping private CacheRefresherCollection CacheRefresherCollection => GetRequiredService(); protected override void CustomTestSetup(IUmbracoBuilder builder) - => builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IServerMessenger), typeof(LocalServerMessenger))); + { + builder.AddNuCache(); + builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IServerMessenger), typeof(LocalServerMessenger))); + } protected override AppCaches GetAppCaches() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index 837536d362..c9f8dc5391 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -22,45 +22,50 @@ using Umbraco.Tests.Testing; namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { + /// /// Tests covering all methods in the ContentService class. /// [TestFixture] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, + [UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true, WithApplication = true)] public class ContentServiceTests : UmbracoIntegrationTestWithContent { // TODO: Add test to verify there is only ONE newest document/content in {Constants.DatabaseSchema.Tables.Document} table after updating. // TODO: Add test to delete specific version (with and without deleting prior versions) and versions by date. - - private IMediaTypeService MediaTypeService => GetRequiredService(); - private MediaService MediaService => (MediaService)GetRequiredService(); private IDataTypeService DataTypeService => GetRequiredService(); + private ILocalizationService LocalizationService => GetRequiredService(); + private IAuditService AuditService => GetRequiredService(); + private IUserService UserService => GetRequiredService(); + private IRelationService RelationService => GetRequiredService(); + private ILocalizedTextService TextService => GetRequiredService(); + private ITagService TagService => GetRequiredService(); + private IPublicAccessService PublicAccessService => GetRequiredService(); + private IDomainService DomainService => GetRequiredService(); + private INotificationService NotificationService => GetRequiredService(); + private PropertyEditorCollection PropertyEditorCollection => GetRequiredService(); + private IDocumentRepository DocumentRepository => GetRequiredService(); + private IJsonSerializer Serializer => GetRequiredService(); [SetUp] - public void Setup() - { - ContentRepositoryBase.ThrowOnWarning = true; - } + public void Setup() => ContentRepositoryBase.ThrowOnWarning = true; [TearDown] - public void Teardown() - { - ContentRepositoryBase.ThrowOnWarning = false; - } + public void Teardown() => ContentRepositoryBase.ThrowOnWarning = false; [Test] public void Create_Blueprint() @@ -444,54 +449,6 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services Assert.That(content.HasIdentity, Is.False); } - [Test] - [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, - PublishedRepositoryEvents = true, - WithApplication = true, - Boot = true)] - public void Automatically_Track_Relations() - { - var mt = MediaTypeBuilder.CreateSimpleMediaType("testMediaType", "Test Media Type"); - MediaTypeService.Save(mt); - var m1 = MediaBuilder.CreateSimpleMedia(mt, "hello 1", -1); - var m2 = MediaBuilder.CreateSimpleMedia(mt, "hello 1", -1); - MediaService.Save(m1); - MediaService.Save(m2); - - var template = TemplateBuilder.CreateTextPageTemplate(); - FileService.SaveTemplate(template); - - var ct = ContentTypeBuilder.CreateTextPageContentType("richTextTest", defaultTemplateId: template.Id); - ct.AllowedTemplates = Enumerable.Empty(); - - ContentTypeService.Save(ct); - - var c1 = ContentBuilder.CreateTextpageContent(ct, "my content 1", -1); - ContentService.Save(c1); - - var c2 = ContentBuilder.CreateTextpageContent(ct, "my content 2", -1); - - //'bodyText' is a property with a RTE property editor which we knows tracks relations - c2.Properties["bodyText"].SetValue(@"

- -

-

-

- hello -

"); - - ContentService.Save(c2); - - var relations = RelationService.GetByParentId(c2.Id).ToList(); - Assert.AreEqual(3, relations.Count); - Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias); - Assert.AreEqual(m1.Id, relations[0].ChildId); - Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias); - Assert.AreEqual(m2.Id, relations[1].ChildId); - Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias); - Assert.AreEqual(c1.Id, relations[2].ChildId); - } - [Test] public void Can_Create_Content_Without_Explicitly_Set_User() { diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs new file mode 100644 index 0000000000..e4095c1d33 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackRelationsTests.cs @@ -0,0 +1,74 @@ +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.DependencyInjection; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Core.Services.Implement; +using Umbraco.Infrastructure.PublishedCache.DependencyInjection; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Integration.Testing; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services +{ + [UmbracoTest( + Database = UmbracoTestOptions.Database.NewSchemaPerTest, + PublishedRepositoryEvents = true, + WithApplication = true, + Boot = true)] + public class TrackRelationsTests : UmbracoIntegrationTestWithContent + { + protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddNuCache(); + + private IMediaTypeService MediaTypeService => GetRequiredService(); + + private IMediaService MediaService => GetRequiredService(); + + private IRelationService RelationService => GetRequiredService(); + + [Test] + public void Automatically_Track_Relations() + { + var mt = MediaTypeBuilder.CreateSimpleMediaType("testMediaType", "Test Media Type"); + MediaTypeService.Save(mt); + var m1 = MediaBuilder.CreateSimpleMedia(mt, "hello 1", -1); + var m2 = MediaBuilder.CreateSimpleMedia(mt, "hello 1", -1); + MediaService.Save(m1); + MediaService.Save(m2); + + var template = TemplateBuilder.CreateTextPageTemplate(); + FileService.SaveTemplate(template); + + var ct = ContentTypeBuilder.CreateTextPageContentType("richTextTest", defaultTemplateId: template.Id); + ct.AllowedTemplates = Enumerable.Empty(); + + ContentTypeService.Save(ct); + + var c1 = ContentBuilder.CreateTextpageContent(ct, "my content 1", -1); + ContentService.Save(c1); + + var c2 = ContentBuilder.CreateTextpageContent(ct, "my content 2", -1); + + // 'bodyText' is a property with a RTE property editor which we knows tracks relations + c2.Properties["bodyText"].SetValue(@"

+ +

+

+

+ hello +

"); + + ContentService.Save(c2); + + var relations = RelationService.GetByParentId(c2.Id).ToList(); + Assert.AreEqual(3, relations.Count); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias); + Assert.AreEqual(m1.Id, relations[0].ChildId); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias); + Assert.AreEqual(m2.Id, relations[1].ChildId); + Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias); + Assert.AreEqual(c1.Id, relations[2].ChildId); + } + } +} From b20ce5a92ea8adcc2b134679ca0c7aae8225495f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 09:50:05 +1100 Subject: [PATCH 048/127] 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 2798f31c67fcc90dcb6ffb180c255d8ba723101d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 09:56:31 +1100 Subject: [PATCH 049/127] fixes test --- src/Umbraco.Tests.Integration/RuntimeTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 9b09f7c562..258f1992ff 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -10,6 +10,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; @@ -65,6 +66,7 @@ namespace Umbraco.Tests.Integration builder.Services.AddUnique(AppCaches.NoCache); builder.AddConfiguration() .AddUmbracoCore() + .AddNuCache() .Build(); services.AddRouting(); // LinkGenerator From 1f6297ad6bb75b0b2dd74d2808885e234465592c Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 24 Dec 2020 11:46:17 +1100 Subject: [PATCH 050/127] 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 051/127] 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 052/127] 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 053/127] 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 054/127] 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 055/127] 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 056/127] 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 057/127] 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 058/127] 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 059/127] 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 060/127] 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 061/127] 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 062/127] 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 063/127] 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 064/127] 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 065/127] 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 066/127] 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 067/127] 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 068/127] 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 069/127] 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 070/127] 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 071/127] 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 072/127] 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 073/127] 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 074/127] 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 075/127] 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 076/127] 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 077/127] 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 078/127] 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 079/127] 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 080/127] 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 081/127] 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 082/127] 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 083/127] 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 084/127] 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 085/127] 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 086/127] 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 087/127] 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 088/127] 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 089/127] 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 090/127] 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 091/127] 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 092/127] 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 093/127] 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 094/127] 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 095/127] 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 096/127] 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 097/127] 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 098/127] 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 099/127] 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 100/127] 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 101/127] 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 102/127] 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 103/127] 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 104/127] 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 105/127] 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 106/127] 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 107/127] 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 108/127] 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 109/127] 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 110/127] 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 111/127] 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 112/127] 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 113/127] 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 114/127] 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 115/127] 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 116/127] 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 117/127] 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 118/127] 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 119/127] 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 120/127] 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 121/127] 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 122/127] 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 123/127] 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 124/127] 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 125/127] 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 126/127] 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 127/127] 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); - } - - } -}