Gets auto-routing back office api controllers working, lots of notes

This commit is contained in:
Shannon
2020-05-14 17:04:16 +10:00
parent 64b78676da
commit e3591923c3
23 changed files with 225 additions and 122 deletions

View File

@@ -94,7 +94,7 @@ namespace Umbraco.Core.Composing
/// <para>Enables support for MVC, WebAPI, but *not* per-request scope. This is used early in the boot
/// process, where anything "scoped" should not be linked to a web request.</para>
/// </remarks>
void ConfigureForWeb();
void ConfigureForWeb(); // TODO: Unsure if we need this anymore
/// <summary>
/// Creates the factory.

View File

@@ -1,7 +1,8 @@
namespace Umbraco.Web.Trees
{
// TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used
//leave as internal for now, maybe we'll use in the future, means we could pass around ITree
// leave as internal for now, maybe we'll use in the future, means we could pass around ITree
// TODO: We should make this a thing, a tree should just be an interface *not* a controller
internal interface ITree
{
/// <summary>

View File

@@ -37,7 +37,7 @@ namespace Umbraco.Tests.Integration.Implementations
var httpContext = new DefaultHttpContext();
httpContext.Connection.RemoteIpAddress = IPAddress.Parse("127.0.0.1");
_httpContextAccessor = Mock.Of<IHttpContextAccessor>(x => x.HttpContext == httpContext);
_ipResolver = new AspNetIpResolver(_httpContextAccessor);
_ipResolver = new AspNetCoreIpResolver(_httpContextAccessor);
var hostEnvironment = new Mock<IWebHostEnvironment>();
hostEnvironment.Setup(x => x.ApplicationName).Returns("UmbracoIntegrationTests");

View File

@@ -1,13 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Filters;
using Constants = Umbraco.Core.Constants;
namespace Umbraco.Web.BackOffice.Controllers
{
[Area(Umbraco.Core.Constants.Web.Mvc.BackOfficeArea)]
[Area(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))]
[IsBackOffice]
[TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] // TODO: This could be applied with our Application Model conventions
[IsBackOffice] // TODO: This could be applied with our Application Model conventions
public class AuthenticationController : ControllerBase
{
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController and it should not be an auto-routed api controller

View File

@@ -3,23 +3,34 @@ using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.WebApi;
namespace Umbraco.Web.BackOffice.Routing
{
/// <summary>
/// Creates routes for the back office area
/// </summary>
public class BackOfficeAreaRoutes : IAreaRoutes
{
private readonly IGlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IRuntimeState _runtimeState;
private readonly string _umbracoAreaPathSegment;
private readonly UmbracoApiControllerTypeCollection _apiControllers;
private readonly string _umbracoPathSegment;
public BackOfficeAreaRoutes(IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IRuntimeState runtimeState)
public BackOfficeAreaRoutes(
IGlobalSettings globalSettings,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState,
UmbracoApiControllerTypeCollection apiControllers)
{
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
_runtimeState = runtimeState;
_umbracoAreaPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
_apiControllers = apiControllers;
_umbracoPathSegment = _globalSettings.GetUmbracoMvcArea(_hostingEnvironment);
}
public void CreateRoutes(IEndpointRouteBuilder endpoints)
@@ -36,7 +47,7 @@ namespace Umbraco.Web.BackOffice.Routing
case RuntimeLevel.Run:
MapMinimalBackOffice(endpoints);
endpoints.MapUmbracoRoute<PreviewController>(_umbracoAreaPathSegment, Constants.Web.Mvc.BackOfficeArea, "preview");
endpoints.MapUmbracoRoute<PreviewController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, "preview");
AutoRouteBackOfficeControllers(endpoints);
break;
@@ -53,7 +64,7 @@ namespace Umbraco.Web.BackOffice.Routing
/// <param name="endpoints"></param>
private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints)
{
endpoints.MapUmbracoRoute<BackOfficeController>(_umbracoAreaPathSegment, Constants.Web.Mvc.BackOfficeArea,
endpoints.MapUmbracoRoute<BackOfficeController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea,
string.Empty,
"Default",
constraints:
@@ -66,7 +77,7 @@ namespace Umbraco.Web.BackOffice.Routing
id = @"[a-zA-Z]*"
});
endpoints.MapUmbracoRoute<AuthenticationController>(_umbracoAreaPathSegment, Constants.Web.Mvc.BackOfficeArea, true);
endpoints.MapUmbracoApiRoute<AuthenticationController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, true, defaultAction: string.Empty);
}
/// <summary>
@@ -74,7 +85,24 @@ namespace Umbraco.Web.BackOffice.Routing
/// </summary>
private void AutoRouteBackOfficeControllers(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)
{
// exclude front-end api controllers
var meta = PluginController.GetMetadata(controller);
if (!meta.IsBackOffice) continue;
endpoints.MapUmbracoApiRoute(
meta.ControllerType,
_umbracoPathSegment,
meta.AreaName,
true,
defaultAction: string.Empty); // no default action (this is what we had before)
}
}
}
}

View File

@@ -1,5 +1,7 @@
using Umbraco.Core;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Routing;
namespace Umbraco.Web.BackOffice.Runtime

View File

@@ -3,11 +3,11 @@ using Umbraco.Net;
namespace Umbraco.Web.Common.AspNetCore
{
public class AspNetIpResolver : IIpResolver
public class AspNetCoreIpResolver : IIpResolver
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AspNetIpResolver(IHttpContextAccessor httpContextAccessor)
public AspNetCoreIpResolver(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}

View File

@@ -1,4 +1,5 @@
using System;
using Microsoft.AspNetCore.Mvc;
using System;
namespace Umbraco.Web.Common.Attributes
{
@@ -8,6 +9,6 @@ namespace Umbraco.Web.Common.Attributes
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class IsBackOfficeAttribute : Attribute
{
{
}
}

View File

@@ -1,19 +1,20 @@
using System;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Linq;
namespace Umbraco.Web.Common.Attributes
{
/// <summary>
/// Indicates that a controller is a plugin controller and should be routed to its own area.
/// Indicates that a controller is a plugin controller and will be routed to its own area.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginControllerAttribute : Attribute
public class PluginControllerAttribute : AreaAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginControllerAttribute"/> class.
/// </summary>
/// <param name="areaName"></param>
public PluginControllerAttribute(string areaName)
public PluginControllerAttribute(string areaName) : base(areaName)
{
// validate this, only letters and digits allowed.
if (areaName.Any(c => !char.IsLetterOrDigit(c)))

View File

@@ -79,7 +79,7 @@ namespace Umbraco.Web.Common.Controllers
/// </summary>
/// <param name="controllerType">The controller type.</param>
/// <returns>Metadata for the controller type.</returns>
internal static PluginControllerMetadata GetMetadata(Type controllerType)
public static PluginControllerMetadata GetMetadata(Type controllerType)
{
return MetadataStorage.GetOrAdd(controllerType, type =>
{

View File

@@ -14,8 +14,8 @@ namespace Umbraco.Web.Common.Controllers
/// <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>
[FeatureAuthorize]
[TypeFilter(typeof(HttpResponseExceptionFilter))]
[FeatureAuthorize] // TODO: This could be part of our conventions
[TypeFilter(typeof(HttpResponseExceptionFilter))] // TODO: This could be part of our conventions
[UmbracoApiController]
public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature
{

View File

@@ -0,0 +1,10 @@
using Umbraco.Core.Composing;
using Umbraco.Web.WebApi;
namespace Umbraco.Web.Common.Controllers
{
public class UmbracoApiControllerTypeCollectionBuilder : TypeCollectionBuilderBase<UmbracoApiControllerTypeCollectionBuilder, UmbracoApiControllerTypeCollection, UmbracoApiController>
{
protected override UmbracoApiControllerTypeCollectionBuilder This => this;
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using Umbraco.Core.Composing;
using Umbraco.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

@@ -1,13 +0,0 @@
using Umbraco.Core;
using Umbraco.Core.Composing;
namespace Umbraco.Web.Common.Install
{
public class InstallerComposer : IComposer
{
public void Compose(Composition composition)
{
composition.RegisterUnique<InstallAreaRoutes>();
}
}
}

View File

@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using NUglify.Helpers;
using System;
using System.Text;
using Umbraco.Extensions;
@@ -9,6 +10,64 @@ namespace Umbraco.Web.Common.Routing
{
public static class EndpointRouteBuilderExtensions
{
/// <summary>
/// Used to map Umbraco controllers consistently
/// </summary>
/// <param name="endpoints"></param>
/// <param name="controllerType"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="prefixPathSegment"></param>
/// <param name="defaultAction"></param>
/// <param name="includeControllerNameInRoute"></param>
/// <param name="constraints"></param>
public static void MapUmbracoRoute(
this IEndpointRouteBuilder endpoints,
Type controllerType,
string rootSegment,
string areaName,
string prefixPathSegment,
string defaultAction = "Index",
bool includeControllerNameInRoute = true,
object constraints = null)
{
var controllerName = ControllerExtensions.GetControllerName(controllerType);
// build the route pattern
var pattern = new StringBuilder(rootSegment);
if (!prefixPathSegment.IsNullOrWhiteSpace())
pattern.Append("/").Append(prefixPathSegment);
if (includeControllerNameInRoute)
pattern.Append("/").Append(controllerName);
pattern.Append("/").Append("{action}/{id?}");
var defaults = defaultAction.IsNullOrWhiteSpace()
? (object) new { controller = controllerName }
: new { controller = controllerName, action = defaultAction };
if (areaName.IsNullOrWhiteSpace())
{
endpoints.MapControllerRoute(
// named consistently
$"umbraco-{areaName}-{controllerName}".ToLowerInvariant(),
pattern.ToString().ToLowerInvariant(),
defaults,
constraints);
}
else
{
endpoints.MapAreaControllerRoute(
// named consistently
$"umbraco-{areaName}-{controllerName}".ToLowerInvariant(),
areaName,
pattern.ToString().ToLowerInvariant(),
defaults,
constraints);
}
}
/// <summary>
/// Used to map Umbraco controllers consistently
/// </summary>
@@ -28,43 +87,49 @@ namespace Umbraco.Web.Common.Routing
bool includeControllerNameInRoute = true,
object constraints = null)
where T : ControllerBase
{
var controllerName = ControllerExtensions.GetControllerName<T>();
// build the route pattern
var pattern = new StringBuilder(rootSegment);
if (!prefixPathSegment.IsNullOrWhiteSpace())
pattern.Append("/").Append(prefixPathSegment);
if (includeControllerNameInRoute)
pattern.Append("/").Append(controllerName);
pattern.Append("/").Append("{action}/{id?}");
endpoints.MapAreaControllerRoute(
// named consistently
$"umbraco-{areaName}-{controllerName}".ToLowerInvariant(),
areaName,
pattern.ToString().ToLowerInvariant(),
new { controller = controllerName, action = defaultAction },
constraints);
}
=> endpoints.MapUmbracoRoute(typeof(T), rootSegment, areaName, prefixPathSegment, defaultAction, includeControllerNameInRoute, constraints);
/// <summary>
/// Used to map Umbraco controllers consistently
/// Used to map Umbraco api controllers consistently
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="endpoints"></param>
/// <param name="rootPathSegment"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="isBackOffice"></param>
/// <param name="isBackOffice">If the route is a back office route</param>
/// <param name="constraints"></param>
public static void MapUmbracoRoute<T>(
public static void MapUmbracoApiRoute<T>(
this IEndpointRouteBuilder endpoints,
string rootPathSegment,
string rootSegment,
string areaName,
bool isBackOffice,
string defaultAction = "Index",
object constraints = null)
where T : ControllerBase
=> endpoints.MapUmbracoRoute<T>(rootPathSegment, areaName, isBackOffice ? "BackOffice" : "Api", defaultAction, true, constraints);
=> endpoints.MapUmbracoRoute(typeof(T), rootSegment, areaName, isBackOffice ? "BackOffice/Api" : "Api", defaultAction, true, constraints);
/// <summary>
/// Used to map Umbraco api controllers consistently
/// </summary>
/// <param name="endpoints"></param>
/// <param name="controllerType"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="isBackOffice">If the route is a back office route</param>
/// <param name="defaultAction"></param>
/// <param name="constraints"></param>
public static void MapUmbracoApiRoute(
this IEndpointRouteBuilder endpoints,
Type controllerType,
string rootSegment,
string areaName,
bool isBackOffice,
string defaultAction = "Index",
object constraints = null)
=> endpoints.MapUmbracoRoute(controllerType, rootSegment, areaName,
isBackOffice
? (areaName.IsNullOrWhiteSpace() ? "BackOffice/Api" : $"BackOffice/{areaName}")
: (areaName.IsNullOrWhiteSpace() ? "Api" : areaName),
defaultAction, true, constraints);
}
}

View File

@@ -14,6 +14,10 @@ using Umbraco.Web.Macros;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Logging;
using Umbraco.Web.Common.Profiler;
using Umbraco.Web.Common.Install;
using Umbraco.Extensions;
using System.Linq;
using Umbraco.Web.Common.Controllers;
namespace Umbraco.Web.Common.Runtime
{
@@ -40,12 +44,12 @@ namespace Umbraco.Web.Common.Runtime
// The umbraco request lifetime
composition.RegisterMultipleUnique<IUmbracoRequestLifetime, IUmbracoRequestLifetimeManager, UmbracoRequestLifetime>();
//Password hasher
composition.RegisterUnique<IPasswordHasher, AspNetCorePasswordHasher>();
composition.RegisterUnique<ICookieManager, AspNetCoreCookieManager>();
composition.Register<IIpResolver, AspNetCoreIpResolver>();
composition.RegisterUnique<IUserAgentProvider, AspNetCoreUserAgentProvider>();
composition.RegisterMultipleUnique<ISessionIdResolver, ISessionManager, AspNetCoreSessionManager>();
@@ -67,6 +71,12 @@ namespace Umbraco.Web.Common.Runtime
//it still needs to use the install controller so we can't do that
composition.ComposeInstaller();
var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList();
composition.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
.Add(umbracoApiControllerTypes);
composition.RegisterUnique<InstallAreaRoutes>();
}
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Controllers;
namespace Umbraco.Web.UI.NetCore.Controllers
{
[PluginController("Test")]
[IsBackOffice]
public class TestBackOfficeUmbracoApiController : UmbracoApiController
{
[HttpGet]
public IActionResult Index()
{
return Content("hello world");
}
}
}

View File

@@ -30,7 +30,14 @@ namespace Umbraco.Web.UI.BackOffice
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
{
// TODO: We will need to decide on if we want to use the ServiceBasedControllerActivator to create our controllers
// or use the default IControllerActivator: DefaultControllerActivator (which doesn't directly use the container to resolve controllers)
// This will affect whether we need to explicitly register controllers in the container like we do today in v8.
// What we absolutely must do though is make sure we explicitly opt-in to using one or the other *always* for our controllers instead of
// relying on a global configuration set by a user since if a custom IControllerActivator is used for our own controllers we may not
// guarantee it will work. And then... is that even possible?
services.AddUmbracoConfiguration(_config);
services.AddUmbracoCore(_env, out var factory);
services.AddUmbracoWebComponents();

View File

@@ -3,9 +3,8 @@ using System.Linq;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// Indicates that a controller is a plugin tree controller and should be routed to its own area.
/// </summary>
// TODO: This has been moved to netcore so can be removed when ready
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginControllerAttribute : Attribute
{

View File

@@ -1,19 +1,13 @@
using System.Linq;
using System.Web.Security;
using Examine;
using Microsoft.AspNet.SignalR;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Hosting;
using Umbraco.Core.Install;
using Umbraco.Core.Runtime;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Composing.CompositionExtensions;
using Umbraco.Web.Hosting;
using Umbraco.Web.Install;
using Umbraco.Web.Macros;
using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
@@ -22,12 +16,6 @@ using Umbraco.Web.Security.Providers;
using Umbraco.Web.SignalR;
using Umbraco.Web.Templates;
using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
using Umbraco.Net;
using Umbraco.Web.AspNet;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Logging;
using Umbraco.Web.Logging;
namespace Umbraco.Web.Runtime
{
@@ -41,28 +29,9 @@ namespace Umbraco.Web.Runtime
base.Compose(composition);
composition.Register<UmbracoInjectedModule>();
composition.Register<IIpResolver, AspNetIpResolver>();
composition.Register<IUserAgentProvider, AspNetUserAgentProvider>();
composition.Register<IRequestAccessor, AspNetRequestAccessor>(Lifetime.Singleton);
composition.Register<IUmbracoApplicationLifetime, AspNetUmbracoApplicationLifetime>(Lifetime.Singleton);
composition.Register<IPasswordHasher, AspNetPasswordHasher>();
composition.RegisterUnique<IMarchal, FrameworkMarchal>();
composition.RegisterUnique<IProfilerHtml, WebProfiler>();
composition.ComposeWebMappingProfiles();
//register the install components
//NOTE: i tried to not have these registered if we weren't installing or upgrading but post install when the site restarts
//it still needs to use the install controller so we can't do that
composition.ComposeInstaller();
// register membership stuff
composition.Register(factory => MembershipProviderExtensions.GetMembersMembershipProvider());
composition.Register(factory => Roles.Enabled ? Roles.Provider : new MembersRoleProvider(factory.GetInstance<IMemberService>()));
@@ -71,11 +40,7 @@ namespace Umbraco.Web.Runtime
composition.RegisterUnique<IMemberUserKeyProvider, MemberUserKeyProvider>();
composition.RegisterUnique<IPublicAccessChecker, PublicAccessChecker>();
// register the umbraco context factory
composition.RegisterUnique<IUmbracoContextFactory, UmbracoContextFactory>();
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
composition.RegisterUnique<IMacroRenderer, MacroRenderer>();
composition.RegisterUnique<IUmbracoComponentRenderer, UmbracoComponentRenderer>();
@@ -98,16 +63,14 @@ namespace Umbraco.Web.Runtime
composition.ConfigureForWeb();
composition
// TODO: This will depend on if we use ServiceBasedControllerActivator - see notes in Startup.cs
.ComposeUmbracoControllers(GetType().Assembly)
.SetDefaultRenderMvcController<RenderMvcController>(); // default controller for template views
//we need to eagerly scan controller types since they will need to be routed
composition.WithCollectionBuilder<SurfaceControllerTypeCollectionBuilder>()
.Add(composition.TypeLoader.GetSurfaceControllers());
var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList();
composition.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
.Add(umbracoApiControllerTypes);
// add all known factories, devs can then modify this list on application
// startup either by binding to events or in their own global.asax
@@ -120,6 +83,8 @@ namespace Umbraco.Web.Runtime
// register preview SignalR hub
composition.RegisterUnique(_ => GlobalHost.ConnectionManager.GetHubContext<PreviewHub>());
var umbracoApiControllerTypes = composition.TypeLoader.GetUmbracoApiControllers().ToList();
// register back office trees
// the collection builder only accepts types inheriting from TreeControllerBase
// and will filter out those that are not attributed with TreeAttribute
@@ -127,18 +92,6 @@ namespace Umbraco.Web.Runtime
.AddTreeControllers(umbracoApiControllerTypes.Where(x => typeof(TreeControllerBase).IsAssignableFrom(x)));
// STUFF that do not have to be moved to .NET CORE
//----------------------------------------
composition.RegisterUnique<ICookieManager, AspNetCookieManager>();
composition.RegisterUnique<IApplicationShutdownRegistry, AspNetApplicationShutdownRegistry>();
composition.RegisterUnique<IConfigManipulator, XmlConfigManipulator>();
composition.RegisterUnique<IHttpContextAccessor, AspNetHttpContextAccessor>(); // required for hybrid accessors
composition.Register<AspNetSessionManager>(Lifetime.Singleton);
composition.Register<ISessionIdResolver>(factory => factory.GetInstance<AspNetSessionManager>(), Lifetime.Singleton);
composition.Register<ISessionManager>(factory => factory.GetInstance<AspNetSessionManager>(), Lifetime.Singleton);
}
}
}

View File

@@ -2,10 +2,8 @@
namespace Umbraco.Web.WebApi
{
/// <summary>
/// When applied to an api controller it will be routed to the /Umbraco/BackOffice prefix route so we can determine if it
/// is a back office route or not.
/// </summary>
// TODO: This has been moved to netcore so can be removed when ready
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class IsBackOfficeAttribute : Attribute
{

View File

@@ -2,6 +2,8 @@
namespace Umbraco.Web.WebApi
{
// TODO: This is moved to netcore so can be deleted when possible
public class UmbracoApiControllerTypeCollectionBuilder : TypeCollectionBuilderBase<UmbracoApiControllerTypeCollectionBuilder, UmbracoApiControllerTypeCollection, UmbracoApiController>
{
protected override UmbracoApiControllerTypeCollectionBuilder This => this;