gets surface controllers and front-end api controllers auto-routed, adds tests
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Integration.TestServerTest;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Website.Controllers;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Umbraco.Web.Website.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class SurfaceControllerTests : UmbracoTestServerTestBase
|
||||
{
|
||||
[Test]
|
||||
public async Task Auto_Routes_For_Default_Action()
|
||||
{
|
||||
string url = PrepareSurfaceControllerUrl<TestSurfaceController>(x => x.Index());
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await Client.GetAsync(url);
|
||||
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Auto_Routes_For_Custom_Action()
|
||||
{
|
||||
string url = PrepareSurfaceControllerUrl<TestSurfaceController>(x => x.News());
|
||||
|
||||
// Act
|
||||
HttpResponseMessage response = await Client.GetAsync(url);
|
||||
|
||||
string body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Test controllers must be non-nested, else we need to jump through some hoops with custom
|
||||
// IApplicationFeatureProvider<ControllerFeature>
|
||||
// For future notes if we want this, some example code of this is here
|
||||
// https://tpodolak.com/blog/2020/06/22/asp-net-core-adding-controllers-directly-integration-tests/
|
||||
public class TestSurfaceController : SurfaceController
|
||||
{
|
||||
public TestSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
|
||||
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public IActionResult Index() => Ok();
|
||||
|
||||
public IActionResult News() => Forbid();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
@@ -8,6 +9,7 @@ using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Routing
|
||||
@@ -15,7 +17,7 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
/// <summary>
|
||||
/// Creates routes for the back office area
|
||||
/// </summary>
|
||||
public class BackOfficeAreaRoutes : IAreaRoutes
|
||||
public sealed class BackOfficeAreaRoutes : IAreaRoutes
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -23,6 +25,9 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
private readonly UmbracoApiControllerTypeCollection _apiControllers;
|
||||
private readonly string _umbracoPathSegment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackOfficeAreaRoutes"/> class.
|
||||
/// </summary>
|
||||
public BackOfficeAreaRoutes(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
@@ -36,6 +41,7 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
_umbracoPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CreateRoutes(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
switch (_runtimeState.Level)
|
||||
@@ -50,7 +56,7 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
case RuntimeLevel.Run:
|
||||
|
||||
MapMinimalBackOffice(endpoints);
|
||||
AutoRouteBackOfficeControllers(endpoints);
|
||||
AutoRouteBackOfficeApiControllers(endpoints);
|
||||
break;
|
||||
case RuntimeLevel.BootFailed:
|
||||
case RuntimeLevel.Unknown:
|
||||
@@ -85,26 +91,30 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-routes all back office controllers
|
||||
/// Auto-routes all back office api controllers
|
||||
/// </summary>
|
||||
private void AutoRouteBackOfficeControllers(IEndpointRouteBuilder endpoints)
|
||||
private void AutoRouteBackOfficeApiControllers(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
// TODO: We could investigate dynamically routing plugin controllers so we don't have to eagerly type scan for them,
|
||||
// it would probably work well, see https://www.strathweb.com/2019/08/dynamic-controller-routing-in-asp-net-core-3-0/
|
||||
// will probably be what we use for front-end routing too. BTW the orig article about migrating from IRouter to endpoint
|
||||
// routing for things like a CMS is here https://github.com/dotnet/aspnetcore/issues/4221
|
||||
|
||||
foreach (var controller in _apiControllers)
|
||||
foreach (Type controller in _apiControllers)
|
||||
{
|
||||
PluginControllerMetadata meta = PluginController.GetMetadata(controller);
|
||||
|
||||
// exclude front-end api controllers
|
||||
var meta = PluginController.GetMetadata(controller);
|
||||
if (!meta.IsBackOffice) continue;
|
||||
if (!meta.IsBackOffice)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
endpoints.MapUmbracoApiRoute(
|
||||
meta.ControllerType,
|
||||
_umbracoPathSegment,
|
||||
meta.AreaName,
|
||||
true,
|
||||
meta.IsBackOffice,
|
||||
defaultAction: string.Empty); // no default action (this is what we had before)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Web.BackOffice.Routing
|
||||
/// <summary>
|
||||
/// Creates routes for the preview hub
|
||||
/// </summary>
|
||||
public class PreviewRoutes : IAreaRoutes
|
||||
public sealed class PreviewRoutes : IAreaRoutes
|
||||
{
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly string _umbracoPathSegment;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Common.Controllers
|
||||
{
|
||||
@@ -7,8 +7,9 @@ namespace Umbraco.Web.Common.Controllers
|
||||
/// </summary>
|
||||
public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
|
||||
{
|
||||
// TODO: Should this only exist in the back office project? These really are only ever used for the back office AFAIK
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoApiController"/> class.
|
||||
/// </summary>
|
||||
protected UmbracoApiController()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Authorization;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Features;
|
||||
|
||||
namespace Umbraco.Web.Common.Controllers
|
||||
@@ -18,9 +17,10 @@ namespace Umbraco.Web.Common.Controllers
|
||||
[UmbracoApiController]
|
||||
public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature
|
||||
{
|
||||
// TODO: Should this only exist in the back office project? These really are only ever used for the back office AFAIK
|
||||
|
||||
public UmbracoApiControllerBase()
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoApiControllerBase"/> class.
|
||||
/// </summary>
|
||||
protected UmbracoApiControllerBase()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,21 +29,20 @@ namespace Umbraco.Web.Common.Extensions
|
||||
var pattern = new StringBuilder(rootSegment);
|
||||
if (!prefixPathSegment.IsNullOrWhiteSpace())
|
||||
{
|
||||
pattern.Append("/").Append(prefixPathSegment);
|
||||
pattern.Append('/').Append(prefixPathSegment);
|
||||
}
|
||||
|
||||
if (includeControllerNameInRoute)
|
||||
{
|
||||
pattern.Append("/").Append(controllerName);
|
||||
pattern.Append('/').Append(controllerName);
|
||||
}
|
||||
|
||||
pattern.Append("/").Append("{action}/{id?}");
|
||||
pattern.Append('/').Append("{action}/{id?}");
|
||||
|
||||
var defaults = defaultAction.IsNullOrWhiteSpace()
|
||||
? (object)new { controller = controllerName }
|
||||
: new { controller = controllerName, action = defaultAction };
|
||||
|
||||
|
||||
if (areaName.IsNullOrWhiteSpace())
|
||||
{
|
||||
endpoints.MapControllerRoute(
|
||||
@@ -70,6 +69,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
/// <summary>
|
||||
/// Used to map Umbraco controllers consistently
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="ControllerBase"/> type to route</typeparam>
|
||||
public static void MapUmbracoRoute<T>(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string rootSegment,
|
||||
@@ -82,8 +82,9 @@ namespace Umbraco.Web.Common.Extensions
|
||||
=> endpoints.MapUmbracoRoute(typeof(T), rootSegment, areaName, prefixPathSegment, defaultAction, includeControllerNameInRoute, constraints);
|
||||
|
||||
/// <summary>
|
||||
/// Used to map Umbraco api controllers consistently
|
||||
/// Used to map controllers as Umbraco API routes consistently
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="ControllerBase"/> type to route</typeparam>
|
||||
public static void MapUmbracoApiRoute<T>(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
string rootSegment,
|
||||
@@ -95,7 +96,7 @@ namespace Umbraco.Web.Common.Extensions
|
||||
=> endpoints.MapUmbracoApiRoute(typeof(T), rootSegment, areaName, isBackOffice, defaultAction, constraints);
|
||||
|
||||
/// <summary>
|
||||
/// Used to map Umbraco api controllers consistently
|
||||
/// Used to map controllers as Umbraco API routes consistently
|
||||
/// </summary>
|
||||
public static void MapUmbracoApiRoute(
|
||||
this IEndpointRouteBuilder endpoints,
|
||||
|
||||
@@ -2,13 +2,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System.Reflection;
|
||||
using Umbraco.Web.Common.Install;
|
||||
using Umbraco.Core.Hosting;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Install;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -17,8 +19,6 @@ namespace Umbraco.Extensions
|
||||
/// <summary>
|
||||
/// Return the back office url if the back office is installed
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetBackOfficeUrl(this LinkGenerator linkGenerator, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace Umbraco.Extensions
|
||||
try
|
||||
{
|
||||
backOfficeControllerType = Assembly.Load("Umbraco.Web.BackOffice")?.GetType("Umbraco.Web.BackOffice.Controllers.BackOfficeController");
|
||||
if (backOfficeControllerType == null) return "/"; // this would indicate that the installer is installed without the back office
|
||||
if (backOfficeControllerType == null)
|
||||
{
|
||||
return "/"; // this would indicate that the installer is installed without the back office
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -39,47 +42,33 @@ namespace Umbraco.Extensions
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer
|
||||
/// </summary>
|
||||
/// <param name="linkGenerator"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetInstallerUrl(this LinkGenerator linkGenerator)
|
||||
{
|
||||
return linkGenerator.GetPathByAction(nameof(InstallController.Index), ControllerExtensions.GetControllerName<InstallController>(), new { area = Constants.Web.Mvc.InstallArea });
|
||||
}
|
||||
=> linkGenerator.GetPathByAction(nameof(InstallController.Index), ControllerExtensions.GetControllerName<InstallController>(), new { area = Constants.Web.Mvc.InstallArea });
|
||||
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer api
|
||||
/// </summary>
|
||||
/// <param name="linkGenerator"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetInstallerApiUrl(this LinkGenerator linkGenerator)
|
||||
{
|
||||
return linkGenerator.GetPathByAction(nameof(InstallApiController.GetSetup),
|
||||
=> linkGenerator.GetPathByAction(
|
||||
nameof(InstallApiController.GetSetup),
|
||||
ControllerExtensions.GetControllerName<InstallApiController>(),
|
||||
new { area = Constants.Web.Mvc.InstallArea }).TrimEnd(nameof(InstallApiController.GetSetup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
/// <typeparam name="T">The <see cref="UmbracoApiControllerBase"/></typeparam>
|
||||
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, object id = null)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), new Dictionary<string, object>()
|
||||
{
|
||||
["id"] = id
|
||||
});
|
||||
}
|
||||
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(
|
||||
actionName,
|
||||
typeof(T),
|
||||
new Dictionary<string, object>()
|
||||
{
|
||||
["id"] = id
|
||||
});
|
||||
|
||||
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, string actionName, IDictionary<string, object> values)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), values);
|
||||
}
|
||||
where T : UmbracoApiControllerBase => linkGenerator.GetUmbracoControllerUrl(actionName, typeof(T), values);
|
||||
|
||||
public static string GetUmbracoApiServiceBaseUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
@@ -93,66 +82,86 @@ namespace Umbraco.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// Return the Url for an Umbraco controller
|
||||
/// </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 LinkGenerator linkGenerator, string actionName, string controllerName, string area, IDictionary<string,object> dict = null)
|
||||
public static string GetUmbracoControllerUrl(this LinkGenerator linkGenerator, string actionName, string controllerName, string area, IDictionary<string, object> dict = 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 (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 (dict is null)
|
||||
{
|
||||
dict = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!area.IsNullOrWhiteSpace())
|
||||
{
|
||||
dict["area"] = area;
|
||||
}
|
||||
|
||||
|
||||
var values = dict.Aggregate(new ExpandoObject() as IDictionary<string, object>,
|
||||
(a, p) => { a.Add(p.Key, p.Value); return a; });
|
||||
IDictionary<string, object> values = dict.Aggregate(
|
||||
new ExpandoObject() as IDictionary<string, object>,
|
||||
(a, p) =>
|
||||
{
|
||||
a.Add(p.Key, p.Value);
|
||||
return a;
|
||||
});
|
||||
|
||||
return linkGenerator.GetPathByAction(actionName, controllerName, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Web Api service
|
||||
/// Return the Url for an Umbraco controller
|
||||
/// </summary>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="actionName"></param>
|
||||
/// <param name="apiControllerType"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetUmbracoApiService(this LinkGenerator linkGenerator, string actionName, Type apiControllerType, IDictionary<string,object> values = null)
|
||||
public static string GetUmbracoControllerUrl(this LinkGenerator linkGenerator, string actionName, Type controllerType, IDictionary<string, object> values = 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));
|
||||
if (actionName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionName));
|
||||
}
|
||||
|
||||
var area = "";
|
||||
if (string.IsNullOrWhiteSpace(actionName))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(actionName));
|
||||
}
|
||||
|
||||
if (!typeof(UmbracoApiControllerBase).IsAssignableFrom(apiControllerType))
|
||||
throw new InvalidOperationException($"The controller {apiControllerType} is of type {typeof(UmbracoApiControllerBase)}");
|
||||
if (controllerType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(controllerType));
|
||||
}
|
||||
|
||||
var metaData = PluginController.GetMetadata(apiControllerType);
|
||||
var area = string.Empty;
|
||||
|
||||
if (!typeof(ControllerBase).IsAssignableFrom(controllerType))
|
||||
{
|
||||
throw new InvalidOperationException($"The controller {controllerType} is of type {typeof(ControllerBase)}");
|
||||
}
|
||||
|
||||
PluginControllerMetadata metaData = PluginController.GetMetadata(controllerType);
|
||||
if (metaData.AreaName.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
//set the area to the plugin area
|
||||
// set the area to the plugin area
|
||||
area = metaData.AreaName;
|
||||
}
|
||||
return linkGenerator.GetUmbracoApiService(actionName, ControllerExtensions.GetControllerName(apiControllerType), area, values);
|
||||
|
||||
return linkGenerator.GetUmbracoControllerUrl(actionName, ControllerExtensions.GetControllerName(controllerType), area, values);
|
||||
}
|
||||
|
||||
public static string GetUmbracoApiService<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
|
||||
@@ -170,6 +179,7 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
return linkGenerator.GetUmbracoApiService<T>(method.Name);
|
||||
}
|
||||
|
||||
return linkGenerator.GetUmbracoApiService<T>(method.Name, methodParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,12 @@ namespace Umbraco.Web.Website.Controllers
|
||||
// [MergeParentContextViewData]
|
||||
public abstract class SurfaceController : PluginController
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SurfaceController"/> class.
|
||||
/// </summary>
|
||||
protected SurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider)
|
||||
: base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger)
|
||||
{
|
||||
PublishedUrlProvider = publishedUrlProvider;
|
||||
}
|
||||
=> PublishedUrlProvider = publishedUrlProvider;
|
||||
|
||||
protected IPublishedUrlProvider PublishedUrlProvider { get; }
|
||||
|
||||
@@ -52,49 +53,37 @@ namespace Umbraco.Web.Website.Controllers
|
||||
/// Redirects to the Umbraco page with the given id
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey)
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(contentKey, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the Umbraco page with the given id and passes provided querystring
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(Guid contentKey, QueryString queryString)
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(contentKey, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the Umbraco page with the given published content
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent)
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(publishedContent, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the Umbraco page with the given published content and passes provided querystring
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToUmbracoPage(IPublishedContent publishedContent, QueryString queryString)
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(publishedContent, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the currently rendered Umbraco page
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage()
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(CurrentPage, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the currently rendered Umbraco page and passes provided querystring
|
||||
/// </summary>
|
||||
protected RedirectToUmbracoPageResult RedirectToCurrentUmbracoPage(QueryString queryString)
|
||||
{
|
||||
return new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
}
|
||||
=> new RedirectToUmbracoPageResult(CurrentPage, queryString, PublishedUrlProvider, UmbracoContextAccessor);
|
||||
|
||||
/// <summary>
|
||||
/// Redirects to the currently rendered Umbraco URL
|
||||
@@ -105,17 +94,13 @@ namespace Umbraco.Web.Website.Controllers
|
||||
/// Server.Transfer.*
|
||||
/// </remarks>
|
||||
protected RedirectToUmbracoUrlResult RedirectToCurrentUmbracoUrl()
|
||||
{
|
||||
return new RedirectToUmbracoUrlResult(UmbracoContext);
|
||||
}
|
||||
=> new RedirectToUmbracoUrlResult(UmbracoContext);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently rendered Umbraco page
|
||||
/// </summary>
|
||||
protected UmbracoPageResult CurrentUmbracoPage()
|
||||
{
|
||||
return new UmbracoPageResult(ProfilingLogger);
|
||||
}
|
||||
=> new UmbracoPageResult(ProfilingLogger);
|
||||
|
||||
/// <summary>
|
||||
/// we need to recursively find the route definition based on the parent view context
|
||||
@@ -126,9 +111,9 @@ namespace Umbraco.Web.Website.Controllers
|
||||
while (!(currentContext is null))
|
||||
{
|
||||
var currentRouteData = currentContext.RouteData;
|
||||
if (currentRouteData.Values.ContainsKey(Core.Constants.Web.UmbracoRouteDefinitionDataToken))
|
||||
if (currentRouteData.Values.ContainsKey(Constants.Web.UmbracoRouteDefinitionDataToken))
|
||||
{
|
||||
return Attempt.Succeed((UmbracoRouteValues)currentRouteData.Values[Core.Constants.Web.UmbracoRouteDefinitionDataToken]);
|
||||
return Attempt.Succeed((UmbracoRouteValues)currentRouteData.Values[Constants.Web.UmbracoRouteDefinitionDataToken]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.DependencyInjection;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Infrastructure.DependencyInjection;
|
||||
using Umbraco.Infrastructure.PublishedCache.DependencyInjection;
|
||||
using Umbraco.ModelsBuilder.Embedded.DependencyInjection;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Website.Collections;
|
||||
@@ -44,6 +42,8 @@ namespace Umbraco.Web.Website.DependencyInjection
|
||||
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
|
||||
builder.Services.AddSingleton<IRoutableDocumentFilter, RoutableDocumentFilter>();
|
||||
|
||||
builder.Services.AddSingleton<FrontEndRoutes>();
|
||||
|
||||
builder
|
||||
.AddDistributedCache()
|
||||
.AddModelsBuilder();
|
||||
|
||||
@@ -35,34 +35,20 @@ namespace Umbraco.Extensions
|
||||
public static class HtmlHelperRenderExtensions
|
||||
{
|
||||
private static T GetRequiredService<T>(IHtmlHelper htmlHelper)
|
||||
{
|
||||
return GetRequiredService<T>(htmlHelper.ViewContext);
|
||||
}
|
||||
=> GetRequiredService<T>(htmlHelper.ViewContext);
|
||||
|
||||
private static T GetRequiredService<T>(ViewContext viewContext)
|
||||
{
|
||||
return viewContext.HttpContext.RequestServices.GetRequiredService<T>();
|
||||
}
|
||||
=> viewContext.HttpContext.RequestServices.GetRequiredService<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Renders the markup for the profiler
|
||||
/// </summary>
|
||||
/// <param name="helper"></param>
|
||||
/// <returns></returns>
|
||||
public static IHtmlContent RenderProfiler(this IHtmlHelper helper)
|
||||
{
|
||||
return new HtmlString(GetRequiredService<IProfilerHtml>(helper).Render());
|
||||
}
|
||||
=> new HtmlString(GetRequiredService<IProfilerHtml>(helper).Render());
|
||||
|
||||
/// <summary>
|
||||
/// Renders a partial view that is found in the specified area
|
||||
/// </summary>
|
||||
/// <param name="helper"></param>
|
||||
/// <param name="partial"></param>
|
||||
/// <param name="area"></param>
|
||||
/// <param name="model"></param>
|
||||
/// <param name="viewData"></param>
|
||||
/// <returns></returns>
|
||||
public static IHtmlContent AreaPartial(this IHtmlHelper helper, string partial, string area, object model = null, ViewDataDictionary viewData = null)
|
||||
{
|
||||
var originalArea = helper.ViewContext.RouteData.DataTokens["area"];
|
||||
@@ -76,8 +62,6 @@ namespace Umbraco.Extensions
|
||||
/// Will render the preview badge when in preview mode which is not required ever unless the MVC page you are
|
||||
/// using does not inherit from UmbracoViewPage
|
||||
/// </summary>
|
||||
/// <param name="helper"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// See: http://issues.umbraco.org/issue/U4-1614
|
||||
/// </remarks>
|
||||
@@ -109,9 +93,9 @@ namespace Umbraco.Extensions
|
||||
Func<object, ViewDataDictionary, string> contextualKeyBuilder = null)
|
||||
{
|
||||
var cacheKey = new StringBuilder(partialViewName);
|
||||
//let's always cache by the current culture to allow variants to have different cache results
|
||||
// let's always cache by the current culture to allow variants to have different cache results
|
||||
var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
|
||||
if (!String.IsNullOrEmpty(cultureName))
|
||||
if (!string.IsNullOrEmpty(cultureName))
|
||||
{
|
||||
cacheKey.AppendFormat("{0}-", cultureName);
|
||||
}
|
||||
@@ -123,16 +107,19 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request");
|
||||
}
|
||||
|
||||
cacheKey.AppendFormat("{0}-", umbracoContext.PublishedRequest?.PublishedContent?.Id ?? 0);
|
||||
}
|
||||
|
||||
if (cacheByMember)
|
||||
{
|
||||
//TODO reintroduce when members are migrated
|
||||
// TODO reintroduce when members are migrated
|
||||
throw new NotImplementedException("Reintroduce when members are migrated");
|
||||
// var helper = Current.MembershipHelper;
|
||||
// var currentMember = helper.GetCurrentMember();
|
||||
// cacheKey.AppendFormat("m{0}-", currentMember?.Id ?? 0);
|
||||
}
|
||||
|
||||
if (contextualKeyBuilder != null)
|
||||
{
|
||||
var contextualKey = contextualKeyBuilder(model, viewData);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Website.Controllers;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class LinkGeneratorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the Url for a Surface Controller
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="SurfaceController"/></typeparam>
|
||||
public static string GetUmbracoSurfaceUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
|
||||
where T : SurfaceController
|
||||
{
|
||||
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.GetUmbracoSurfaceUrl<T>(method.Name);
|
||||
}
|
||||
|
||||
return linkGenerator.GetUmbracoSurfaceUrl<T>(method.Name, methodParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the Url for a Surface Controller
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The <see cref="SurfaceController"/></typeparam>
|
||||
public static string GetUmbracoSurfaceUrl<T>(this LinkGenerator linkGenerator, string actionName, object id = null)
|
||||
where T : SurfaceController => linkGenerator.GetUmbracoControllerUrl(
|
||||
actionName,
|
||||
typeof(T),
|
||||
new Dictionary<string, object>()
|
||||
{
|
||||
["id"] = id
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,9 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
FrontEndRoutes surfaceRoutes = app.ApplicationServices.GetRequiredService<FrontEndRoutes>();
|
||||
surfaceRoutes.CreateRoutes(endpoints);
|
||||
|
||||
endpoints.MapDynamicControllerRoute<UmbracoRouteValueTransformer>("/{**slug}");
|
||||
});
|
||||
|
||||
|
||||
105
src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs
Normal file
105
src/Umbraco.Web.Website/Routing/FrontEndRoutes.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
using Umbraco.Web.Common.Routing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.Website.Collections;
|
||||
|
||||
namespace Umbraco.Web.Website.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates routes for surface controllers
|
||||
/// </summary>
|
||||
public sealed class FrontEndRoutes : IAreaRoutes
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IRuntimeState _runtimeState;
|
||||
private readonly SurfaceControllerTypeCollection _surfaceControllerTypeCollection;
|
||||
private readonly UmbracoApiControllerTypeCollection _apiControllers;
|
||||
private readonly string _umbracoPathSegment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FrontEndRoutes"/> class.
|
||||
/// </summary>
|
||||
public FrontEndRoutes(
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
SurfaceControllerTypeCollection surfaceControllerTypeCollection,
|
||||
UmbracoApiControllerTypeCollection apiControllers)
|
||||
{
|
||||
_globalSettings = globalSettings.Value;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_runtimeState = runtimeState;
|
||||
_surfaceControllerTypeCollection = surfaceControllerTypeCollection;
|
||||
_apiControllers = apiControllers;
|
||||
_umbracoPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CreateRoutes(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
if (_runtimeState.Level != RuntimeLevel.Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AutoRouteSurfaceControllers(endpoints);
|
||||
AutoRouteFrontEndApiControllers(endpoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Auto-routes all front-end surface controllers
|
||||
/// </summary>
|
||||
private void AutoRouteSurfaceControllers(IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
foreach (Type controller in _surfaceControllerTypeCollection)
|
||||
{
|
||||
// exclude front-end api controllers
|
||||
PluginControllerMetadata meta = PluginController.GetMetadata(controller);
|
||||
|
||||
endpoints.MapUmbracoRoute(
|
||||
meta.ControllerType,
|
||||
_umbracoPathSegment,
|
||||
meta.AreaName,
|
||||
"Surface");
|
||||
}
|
||||
}
|
||||
|
||||
/// <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,
|
||||
defaultAction: string.Empty); // no default action (this is what we had before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user