V14: Reintroduce umbraco api controller as obsolete (#16263)

* Revert "v14: Remove mentions of UmbracoApiController (#15863)"

This reverts commit 30e2dea57a.

* Obsolete UmbracoApiController

* Added a few more obsoletion messages

* Removed some of the reintroduced stuff again

* Add obsoletion to FrontEndRoutes controller

---------

Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
Bjarke Berg
2024-05-13 08:28:42 +02:00
committed by GitHub
parent b4b512dd53
commit 87c7347900
17 changed files with 581 additions and 9 deletions

View File

@@ -19,8 +19,6 @@ namespace Umbraco.Cms.Api.Management.Routing;
/// </summary>
public sealed class BackOfficeAreaRoutes : IAreaRoutes
{
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IRuntimeState _runtimeState;
private readonly string _umbracoPathSegment;
@@ -32,10 +30,18 @@ public sealed class BackOfficeAreaRoutes : IAreaRoutes
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState)
{
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
_runtimeState = runtimeState;
_umbracoPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
}
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public BackOfficeAreaRoutes(
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState,
UmbracoApiControllerTypeCollection apiControllers) : this(globalSettings, hostingEnvironment, runtimeState)
{
}
/// <inheritdoc />

View File

@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Core;
[Obsolete("This will be removed in Umbraco 15.")]
public class UmbracoApiControllerTypeCollection : BuilderCollectionBase<Type>
{
public UmbracoApiControllerTypeCollection(Func<IEnumerable<Type>> items)
: base(items)
{
}
}

View File

@@ -0,0 +1,10 @@
using Umbraco.Cms.Web.Common.ApplicationModels;
namespace Umbraco.Cms.Web.Common.Attributes;
[AttributeUsage(AttributeTargets.Class)]
[Obsolete("No-op attribute. Will be removed in Umbraco 15.")]
public sealed class UmbracoApiControllerAttribute : Attribute
{
}

View File

@@ -0,0 +1,26 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Web.Common.Controllers;
/// <summary>
/// Provides a base class for auto-routed Umbraco API controllers.
/// </summary>
[Obsolete("""
WARNING
The UmbracoAPIController does not work exactly as in previous versions of Umbraco because serialization is now done using System.Text.Json.
Please verify your API responses still work as expect.
We recommend using regular ASP.NET Core ApiControllers for your APIs so that OpenAPI specifications are generated.
Read more about this here: https://learn.microsoft.com/en-us/aspnet/core/web-api/
UmbracoAPIController will be removed in Umbraco 15.
""")]
public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApiController" /> class.
/// </summary>
protected UmbracoApiController()
{
}
}

View File

@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Features;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Web.Common.Controllers;
/// <summary>
/// Provides a base class for Umbraco API controllers.
/// </summary>
/// <remarks>
/// <para>These controllers are NOT auto-routed.</para>
/// <para>The base class is <see cref="ControllerBase" /> which are netcore API controllers without any view support</para>
/// </remarks>
[Authorize(Policy = AuthorizationPolicies.UmbracoFeatureEnabled)]
[UmbracoApiController]
[Obsolete("This will be removed in Umbraco 15.")]
public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApiControllerBase" /> class.
/// </summary>
protected UmbracoApiControllerBase()
{
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Web.Common.Controllers;
[Obsolete("This will be removed in Umbraco 15.")]
public class UmbracoApiControllerTypeCollectionBuilder : TypeCollectionBuilderBase<
UmbracoApiControllerTypeCollectionBuilder, UmbracoApiControllerTypeCollection, UmbracoApiController>
{
protected override UmbracoApiControllerTypeCollectionBuilder This => this;
}

View File

@@ -44,6 +44,7 @@ using Umbraco.Cms.Web.Common.ApplicationModels;
using Umbraco.Cms.Web.Common.AspNetCore;
using Umbraco.Cms.Web.Common.Blocks;
using Umbraco.Cms.Web.Common.Configuration;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Cms.Web.Common.FileProviders;
using Umbraco.Cms.Web.Common.Helpers;
@@ -287,6 +288,10 @@ public static partial class UmbracoBuilderExtensions
builder.Services.AddUnique<IUmbracoContextFactory, UmbracoContextFactory>();
builder.Services.AddUnique<IBackOfficeSecurityAccessor, BackOfficeSecurityAccessor>();
var umbracoApiControllerTypes = builder.TypeLoader.GetUmbracoApiControllers().ToList();
builder.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
.Add(umbracoApiControllerTypes);
builder.Services.AddSingleton<UmbracoRequestLoggingMiddleware>();
builder.Services.AddSingleton<PreviewAuthenticationMiddleware>();
builder.Services.AddSingleton<UmbracoRequestMiddleware>();

View File

@@ -12,6 +12,36 @@ namespace Umbraco.Extensions;
public static class LinkGeneratorExtensions
{
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <typeparam name="T">The <see cref="UmbracoApiControllerBase" /></typeparam>
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, object? id = null)
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(
actionName,
typeof(T),
new Dictionary<string, object?> { ["id"] = id });
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, IDictionary<string, object?>? values)
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(actionName, typeof(T), values);
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiServiceBaseUrl<T>(
this LinkGenerator linkGenerator,
Expression<Func<T, object?>> methodSelector)
where T : UmbracoApiControllerBase
{
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
if (method == null)
{
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
" or the result ");
}
return linkGenerator.GetUmbracoApiService<T>(method.Name)?.TrimEnd(method.Name);
}
/// <summary>
/// Return the Url for an Umbraco controller
@@ -101,4 +131,26 @@ public static class LinkGeneratorExtensions
return linkGenerator.GetUmbracoControllerUrl(actionName, ControllerExtensions.GetControllerName(controllerType), area, values);
}
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService<T>(
this LinkGenerator linkGenerator,
Expression<Func<T, object>> methodSelector)
where T : UmbracoApiController
{
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
IDictionary<string, object?>? methodParams = ExpressionHelper.GetMethodParams(methodSelector);
if (method == null)
{
throw new MissingMethodException(
$"Could not find the method {methodSelector} on type {typeof(T)} or the result ");
}
if (methodParams?.Any() == false)
{
return linkGenerator.GetUmbracoApiService<T>(method.Name);
}
return linkGenerator.GetUmbracoApiService<T>(method.Name, methodParams);
}
}

View File

@@ -0,0 +1,13 @@
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Web.Common.Controllers;
namespace Umbraco.Extensions;
public static class TypeLoaderExtensions
{
/// <summary>
/// Gets all types implementing <see cref="UmbracoApiController" />.
/// </summary>
public static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader typeLoader)
=> typeLoader.GetTypes<UmbracoApiController>();
}

View File

@@ -21,6 +21,219 @@ namespace Umbraco.Extensions;
public static class UrlHelperExtensions
{
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="umbracoApiControllerTypeCollection"></param>
/// <param name="actionName"></param>
/// <param name="id"></param>
/// <returns></returns>
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService<T>(
this IUrlHelper url,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
string actionName,
object? id = null)
where T : UmbracoApiController =>
url.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, typeof(T), id);
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService<T>(
this IUrlHelper url,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
Expression<Func<T, object>> methodSelector)
where T : UmbracoApiController
{
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
IDictionary<string, object?>? methodParams = ExpressionHelper.GetMethodParams(methodSelector);
if (method == null)
{
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
" or the result ");
}
if (methodParams?.Any() == false)
{
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name);
}
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name, methodParams?.Values.First());
}
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <param name="url"></param>
/// <param name="umbracoApiControllerTypeCollection"></param>
/// <param name="actionName"></param>
/// <param name="apiControllerType"></param>
/// <param name="id"></param>
/// <returns></returns>
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiService(
this IUrlHelper url,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
string actionName,
Type apiControllerType,
object? id = null)
{
if (actionName == null)
{
throw new ArgumentNullException(nameof(actionName));
}
if (string.IsNullOrWhiteSpace(actionName))
{
throw new ArgumentException(
"Value can't be empty or consist only of white-space characters.",
nameof(actionName));
}
if (apiControllerType == null)
{
throw new ArgumentNullException(nameof(apiControllerType));
}
var area = string.Empty;
Type? apiController = umbracoApiControllerTypeCollection.SingleOrDefault(x => x == apiControllerType);
if (apiController == null)
{
throw new InvalidOperationException("Could not find the umbraco api controller of type " +
apiControllerType.FullName);
}
PluginControllerMetadata metaData = PluginController.GetMetadata(apiController);
if (metaData.AreaName.IsNullOrWhiteSpace() == false)
{
// set the area to the plugin area
area = metaData.AreaName;
}
return url.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area!, id);
}
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <param name="url"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string? GetUmbracoApiService(this IUrlHelper url, string actionName, string controllerName, object? id = null) => url.GetUmbracoApiService(actionName, controllerName, string.Empty, id);
/// <summary>
/// Return the Url for a Web Api service
/// </summary>
/// <param name="url"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="area"></param>
/// <param name="id"></param>
/// <returns></returns>
public static string? GetUmbracoApiService(
this IUrlHelper url,
string actionName,
string controllerName,
string area,
object? id = null)
{
if (actionName == null)
{
throw new ArgumentNullException(nameof(actionName));
}
if (string.IsNullOrWhiteSpace(actionName))
{
throw new ArgumentException(
"Value can't be empty or consist only of white-space characters.",
nameof(actionName));
}
if (controllerName == null)
{
throw new ArgumentNullException(nameof(controllerName));
}
if (string.IsNullOrWhiteSpace(controllerName))
{
throw new ArgumentException(
"Value can't be empty or consist only of white-space characters.",
nameof(controllerName));
}
if (area.IsNullOrWhiteSpace())
{
if (id == null)
{
return url.Action(actionName, controllerName);
}
return url.Action(actionName, controllerName, new { id });
}
if (id == null)
{
return url.Action(actionName, controllerName, new { area });
}
return url.Action(actionName, controllerName, new { area, id });
}
/// <summary>
/// Return the Base Url (not including the action) for a Web Api service
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="umbracoApiControllerTypeCollection"></param>
/// <param name="actionName"></param>
/// <returns></returns>
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiServiceBaseUrl<T>(
this IUrlHelper url,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
string actionName)
where T : UmbracoApiController =>
url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, actionName)?.TrimEnd(actionName);
[Obsolete("This will be removed in Umbraco 15.")]
public static string? GetUmbracoApiServiceBaseUrl<T>(
this IUrlHelper url,
UmbracoApiControllerTypeCollection umbracoApiControllerTypeCollection,
Expression<Func<T, object>> methodSelector)
where T : UmbracoApiController
{
MethodInfo? method = ExpressionHelper.GetMethodInfo(methodSelector);
if (method == null)
{
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) +
" or the result ");
}
return url.GetUmbracoApiService<T>(umbracoApiControllerTypeCollection, method.Name)?.TrimEnd(method.Name);
}
/// <summary>
/// Return the Url for an action with a cache-busting hash appended
/// </summary>
/// <returns></returns>
public static string GetUrlWithCacheBust(
this IUrlHelper url,
string actionName,
string controllerName,
RouteValueDictionary routeVals,
IHostingEnvironment hostingEnvironment,
IUmbracoVersion umbracoVersion)
{
var applicationJs = url.Action(actionName, controllerName, routeVals);
applicationJs = applicationJs + "?umb__rnd=" +
GetCacheBustHash(hostingEnvironment, umbracoVersion);
return applicationJs;
}
/// <summary>
/// </summary>
/// <returns></returns>

View File

@@ -60,7 +60,17 @@ public sealed class ConfigureMemberCookieOptions : IConfigureNamedOptions<Cookie
},
OnRedirectToAccessDenied = ctx =>
{
new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx);
// When the controller is an UmbracoAPIController, we want to return a StatusCode instead of a redirect.
// All other cases should use the default Redirect of the CookieAuthenticationEvent.
var controllerDescriptor = ctx.HttpContext.GetEndpoint()?.Metadata
.OfType<ControllerActionDescriptor>()
.FirstOrDefault();
if (!controllerDescriptor?.ControllerTypeInfo.IsSubclassOf(typeof(UmbracoApiController)) ?? false)
{
new CookieAuthenticationEvents().OnRedirectToAccessDenied(ctx);
}
return Task.CompletedTask;
},
};

View File

@@ -15,4 +15,10 @@ public static class TypeLoaderExtensions
/// </summary>
internal static IEnumerable<Type> GetSurfaceControllers(this TypeLoader typeLoader)
=> typeLoader.GetTypes<SurfaceController>();
/// <summary>
/// Gets all types implementing <see cref="UmbracoApiController" />.
/// </summary>
internal static IEnumerable<Type> GetUmbracoApiControllers(this TypeLoader typeLoader)
=> typeLoader.GetTypes<UmbracoApiController>();
}

View File

@@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Web.Mvc;
@@ -17,10 +19,25 @@ namespace Umbraco.Cms.Web.Website.Routing;
/// </summary>
public sealed class FrontEndRoutes : IAreaRoutes
{
private readonly UmbracoApiControllerTypeCollection _apiControllers;
private readonly IRuntimeState _runtimeState;
private readonly SurfaceControllerTypeCollection _surfaceControllerTypeCollection;
private readonly string _umbracoPathSegment;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public FrontEndRoutes(
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState,
SurfaceControllerTypeCollection surfaceControllerTypeCollection,
UmbracoApiControllerTypeCollection apiControllers)
{
_runtimeState = runtimeState;
_surfaceControllerTypeCollection = surfaceControllerTypeCollection;
_apiControllers = apiControllers;
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
}
/// <summary>
/// Initializes a new instance of the <see cref="FrontEndRoutes" /> class.
/// </summary>
@@ -29,10 +46,13 @@ public sealed class FrontEndRoutes : IAreaRoutes
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState,
SurfaceControllerTypeCollection surfaceControllerTypeCollection)
: this(
globalSettings,
hostingEnvironment,
runtimeState,
surfaceControllerTypeCollection,
StaticServiceProvider.Instance.GetRequiredService<UmbracoApiControllerTypeCollection>())
{
_runtimeState = runtimeState;
_surfaceControllerTypeCollection = surfaceControllerTypeCollection;
_umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment);
}
/// <inheritdoc />
@@ -45,6 +65,7 @@ public sealed class FrontEndRoutes : IAreaRoutes
case RuntimeLevel.Run:
AutoRouteSurfaceControllers(endpoints);
AutoRouteFrontEndApiControllers(endpoints);
break;
case RuntimeLevel.BootFailed:
case RuntimeLevel.Unknown:
@@ -71,4 +92,28 @@ public sealed class FrontEndRoutes : IAreaRoutes
meta.AreaName);
}
}
/// <summary>
/// Auto-routes all front-end api controllers
/// </summary>
private void AutoRouteFrontEndApiControllers(IEndpointRouteBuilder endpoints)
{
foreach (Type controller in _apiControllers)
{
PluginControllerMetadata meta = PluginController.GetMetadata(controller);
// exclude back-end api controllers
if (meta.IsBackOffice)
{
continue;
}
endpoints.MapUmbracoApiRoute(
meta.ControllerType,
_umbracoPathSegment,
meta.AreaName,
meta.IsBackOffice,
string.Empty); // no default action (this is what we had before)
}
}
}

View File

@@ -105,6 +105,18 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
});
}
/// <summary>
/// Prepare a url before using <see cref="Client"/>.
/// This returns the url but also sets the HttpContext.request into to use this url.
/// </summary>
/// <returns>The string URL of the controller action.</returns>
protected string PrepareApiControllerUrl<T>(Expression<Func<T, object>> methodSelector)
where T : UmbracoApiController
{
var url = LinkGenerator.GetUmbracoApiService(methodSelector);
return PrepareUrl(url);
}
protected string GetManagementApiUrl<T>(Expression<Func<T, object>> methodSelector)
where T : ManagementApiControllerBase
{

View File

@@ -67,6 +67,36 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security
Assert.AreEqual(HttpStatusCode.Redirect, response.StatusCode);
Assert.AreEqual(cookieAuthenticationOptions.Value.AccessDeniedPath.ToString(), response.Headers.Location?.AbsolutePath);
}
[Test]
[LongRunning]
public async Task Secure_ApiController_Should_Return_Unauthorized_WhenNotLoggedIn()
{
_memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(false);
var url = PrepareApiControllerUrl<TestApiController>(x => x.Secure());
var response = await Client.GetAsync(url);
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Test]
[LongRunning]
public async Task Secure_ApiController_Should_Return_Forbidden_WhenNotAuthorized()
{
_memberManagerMock.Setup(x => x.IsLoggedIn()).Returns(true);
_memberManagerMock.Setup(x => x.IsMemberAuthorizedAsync(
It.IsAny<IEnumerable<string>>(),
It.IsAny<IEnumerable<string>>(),
It.IsAny<IEnumerable<int>>()))
.ReturnsAsync(false);
var url = PrepareApiControllerUrl<TestApiController>(x => x.Secure());
var response = await Client.GetAsync(url);
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
}
}
public class TestSurfaceController : SurfaceController
@@ -91,4 +121,10 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.Website.Security
[UmbracoMemberAuthorize]
public IActionResult Secure() => NoContent();
}
public class TestApiController : UmbracoApiController
{
[UmbracoMemberAuthorize]
public IActionResult Secure() => NoContent();
}
}

View File

@@ -47,6 +47,14 @@ internal class UmbracoCustomizations : ICustomization
fixture.Customize<HostingSettings>(x =>
x.With(settings => settings.ApplicationVirtualPath, string.Empty));
fixture.Customize<BackOfficeAreaRoutes>(u => u.FromFactory(
() => new BackOfficeAreaRoutes(
Options.Create(new GlobalSettings()),
Mock.Of<IHostingEnvironment>(x =>
x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty),
Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run),
new UmbracoApiControllerTypeCollection(Enumerable.Empty<Type>))));
fixture.Customize<PreviewRoutes>(u => u.FromFactory(
() => new PreviewRoutes(
Options.Create(new GlobalSettings()),

View File

@@ -0,0 +1,80 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Cms.Api.Management.Controllers.Security;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Extensions;
using static Umbraco.Cms.Core.Constants.Web.Routing;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Routing;
[TestFixture]
public class BackOfficeAreaRoutesTests
{
[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]
[TestCase(RuntimeLevel.Run)]
[TestCase(RuntimeLevel.Upgrade)]
[TestCase(RuntimeLevel.Install)]
public void RuntimeState_All_Routes(RuntimeLevel level)
{
var routes = GetBackOfficeAreaRoutes(level);
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);
}
private void AssertMinimalBackOfficeRoutes(EndpointDataSource route)
{
var endpoint1 = (RouteEndpoint)route.Endpoints[0];
Assert.AreEqual("umbraco/{action}/{id?}", endpoint1.RoutePattern.RawText);
Assert.AreEqual("Index", endpoint1.RoutePattern.Defaults[ActionToken]);
Assert.AreEqual(ControllerExtensions.GetControllerName<BackOfficeDefaultController>(), endpoint1.RoutePattern.Defaults[ControllerToken]);
}
private BackOfficeAreaRoutes GetBackOfficeAreaRoutes(RuntimeLevel level)
{
var globalSettings = new GlobalSettings();
var routes = new BackOfficeAreaRoutes(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(x =>
x.ToAbsolute(It.IsAny<string>()) == "/umbraco" && x.ApplicationVirtualPath == string.Empty),
Mock.Of<IRuntimeState>(x => x.Level == level),
new UmbracoApiControllerTypeCollection(() => new[] { typeof(Testing1Controller) }));
return routes;
}
[IsBackOffice]
private class Testing1Controller : UmbracoApiController
{
}
}