diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
index 95cd27b2cd..5d2beaa387 100644
--- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
+++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs
@@ -1,5 +1,6 @@
using System;
using System.Runtime.Serialization;
+using System.Text;
namespace Umbraco.Core.Exceptions
{
@@ -85,18 +86,28 @@ namespace Umbraco.Core.Exceptions
private static string FormatMessage(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases)
{
- // TODO Add property group aliases to message
- return addedCompositionAlias.IsNullOrWhiteSpace()
- ? string.Format(
- "ContentType with alias '{0}' has an invalid composition " +
- "and there was a conflict on the following PropertyTypes: '{1}'. " +
- "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
- contentTypeAlias, string.Join(", ", propertyTypeAliases))
- : string.Format(
- "ContentType with alias '{0}' was added as a Composition to ContentType with alias '{1}', " +
- "but there was a conflict on the following PropertyTypes: '{2}'. " +
- "PropertyTypes must have a unique alias across all Compositions in order to compose a valid ContentType Composition.",
- addedCompositionAlias, contentTypeAlias, string.Join(", ", propertyTypeAliases));
+ var sb = new StringBuilder();
+
+ if (addedCompositionAlias.IsNullOrWhiteSpace())
+ {
+ sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias);
+ }
+ else
+ {
+ sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias);
+ }
+
+ if (propertyTypeAliases.Length > 0)
+ {
+ sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases));
+ }
+
+ if (propertyGroupAliases.Length > 0)
+ {
+ sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases));
+ }
+
+ return sb.ToString();
}
///
diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 11142ad96e..385bfec923 100644
--- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -97,18 +97,22 @@ namespace Umbraco.Core.Services.Implement
stack.Push(c);
}
+ var duplicatePropertyTypeAliases = new List();
+ var invalidPropertyGroupAliases = new List();
+
foreach (var dependency in dependencies)
{
if (dependency.Id == compositionContentType.Id) continue;
var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
if (contentTypeDependency == null) continue;
- var duplicatePropertyTypeAliases = contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase).ToArray();
- var invalidPropertyGroupAliases = contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias).ToArray();
+ duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
+ invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
+ }
- if (duplicatePropertyTypeAliases.Length == 0 && invalidPropertyGroupAliases.Length == 0) continue;
-
- throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
+ if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
+ {
+ throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
}
}
diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
index 6ac936961f..0f3df41fc4 100644
--- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
+++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
@@ -454,10 +454,12 @@ namespace Umbraco.Web.Editors
var validateAttempt = service.ValidateComposition(composition);
if (validateAttempt == false)
{
- //if it's not successful then we need to return some model state for the property aliases that
- // are duplicated
- var invalidPropertyAliases = validateAttempt.Result.Distinct();
- AddCompositionValidationErrors(contentTypeSave, invalidPropertyAliases);
+ // if it's not successful then we need to return some model state for the property type and property group
+ // aliases that are duplicated
+ var duplicatePropertyTypeAliases = validateAttempt.Result.Distinct();
+ var invalidPropertyGroupAliases = (validateAttempt.Exception as InvalidCompositionException)?.PropertyGroupAliases ?? Array.Empty();
+
+ AddCompositionValidationErrors(contentTypeSave, duplicatePropertyTypeAliases, invalidPropertyGroupAliases);
var display = Mapper.Map(composition);
//map the 'save' data on top
@@ -472,22 +474,32 @@ namespace Umbraco.Web.Editors
/// Adds errors to the model state if any invalid aliases are found then throws an error response if there are errors
///
///
- ///
+ ///
+ ///
///
- private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, IEnumerable invalidPropertyAliases)
+ private void AddCompositionValidationErrors(TContentTypeSave contentTypeSave, IEnumerable duplicatePropertyTypeAliases, IEnumerable invalidPropertyGroupAliases)
where TContentTypeSave : ContentTypeSave
where TPropertyType : PropertyTypeBasic
{
- foreach (var propertyAlias in invalidPropertyAliases)
+ foreach (var propertyTypeAlias in duplicatePropertyTypeAliases)
{
- // Find the property relating to these
- var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
+ // Find the property type relating to these
+ var property = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyTypeAlias);
var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(property));
var propertyIndex = group.Properties.IndexOf(property);
var groupIndex = contentTypeSave.Groups.IndexOf(group);
var key = $"Groups[{groupIndex}].Properties[{propertyIndex}].Alias";
- ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
+ ModelState.AddModelError(key, "Duplicate property aliases aren't allowed between compositions");
+ }
+
+ foreach (var propertyGroupAlias in invalidPropertyGroupAliases)
+ {
+ // Find the property group relating to these
+ var group = contentTypeSave.Groups.Single(x => x.Alias == propertyGroupAlias);
+ var groupIndex = contentTypeSave.Groups.IndexOf(group);
+ var key = $"Groups[{groupIndex}].Name";
+ ModelState.AddModelError(key, "Different group types aren't allowed between compositions");
}
}
@@ -519,7 +531,7 @@ namespace Umbraco.Web.Editors
}
if (invalidCompositionException != null)
{
- AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
+ AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases, invalidCompositionException.PropertyGroupAliases);
return CreateModelStateValidationException(ctId, contentTypeSave, ct);
}
return null;