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/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 + 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.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index df1a87f6c6..9a7016b6be 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; diff --git a/src/Umbraco.Core/Models/ContentModel.cs b/src/Umbraco.Core/Models/ContentModel.cs index 0aa123f030..e62f51fd16 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.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Models @@ -11,12 +11,7 @@ namespace Umbraco.Cms.Core.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 7b07b08847..ab882342b5 100644 --- a/src/Umbraco.Core/Models/ContentModelOfTContent.cs +++ b/src/Umbraco.Core/Models/ContentModelOfTContent.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Models { @@ -8,12 +8,8 @@ namespace Umbraco.Cms.Core.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.Infrastructure/Services/Implement/FileService.cs b/src/Umbraco.Infrastructure/Services/Implement/FileService.cs index b225f2b051..acab6caae4 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; @@ -33,7 +33,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.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/ViewHelperTests.cs index 5e8c6ce3fc..186e75b2f5 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; 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 9728fd7195..c0ed21e1f7 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/ControllerActionSearcherTests.cs @@ -49,7 +49,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website.Routing private class Render2Controller : RenderController { - public Render2Controller(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor 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 d2e2597415..4d74ea6427 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformerTests.cs @@ -189,7 +189,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Website.Routing private class TestController : RenderController { - public TestController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor 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 3877f9a0e2..84f2b5f574 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -35,14 +35,15 @@ namespace Umbraco.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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.Cms.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,8 @@ namespace Umbraco.Cms.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. - 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() { @@ -324,9 +351,12 @@ namespace Umbraco.Cms.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 7a14dee606..146edb19e9 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeApplicationModelProvider.cs @@ -6,6 +6,7 @@ using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.Common.ApplicationModels { + // TODO: This should just exist in the back office project /// @@ -13,45 +14,43 @@ namespace Umbraco.Cms.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 537ae58e89..8414662816 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/BackOfficeIdentityCultureConvention.cs @@ -8,9 +8,7 @@ namespace Umbraco.Cms.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 118603ced9..b80104a7bf 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -26,13 +26,18 @@ namespace Umbraco.Cms.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. @@ -46,32 +51,33 @@ namespace Umbraco.Cms.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 e5f4f1c307..e96bda8771 100644 --- a/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoJsonModelBinderConvention.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; using Umbraco.Cms.Web.Common.ModelBinders; @@ -13,14 +13,13 @@ namespace Umbraco.Cms.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..bfea5c8d87 --- /dev/null +++ b/src/Umbraco.Web.Common/Controllers/IVirtualPageController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc.Filters; +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(ActionExecutingContext actionExecutingContext); + } +} diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index de9e51d145..5fe8adb174 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; @@ -15,107 +14,32 @@ using Umbraco.Cms.Web.Common.Routing; namespace Umbraco.Cms.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) + : base(logger, compositeViewEngine) { _logger = logger; - _compositeViewEngine = compositeViewEngine; _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..33fa4ca53e --- /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(ILogger logger, ICompositeViewEngine compositeViewEngine) + { + _logger = logger; + _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 067c55225f..e80096dcbe 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -57,6 +57,7 @@ using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions { + // TODO: We could add parameters to configure each of these for flexibility /// @@ -102,6 +103,10 @@ namespace Umbraco.Extensions 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.Extensions 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..d30436c87b --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/ControllerActionEndpointConventionBuilderExtensions.cs @@ -0,0 +1,55 @@ +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(); + + // 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 new file mode 100644 index 0000000000..988608c2c2 --- /dev/null +++ b/src/Umbraco.Web.Common/Filters/UmbracoVirtualPageFilterAttribute.cs @@ -0,0 +1,75 @@ +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; +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)); + } + else + { + // Check if the controller is IVirtualPageController and then use that to FindContent + if (context.Controller is IVirtualPageController ctrl) + { + await SetUmbracoRouteValues(context, ctrl.FindContent(context)); + } + } + + // if we've assigned not found, just exit + if (!(context.Result is NotFoundResult)) + { + 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); + } + else + { + // if there is no content then it should be a not found + context.Result = new NotFoundResult(); + } + } + } +} diff --git a/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs b/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs index 82a7a7b036..2c8a77bd05 100644 --- a/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs +++ b/src/Umbraco.Web.Common/Macros/PartialViewMacroPage.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Web.Common.AspNetCore; namespace Umbraco.Cms.Web.Common.Macros diff --git a/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs new file mode 100644 index 0000000000..5be3b4b952 --- /dev/null +++ b/src/Umbraco.Web.Common/Routing/CustomRouteContentFinderDelegate.cs @@ -0,0 +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; + + public CustomRouteContentFinderDelegate(Func findContent) => _findContent = findContent; + + public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) => _findContent(actionExecutingContext); + } +} diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index 33d08ae252..a4311b988c 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -177,7 +177,19 @@ namespace Umbraco.Cms.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.Common/Routing/UmbracoRouteValues.cs b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs index 64fd998065..6088f21931 100644 --- a/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs +++ b/src/Umbraco.Web.Common/Routing/UmbracoRouteValues.cs @@ -21,12 +21,10 @@ namespace Umbraco.Cms.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; } @@ -59,10 +57,5 @@ namespace Umbraco.Cms.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.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 0d85d16a7e..d96ed397c5 100644 --- a/src/Umbraco.Web.Common/AspNetCore/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -20,8 +20,6 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Web.Common.AspNetCore { - // TODO: Should be in Views namespace? - public abstract class UmbracoViewPage : UmbracoViewPage { 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, diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 9c5606fa4d..85aa067627 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -59,7 +59,6 @@ namespace Umbraco.Cms.Web.UI.NetCore app.UseDeveloperExceptionPage(); } - app.UseUmbraco(); app.UseUmbracoBackOffice(); app.UseUmbracoWebsite(); } 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 b9207ece9b..38f7431f25 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.Cms.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Cms.Web.Common.AspNetCore.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 cc9bb2bb06..840c9c1218 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,4 +1,4 @@ -@using System.Web +@using System.Web @using Microsoft.AspNetCore.Html @using Newtonsoft.Json.Linq @inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage 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 d6728a5019..892dc84afe 100644 --- a/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml +++ b/src/Umbraco.Web.UI.NetCore/Views/Partials/grid/bootstrap3.cshtml @@ -1,4 +1,4 @@ -@using System.Web +@using System.Web @using Microsoft.AspNetCore.Html @using Newtonsoft.Json.Linq @inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage 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 c28b375fc8..dbd9438fa7 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,4 @@ -@using Umbraco.Core -@using Umbraco.Cms.Core +@using Umbraco.Cms.Core @inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage @{ 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 59416a4ea5..4305a1e4cf 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.Cms.Web.Common.AspNetCore.UmbracoViewPage +@inherits Umbraco.Cms.Web.Common.AspNetCore.UmbracoViewPage @if (Model.value != null) { diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index e217ea2777..f44890cf2f 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -93,9 +93,9 @@ namespace Umbraco.Cms.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; } @@ -103,7 +103,7 @@ namespace Umbraco.Cms.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; @@ -113,21 +113,23 @@ namespace Umbraco.Cms.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; @@ -138,7 +140,7 @@ namespace Umbraco.Cms.Web.Website.Routing if (request.HasPublishedContent() && !request.HasTemplate() && !_umbracoFeatures.Disabled.DisableTemplates - && !def.HasHijackedRoute) + && !hasHijackedRoute) { IPublishedContent content = request.PublishedContent; @@ -162,7 +164,7 @@ namespace Umbraco.Cms.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/EnsurePublishedContentRequestAttribute.cs b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs index 2961e84dd3..e69de29bb2 100644 --- a/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs +++ b/src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs @@ -1,128 +0,0 @@ -using System; -using System.Web.Mvc; -using Microsoft.Extensions.DependencyInjection; -using Umbraco.Web.Routing; -using Umbraco.Core; -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/Mvc/UmbracoVirtualNodeRouteHandler.cs b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs index 32807448db..7a4c9474a0 100644 --- a/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoVirtualNodeRouteHandler.cs @@ -1,15 +1,10 @@ +using System; using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Extensions.DependencyInjection; using Umbraco.Web.Models; using Umbraco.Web.Routing; -using Umbraco.Core; -using Umbraco.Web.Composing; -using System; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Routing; -using Umbraco.Cms.Core.Web; namespace Umbraco.Web.Mvc { diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 92009208b1..93cb8da83d 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -170,7 +170,6 @@ -