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

@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
@@ -16,8 +17,27 @@ public interface ITrackedReferencesService
/// dependencies (isDependency field is set to true).
/// </param>
/// <returns>A paged result of <see cref="RelationItemModel" /> objects.</returns>
[Obsolete("Use GetPagedRelationsForItemAsync which returns an Attempt with operation status. Scheduled for removal in Umbraco 19.")]
Task<PagedModel<RelationItemModel>> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency);
/// <summary>
/// Gets a paged result of items which are in relation with the current item.
/// Basically, shows the items which depend on the current item.
/// </summary>
/// <param name="key">The identifier of the entity to retrieve relations for.</param>
/// <param name="objectType">The Umbraco object type of the parent.</param>
/// <param name="skip">The amount of items to skip</param>
/// <param name="take">The amount of items to take.</param>
/// <param name="filterMustBeIsDependency">
/// A boolean indicating whether to filter only the RelationTypes which are
/// dependencies (isDependency field is set to true).
/// </param>
/// <returns>A paged result of <see cref="RelationItemModel" /> objects.</returns>
async Task<Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus>> GetPagedRelationsForItemAsync(Guid key, UmbracoObjectTypes objectType, long skip, long take, bool filterMustBeIsDependency)
#pragma warning disable CS0618 // Type or member is obsolete
=> Attempt.SucceedWithStatus(GetReferencesOperationStatus.Success, await GetPagedRelationsForItemAsync(key, skip, take, filterMustBeIsDependency));
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Gets a paged result of items which are in relation with an item in the recycle bin.
/// </summary>
@@ -42,8 +62,26 @@ public interface ITrackedReferencesService
/// dependencies (isDependency field is set to true).
/// </param>
/// <returns>A paged result of <see cref="RelationItemModel" /> objects.</returns>
[Obsolete("Use GetPagedDescendantsInReferencesAsync which returns an Attempt with operation status. Scheduled for removal in Umbraco 19.")]
Task<PagedModel<RelationItemModel>> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency);
/// <summary>
/// Gets a paged result of the descending items that have any references, given a parent id.
/// </summary>
/// <param name="parentKey">The unique identifier of the parent to retrieve descendants for.</param>
/// <param name="objectType">The Umbraco object type of the parent.</param>
/// <param name="skip">The amount of items to skip</param>
/// <param name="take">The amount of items to take.</param>
/// <param name="filterMustBeIsDependency">
/// A boolean indicating whether to filter only the RelationTypes which are
/// dependencies (isDependency field is set to true).
/// </param>
/// <returns>An <see cref="Attempt"/> wrapping a paged result of <see cref="RelationItemModel" /> objects.</returns>
async Task<Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus>> GetPagedDescendantsInReferencesAsync(Guid parentKey, UmbracoObjectTypes objectType, long skip, long take, bool filterMustBeIsDependency)
#pragma warning disable CS0618 // Type or member is obsolete
=> Attempt.SucceedWithStatus(GetReferencesOperationStatus.Success, await GetPagedDescendantsInReferencesAsync(parentKey, skip, take, filterMustBeIsDependency));
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Gets a paged result of items used in any kind of relation from selected integer ids.
/// </summary>

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Cms.Core.Services.OperationStatus;
public enum GetReferencesOperationStatus
{
Success,
ContentNotFound
}

View File

@@ -1,6 +1,8 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
@@ -20,6 +22,7 @@ public class TrackedReferencesService : ITrackedReferencesService
_entityService = entityService;
}
[Obsolete("Use the GetPagedRelationsForItemAsync overload which returns an Attempt with operation status. Scheduled for removal in Umbraco 19.")]
public Task<PagedModel<RelationItemModel>> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
@@ -29,6 +32,21 @@ public class TrackedReferencesService : ITrackedReferencesService
return Task.FromResult(pagedModel);
}
public async Task<Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus>> GetPagedRelationsForItemAsync(Guid key, UmbracoObjectTypes objectType, long skip, long take, bool filterMustBeIsDependency)
{
IEntitySlim? entity = _entityService.Get(key, objectType);
if (entity is null)
{
return Attempt.FailWithStatus(GetReferencesOperationStatus.ContentNotFound, new PagedModel<RelationItemModel>());
}
#pragma warning disable CS0618 // Type or member is obsolete (but using whilst it exists to avoid code repetition)
PagedModel<RelationItemModel> pagedModel = await GetPagedRelationsForItemAsync(key, skip, take, filterMustBeIsDependency);
#pragma warning restore CS0618 // Type or member is obsolete
return Attempt.SucceedWithStatus(GetReferencesOperationStatus.Success, pagedModel);
}
public Task<PagedModel<RelationItemModel>> GetPagedRelationsForRecycleBinAsync(UmbracoObjectTypes objectType, long skip, long take, bool filterMustBeIsDependency)
{
Guid objectTypeKey = objectType switch
@@ -44,6 +62,7 @@ public class TrackedReferencesService : ITrackedReferencesService
return Task.FromResult(pagedModel);
}
[Obsolete("Use GetPagedDescendantsInReferencesAsync which returns an Attempt with operation status. Scheduled for removal in Umbraco 19.")]
public Task<PagedModel<RelationItemModel>> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
@@ -59,6 +78,21 @@ public class TrackedReferencesService : ITrackedReferencesService
return Task.FromResult(pagedModel);
}
public async Task<Attempt<PagedModel<RelationItemModel>, GetReferencesOperationStatus>> GetPagedDescendantsInReferencesAsync(Guid parentKey, UmbracoObjectTypes objectType, long skip, long take, bool filterMustBeIsDependency)
{
IEntitySlim? entity = _entityService.Get(parentKey, objectType);
if (entity is null)
{
return Attempt.FailWithStatus(GetReferencesOperationStatus.ContentNotFound, new PagedModel<RelationItemModel>());
}
#pragma warning disable CS0618 // Type or member is obsolete (but using whilst it exists to avoid code repetition)
PagedModel<RelationItemModel> pagedModel = await GetPagedDescendantsInReferencesAsync(parentKey, skip, take, filterMustBeIsDependency);
#pragma warning restore CS0618 // Type or member is obsolete
return Attempt.SucceedWithStatus(GetReferencesOperationStatus.Success, pagedModel);
}
public Task<PagedModel<RelationItemModel>> GetPagedItemsWithRelationsAsync(ISet<Guid> keys, long skip, long take, bool filterMustBeIsDependency)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);