Management API: Return not found from request for content references when entity does not exist (closes #20997) (#20999)

* Return not found when request for content references when entity does not exist.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Move check for entity existence from controller to the service.

* Update OpenApi.json.

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Addressed points raised in code review.

* Update OpenApi.json

* Resolved breaking changes.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Andy Butland
2025-12-02 05:25:43 +01:00
committed by GitHub
parent 84c15ff4d7
commit da94e0953b
12 changed files with 443 additions and 68 deletions

View File

@@ -4,8 +4,10 @@ using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document.References;
@@ -15,22 +17,16 @@ public class ReferencedByDocumentController : DocumentControllerBase
private readonly ITrackedReferencesService _trackedReferencesService;
private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
public ReferencedByDocumentController(ITrackedReferencesService trackedReferencesService, IRelationTypePresentationFactory relationTypePresentationFactory)
public ReferencedByDocumentController(
ITrackedReferencesService trackedReferencesService,
IRelationTypePresentationFactory relationTypePresentationFactory)
{
_trackedReferencesService = trackedReferencesService;
_relationTypePresentationFactory = relationTypePresentationFactory;
}
/// <summary>
/// Gets a paged list of tracked references for the current item, so you can see where an item is being used.
/// </summary>
/// <remarks>
/// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
/// This is basically finding parents of relations.
/// </remarks>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
[Obsolete("Use the ReferencedBy2 action method instead. Scheduled for removal in Umbraco 19, when ReferencedBy2 will be renamed back to ReferencedBy.")]
[NonAction]
public async Task<ActionResult<PagedViewModel<IReferenceResponseModel>>> ReferencedBy(
CancellationToken cancellationToken,
Guid id,
@@ -47,4 +43,37 @@ public class ReferencedByDocumentController : DocumentControllerBase
return pagedViewModel;
}
/// <summary>
/// Gets a paged list of tracked references for the current item, so you can see where an item is being used.
/// </summary>
/// <remarks>
/// Used by info tabs on content, media etc. and for the delete and unpublish of single items.
/// This is basically finding parents of relations.
/// </remarks>
[HttpGet("{id:guid}/referenced-by")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<IReferenceResponseModel>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ReferencedBy2(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus> relationItemsAttempt = await _trackedReferencesService.GetPagedRelationsForItemAsync(id, UmbracoObjectTypes.Document, skip, take, true);
if (relationItemsAttempt.Success is false)
{
return GetReferencesOperationStatusResult(relationItemsAttempt.Status);
}
var pagedViewModel = new PagedViewModel<IReferenceResponseModel>
{
Total = relationItemsAttempt.Result.Total,
Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItemsAttempt.Result.Items),
};
return Ok(pagedViewModel);
}
}

View File

@@ -3,9 +3,11 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.ViewModels;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Document.References;
@@ -15,12 +17,32 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
private readonly ITrackedReferencesService _trackedReferencesSkipTakeService;
private readonly IUmbracoMapper _umbracoMapper;
public ReferencedDescendantsDocumentController(ITrackedReferencesService trackedReferencesSkipTakeService, IUmbracoMapper umbracoMapper)
public ReferencedDescendantsDocumentController(
ITrackedReferencesService trackedReferencesSkipTakeService,
IUmbracoMapper umbracoMapper)
{
_trackedReferencesSkipTakeService = trackedReferencesSkipTakeService;
_umbracoMapper = umbracoMapper;
}
[Obsolete("Use the ReferencedDescendants2 action method instead. Scheduled for removal in Umbraco 19, when ReferencedDescendants2 will be renamed back to ReferencedDescendants.")]
[NonAction]
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDescendants(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, skip, take, true);
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = relationItems.Total,
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
};
return pagedViewModel;
}
/// <summary>
/// Gets a paged list of the descendant nodes of the current item used in any kind of relation.
/// </summary>
@@ -32,19 +54,26 @@ public class ReferencedDescendantsDocumentController : DocumentControllerBase
[HttpGet("{id:guid}/referenced-descendants")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<ReferenceByIdModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<ReferenceByIdModel>>> ReferencedDescendants(
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ReferencedDescendants2(
CancellationToken cancellationToken,
Guid id,
int skip = 0,
int take = 20)
{
PagedModel<RelationItemModel> relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, skip, take, true);
Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus> relationItemsAttempt = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(id, UmbracoObjectTypes.Document, skip, take, true);
if (relationItemsAttempt.Success is false)
{
return GetReferencesOperationStatusResult(relationItemsAttempt.Status);
}
var pagedViewModel = new PagedViewModel<ReferenceByIdModel>
{
Total = relationItems.Total,
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItems.Items),
Total = relationItemsAttempt.Result.Total,
Items = _umbracoMapper.MapEnumerable<RelationItemModel, ReferenceByIdModel>(relationItemsAttempt.Result.Items),
};
return pagedViewModel;
return Ok(pagedViewModel);
}
}