diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index a319eb5a9e..5d5fb4646b 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -54,7 +54,7 @@ { public const string InstallArea = "UmbracoInstall"; - public const string BackOfficeArea = "UmbracoBackOffice"; + public const string BackOfficeArea = "UmbracoApi"; } } } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 0c68580204..ad785c111f 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -41,6 +41,7 @@ namespace Umbraco.Web // adds the virtual directory if any // see also VirtualPathUtility.ToAbsolute + // TODO: Does this do anything differently than IHostingEnvironment.ToAbsolute? Seems it does less, maybe should be removed? public string ToAbsolute(string url) { //return ResolveUrl(url); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index c5732870f3..67d822fb03 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs new file mode 100644 index 0000000000..a3a992bc22 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/BackOfficeAreaRoutesTests.cs @@ -0,0 +1,108 @@ +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Hosting; +using Umbraco.Extensions; +using Umbraco.Web.BackOffice.Controllers; +using Umbraco.Web.BackOffice.Routing; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Controllers; +using Umbraco.Web.WebApi; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing +{ + + [TestFixture] + public class BackOfficeAreaRoutesTests + { + [TestCase(RuntimeLevel.Install)] + [TestCase(RuntimeLevel.BootFailed)] + [TestCase(RuntimeLevel.Unknown)] + [TestCase(RuntimeLevel.Boot)] + public void RuntimeState_No_Routes(RuntimeLevel level) + { + var routes = GetBackOfficeAreaRoutes(level); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(0, endpoints.DataSources.Count); + } + + [Test] + public void RuntimeState_Upgrade() + { + var routes = GetBackOfficeAreaRoutes(RuntimeLevel.Upgrade); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(1, endpoints.DataSources.Count); + var route = endpoints.DataSources.First(); + Assert.AreEqual(2, route.Endpoints.Count); + AssertMinimalBackOfficeRoutes(route); + } + + [Test] + public void RuntimeState_Run() + { + var routes = GetBackOfficeAreaRoutes(RuntimeLevel.Run); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(1, endpoints.DataSources.Count); + var route = endpoints.DataSources.First(); + Assert.AreEqual(4, route.Endpoints.Count); + AssertMinimalBackOfficeRoutes(route); + + var endpoint3 = (RouteEndpoint)route.Endpoints[2]; + var previewControllerName = ControllerExtensions.GetControllerName(); + Assert.AreEqual($"umbraco/{previewControllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint3.RoutePattern.RawText); + Assert.AreEqual(Constants.Web.Mvc.BackOfficeArea, endpoint3.RoutePattern.Defaults["area"]); + Assert.AreEqual("Index", endpoint3.RoutePattern.Defaults["action"]); + Assert.AreEqual(previewControllerName, endpoint3.RoutePattern.Defaults["controller"]); + + var endpoint4 = (RouteEndpoint)route.Endpoints[3]; + var apiControllerName = ControllerExtensions.GetControllerName(); + Assert.AreEqual($"umbraco/backoffice/api/{apiControllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint4.RoutePattern.RawText); + Assert.IsFalse(endpoint4.RoutePattern.Defaults.ContainsKey("area")); + Assert.IsFalse(endpoint4.RoutePattern.Defaults.ContainsKey("action")); + Assert.AreEqual(apiControllerName, endpoint4.RoutePattern.Defaults["controller"]); + } + + private void AssertMinimalBackOfficeRoutes(EndpointDataSource route) + { + var endpoint1 = (RouteEndpoint)route.Endpoints[0]; + Assert.AreEqual($"umbraco/{{action}}/{{id?}}", endpoint1.RoutePattern.RawText); + Assert.AreEqual(Constants.Web.Mvc.BackOfficeArea, endpoint1.RoutePattern.Defaults["area"]); + Assert.AreEqual("Default", endpoint1.RoutePattern.Defaults["action"]); + Assert.AreEqual(ControllerExtensions.GetControllerName(), endpoint1.RoutePattern.Defaults["controller"]); + + var endpoint2 = (RouteEndpoint)route.Endpoints[1]; + var controllerName = ControllerExtensions.GetControllerName(); + Assert.AreEqual($"umbraco/backoffice/{Constants.Web.Mvc.BackOfficeArea.ToLowerInvariant()}/{controllerName.ToLowerInvariant()}/{{action}}/{{id?}}", endpoint2.RoutePattern.RawText); + Assert.AreEqual(Constants.Web.Mvc.BackOfficeArea, endpoint2.RoutePattern.Defaults["area"]); + Assert.IsFalse(endpoint2.RoutePattern.Defaults.ContainsKey("action")); + Assert.AreEqual(controllerName, endpoint2.RoutePattern.Defaults["controller"]); + } + + private BackOfficeAreaRoutes GetBackOfficeAreaRoutes(RuntimeLevel level) + { + var routes = new BackOfficeAreaRoutes( + Mock.Of(x => x.UmbracoPath == "~/umbraco"), + Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty), + Mock.Of(x => x.Level == level), + new UmbracoApiControllerTypeCollection(new[] { typeof(Testing1Controller) })); + + return routes; + } + + [IsBackOffice] + private class Testing1Controller : UmbracoApiController + { + + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs index 2c6b17e08b..4c4d282813 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs @@ -1,21 +1,18 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Moq; -using NUglify.Helpers; using NUnit.Framework; using System; -using System.Collections.Generic; using System.Linq; using System.Text; +using Umbraco.Core; using Umbraco.Extensions; using Umbraco.Web.Common.Routing; using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing { + [TestFixture] public class EndpointRouteBuilderExtensionsTests { @@ -110,41 +107,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing Assert.AreEqual(controllerName, endpoint.RoutePattern.Defaults["controller"]); } - private class TestRouteBuilder : IEndpointRouteBuilder - { - private readonly ServiceProvider _serviceProvider; - - public TestRouteBuilder() - { - var services = new ServiceCollection(); - services.AddLogging(); - services.AddMvc(); - _serviceProvider = services.BuildServiceProvider(); - } - - public ICollection DataSources { get; } = new List(); - - public IServiceProvider ServiceProvider => _serviceProvider; - - public IApplicationBuilder CreateApplicationBuilder() - { - return Mock.Of(); - } - } - - private class TestServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - if (serviceType.Name == "MvcMarkerService") - { - // it's internal but we can force make it - return Activator.CreateInstance(serviceType); - } - return null; - } - } - private class Testing1Controller : ControllerBase { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs new file mode 100644 index 0000000000..238554e8b7 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/InstallAreaRoutesTests.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Hosting; +using Umbraco.Extensions; +using Umbraco.Web.Common.Install; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing +{ + [TestFixture] + public class InstallAreaRoutesTests + { + [TestCase(RuntimeLevel.BootFailed)] + [TestCase(RuntimeLevel.Unknown)] + [TestCase(RuntimeLevel.Boot)] + public void RuntimeState_No_Routes(RuntimeLevel level) + { + var routes = GetInstallAreaRoutes(level); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(0, endpoints.DataSources.Count); + } + + [TestCase(RuntimeLevel.Install)] + [TestCase(RuntimeLevel.Upgrade)] + public void RuntimeState_Install(RuntimeLevel level) + { + var routes = GetInstallAreaRoutes(level); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(2, endpoints.DataSources.Count); + var route = endpoints.DataSources.First(); + Assert.AreEqual(2, route.Endpoints.Count); + + var endpoint1 = (RouteEndpoint)route.Endpoints[0]; + Assert.AreEqual($"install/api/{{action}}/{{id?}}", endpoint1.RoutePattern.RawText); + Assert.AreEqual(Constants.Web.Mvc.InstallArea, endpoint1.RoutePattern.Defaults["area"]); + Assert.AreEqual("Index", endpoint1.RoutePattern.Defaults["action"]); + Assert.AreEqual(ControllerExtensions.GetControllerName(), endpoint1.RoutePattern.Defaults["controller"]); + + var endpoint2 = (RouteEndpoint)route.Endpoints[1]; + Assert.AreEqual($"install/{{action}}/{{id?}}", endpoint2.RoutePattern.RawText); + Assert.AreEqual(Constants.Web.Mvc.InstallArea, endpoint2.RoutePattern.Defaults["area"]); + Assert.AreEqual("Index", endpoint2.RoutePattern.Defaults["action"]); + Assert.AreEqual(ControllerExtensions.GetControllerName(), endpoint2.RoutePattern.Defaults["controller"]); + + var fallbackRoute = endpoints.DataSources.Last(); + Assert.AreEqual(1, fallbackRoute.Endpoints.Count); + + Assert.AreEqual("Fallback {*path:nonfile}", fallbackRoute.Endpoints[0].ToString()); + } + + [Test] + public void RuntimeState_Run() + { + var routes = GetInstallAreaRoutes(RuntimeLevel.Run); + var endpoints = new TestRouteBuilder(); + routes.CreateRoutes(endpoints); + + Assert.AreEqual(1, endpoints.DataSources.Count); + var route = endpoints.DataSources.First(); + Assert.AreEqual(1, route.Endpoints.Count); + + Assert.AreEqual("install/{controller?}/{action?} HTTP: GET", route.Endpoints[0].ToString()); + + } + + private InstallAreaRoutes GetInstallAreaRoutes(RuntimeLevel level) + { + var routes = new InstallAreaRoutes( + Mock.Of(x => x.Level == level), + Mock.Of(x => x.ToAbsolute(It.IsAny()) == "/install" && x.ApplicationVirtualPath == string.Empty), + Mock.Of()); + + return routes; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs new file mode 100644 index 0000000000..6d28e8a71e --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/TestRouteBuilder.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System; +using System.Collections.Generic; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing +{ + public class TestRouteBuilder : IEndpointRouteBuilder + { + private readonly ServiceProvider _serviceProvider; + + public TestRouteBuilder() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddMvc(); + _serviceProvider = services.BuildServiceProvider(); + } + + public ICollection DataSources { get; } = new List(); + + public IServiceProvider ServiceProvider => _serviceProvider; + + public IApplicationBuilder CreateApplicationBuilder() + { + return Mock.Of(); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs index 2f87ee8b6f..67a2086b0d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/AuthenticationController.cs @@ -5,7 +5,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { - [Area(Constants.Web.Mvc.BackOfficeArea)] // TODO: Maybe this could be applied with our Application Model conventions + [PluginController(Constants.Web.Mvc.BackOfficeArea)] // TODO: Maybe this could be applied with our Application Model conventions //[ValidationFilter] // TODO: I don't actually think this is required with our custom Application Model conventions applied [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions [IsBackOffice] // TODO: This could be applied with our Application Model conventions diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index 5d5fd120de..c99498bf57 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -7,11 +7,12 @@ using Umbraco.Net; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionResults; using Umbraco.Web.WebAssets; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { - [Area(Umbraco.Core.Constants.Web.Mvc.BackOfficeArea)] + [Area(Constants.Web.Mvc.BackOfficeArea)] public class BackOfficeController : Controller { private readonly IRuntimeMinifier _runtimeMinifier; diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index bd2aeac475..3472302a24 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -22,7 +22,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.BackOffice.Controllers { [DisableBrowserCache] - [Area(Umbraco.Core.Constants.Web.Mvc.BackOfficeArea)] + [Area(Constants.Web.Mvc.BackOfficeArea)] public class PreviewController : Controller { private readonly UmbracoFeatures _features; diff --git a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs index 457a5dc214..42daf6db9b 100644 --- a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.BackOffice.Routing case RuntimeLevel.Run: MapMinimalBackOffice(endpoints); - endpoints.MapUmbracoRoute(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, "preview"); + endpoints.MapUmbracoRoute(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, null); AutoRouteBackOfficeControllers(endpoints); break; @@ -67,6 +67,7 @@ namespace Umbraco.Web.BackOffice.Routing endpoints.MapUmbracoRoute(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, string.Empty, "Default", + includeControllerNameInRoute: false, constraints: // Limit the action/id to only allow characters - this is so this route doesn't hog all other // routes like: /umbraco/channels/word.aspx, etc... diff --git a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs index b024743e72..6f1f378809 100644 --- a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs +++ b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Routing; using System; using System.Threading.Tasks; using Umbraco.Core; +using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Extensions; using Umbraco.Web.Common.Routing; @@ -14,19 +15,19 @@ namespace Umbraco.Web.Common.Install public class InstallAreaRoutes : IAreaRoutes { private readonly IRuntimeState _runtime; - private readonly UriUtility _uriUtility; + private readonly IHostingEnvironment _hostingEnvironment; private readonly LinkGenerator _linkGenerator; - public InstallAreaRoutes(IRuntimeState runtime, UriUtility uriUtility, LinkGenerator linkGenerator) + public InstallAreaRoutes(IRuntimeState runtime, IHostingEnvironment hostingEnvironment, LinkGenerator linkGenerator) { _runtime = runtime; - _uriUtility = uriUtility; + _hostingEnvironment = hostingEnvironment; _linkGenerator = linkGenerator; } public void CreateRoutes(IEndpointRouteBuilder endpoints) { - var installPathSegment = _uriUtility.ToAbsolute(Core.Constants.SystemDirectories.Install); + var installPathSegment = _hostingEnvironment.ToAbsolute(Core.Constants.SystemDirectories.Install).TrimStart('/'); switch (_runtime.Level) {