From af42af74259668ef136bee1a61b7086a939cd17f Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Feb 2021 16:54:19 +1100 Subject: [PATCH 01/10] Simplify UmbracoRouteValues since this is an http request feature which dictates what an Umbraco route is --- .../Routing/UmbracoRouteValues.cs | 9 +-------- .../Routing/UmbracoRouteValuesFactory.cs | 18 ++++++++++-------- .../Mvc/UmbracoVirtualNodeRouteHandler.cs | 6 +++--- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs index 9ac8c1d2e6..20381fea80 100644 --- a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs +++ b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs @@ -23,12 +23,10 @@ namespace Umbraco.Web.Common.Routing public UmbracoRouteValues( IPublishedRequest publishedRequest, ControllerActionDescriptor controllerActionDescriptor, - string templateName = null, - bool hasHijackedRoute = false) + string templateName = null) { PublishedRequest = publishedRequest; ControllerActionDescriptor = controllerActionDescriptor; - HasHijackedRoute = hasHijackedRoute; TemplateName = templateName; } @@ -61,10 +59,5 @@ namespace Umbraco.Web.Common.Routing /// Gets the /// public IPublishedRequest PublishedRequest { 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/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index 000a3e252a..1e0beca333 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -95,9 +95,9 @@ namespace Umbraco.Web.Website.Routing _defaultControllerDescriptor.Value, templateName: customActionName); - def = CheckHijackedRoute(httpContext, def); + def = CheckHijackedRoute(httpContext, def, out bool hasHijackedRoute); - def = CheckNoTemplate(httpContext, def); + def = CheckNoTemplate(httpContext, def, hasHijackedRoute); return def; } @@ -105,7 +105,7 @@ namespace Umbraco.Web.Website.Routing /// /// Check if the route is hijacked and return new route values /// - private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def) + private UmbracoRouteValues CheckHijackedRoute(HttpContext httpContext, UmbracoRouteValues def, out bool hasHijackedRoute) { IPublishedRequest request = def.PublishedRequest; @@ -115,21 +115,23 @@ namespace Umbraco.Web.Website.Routing ControllerActionDescriptor descriptor = _controllerActionSearcher.Find(httpContext, customControllerName, def.TemplateName); if (descriptor != null) { + hasHijackedRoute = true; + return new UmbracoRouteValues( request, descriptor, - def.TemplateName, - true); + def.TemplateName); } } + hasHijackedRoute = false; 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(HttpContext httpContext, UmbracoRouteValues def) + private UmbracoRouteValues CheckNoTemplate(HttpContext httpContext, UmbracoRouteValues def, bool hasHijackedRoute) { IPublishedRequest request = def.PublishedRequest; @@ -140,7 +142,7 @@ namespace Umbraco.Web.Website.Routing if (request.HasPublishedContent() && !request.HasTemplate() && !_umbracoFeatures.Disabled.DisableTemplates - && !def.HasHijackedRoute) + && !hasHijackedRoute) { Core.Models.PublishedContent.IPublishedContent content = request.PublishedContent; @@ -164,7 +166,7 @@ namespace Umbraco.Web.Website.Routing // if the content has changed, we must then again check for hijacked routes if (content != request.PublishedContent) { - def = CheckHijackedRoute(httpContext, def); + def = CheckHijackedRoute(httpContext, def, out _); } } diff --git a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index 9d5449f340..1ef111b32d 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -1,13 +1,13 @@ +using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Composing; using Umbraco.Web.Models; using Umbraco.Web.Routing; -using Umbraco.Core; -using Umbraco.Web.Composing; -using System; namespace Umbraco.Web.Mvc { From 996c2b4277bbae5f3bcfa1c6df89fee75a81c9e5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 12 Feb 2021 17:23:14 +1100 Subject: [PATCH 02/10] Changes namespace of UmbracoViewPage --- src/Umbraco.Core/IO/ViewHelper.cs | 4 ++-- .../Services/Implement/FileService.cs | 4 ++-- .../Repositories/TemplateRepositoryTest.cs | 2 +- .../Umbraco.Core/Templates/ViewHelperTests.cs | 16 ++++++++-------- .../Views/UmbracoViewPageTests.cs | 2 +- .../Macros/PartialViewMacroPage.cs | 2 +- .../{AspNetCore => Views}/UmbracoViewPage.cs | 4 +--- .../Views/Partials/blocklist/default.cshtml | 2 +- .../Views/Partials/grid/bootstrap3-fluid.cshtml | 4 ++-- .../Views/Partials/grid/bootstrap3.cshtml | 4 ++-- .../Views/Partials/grid/editors/embed.cshtml | 4 ++-- .../Views/Partials/grid/editors/macro.cshtml | 2 +- 12 files changed, 24 insertions(+), 26 deletions(-) rename src/Umbraco.Web.Common/{AspNetCore => Views}/UmbracoViewPage.cs (99%) diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 569d8cdfc9..954bdded00 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Text; @@ -70,7 +70,7 @@ namespace Umbraco.Core.IO // @inherits Umbraco.Web.Mvc.UmbracoViewPage // @inherits Umbraco.Web.Mvc.UmbracoViewPage content.AppendLine("@using Umbraco.Web.PublishedModels;"); - content.Append("@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage"); + content.Append("@inherits Umbraco.Web.Common.Views.UmbracoViewPage"); if (modelClassName.IsNullOrWhiteSpace() == false) { content.Append("<"); diff --git a/src/Umbraco.Infrastructure/Services/Implement/FileService.cs b/src/Umbraco.Infrastructure/Services/Implement/FileService.cs index e1f4a017b3..c893af6892 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/FileService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/FileService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -34,7 +34,7 @@ namespace Umbraco.Core.Services.Implement private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; - private const string PartialViewHeader = "@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage"; + private const string PartialViewHeader = "@inherits Umbraco.Web.Common.Views.UmbracoViewPage"; private const string PartialViewMacroHeader = "@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage"; public FileService(IScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, 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 9780b53997..0f8f15e9f4 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -122,7 +122,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.That(repository.Get("test2"), Is.Not.Null); Assert.That(FileSystems.MvcViewsFileSystem.FileExists("test2.cshtml"), Is.True); Assert.AreEqual( - "@usingUmbraco.Web.PublishedModels;@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage @{ Layout = \"test.cshtml\";}".StripWhitespace(), + "@usingUmbraco.Web.PublishedModels;@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ Layout = \"test.cshtml\";}".StripWhitespace(), template2.Content.StripWhitespace()); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs index 85c96cf5be..73eae0d51c 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using NUnit.Framework; @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ Layout = null; }"), FixView(view)); @@ -27,7 +27,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ Layout = ""Dharznoik.cshtml""; }"), FixView(view)); @@ -39,7 +39,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ Layout = null; }"), FixView(view)); @@ -51,7 +51,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(modelNamespace: "Models"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ Layout = null; }"), FixView(view)); @@ -63,7 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @using ContentModels = My.Models; @{ Layout = null; @@ -76,7 +76,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @using MyModels = My.Models; @{ Layout = null; @@ -89,7 +89,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Templates var view = ViewHelper.GetDefaultFileContent(layoutPageAlias: "Dharznoik", modelClassName: "ClassName", modelNamespace: "My.Models", modelNamespaceAlias: "MyModels"); Assert.AreEqual( FixView(@"@using Umbraco.Web.PublishedModels; -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @using MyModels = My.Models; @{ Layout = ""Dharznoik.cshtml""; 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 b227d8a903..ed26d5d75f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs @@ -10,8 +10,8 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Events; using Umbraco.Core.Models.PublishedContent; -using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.ModelBinders; +using Umbraco.Web.Common.Views; using Umbraco.Web.Models; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Views diff --git a/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs b/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs index ee80c40a53..22c90e8ac2 100644 --- a/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs +++ b/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs @@ -1,4 +1,4 @@ -using Umbraco.Web.Common.AspNetCore; +using Umbraco.Web.Common.Views; using Umbraco.Web.Models; namespace Umbraco.Web.Common.Macros diff --git a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs similarity index 99% rename from src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs rename to src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index 23ae7d7f32..aea63e762e 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -19,10 +19,8 @@ using Umbraco.Web.Common.ModelBinders; using Umbraco.Web.Models; using Umbraco.Web.Website; -namespace Umbraco.Web.Common.AspNetCore +namespace Umbraco.Web.Common.Views { - // TODO: Should be in Views namespace? - public abstract class UmbracoViewPage : UmbracoViewPage { diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml index 0ad9fd83c3..c80a5f5400 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/blocklist/default.cshtml @@ -1,4 +1,4 @@ -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ if (!Model.Any()) { return; } } diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml index 30f55f2058..d2e1f76eee 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3-fluid.cshtml @@ -1,7 +1,7 @@ -@using System.Web +@using System.Web @using Microsoft.AspNetCore.Html @using Newtonsoft.Json.Linq -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @* Razor helpers located at the bottom of this file diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml index 68ded16619..7d0c17becb 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml @@ -1,7 +1,7 @@ -@using System.Web +@using System.Web @using Microsoft.AspNetCore.Html @using Newtonsoft.Json.Linq -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @if (Model != null && Model.sections != null) { diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml index 1cb413ef06..dfb7399ef9 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml @@ -1,5 +1,5 @@ -@using Umbraco.Core -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@ using Umbraco.Core +@inherits UmbracoViewPage @{ string embedValue = Convert.ToString(Model.value); diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml index 87f6ec04af..ee24207e5d 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml @@ -1,4 +1,4 @@ -@inherits Umbraco.Web.Common.AspNetCore.UmbracoViewPage +@inherits UmbracoViewPage @if (Model.value != null) { From 4f2682678e3fb21c920651a35b77b79fa92b85e5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 15 Feb 2021 18:50:16 +1100 Subject: [PATCH 03/10] Gets virtual page routing working, now just need to document. --- .../Routing/ControllerActionSearcherTests.cs | 4 +- .../Trees/ApplicationTreeController.cs | 47 ++++++-- .../BackOfficeApplicationModelProvider.cs | 33 +++--- .../BackOfficeIdentityCultureConvention.cs | 8 +- ...racoApiBehaviorApplicationModelProvider.cs | 26 +++-- .../UmbracoJsonModelBinderConvention.cs | 12 +- .../VirtualPageApplicationModelProvider.cs | 61 ++++++++++ .../VirtualPageConvention.cs | 16 +++ .../Controllers/IVirtualPageController.cs | 15 +++ .../Controllers/RenderController.cs | 87 +-------------- .../Controllers/UmbracoPageController.cs | 104 ++++++++++++++++++ .../UmbracoBuilderExtensions.cs | 6 + .../UmbracoStartupFilter.cs | 22 ++++ ...tionEndpointConventionBuilderExtensions.cs | 45 ++++++++ .../UmbracoVirtualPageFilterAttribute.cs | 65 +++++++++++ .../CustomRouteContentFinderDelegate.cs | 15 +++ .../Routing/RoutableDocumentFilter.cs | 14 ++- src/Umbraco.Web.UI.NetCore/Startup.cs | 1 - 18 files changed, 447 insertions(+), 134 deletions(-) create mode 100644 src/Umbraco.Web.Common/ApplicationModels/VirtualPageApplicationModelProvider.cs create mode 100644 src/Umbraco.Web.Common/ApplicationModels/VirtualPageConvention.cs create mode 100644 src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs create mode 100644 src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs create mode 100644 src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs create mode 100644 src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs create mode 100644 src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs create mode 100644 src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs index d5d3b3e26b..7c96738a1e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs @@ -53,8 +53,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing private class Render2Controller : RenderController { - public Render2Controller(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) - : base(logger, compositeViewEngine, umbracoContextAccessor) + public Render2Controller(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(loggerFactory, compositeViewEngine, umbracoContextAccessor) { } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index ec90965455..c938dced9d 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -35,14 +35,15 @@ namespace Umbraco.Web.BackOffice.Trees private readonly IControllerFactory _controllerFactory; private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; - + /// + /// Initializes a new instance of the class. + /// public ApplicationTreeController( ITreeService treeService, ISectionService sectionService, ILocalizedTextService localizedTextService, IControllerFactory controllerFactory, - IActionDescriptorCollectionProvider actionDescriptorCollectionProvider - ) + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) { _treeService = treeService; _sectionService = sectionService; @@ -56,28 +57,31 @@ namespace Umbraco.Web.BackOffice.Trees /// /// The application to load tree for /// An optional single tree alias, if specified will only load the single tree for the request app - /// + /// The query strings /// Tree use. - /// public async Task> GetApplicationTrees(string application, string tree, [ModelBinder(typeof(HttpQueryStringModelBinder))] FormCollection queryStrings, TreeUse use = TreeUse.Main) { application = application.CleanForXss(); if (string.IsNullOrEmpty(application)) + { return NotFound(); + } var section = _sectionService.GetByAlias(application); if (section == null) + { return NotFound(); + } - //find all tree definitions that have the current application alias + // find all tree definitions that have the current application alias var groupedTrees = _treeService.GetBySectionGrouped(application, use); var allTrees = groupedTrees.Values.SelectMany(x => x).ToList(); if (allTrees.Count == 0) { - //if there are no trees defined for this section but the section is defined then we can have a simple - //full screen section without trees + // if there are no trees defined for this section but the section is defined then we can have a simple + // full screen section without trees var name = _localizedTextService.Localize("sections/" + application); return TreeRootNode.CreateSingleTreeRoot(Constants.System.RootString, null, null, name, TreeNodeCollection.Empty, true); } @@ -90,7 +94,9 @@ namespace Umbraco.Web.BackOffice.Trees : allTrees.FirstOrDefault(x => x.TreeAlias == tree); if (t == null) + { return NotFound(); + } var treeRootNode = await GetTreeRootNode(t, Constants.System.Root, queryStrings); @@ -114,9 +120,12 @@ namespace Umbraco.Web.BackOffice.Trees { return nodeResult.Result; } + var node = nodeResult.Value; if (node != null) + { nodes.Add(node); + } } var name = _localizedTextService.Localize("sections/" + application); @@ -148,11 +157,15 @@ namespace Umbraco.Web.BackOffice.Trees var node = nodeResult.Value; if (node != null) + { nodes.Add(node); + } } if (nodes.Count == 0) + { continue; + } // no name => third party // use localization key treeHeaders/thirdPartyGroup @@ -179,7 +192,9 @@ namespace Umbraco.Web.BackOffice.Trees private async Task> TryGetRootNode(Tree tree, FormCollection querystring) { if (tree == null) + { throw new ArgumentNullException(nameof(tree)); + } return await GetRootNode(tree, querystring); } @@ -190,7 +205,9 @@ namespace Umbraco.Web.BackOffice.Trees private async Task> GetTreeRootNode(Tree tree, int id, FormCollection querystring) { if (tree == null) + { throw new ArgumentNullException(nameof(tree)); + } var childrenResult = await GetChildren(tree, id, querystring); if (!(childrenResult.Result is null)) @@ -222,7 +239,9 @@ namespace Umbraco.Web.BackOffice.Trees sectionRoot.Path = rootNode.Path; foreach (var d in rootNode.AdditionalData) + { sectionRoot.AdditionalData[d.Key] = d.Value; + } return sectionRoot; } @@ -233,7 +252,9 @@ namespace Umbraco.Web.BackOffice.Trees private async Task> GetRootNode(Tree tree, FormCollection querystring) { if (tree == null) + { throw new ArgumentNullException(nameof(tree)); + } var result = await GetApiControllerProxy(tree.TreeControllerType, "GetRootNode", querystring); @@ -253,7 +274,10 @@ namespace Umbraco.Web.BackOffice.Trees var rootNode = rootNodeResult.Value; if (rootNode == null) + { throw new InvalidOperationException($"Failed to get root node for tree \"{tree.TreeAlias}\"."); + } + return rootNode; } @@ -263,7 +287,9 @@ namespace Umbraco.Web.BackOffice.Trees private async Task> GetChildren(Tree tree, int id, FormCollection querystring) { 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, @@ -299,7 +325,9 @@ namespace Umbraco.Web.BackOffice.Trees // 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. + // TODO: We have a method for this: ControllerExtensions.GetControllerName var controllerName = controllerType.Name.Substring(0, controllerType.Name.Length - 10); // remove controller part of name; + // create proxy route data specifying the action & controller to execute var routeData = new RouteData(new RouteValueDictionary() { @@ -324,9 +352,12 @@ namespace Umbraco.Web.BackOffice.Trees var proxyControllerContext = new ControllerContext(actionContext); var controller = (TreeController)_controllerFactory.CreateController(proxyControllerContext); + // TODO: What about other filters? Will they execute? var isAllowed = await controller.ControllerContext.InvokeAuthorizationFiltersForRequest(actionContext); if (!isAllowed) + { return Forbid(); + } return controller; } diff --git a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs index 11d82d4db5..4d75e2219f 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs @@ -1,11 +1,12 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Web.Common.Attributes; namespace Umbraco.Web.Common.ApplicationModels { + // TODO: This should just exist in the back office project /// @@ -13,45 +14,43 @@ namespace Umbraco.Web.Common.ApplicationModels /// public class BackOfficeApplicationModelProvider : IApplicationModelProvider { - public BackOfficeApplicationModelProvider(IModelMetadataProvider modelMetadataProvider) + private readonly List _actionModelConventions = new List() { - ActionModelConventions = new List() - { - new BackOfficeIdentityCultureConvention() - }; - } + new BackOfficeIdentityCultureConvention() + }; + /// /// /// Will execute after /// public int Order => 0; - public List ActionModelConventions { get; } - + /// public void OnProvidersExecuted(ApplicationModelProviderContext context) { } + /// public void OnProvidersExecuting(ApplicationModelProviderContext context) { - foreach (var controller in context.Result.Controllers) + foreach (ControllerModel controller in context.Result.Controllers) { if (!IsBackOfficeController(controller)) - continue; - - foreach (var action in controller.Actions) { - foreach (var convention in ActionModelConventions) + continue; + } + + foreach (ActionModel action in controller.Actions) + { + foreach (IActionModelConvention convention in _actionModelConventions) { convention.Apply(action); } } - } } private bool IsBackOfficeController(ControllerModel controller) => controller.Attributes.OfType().Any(); - } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs index 0a5a1f9945..ffbb76dd0d 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationModels; using Umbraco.Web.Common.Filters; namespace Umbraco.Web.Common.ApplicationModels @@ -8,9 +8,7 @@ namespace Umbraco.Web.Common.ApplicationModels public class BackOfficeIdentityCultureConvention : IActionModelConvention { - public void Apply(ActionModel action) - { - action.Filters.Add(new BackOfficeCultureFilter()); - } + /// + public void Apply(ActionModel action) => action.Filters.Add(new BackOfficeCultureFilter()); } } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs index be296969e7..edf4571a7e 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -1,8 +1,8 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Core; using Umbraco.Web.Common.Attributes; @@ -27,13 +27,18 @@ namespace Umbraco.Web.Common.ApplicationModels /// public class UmbracoApiBehaviorApplicationModelProvider : IApplicationModelProvider { + private readonly List _actionModelConventions; + + /// + /// Initializes a new instance of the class. + /// public UmbracoApiBehaviorApplicationModelProvider(IModelMetadataProvider modelMetadataProvider) { // see see https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute // for what these things actually do // NOTE: we don't have attribute routing requirements and we cannot use ApiVisibilityConvention without attribute routing - ActionModelConventions = new List() + _actionModelConventions = new List() { new ClientErrorResultFilterConvention(), // Ensures the responses without any body is converted into a simple json object with info instead of a string like "Status Code: 404; Not Found" new ConsumesConstraintForFormFileParameterConvention(), // If an controller accepts files, it must accept multipart/form-data. @@ -47,32 +52,33 @@ namespace Umbraco.Web.Common.ApplicationModels // TODO: Need to determine exactly how this affects errors var defaultErrorType = typeof(ProblemDetails); var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); - ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); + _actionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); } + /// /// /// Will execute after /// public int Order => 0; - public List ActionModelConventions { get; } - + /// public void OnProvidersExecuted(ApplicationModelProviderContext context) { } + /// public void OnProvidersExecuting(ApplicationModelProviderContext context) { foreach (var controller in context.Result.Controllers) { if (!IsUmbracoApiController(controller)) + { continue; - - + } foreach (var action in controller.Actions) { - foreach (var convention in ActionModelConventions) + foreach (var convention in _actionModelConventions) { convention.Apply(action); } diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs index 5a9e3ff90d..affcc2e7e5 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs @@ -1,9 +1,6 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Linq; -using Umbraco.Web.Common.Attributes; -using Umbraco.Web.Actions; -using Umbraco.Web.Common.Filters; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Web.Common.ModelBinders; namespace Umbraco.Web.Common.ApplicationModels @@ -16,14 +13,13 @@ namespace Umbraco.Web.Common.ApplicationModels /// public class UmbracoJsonModelBinderConvention : IActionModelConvention { + /// public void Apply(ActionModel action) { - foreach (var p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body)) + foreach (ParameterModel p in action.Parameters.Where(p => p.BindingInfo?.BindingSource == BindingSource.Body)) { p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder); } } } - - } diff --git a/src/Umbraco.Web.Common/ApplicationModels/VirtualPageApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/VirtualPageApplicationModelProvider.cs new file mode 100644 index 0000000000..62867d045b --- /dev/null +++ b/src/Umbraco.Web.Common/ApplicationModels/VirtualPageApplicationModelProvider.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Controllers; + +namespace Umbraco.Web.Common.ApplicationModels +{ + /// + /// Applies the to any action on a controller that is + /// + public class VirtualPageApplicationModelProvider : IApplicationModelProvider + { + private readonly List _actionModelConventions = new List() + { + new VirtualPageConvention() + }; + + /// + /// + /// Will execute after + /// + public int Order => 0; + + /// + public void OnProvidersExecuted(ApplicationModelProviderContext context) { } + + /// + public void OnProvidersExecuting(ApplicationModelProviderContext context) + { + foreach (ControllerModel controller in context.Result.Controllers) + { + if (!IsVirtualPageController(controller)) + { + continue; + } + + foreach (ActionModel action in controller.Actions.ToList()) + { + if (action.ActionName == nameof(IVirtualPageController.FindContent) + && action.ActionMethod.ReturnType == typeof(IPublishedContent)) + { + // this is not an action, it's just the implementation of IVirtualPageController + controller.Actions.Remove(action); + } + else + { + foreach (IActionModelConvention convention in _actionModelConventions) + { + convention.Apply(action); + } + } + } + } + } + + private bool IsVirtualPageController(ControllerModel controller) + => controller.ControllerType.Implements(); + } +} diff --git a/src/Umbraco.Web.Common/ApplicationModels/VirtualPageConvention.cs b/src/Umbraco.Web.Common/ApplicationModels/VirtualPageConvention.cs new file mode 100644 index 0000000000..d35af70bb0 --- /dev/null +++ b/src/Umbraco.Web.Common/ApplicationModels/VirtualPageConvention.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Filters; + +namespace Umbraco.Web.Common.ApplicationModels +{ + /// + /// Adds the as a convention + /// + public class VirtualPageConvention : IActionModelConvention + { + /// + public void Apply(ActionModel action) => action.Filters.Add(new UmbracoVirtualPageFilterAttribute()); + } +} diff --git a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs new file mode 100644 index 0000000000..fc36207ee0 --- /dev/null +++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Common.Controllers +{ + /// + /// Used for custom routed controllers to execute within the context of Umbraco + /// + public interface IVirtualPageController + { + /// + /// Returns the to use as the current page for the request + /// + IPublishedContent FindContent(); + } +} diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 48fe50facc..bfa129df25 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -6,7 +5,6 @@ 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; using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Routing; @@ -15,107 +13,32 @@ using Umbraco.Web.Routing; namespace Umbraco.Web.Common.Controllers { + /// /// Represents the default front-end rendering controller. /// [ModelBindingException] [PublishedRequestFilter] - public class RenderController : UmbracoController, IRenderController + public class RenderController : UmbracoPageController, 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, IUmbracoContextAccessor umbracoContextAccessor) + public RenderController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(loggerFactory, compositeViewEngine) { - _logger = logger; - _compositeViewEngine = compositeViewEngine; + _logger = loggerFactory.CreateLogger(); _umbracoContextAccessor = umbracoContextAccessor; } - /// - /// Gets the current content item. - /// - 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 - /// - protected UmbracoRouteValues UmbracoRouteValues - { - get - { - if (_umbracoRouteValues != null) - { - return _umbracoRouteValues; - } - - _umbracoRouteValues = HttpContext.Features.Get(); - - if (_umbracoRouteValues == null) - { - throw new InvalidOperationException($"No {nameof(UmbracoRouteValues)} feature was found in the HttpContext"); - } - - return _umbracoRouteValues; - } - } - - /// - /// Ensures that a physical view file exists on disk. - /// - /// The view name. - protected bool EnsurePhsyicalViewExists(string template) - { - 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; - } - - /// - /// Gets an action result based on the template name found in the route values and a model. - /// - /// The type of the model. - /// The model. - /// The action result. - /// If the template found in the route values doesn't physically exist and exception is thrown - protected IActionResult CurrentTemplate(T model) - { - if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) - { - throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName); - } - - return View(UmbracoRouteValues.TemplateName, model); - } - /// /// The default action to render the front-end view. /// diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs new file mode 100644 index 0000000000..bc0181412e --- /dev/null +++ b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs @@ -0,0 +1,104 @@ +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Controllers +{ + /// + /// An abstract controller for a front-end Umbraco page + /// + public abstract class UmbracoPageController : UmbracoController + { + private UmbracoRouteValues _umbracoRouteValues; + private readonly ICompositeViewEngine _compositeViewEngine; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + protected UmbracoPageController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine) + { + _logger = loggerFactory.CreateLogger(); + _compositeViewEngine = compositeViewEngine; + } + + /// + /// Gets the + /// + protected virtual UmbracoRouteValues UmbracoRouteValues + { + get + { + if (_umbracoRouteValues != null) + { + return _umbracoRouteValues; + } + + _umbracoRouteValues = HttpContext.Features.Get(); + + if (_umbracoRouteValues == null) + { + throw new InvalidOperationException($"No {nameof(UmbracoRouteValues)} feature was found in the HttpContext"); + } + + return _umbracoRouteValues; + } + } + + /// + /// Gets the current content item. + /// + protected virtual 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 an action result based on the template name found in the route values and a model. + /// + /// The type of the model. + /// The model. + /// The action result. + /// If the template found in the route values doesn't physically exist and exception is thrown + protected IActionResult CurrentTemplate(T model) + { + if (EnsurePhsyicalViewExists(UmbracoRouteValues.TemplateName) == false) + { + throw new InvalidOperationException("No physical template file was found for template " + UmbracoRouteValues.TemplateName); + } + + return View(UmbracoRouteValues.TemplateName, model); + } + + /// + /// Ensures that a physical view file exists on disk. + /// + /// The view name. + protected bool EnsurePhsyicalViewExists(string template) + { + 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; + } + + } +} diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index ba6cbe03a9..e8097335d6 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -57,6 +57,7 @@ using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.Common.DependencyInjection { + // TODO: We could add parameters to configure each of these for flexibility /// @@ -102,6 +103,10 @@ namespace Umbraco.Web.Common.DependencyInjection ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler); + // adds the umbraco startup filter which will call UseUmbraco early on before + // other start filters are applied (depending on the ordering of IStartupFilters in DI). + services.AddTransient(); + return new UmbracoBuilder(services, config, typeLoader, loggerFactory); } @@ -228,6 +233,7 @@ namespace Umbraco.Web.Common.DependencyInjection builder.Services.ConfigureOptions(); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.AddUmbracoImageSharp(builder.Config); // AspNetCore specific services diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs new file mode 100644 index 0000000000..008f8b0b35 --- /dev/null +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoStartupFilter.cs @@ -0,0 +1,22 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Umbraco.Extensions; + +namespace Umbraco.Web.Common.DependencyInjection +{ + /// + /// A registered early in DI so that it executes before any user IStartupFilters + /// to ensure that all Umbraco service and requirements are started correctly and in order. + /// + public sealed class UmbracoStartupFilter : IStartupFilter + { + /// + public Action Configure(Action next) => + app => + { + app.UseUmbraco(); + next(app); + }; + } +} diff --git a/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs new file mode 100644 index 0000000000..becb79a70e --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Filters; + +namespace Umbraco.Web.Common.Extensions +{ + public static class ControllerActionEndpointConventionBuilderExtensions + { + /// + /// Allows for defining a callback to set the returned for the current request for this route + /// + public static void ForUmbracoPage( + this ControllerActionEndpointConventionBuilder builder, + Func findContent) + => builder.Add(convention => + { + // filter out matched endpoints that are suppressed + if (convention.Metadata.OfType().FirstOrDefault()?.SuppressMatching != true) + { + // Get the controller action descriptor + ControllerActionDescriptor actionDescriptor = convention.Metadata.OfType().FirstOrDefault(); + if (actionDescriptor != null) + { + // This is more or less like the IApplicationModelProvider, it allows us to add filters, etc... to the ControllerActionDescriptor + // dynamically. Here we will add our custom virtual page filter along with a callback in the endpoint's metadata + // to execute in order to find the IPublishedContent for the request. + + var filter = new UmbracoVirtualPageFilterAttribute(); + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filter, 0)); + convention.Metadata.Add(filter); + convention.Metadata.Add(new CustomRouteContentFinderDelegate(findContent)); + } + } + }); + } +} diff --git a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs new file mode 100644 index 0000000000..b6b372c14a --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Extensions; +using Umbraco.Web.Common.Routing; +using Umbraco.Web.Routing; + +namespace Umbraco.Web.Common.Filters +{ + /// + /// Used to set the request feature based on the specified (if any) + /// for the custom route. + /// + public class UmbracoVirtualPageFilterAttribute : Attribute, IAsyncActionFilter + { + /// + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + Endpoint endpoint = context.HttpContext.GetEndpoint(); + + // Check if there is any delegate in the metadata of the route, this + // will occur when using the ForUmbraco method during routing. + CustomRouteContentFinderDelegate contentFinder = endpoint.Metadata.OfType().FirstOrDefault(); + + if (contentFinder != null) + { + await SetUmbracoRouteValues(context, contentFinder.FindContent(context.HttpContext)); + } + else + { + // Check if the controller is IVirtualPageController and then use that to FindContent + if (context.Controller is IVirtualPageController ctrl) + { + await SetUmbracoRouteValues(context, ctrl.FindContent()); + } + } + + await next(); + } + + private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent content) + { + if (content != null) + { + IUmbracoContextAccessor umbracoContext = context.HttpContext.RequestServices.GetRequiredService(); + IPublishedRouter router = context.HttpContext.RequestServices.GetRequiredService(); + IPublishedRequestBuilder requestBuilder = await router.CreateRequestAsync(umbracoContext.UmbracoContext.CleanedUmbracoUrl); + requestBuilder.SetPublishedContent(content); + IPublishedRequest publishedRequest = requestBuilder.Build(); + + var routeValues = new UmbracoRouteValues( + publishedRequest, + (ControllerActionDescriptor)context.ActionDescriptor); + + context.HttpContext.Features.Set(routeValues); + } + } + } +} diff --git a/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs new file mode 100644 index 0000000000..e81f5f75f8 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.AspNetCore.Http; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Web.Common.Extensions +{ + internal class CustomRouteContentFinderDelegate + { + private readonly Func _findContent; + + public CustomRouteContentFinderDelegate(Func findContent) => _findContent = findContent; + + public IPublishedContent FindContent(HttpContext httpContext) => _findContent(httpContext); + } +} diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 18190f9ad9..71c01be5e0 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -178,7 +178,19 @@ namespace Umbraco.Web.Common.Routing // 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; + if (isDynamic) + { + return false; + } + + // filter out matched endpoints that are suppressed + var isSuppressed = x.Metadata.OfType().FirstOrDefault()?.SuppressMatching == true; + if (isSuppressed) + { + return false; + } + + return true; }); var routeValues = new RouteValueDictionary(); diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 7f2ede1845..c3d3d18451 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -64,7 +64,6 @@ namespace Umbraco.Web.UI.NetCore app.UseDeveloperExceptionPage(); } - app.UseUmbraco(); app.UseUmbracoBackOffice(); app.UseUmbracoWebsite(); } From f40a6be9b659fc67960e7a38f78458cfdbafa50f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Feb 2021 12:19:25 +1100 Subject: [PATCH 04/10] Remove EnsurePublishedContentRequestAttribute --- .../EnsurePublishedContentRequestAttribute.cs | 130 ------------------ src/Umbraco.Web/Umbraco.Web.csproj | 1 - 2 files changed, 131 deletions(-) delete mode 100644 src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs diff --git a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs deleted file mode 100644 index 8d044ea8dd..0000000000 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Web.Routing; -using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.Models.PublishedContent; -using Current = Umbraco.Web.Composing.Current; - -namespace Umbraco.Web.Mvc -{ - /// - /// Used for custom routed pages that are being integrated with the Umbraco data but are not - /// part of the umbraco request pipeline. This allows umbraco macros to be able to execute in this scenario. - /// - /// - /// This is inspired from this discussion: - /// https://our.umbraco.com/forum/developers/extending-umbraco/41367-Umbraco-6-MVC-Custom-MVC-Route?p=3 - /// - /// which is based on custom routing found here: - /// http://shazwazza.com/post/Custom-MVC-routing-in-Umbraco - /// - public class EnsurePublishedContentRequestAttribute : ActionFilterAttribute - { - // TODO: Need to port this to netcore and figure out if its needed or how this should work (part of a different task) - - //private readonly string _dataTokenName; - //private IUmbracoContextAccessor _umbracoContextAccessor; - //private readonly int? _contentId; - //private IPublishedContentQuery _publishedContentQuery; - - ///// - ///// 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 an explicit content Id to the PublishedRequest that will be created - ///// - ///// - //public EnsurePublishedContentRequestAttribute(int contentId) - //{ - // _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; - //} - - ///// - ///// Constructor - can be used for testing - ///// - ///// - ///// - //public EnsurePublishedContentRequestAttribute(IUmbracoContextAccessor umbracoContextAccessor, string dataTokenName) - //{ - // _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - // _dataTokenName = dataTokenName; - //} - - ///// - ///// Exposes the UmbracoContext - ///// - //protected IUmbracoContext UmbracoContext => _umbracoContextAccessor?.UmbracoContext ?? Current.UmbracoContext; - - //// TODO: try lazy property injection? - //private IPublishedRouter PublishedRouter => Current.Factory.GetRequiredService(); - - //private IPublishedContentQuery PublishedContentQuery => _publishedContentQuery ?? (_publishedContentQuery = Current.Factory.GetRequiredService()); - - //public override void OnActionExecuted(ActionExecutedContext filterContext) - //{ - // base.OnActionExecuted(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; - // } - - // Current.UmbracoContext.PublishedRequest = PublishedRouter.CreateRequest(Current.UmbracoContext); - // ConfigurePublishedContentRequest(Current.UmbracoContext.PublishedRequest, filterContext); - //} - - ///// - ///// 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/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index b56b4b1450..efdcbba045 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -173,7 +173,6 @@ - From 6fcfcb0003d6470da4b0a7bafbfc641d1b3c766f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 16 Feb 2021 07:19:47 +0100 Subject: [PATCH 05/10] https://github.com/umbraco/Umbraco-CMS/issues/9811 Copy static files to publish directory in dotnet template --- build/templates/UmbracoSolution/UmbracoSolution.csproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build/templates/UmbracoSolution/UmbracoSolution.csproj b/build/templates/UmbracoSolution/UmbracoSolution.csproj index 87271c3dd7..278720afef 100644 --- a/build/templates/UmbracoSolution/UmbracoSolution.csproj +++ b/build/templates/UmbracoSolution/UmbracoSolution.csproj @@ -26,6 +26,16 @@ + + true + PreserveNewest + Always + + + true + PreserveNewest + Always + From 9ef8de36e56965285c07a7b130fa461a35512b39 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Feb 2021 18:02:05 +1100 Subject: [PATCH 06/10] Ensures that we don't add duplicate UmbracoVirtualPageFilterAttribute and that we use the ActionExecutingContext as the context during the FindContent operation --- src/Umbraco.Core/Models/ContentModel.cs | 9 ++------- .../Models/ContentModelOfTContent.cs | 8 ++------ .../Controllers/IVirtualPageController.cs | 5 +++-- ...rActionEndpointConventionBuilderExtensions.cs | 16 +++++++++++++--- .../Filters/UmbracoVirtualPageFilterAttribute.cs | 16 +++++++++++++--- .../Routing/CustomRouteContentFinderDelegate.cs | 7 ++++--- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentModel.cs b/src/Umbraco.Core/Models/ContentModel.cs index 9e4a9a3024..5ab2c0b7b3 100644 --- a/src/Umbraco.Core/Models/ContentModel.cs +++ b/src/Umbraco.Core/Models/ContentModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models @@ -11,12 +11,7 @@ namespace Umbraco.Web.Models /// /// Initializes a new instance of the class with a content. /// - /// - public ContentModel(IPublishedContent content) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - Content = content; - } + public ContentModel(IPublishedContent content) => Content = content ?? throw new ArgumentNullException(nameof(content)); /// /// Gets the content. diff --git a/src/Umbraco.Core/Models/ContentModelOfTContent.cs b/src/Umbraco.Core/Models/ContentModelOfTContent.cs index bf4d81dbf3..31c8cbb2c7 100644 --- a/src/Umbraco.Core/Models/ContentModelOfTContent.cs +++ b/src/Umbraco.Core/Models/ContentModelOfTContent.cs @@ -1,4 +1,4 @@ -using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Models { @@ -8,12 +8,8 @@ namespace Umbraco.Web.Models /// /// Initializes a new instance of the class with a content. /// - /// public ContentModel(TContent content) - : base(content) - { - Content = content; - } + : base(content) => Content = content; /// /// Gets the content. diff --git a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs index fc36207ee0..bfea5c8d87 100644 --- a/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs +++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Models.PublishedContent; +using Microsoft.AspNetCore.Mvc.Filters; +using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Common.Controllers { @@ -10,6 +11,6 @@ namespace Umbraco.Web.Common.Controllers /// /// Returns the to use as the current page for the request /// - IPublishedContent FindContent(); + IPublishedContent FindContent(ActionExecutingContext actionExecutingContext); } } diff --git a/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs index becb79a70e..d30436c87b 100644 --- a/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Common.Extensions /// public static void ForUmbracoPage( this ControllerActionEndpointConventionBuilder builder, - Func findContent) + Func findContent) => builder.Add(convention => { // filter out matched endpoints that are suppressed @@ -35,8 +35,18 @@ namespace Umbraco.Web.Common.Extensions // to execute in order to find the IPublishedContent for the request. var filter = new UmbracoVirtualPageFilterAttribute(); - actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filter, 0)); - convention.Metadata.Add(filter); + + // Check if this already contains this filter since we don't want it applied twice. + // This could occur if the controller being routed is IVirtualPageController AND + // is being routed with ForUmbracoPage. In that case, ForUmbracoPage wins + // because the UmbracoVirtualPageFilterAttribute will check for the metadata first since + // that is more explicit and flexible in case the same controller is routed multiple times. + if (!actionDescriptor.FilterDescriptors.Any(x => x.Filter is UmbracoVirtualPageFilterAttribute)) + { + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filter, 0)); + convention.Metadata.Add(filter); + } + convention.Metadata.Add(new CustomRouteContentFinderDelegate(findContent)); } } diff --git a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs index b6b372c14a..988608c2c2 100644 --- a/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs @@ -2,6 +2,7 @@ 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.Filters; using Microsoft.Extensions.DependencyInjection; @@ -30,18 +31,22 @@ namespace Umbraco.Web.Common.Filters if (contentFinder != null) { - await SetUmbracoRouteValues(context, contentFinder.FindContent(context.HttpContext)); + await SetUmbracoRouteValues(context, contentFinder.FindContent(context)); } else { // Check if the controller is IVirtualPageController and then use that to FindContent if (context.Controller is IVirtualPageController ctrl) { - await SetUmbracoRouteValues(context, ctrl.FindContent()); + await SetUmbracoRouteValues(context, ctrl.FindContent(context)); } } - await next(); + // if we've assigned not found, just exit + if (!(context.Result is NotFoundResult)) + { + await next(); + } } private async Task SetUmbracoRouteValues(ActionExecutingContext context, IPublishedContent content) @@ -60,6 +65,11 @@ namespace Umbraco.Web.Common.Filters context.HttpContext.Features.Set(routeValues); } + else + { + // if there is no content then it should be a not found + context.Result = new NotFoundResult(); + } } } } diff --git a/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs index e81f5f75f8..5be3b4b952 100644 --- a/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs +++ b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs @@ -1,15 +1,16 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Web.Common.Extensions { internal class CustomRouteContentFinderDelegate { - private readonly Func _findContent; + private readonly Func _findContent; - public CustomRouteContentFinderDelegate(Func findContent) => _findContent = findContent; + public CustomRouteContentFinderDelegate(Func findContent) => _findContent = findContent; - public IPublishedContent FindContent(HttpContext httpContext) => _findContent(httpContext); + public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) => _findContent(actionExecutingContext); } } From 7e40a66fedf7c1ccb51690d0b35682b499fbf498 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 16 Feb 2021 18:04:32 +1100 Subject: [PATCH 07/10] Fixing tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0f8f15e9f4..d92493b859 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -94,7 +94,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor Assert.That(repository.Get("test"), Is.Not.Null); Assert.That(FileSystems.MvcViewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.AreEqual( - @"@usingUmbraco.Web.PublishedModels;@inheritsUmbraco.Web.Common.AspNetCore.UmbracoViewPage@{Layout=null;}".StripWhitespace(), + @"@usingUmbraco.Web.PublishedModels;@inheritsUmbraco.Web.Common.Views.UmbracoViewPage@{Layout=null;}".StripWhitespace(), template.Content.StripWhitespace()); } } From 02ac81d53f28565a6316305c98ff2e07abfdb9ec Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 16 Feb 2021 12:28:37 +0100 Subject: [PATCH 08/10] Fixed buildscripts to work with latest Umbraco.Build (0.2.17) and Directory.Build.props. Also updates the port (Now located in launchSettings.json) number if SetUmbracoVersion is called. --- build/build-bootstrap.ps1 | 2 +- build/build.ps1 | 12 +++++----- src/Directory.Build.props | 11 +++++----- src/SolutionInfo.cs | 22 ------------------- .../Properties/launchSettings.json | 2 +- 5 files changed, 14 insertions(+), 35 deletions(-) delete mode 100644 src/SolutionInfo.cs diff --git a/build/build-bootstrap.ps1 b/build/build-bootstrap.ps1 index 645f6c7d41..4c946ba289 100644 --- a/build/build-bootstrap.ps1 +++ b/build/build-bootstrap.ps1 @@ -23,7 +23,7 @@ $cache = 4 $nuget = "$scriptTemp\nuget.exe" # ensure the correct NuGet-source is used. This one is used by Umbraco - $nugetsourceUmbraco = "https://www.myget.org/F/umbracocore/api/v3/index.json" + $nugetsourceUmbraco = "https://www.myget.org/F/umbracoprereleases/api/v3/index.json" if (-not $local) { $source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" diff --git a/build/build.ps1 b/build/build.ps1 index ae59874e6a..07d856d075 100644 --- a/build/build.ps1 +++ b/build/build.ps1 @@ -51,12 +51,12 @@ { param ( $semver ) - $release = "" + $semver.Major + "." + $semver.Minor + "." + $semver.Patch - - Write-Host "Update IIS Express port in csproj" - $updater = New-Object "Umbraco.Build.ExpressPortUpdater" - $csproj = "$($this.SolutionRoot)\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" - $updater.Update($csproj, $release) + $port = "" + $semver.Major + $semver.Minor + ("" + $semver.Patch).PadLeft(2, '0') + Write-Host "Update port in launchSettings.json to $port" + $filePath = "$($this.SolutionRoot)\src\Umbraco.Web.UI.NetCore\Properties\launchSettings.json" + $this.ReplaceFileText($filePath, ` + "http://localhost:(\d+)?", ` + "http://localhost:$port") }) $ubuild.DefineMethod("SandboxNode", diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 23b3080ad2..cdce38df2f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,12 +1,13 @@ - + - 0.5.0 - 0.5.0 - 0.5.0-beta001 - 0.5.0 + 9.0.0 + 9.0.0 + 9.0.0-beta001 + 9.0.0 9.0 en-US Umbraco CMS Copyright © Umbraco 2021 + diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs deleted file mode 100644 index dc2c74bebb..0000000000 --- a/src/SolutionInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Resources; - -[assembly: AssemblyCompany("Umbraco")] -[assembly: AssemblyCopyright("Copyright © Umbraco 2021")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: NeutralResourcesLanguage("en-US")] - -// versions -// read https://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin - -// note: do NOT change anything here manually, use the build scripts - -// this is the ONLY ONE the CLR cares about for compatibility -// should change ONLY when "hard" breaking compatibility (manual change) -[assembly: AssemblyVersion("0.5.0")] - -// these are FYI and changed automatically -[assembly: AssemblyFileVersion("0.5.0")] -[assembly: AssemblyInformationalVersion("0.5.0-beta001")] \ No newline at end of file diff --git a/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json b/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json index 65e9736518..b16945dcb0 100644 --- a/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json +++ b/src/Umbraco.Web.UI.NetCore/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, From b0150dc8a30e9f3d044ab740266485ecfa3e1c9f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 16 Feb 2021 14:20:20 +0100 Subject: [PATCH 09/10] Fixed tests --- .../Routing/UmbracoRouteValueTransformerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 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 a47d3acb20..5dbbb87813 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -188,8 +188,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing private class TestController : RenderController { - public TestController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) - : base(logger, compositeViewEngine, umbracoContextAccessor) + public TestController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(loggerFactory, compositeViewEngine, umbracoContextAccessor) { } } From 2ba3eb436cb3341b08d5f4f160deec6e237c1dbf Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 17 Feb 2021 12:00:57 +0100 Subject: [PATCH 10/10] Fixed up small findings in refiew.. - Uses ILogger instead of ILoggerFactory - Uses the GetControllerName extension - Fixes views --- .../Routing/ControllerActionSearcherTests.cs | 7 ++----- .../Routing/UmbracoRouteValueTransformerTests.cs | 4 ++-- .../Trees/ApplicationTreeController.cs | 3 +-- src/Umbraco.Web.Common/Controllers/RenderController.cs | 8 +++----- .../Controllers/UmbracoPageController.cs | 4 ++-- .../Views/Partials/grid/editors/embed.cshtml | 4 ++-- .../Views/Partials/grid/editors/macro.cshtml | 2 +- 7 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs index 7c96738a1e..9382621c99 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs @@ -3,15 +3,12 @@ using System.Linq; using System.Reflection; 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.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Primitives; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -53,8 +50,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing private class Render2Controller : RenderController { - public Render2Controller(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) - : base(loggerFactory, compositeViewEngine, umbracoContextAccessor) + public Render2Controller(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) { } } 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 5dbbb87813..9d7560060d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -188,8 +188,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Routing private class TestController : RenderController { - public TestController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) - : base(loggerFactory, compositeViewEngine, umbracoContextAccessor) + public TestController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) { } } diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index c938dced9d..36eddd0d32 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -325,8 +325,7 @@ namespace Umbraco.Web.BackOffice.Trees // 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. - // TODO: We have a method for this: ControllerExtensions.GetControllerName - var controllerName = controllerType.Name.Substring(0, controllerType.Name.Length - 10); // remove controller part of name; + var controllerName = ControllerExtensions.GetControllerName(controllerType); // create proxy route data specifying the action & controller to execute var routeData = new RouteData(new RouteValueDictionary() diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index bfa129df25..a1453ee6cd 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -3,11 +3,9 @@ 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.Web.Common.ActionsResults; using Umbraco.Web.Common.Filters; -using Umbraco.Web.Common.Routing; using Umbraco.Web.Models; using Umbraco.Web.Routing; @@ -27,10 +25,10 @@ namespace Umbraco.Web.Common.Controllers /// /// Initializes a new instance of the class. /// - public RenderController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) - : base(loggerFactory, compositeViewEngine) + public RenderController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine) { - _logger = loggerFactory.CreateLogger(); + _logger = logger; _umbracoContextAccessor = umbracoContextAccessor; } diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs index bc0181412e..33fa4ca53e 100644 --- a/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs +++ b/src/Umbraco.Web.Common/Controllers/UmbracoPageController.cs @@ -20,9 +20,9 @@ namespace Umbraco.Web.Common.Controllers /// /// Initializes a new instance of the class. /// - protected UmbracoPageController(ILoggerFactory loggerFactory, ICompositeViewEngine compositeViewEngine) + protected UmbracoPageController(ILogger logger, ICompositeViewEngine compositeViewEngine) { - _logger = loggerFactory.CreateLogger(); + _logger = logger; _compositeViewEngine = compositeViewEngine; } diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml index dfb7399ef9..39ba997194 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/embed.cshtml @@ -1,5 +1,5 @@ -@ using Umbraco.Core -@inherits UmbracoViewPage +@using Umbraco.Core +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @{ string embedValue = Convert.ToString(Model.value); diff --git a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml index ee24207e5d..6cbc5c49cd 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/editors/macro.cshtml @@ -1,4 +1,4 @@ -@inherits UmbracoViewPage +@inherits Umbraco.Web.Common.Views.UmbracoViewPage @if (Model.value != null) {