diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs
new file mode 100644
index 000000000000..b36815d408ee
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/References/ReferencedByDataTypeController.cs
@@ -0,0 +1,46 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+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.Models;
+using Umbraco.Cms.Core.Services;
+
+namespace Umbraco.Cms.Api.Management.Controllers.DataType.References;
+
+[ApiVersion("1.0")]
+public class ReferencedByDataTypeController : DataTypeControllerBase
+{
+ private readonly IDataTypeService _dataTypeService;
+ private readonly IRelationTypePresentationFactory _relationTypePresentationFactory;
+
+ public ReferencedByDataTypeController(IDataTypeService dataTypeService, IRelationTypePresentationFactory relationTypePresentationFactory)
+ {
+ _dataTypeService = dataTypeService;
+ _relationTypePresentationFactory = relationTypePresentationFactory;
+ }
+
+ ///
+ /// Gets a paged list of references for the current data type, so you can see where it is being used.
+ ///
+ [HttpGet("{id:guid}/referenced-by")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)]
+ public async Task>> ReferencedBy(
+ CancellationToken cancellationToken,
+ Guid id,
+ int skip = 0,
+ int take = 20)
+ {
+ PagedModel relationItems = await _dataTypeService.GetPagedRelationsAsync(id, skip, take);
+
+ var pagedViewModel = new PagedViewModel
+ {
+ Total = relationItems.Total,
+ Items = await _relationTypePresentationFactory.CreateReferenceResponseModelsAsync(relationItems.Items),
+ };
+
+ return pagedViewModel;
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
index c25586e93cad..0eee28e49b80 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/ReferencesDataTypeController.cs
@@ -1,4 +1,4 @@
-using Asp.Versioning;
+using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
@@ -10,6 +10,7 @@
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
[ApiVersion("1.0")]
+[Obsolete("Please use ReferencedByDataTypeController and the referenced-by endpoint. Scheduled for removal in Umbraco 17.")]
public class ReferencesDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeService _dataTypeService;
diff --git a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
index a6fb1cbae85f..40bcf9f04c16 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/RelationTypePresentationFactory.cs
@@ -56,9 +56,12 @@ public async Task> CreateReferenceResponseM
IReferenceResponseModel[] result = relationItemModelsCollection.Select(relationItemModel =>
relationItemModel.NodeType switch
{
- Constants.UdiEntityType.Document => MapDocumentReference(relationItemModel, slimEntities),
- Constants.UdiEntityType.Media => _umbracoMapper.Map(relationItemModel),
- Constants.UdiEntityType.Member => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.Document => MapDocumentReference(relationItemModel, slimEntities),
+ Constants.ReferenceType.Media => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.Member => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.DocumentTypePropertyType => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.MediaTypePropertyType => _umbracoMapper.Map(relationItemModel),
+ Constants.ReferenceType.MemberTypePropertyType => _umbracoMapper.Map(relationItemModel),
_ => _umbracoMapper.Map(relationItemModel),
}).WhereNotNull().ToArray();
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
index e9f2700f5ad2..c4efca308829 100644
--- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
+++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs
@@ -12,6 +12,9 @@ public void DefineMaps(IUmbracoMapper mapper)
mapper.Define((source, context) => new DocumentReferenceResponseModel(), Map);
mapper.Define((source, context) => new MediaReferenceResponseModel(), Map);
mapper.Define((source, context) => new MemberReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new DocumentTypePropertyTypeReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new MediaTypePropertyTypeReferenceResponseModel(), Map);
+ mapper.Define((source, context) => new MemberTypePropertyTypeReferenceResponseModel(), Map);
mapper.Define((source, context) => new DefaultReferenceResponseModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
mapper.Define((source, context) => new ReferenceByIdModel(), Map);
@@ -25,6 +28,7 @@ private void Map(RelationItemModel source, DocumentReferenceResponseModel target
target.Published = source.NodePublished;
target.DocumentType = new TrackedReferenceDocumentType
{
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
@@ -38,6 +42,7 @@ private void Map(RelationItemModel source, MediaReferenceResponseModel target, M
target.Name = source.NodeName;
target.MediaType = new TrackedReferenceMediaType
{
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
@@ -51,6 +56,52 @@ private void Map(RelationItemModel source, MemberReferenceResponseModel target,
target.Name = source.NodeName;
target.MemberType = new TrackedReferenceMemberType
{
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, DocumentTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.DocumentType = new TrackedReferenceDocumentType
+ {
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, MediaTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.MediaType = new TrackedReferenceMediaType
+ {
+ Id = source.ContentTypeKey,
+ Alias = source.ContentTypeAlias,
+ Icon = source.ContentTypeIcon,
+ Name = source.ContentTypeName,
+ };
+ }
+
+ // Umbraco.Code.MapAll
+ private void Map(RelationItemModel source, MemberTypePropertyTypeReferenceResponseModel target, MapperContext context)
+ {
+ target.Id = source.NodeKey;
+ target.Name = source.NodeName;
+ target.Alias = source.NodeAlias;
+ target.MemberType = new TrackedReferenceMemberType
+ {
+ Id = source.ContentTypeKey,
Alias = source.ContentTypeAlias,
Icon = source.ContentTypeIcon,
Name = source.ContentTypeName,
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 44a69ccc957e..06ab75e1b5f9 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -816,6 +816,70 @@
]
}
},
+ "/umbraco/management/api/v1/data-type/{id}/referenced-by": {
+ "get": {
+ "tags": [
+ "Data Type"
+ ],
+ "operationId": "GetDataTypeByIdReferencedBy",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 20
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedIReferenceResponseModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user does not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/data-type/{id}/references": {
"get": {
"tags": [
@@ -872,6 +936,7 @@
"description": "The authenticated user does not have access to this resource"
}
},
+ "deprecated": true,
"security": [
{
"Backoffice User": [ ]
@@ -37705,6 +37770,45 @@
},
"additionalProperties": false
},
+ "DocumentTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "documentType",
+ "id"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "documentType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceDocumentTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "DocumentTypePropertyReferenceResponseModel": "#/components/schemas/DocumentTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"DocumentTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -39769,6 +39873,45 @@
},
"additionalProperties": false
},
+ "MediaTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "id",
+ "mediaType"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "mediaType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceMediaTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "MediaTypePropertyReferenceResponseModel": "#/components/schemas/MediaTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"MediaTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -40547,6 +40690,45 @@
},
"additionalProperties": false
},
+ "MemberTypePropertyReferenceResponseModel": {
+ "required": [
+ "$type",
+ "id",
+ "memberType"
+ ],
+ "type": "object",
+ "properties": {
+ "$type": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "alias": {
+ "type": "string",
+ "nullable": true
+ },
+ "memberType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/TrackedReferenceMemberTypeModel"
+ }
+ ]
+ }
+ },
+ "additionalProperties": false,
+ "discriminator": {
+ "propertyName": "$type",
+ "mapping": {
+ "MemberTypePropertyReferenceResponseModel": "#/components/schemas/MemberTypePropertyReferenceResponseModel"
+ }
+ }
+ },
"MemberTypePropertyTypeContainerResponseModel": {
"required": [
"id",
@@ -41754,11 +41936,20 @@
{
"$ref": "#/components/schemas/DocumentReferenceResponseModel"
},
+ {
+ "$ref": "#/components/schemas/DocumentTypePropertyReferenceResponseModel"
+ },
{
"$ref": "#/components/schemas/MediaReferenceResponseModel"
},
+ {
+ "$ref": "#/components/schemas/MediaTypePropertyReferenceResponseModel"
+ },
{
"$ref": "#/components/schemas/MemberReferenceResponseModel"
+ },
+ {
+ "$ref": "#/components/schemas/MemberTypePropertyReferenceResponseModel"
}
]
}
@@ -44396,8 +44587,15 @@
"additionalProperties": false
},
"TrackedReferenceDocumentTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
@@ -44414,8 +44612,15 @@
"additionalProperties": false
},
"TrackedReferenceMediaTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
@@ -44432,8 +44637,15 @@
"additionalProperties": false
},
"TrackedReferenceMemberTypeModel": {
+ "required": [
+ "id"
+ ],
"type": "object",
"properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
"icon": {
"type": "string",
"nullable": true
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 000000000000..83dc6a1a7a5f
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/ContentTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public abstract class ContentTypePropertyTypeReferenceResponseModel : ReferenceResponseModel
+{
+ public string? Alias { get; set; }
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 000000000000..b2ef3e4a0ad2
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/DocumentTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class DocumentTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceDocumentType DocumentType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 000000000000..1baf6476545f
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MediaTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class MediaTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceMediaType MediaType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs
new file mode 100644
index 000000000000..199a4b0ba18d
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/MemberTypePropertyTypeReferenceResponseModel.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+
+public class MemberTypePropertyTypeReferenceResponseModel : ContentTypePropertyTypeReferenceResponseModel
+{
+ public TrackedReferenceMemberType MemberType { get; set; } = new();
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
index 31456abada1a..15ac365e41e2 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/TrackedReferenceContentType.cs
@@ -1,7 +1,9 @@
-namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
+namespace Umbraco.Cms.Api.Management.ViewModels.TrackedReferences;
public abstract class TrackedReferenceContentType
{
+ public Guid Id { get; set; }
+
public string? Icon { get; set; }
public string? Alias { get; set; }
diff --git a/src/Umbraco.Core/Constants-ReferenceTypes.cs b/src/Umbraco.Core/Constants-ReferenceTypes.cs
new file mode 100644
index 000000000000..b006a0d59007
--- /dev/null
+++ b/src/Umbraco.Core/Constants-ReferenceTypes.cs
@@ -0,0 +1,25 @@
+namespace Umbraco.Cms.Core;
+
+public static partial class Constants
+{
+ ///
+ /// Defines reference types.
+ ///
+ ///
+ /// Reference types are used to identify the type of entity that is being referenced when exposing references
+ /// between Umbraco entities.
+ /// These are used in the management API and backoffice to indicate and warn editors when working with an entity,
+ /// as to what other entities depend on it.
+ /// These consist of references managed by Umbraco relations (e.g. document, media and member).
+ /// But also references that come from schema (e.g. data type usage on content types).
+ ///
+ public static class ReferenceType
+ {
+ public const string Document = UdiEntityType.Document;
+ public const string Media = UdiEntityType.Media;
+ public const string Member = UdiEntityType.Member;
+ public const string DocumentTypePropertyType = "document-type-property-type";
+ public const string MediaTypePropertyType = "media-type-property-type";
+ public const string MemberTypePropertyType = "member-type-property-type";
+ }
+}
diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs
index a865e7cc2fbf..41fde0e867be 100644
--- a/src/Umbraco.Core/Models/RelationItem.cs
+++ b/src/Umbraco.Core/Models/RelationItem.cs
@@ -23,6 +23,9 @@ public class RelationItem
[DataMember(Name = "published")]
public bool? NodePublished { get; set; }
+ [DataMember(Name = "contentTypeKey")]
+ public Guid ContentTypeKey { get; set; }
+
[DataMember(Name = "icon")]
public string? ContentTypeIcon { get; set; }
diff --git a/src/Umbraco.Core/Models/RelationItemModel.cs b/src/Umbraco.Core/Models/RelationItemModel.cs
index a05c8f65918f..1ca3bb9e111e 100644
--- a/src/Umbraco.Core/Models/RelationItemModel.cs
+++ b/src/Umbraco.Core/Models/RelationItemModel.cs
@@ -1,15 +1,19 @@
-namespace Umbraco.Cms.Core.Models;
+namespace Umbraco.Cms.Core.Models;
public class RelationItemModel
{
public Guid NodeKey { get; set; }
+ public string? NodeAlias { get; set; }
+
public string? NodeName { get; set; }
public string? NodeType { get; set; }
public bool? NodePublished { get; set; }
+ public Guid ContentTypeKey { get; set; }
+
public string? ContentTypeIcon { get; set; }
public string? ContentTypeAlias { get; set; }
diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
index ad113533d8d9..f0babf61f378 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs
@@ -24,6 +24,5 @@ public interface IDataTypeRepository : IReadWriteQueryRepository
///
///
///
-
IReadOnlyDictionary> FindListViewUsages(int id) => throw new NotImplementedException();
}
diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs
index f22948968a56..7bc0b4d95f67 100644
--- a/src/Umbraco.Core/Services/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/DataTypeService.cs
@@ -25,12 +25,15 @@ public class DataTypeService : RepositoryService, IDataTypeService
private readonly IDataTypeRepository _dataTypeRepository;
private readonly IDataTypeContainerRepository _dataTypeContainerRepository;
private readonly IContentTypeRepository _contentTypeRepository;
+ private readonly IMediaTypeRepository _mediaTypeRepository;
+ private readonly IMemberTypeRepository _memberTypeRepository;
private readonly IAuditRepository _auditRepository;
private readonly IIOHelper _ioHelper;
private readonly IDataTypeContainerService _dataTypeContainerService;
private readonly IUserIdKeyResolver _userIdKeyResolver;
private readonly Lazy _idKeyMap;
+ [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
public DataTypeService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
@@ -41,12 +44,41 @@ public DataTypeService(
IContentTypeRepository contentTypeRepository,
IIOHelper ioHelper,
Lazy idKeyMap)
+ : this(
+ provider,
+ loggerFactory,
+ eventMessagesFactory,
+ dataTypeRepository,
+ dataValueEditorFactory,
+ auditRepository,
+ contentTypeRepository,
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService(),
+ ioHelper,
+ idKeyMap)
+ {
+ }
+
+ public DataTypeService(
+ ICoreScopeProvider provider,
+ ILoggerFactory loggerFactory,
+ IEventMessagesFactory eventMessagesFactory,
+ IDataTypeRepository dataTypeRepository,
+ IDataValueEditorFactory dataValueEditorFactory,
+ IAuditRepository auditRepository,
+ IContentTypeRepository contentTypeRepository,
+ IMediaTypeRepository mediaTypeRepository,
+ IMemberTypeRepository memberTypeRepository,
+ IIOHelper ioHelper,
+ Lazy idKeyMap)
: base(provider, loggerFactory, eventMessagesFactory)
{
_dataValueEditorFactory = dataValueEditorFactory;
_dataTypeRepository = dataTypeRepository;
_auditRepository = auditRepository;
_contentTypeRepository = contentTypeRepository;
+ _mediaTypeRepository = mediaTypeRepository;
+ _memberTypeRepository = memberTypeRepository;
_ioHelper = ioHelper;
_idKeyMap = idKeyMap;
@@ -703,12 +735,119 @@ public async Task>, DataTyp
return await Task.FromResult(Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, usages));
}
+ ///
public IReadOnlyDictionary> GetListViewReferences(int id)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return _dataTypeRepository.FindListViewUsages(id);
}
+ ///
+ public Task> GetPagedRelationsAsync(Guid key, int skip, int take)
+ {
+ using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
+
+ IDataType? dataType = GetDataTypeFromRepository(key);
+ if (dataType == null)
+ {
+ // Is an unexpected response, but returning an empty collection aligns with how we handle retrieval of concrete Umbraco
+ // relations based on documents, media and members.
+ return Task.FromResult(new PagedModel());
+ }
+
+ // We don't really need true paging here, as the number of data type relations will be small compared to what there could
+ // potentially by for concrete Umbraco relations based on documents, media and members.
+ // So we'll retrieve all usages for the data type and construct a paged response.
+ // This allows us to re-use the existing repository methods used for FindUsages and FindListViewUsages.
+ IReadOnlyDictionary> usages = _dataTypeRepository.FindUsages(dataType.Id);
+ IReadOnlyDictionary> listViewUsages = _dataTypeRepository.FindListViewUsages(dataType.Id);
+
+ // Combine the property and list view usages into a single collection of property aliases and content type UDIs.
+ IList<(string PropertyAlias, Udi Udi)> combinedUsages = usages
+ .SelectMany(kvp => kvp.Value.Select(value => (value, kvp.Key)))
+ .Concat(listViewUsages.SelectMany(kvp => kvp.Value.Select(value => (value, kvp.Key))))
+ .ToList();
+
+ var totalItems = combinedUsages.Count;
+
+ // Create the page of items.
+ IList<(string PropertyAlias, Udi Udi)> pagedUsages = combinedUsages
+ .OrderBy(x => x.Udi.EntityType) // Document types first, then media types, then member types.
+ .ThenBy(x => x.PropertyAlias)
+ .Skip(skip)
+ .Take(take)
+ .ToList();
+
+ // Get the content types for the UDIs referenced in the page of items to construct the response from.
+ // They could be document, media or member types.
+ IList contentTypes = GetReferencedContentTypes(pagedUsages);
+
+ IEnumerable relations = pagedUsages
+ .Select(x =>
+ {
+ // Get the matching content type so we can populate the content type and property details.
+ IContentTypeComposition contentType = contentTypes.Single(y => y.Key == ((GuidUdi)x.Udi).Guid);
+
+ string nodeType = x.Udi.EntityType switch
+ {
+ Constants.UdiEntityType.DocumentType => Constants.ReferenceType.DocumentTypePropertyType,
+ Constants.UdiEntityType.MediaType => Constants.ReferenceType.MediaTypePropertyType,
+ Constants.UdiEntityType.MemberType => Constants.ReferenceType.MemberTypePropertyType,
+ _ => throw new ArgumentOutOfRangeException(nameof(x.Udi.EntityType)),
+ };
+
+ // Look-up the property details from the property alias. This will be null for a list view reference.
+ IPropertyType? propertyType = contentType.PropertyTypes.SingleOrDefault(y => y.Alias == x.PropertyAlias);
+ return new RelationItemModel
+ {
+ ContentTypeKey = contentType.Key,
+ ContentTypeAlias = contentType.Alias,
+ ContentTypeIcon = contentType.Icon,
+ ContentTypeName = contentType.Name,
+ NodeType = nodeType,
+ NodeName = propertyType?.Name ?? x.PropertyAlias,
+ NodeAlias = x.PropertyAlias,
+ NodeKey = propertyType?.Key ?? Guid.Empty,
+ };
+ });
+
+ var pagedModel = new PagedModel(totalItems, relations);
+ return Task.FromResult(pagedModel);
+ }
+
+ private IList GetReferencedContentTypes(IList<(string PropertyAlias, Udi Udi)> pagedUsages)
+ {
+ IEnumerable documentTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.DocumentType,
+ _contentTypeRepository);
+ IEnumerable mediaTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.MediaType,
+ _mediaTypeRepository);
+ IEnumerable memberTypes = GetContentTypes(
+ pagedUsages,
+ Constants.UdiEntityType.MemberType,
+ _memberTypeRepository);
+ return documentTypes.Concat(mediaTypes).Concat(memberTypes).ToList();
+ }
+
+ private static IEnumerable GetContentTypes(
+ IEnumerable<(string PropertyAlias, Udi Udi)> dataTypeUsages,
+ string entityType,
+ IContentTypeRepositoryBase repository)
+ where T : IContentTypeComposition
+ {
+ Guid[] contentTypeKeys = dataTypeUsages
+ .Where(x => x.Udi is GuidUdi && x.Udi.EntityType == entityType)
+ .Select(x => ((GuidUdi)x.Udi).Guid)
+ .Distinct()
+ .ToArray();
+ return contentTypeKeys.Length > 0
+ ? repository.GetMany(contentTypeKeys)
+ : [];
+ }
+
///
public IEnumerable ValidateConfigurationData(IDataType dataType)
{
diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs
index 3a4576552c2f..0f2f58ceb837 100644
--- a/src/Umbraco.Core/Services/IDataTypeService.cs
+++ b/src/Umbraco.Core/Services/IDataTypeService.cs
@@ -18,6 +18,7 @@ public interface IDataTypeService : IService
[Obsolete("Please use GetReferencesAsync. Will be deleted in V15.")]
IReadOnlyDictionary> GetReferences(int id);
+ [Obsolete("Please use GetPagedRelationsAsync. Scheduled for removal in Umbraco 17.")]
IReadOnlyDictionary> GetListViewReferences(int id) => throw new NotImplementedException();
///
@@ -25,8 +26,25 @@ public interface IDataTypeService : IService
///
/// The guid Id of the
///
+ [Obsolete("Please use GetPagedRelationsAsync. Scheduled for removal in Umbraco 17.")]
Task>, DataTypeOperationStatus>> GetReferencesAsync(Guid id);
+ ///
+ /// Gets a paged result of items which are in relation with the current data type.
+ ///
+ /// The identifier of the data type to retrieve relations for.
+ /// The amount of items to skip
+ /// The amount of items to take.
+ /// A paged result of objects.
+ ///
+ /// Note that the model and method signature here aligns with with how we handle retrieval of concrete Umbraco
+ /// relations based on documents, media and members in .
+ /// The intention is that we align data type relations with these so they can be handled polymorphically at the management API
+ /// and backoffice UI level.
+ ///
+ Task> GetPagedRelationsAsync(Guid key, int skip, int take)
+ => Task.FromResult(new PagedModel());
+
[Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")]
Attempt?> CreateContainer(int parentId, Guid key, string name,
int userId = Constants.Security.SuperUserId);
diff --git a/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs b/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
index 8ace25c07f57..1f0c986e0d7c 100644
--- a/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
+++ b/src/Umbraco.Infrastructure/Mapping/RelationModelMapDefinition.cs
@@ -1,4 +1,4 @@
-using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
@@ -19,6 +19,7 @@ private void Map(RelationItemDto source, RelationItemModel target, MapperContext
target.RelationTypeName = source.RelationTypeName;
target.RelationTypeIsBidirectional = source.RelationTypeIsBidirectional;
target.RelationTypeIsDependency = source.RelationTypeIsDependency;
+ target.ContentTypeKey = source.ChildContentTypeKey;
target.ContentTypeAlias = source.ChildContentTypeAlias;
target.ContentTypeIcon = source.ChildContentTypeIcon;
target.ContentTypeName = source.ChildContentTypeName;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
index a38bf4547ff6..2786bbfd1e45 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
@@ -475,6 +475,9 @@ internal class RelationItemDto
[Column(Name = "nodeObjectType")]
public Guid ChildNodeObjectType { get; set; }
+ [Column(Name = "contentTypeKey")]
+ public Guid ChildContentTypeKey { get; set; }
+
[Column(Name = "contentTypeIcon")]
public string? ChildContentTypeIcon { get; set; }
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
index 2b7a43589c01..ff3b671a5cc3 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs
@@ -34,6 +34,7 @@ public IEnumerable GetPagedItemsWithRelations(int[] ids, long page
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -163,6 +164,7 @@ public IEnumerable GetPagedDescendantsInReferences(
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -225,6 +227,7 @@ public IEnumerable GetPagedRelationsForItem(int id, long pageIndex
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -285,6 +288,7 @@ public IEnumerable GetPagedRelationsForItem(
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -360,6 +364,7 @@ public IEnumerable GetPagedRelationsForItem(
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -426,6 +431,7 @@ public IEnumerable GetPagedItemsWithRelations(
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -544,6 +550,7 @@ public IEnumerable GetPagedDescendantsInReferences(Guid paren
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
"[d].[published] as nodePublished",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -620,6 +627,7 @@ public IEnumerable GetPagedItemsWithRelations(
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -698,6 +706,7 @@ public IEnumerable GetPagedDescendantsInReferences(
"[n].[uniqueId] as nodeKey",
"[n].[text] as nodeName",
"[n].[nodeObjectType] as nodeObjectType",
+ "[ctn].[uniqueId] as contentTypeKey",
"[ct].[icon] as contentTypeIcon",
"[ct].[alias] as contentTypeAlias",
"[ctn].[text] as contentTypeName",
@@ -786,6 +795,7 @@ private RelationItem MapDtoToEntity(RelationItemDto dto)
RelationTypeName = dto.RelationTypeName,
RelationTypeIsBidirectional = dto.RelationTypeIsBidirectional,
RelationTypeIsDependency = dto.RelationTypeIsDependency,
+ ContentTypeKey = dto.ChildContentTypeKey,
ContentTypeAlias = dto.ChildContentTypeAlias,
ContentTypeIcon = dto.ChildContentTypeIcon,
ContentTypeName = dto.ChildContentTypeName,
diff --git a/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs b/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
index dcde82b47d4c..6439899955a5 100644
--- a/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
+++ b/tests/Umbraco.Tests.Common/Builders/MediaTypeBuilder.cs
@@ -139,6 +139,7 @@ public static MediaType CreateSimpleMediaType(
var mediaType = builder
.WithAlias(alias)
.WithName(name)
+ .WithIcon("icon-picture")
.WithParentContentType(parent)
.AddPropertyGroup()
.WithAlias(propertyGroupAlias)
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
index 1c2b46137b3c..92a1567a4f33 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs
@@ -30,6 +30,8 @@ public class DataTypeServiceTests : UmbracoIntegrationTest
private IContentTypeService ContentTypeService => GetRequiredService();
+ private IMediaTypeService MediaTypeService => GetRequiredService();
+
private IFileService FileService => GetRequiredService();
private IConfigurationEditorJsonSerializer ConfigurationEditorJsonSerializer =>
@@ -446,4 +448,43 @@ public async Task Cannot_Delete_NonDeletable_DataType(string dataTypeKey)
Assert.IsFalse(result.Success);
Assert.AreEqual(DataTypeOperationStatus.NonDeletable, result.Status);
}
+
+ [Test]
+ public async Task DataTypeService_Can_Get_References()
+ {
+ IEnumerable dataTypeDefinitions = await DataTypeService.GetByEditorAliasAsync(Constants.PropertyEditors.Aliases.RichText);
+
+ IContentType documentType = ContentTypeBuilder.CreateSimpleContentType("umbTextpage", "Text Page");
+ ContentTypeService.Save(documentType);
+
+ IMediaType mediaType = MediaTypeBuilder.CreateSimpleMediaType("umbMediaItem", "Media Item");
+ MediaTypeService.Save(mediaType);
+
+ documentType = ContentTypeService.Get(documentType.Id);
+ Assert.IsNotNull(documentType.PropertyTypes.SingleOrDefault(pt => pt.PropertyEditorAlias is Constants.PropertyEditors.Aliases.RichText));
+
+ mediaType = MediaTypeService.Get(mediaType.Id);
+ Assert.IsNotNull(mediaType.PropertyTypes.SingleOrDefault(pt => pt.PropertyEditorAlias is Constants.PropertyEditors.Aliases.RichText));
+
+ var definition = dataTypeDefinitions.First();
+ var definitionKey = definition.Key;
+ PagedModel result = await DataTypeService.GetPagedRelationsAsync(definitionKey, 0, 10);
+ Assert.AreEqual(2, result.Total);
+
+ RelationItemModel firstResult = result.Items.First();
+ Assert.AreEqual("umbTextpage", firstResult.ContentTypeAlias);
+ Assert.AreEqual("Text Page", firstResult.ContentTypeName);
+ Assert.AreEqual("icon-document", firstResult.ContentTypeIcon);
+ Assert.AreEqual(documentType.Key, firstResult.ContentTypeKey);
+ Assert.AreEqual("bodyText", firstResult.NodeAlias);
+ Assert.AreEqual("Body text", firstResult.NodeName);
+
+ RelationItemModel secondResult = result.Items.Skip(1).First();
+ Assert.AreEqual("umbMediaItem", secondResult.ContentTypeAlias);
+ Assert.AreEqual("Media Item", secondResult.ContentTypeName);
+ Assert.AreEqual("icon-picture", secondResult.ContentTypeIcon);
+ Assert.AreEqual(mediaType.Key, secondResult.ContentTypeKey);
+ Assert.AreEqual("bodyText", secondResult.NodeAlias);
+ Assert.AreEqual("Body text", secondResult.NodeName);
+ }
}