diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
index 8c10fc6bd0..49829d984e 100644
--- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
+++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs
@@ -11,6 +11,7 @@ using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dictionary;
+using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
@@ -131,42 +132,7 @@ namespace Umbraco.Web.Editors
})
.ToList();
}
-
- ///
- /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
- ///
- ///
- ///
- ///
- protected void ValidateComposition(ContentTypeSave contentTypeSave, IContentTypeComposition composition)
- where TPropertyType : PropertyTypeBasic
- {
- var validateAttempt = Services.ContentTypeService.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 propertyAliases = validateAttempt.Result.Distinct();
- foreach (var propertyAlias in propertyAliases)
- {
- //find the property relating to these
- var prop = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
- var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(prop));
- var propIndex = group.Properties.IndexOf(prop);
- var groupIndex = contentTypeSave.Groups.IndexOf(group);
-
- var key = string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propIndex);
- ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
- }
-
- var display = Mapper.Map(composition);
- //map the 'save' data on top
- display = Mapper.Map(contentTypeSave, display);
- display.Errors = ModelState.ToErrorDictionary();
- throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
- }
-
- }
+
protected string TranslateItem(string text)
{
@@ -186,7 +152,6 @@ namespace Umbraco.Web.Editors
TContentTypeSave contentTypeSave,
Func getContentType,
Action saveContentType,
- bool validateComposition = true,
Action beforeCreateNew = null)
where TContentType : class, IContentTypeComposition
where TContentTypeDisplay : ContentTypeCompositionDisplay
@@ -213,22 +178,7 @@ namespace Umbraco.Web.Editors
if (ModelState.IsValid == false)
{
- TContentTypeDisplay forDisplay;
- if (ctId > 0)
- {
- //Required data is invalid so we cannot continue
- forDisplay = Mapper.Map(ct);
- //map the 'save' data on top
- forDisplay = Mapper.Map(contentTypeSave, forDisplay);
- }
- else
- {
- //map the 'save' data to display
- forDisplay = Mapper.Map(contentTypeSave);
- }
-
- forDisplay.Errors = ModelState.ToErrorDictionary();
- throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
+ throw CreateModelStateValidationException(ctId, contentTypeSave, ct);
}
//filter out empty properties
@@ -240,15 +190,21 @@ namespace Umbraco.Web.Editors
if (ctId > 0)
{
- //its an update to an existing
+ //its an update to an existing content type
- Mapper.Map(contentTypeSave, ct);
-
- if (validateComposition)
+ //This mapping will cause a lot of content type validation to occur which we need to deal with
+ try
{
- //NOTE: this throws an error response if it is not valid
- ValidateComposition(contentTypeSave, ct);
+ Mapper.Map(contentTypeSave, ct);
}
+ catch (Exception ex)
+ {
+ var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId);
+ if (responseEx != null) throw responseEx;
+ }
+
+ var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, ct);
+ if (exResult != null) throw exResult;
saveContentType(ct);
@@ -260,12 +216,7 @@ namespace Umbraco.Web.Editors
{
beforeCreateNew(contentTypeSave);
}
-
- //set id to null to ensure its handled as a new type
- contentTypeSave.Id = null;
- contentTypeSave.CreateDate = DateTime.Now;
- contentTypeSave.UpdateDate = DateTime.Now;
-
+
//check if the type is trying to allow type 0 below itself - id zero refers to the currently unsaved type
//always filter these 0 types out
var allowItselfAsChild = false;
@@ -276,13 +227,26 @@ namespace Umbraco.Web.Editors
}
//save as new
- var newCt = Mapper.Map(contentTypeSave);
-
- if (validateComposition)
+
+ TContentType newCt = null;
+ try
{
- //NOTE: this throws an error response if it is not valid
- ValidateComposition(contentTypeSave, newCt);
+ //This mapping will cause a lot of content type validation to occur which we need to deal with
+ newCt = Mapper.Map(contentTypeSave);
}
+ catch (Exception ex)
+ {
+ var responseEx = CreateInvalidCompositionResponseException(ex, contentTypeSave, ct, ctId);
+ if (responseEx != null) throw responseEx;
+ }
+
+ var exResult = CreateCompositionValidationExceptionIfInvalid(contentTypeSave, newCt);
+ if (exResult != null) throw exResult;
+
+ //set id to null to ensure its handled as a new type
+ contentTypeSave.Id = null;
+ contentTypeSave.CreateDate = DateTime.Now;
+ contentTypeSave.UpdateDate = DateTime.Now;
saveContentType(newCt);
@@ -296,7 +260,7 @@ namespace Umbraco.Web.Editors
return newCt;
}
}
-
+
///
/// Change the sort order for media
///
@@ -341,6 +305,121 @@ namespace Umbraco.Web.Editors
}
}
+ ///
+ /// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
+ ///
+ ///
+ ///
+ ///
+ private HttpResponseException CreateCompositionValidationExceptionIfInvalid(TContentTypeSave contentTypeSave, IContentTypeComposition composition)
+ where TContentTypeSave : ContentTypeSave
+ where TPropertyType : PropertyTypeBasic
+ where TContentTypeDisplay : ContentTypeCompositionDisplay
+ {
+ var validateAttempt = Services.ContentTypeService.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);
+
+ var display = Mapper.Map(composition);
+ //map the 'save' data on top
+ display = Mapper.Map(contentTypeSave, display);
+ display.Errors = ModelState.ToErrorDictionary();
+ throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
+ }
+ return null;
+ }
+
+ ///
+ /// 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)
+ where TContentTypeSave : ContentTypeSave
+ where TPropertyType : PropertyTypeBasic
+ {
+ foreach (var propertyAlias in invalidPropertyAliases)
+ {
+ //find the property relating to these
+ var prop = contentTypeSave.Groups.SelectMany(x => x.Properties).Single(x => x.Alias == propertyAlias);
+ var group = contentTypeSave.Groups.Single(x => x.Properties.Contains(prop));
+
+ var key = string.Format("Groups[{0}].Properties[{1}].Alias", group.SortOrder, prop.SortOrder);
+ ModelState.AddModelError(key, "Duplicate property aliases not allowed between compositions");
+ }
+ }
+
+ ///
+ /// If the exception is an InvalidCompositionException create a response exception to be thrown for validation errors
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private HttpResponseException CreateInvalidCompositionResponseException(
+ Exception ex, TContentTypeSave contentTypeSave, TContentType ct, int ctId)
+ where TContentType : class, IContentTypeComposition
+ where TContentTypeDisplay : ContentTypeCompositionDisplay
+ where TContentTypeSave : ContentTypeSave
+ where TPropertyType : PropertyTypeBasic
+ {
+ InvalidCompositionException invalidCompositionException = null;
+ if (ex is AutoMapperMappingException && ex.InnerException is InvalidCompositionException)
+ {
+ invalidCompositionException = (InvalidCompositionException)ex.InnerException;
+ }
+ else if (ex.InnerException is InvalidCompositionException)
+ {
+ invalidCompositionException = (InvalidCompositionException)ex;
+ }
+ if (invalidCompositionException != null)
+ {
+ AddCompositionValidationErrors(contentTypeSave, invalidCompositionException.PropertyTypeAliases);
+ return CreateModelStateValidationException(ctId, contentTypeSave, ct);
+ }
+ return null;
+ }
+
+ ///
+ /// Used to throw the ModelState validation results when the ModelState is invalid
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private HttpResponseException CreateModelStateValidationException(int ctId, ContentTypeSave contentTypeSave, TContentType ct)
+ where TContentType : class, IContentTypeComposition
+ where TContentTypeDisplay : ContentTypeCompositionDisplay
+ {
+ TContentTypeDisplay forDisplay;
+ if (ctId > 0)
+ {
+ //Required data is invalid so we cannot continue
+ forDisplay = Mapper.Map(ct);
+ //map the 'save' data on top
+ forDisplay = Mapper.Map(contentTypeSave, forDisplay);
+ }
+ else
+ {
+ //map the 'save' data to display
+ forDisplay = Mapper.Map(contentTypeSave);
+ }
+
+ forDisplay.Errors = ModelState.ToErrorDictionary();
+ return new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
+ }
+
private ICultureDictionary CultureDictionary
{
get
@@ -351,5 +430,6 @@ namespace Umbraco.Web.Editors
}
}
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Editors/MemberTypeController.cs b/src/Umbraco.Web/Editors/MemberTypeController.cs
index 8e50727cd0..8ed61fd52c 100644
--- a/src/Umbraco.Web/Editors/MemberTypeController.cs
+++ b/src/Umbraco.Web/Editors/MemberTypeController.cs
@@ -134,8 +134,7 @@ namespace Umbraco.Web.Editors
var savedCt = PerformPostSave(
contentTypeSave: contentTypeSave,
getContentType: i => Services.MemberTypeService.Get(i),
- saveContentType: type => Services.MemberTypeService.Save(type),
- validateComposition: false);
+ saveContentType: type => Services.MemberTypeService.Save(type));
var display = Mapper.Map(savedCt);
diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
index f79192caf6..5d961487ad 100644
--- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
+++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeSave.cs
@@ -77,10 +77,10 @@ namespace Umbraco.Web.Models.ContentEditing
if (duplicateGroups.Any())
{
//we need to return the field name with an index so it's wired up correctly
- var firstIndex = Groups.IndexOf(duplicateGroups.First().First());
+ var lastIndex = Groups.IndexOf(duplicateGroups.Last().Last());
yield return new ValidationResult("Duplicate group names not allowed", new[]
{
- string.Format("Groups[{0}].Name", firstIndex)
+ string.Format("Groups[{0}].Name", lastIndex)
});
}
@@ -88,14 +88,12 @@ namespace Umbraco.Web.Models.ContentEditing
if (duplicateProperties.Any())
{
//we need to return the field name with an index so it's wired up correctly
- var firstProperty = duplicateProperties.First().First();
- var propertyGroup = Groups.Single(x => x.Properties.Contains(firstProperty));
- var groupIndex = Groups.IndexOf(propertyGroup);
- var propertyIndex = propertyGroup.Properties.IndexOf(firstProperty);
+ var lastProperty = duplicateProperties.Last().Last();
+ var propertyGroup = Groups.Single(x => x.Properties.Contains(lastProperty));
yield return new ValidationResult("Duplicate property aliases not allowed", new[]
{
- string.Format("Groups[{0}].Properties[{1}].Alias", groupIndex, propertyIndex)
+ string.Format("Groups[{0}].Properties[{1}].Alias", propertyGroup.SortOrder, lastProperty.SortOrder)
});
}