diff --git a/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs b/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs new file mode 100644 index 0000000000..0a50a87ccf --- /dev/null +++ b/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Umbraco.Cms.Api.Common.Mvc.ActionResults; + +/// +/// A "created at" action result with no response body. +/// +public sealed class EmptyCreatedAtActionResult : ActionResult +{ + private readonly string? _actionName; + private readonly string? _controllerName; + private readonly object? _routeValues; + + public EmptyCreatedAtActionResult(string? actionName, string? controllerName, object? routeValues) + { + _actionName = actionName; + _controllerName = controllerName; + _routeValues = routeValues; + } + + public override void ExecuteResult(ActionContext context) + { + ArgumentNullException.ThrowIfNull(context); + + HttpRequest request = context.HttpContext.Request; + IUrlHelper urlHelper = context.HttpContext.RequestServices.GetRequiredService().GetUrlHelper(context); + + var url = urlHelper.Action( + _actionName, + _controllerName, + _routeValues, + request.Scheme, + request.Host.ToUriComponent()); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException("No routes could be found that matched the provided route components"); + } + + context.HttpContext.Response.StatusCode = StatusCodes.Status201Created; + context.HttpContext.Response.Headers.Location = url; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs index ea824f40ca..40773d3bbe 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Attributes; using Umbraco.Cms.Api.Common.Filters; +using Umbraco.Cms.Api.Common.Mvc.ActionResults; using Umbraco.Cms.Api.Management.DependencyInjection; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Features; @@ -18,10 +19,10 @@ namespace Umbraco.Cms.Api.Management.Controllers; [JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature { - protected CreatedAtActionResult CreatedAtAction(Expression> action, Guid id) + protected IActionResult CreatedAtAction(Expression> action, Guid id) => CreatedAtAction(action, new { id = id }); - protected CreatedAtActionResult CreatedAtAction(Expression> action, object routeValues) + protected IActionResult CreatedAtAction(Expression> action, object routeValues) { if (action.Body is not ConstantExpression constantExpression) { @@ -31,10 +32,10 @@ public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty); var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value."); - return base.CreatedAtAction(actionName, controllerName, routeValues, null); + return new EmptyCreatedAtActionResult(actionName, controllerName, routeValues); } - protected CreatedAtActionResult CreatedAtAction(Expression> action, string name) + protected IActionResult CreatedAtAction(Expression> action, string name) { if (action.Body is not ConstantExpression constantExpression) { @@ -44,7 +45,7 @@ public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty); var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value."); - return base.CreatedAtAction(actionName, controllerName, new { name = name }, null); + return new EmptyCreatedAtActionResult(actionName, controllerName, new { name = name }); } protected static Guid CurrentUserKey(IBackOfficeSecurityAccessor backOfficeSecurityAccessor)