Gets validation working with composition alias conflicts

This commit is contained in:
Shannon
2015-10-09 19:03:11 +02:00
parent 6017e9ddf9
commit 2922e962e0
7 changed files with 114 additions and 30 deletions

View File

@@ -3,22 +3,41 @@
namespace Umbraco.Core.Exceptions
{
public class InvalidCompositionException : Exception
{
public string ContentTypeAlias { get; set; }
{
public InvalidCompositionException(string contentTypeAlias, string addedCompositionAlias, string[] propertyTypeAliass)
{
ContentTypeAlias = contentTypeAlias;
AddedCompositionAlias = addedCompositionAlias;
PropertyTypeAliases = propertyTypeAliass;
}
public string AddedCompositionAlias { get; set; }
public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliass)
{
ContentTypeAlias = contentTypeAlias;
PropertyTypeAliases = propertyTypeAliass;
}
public string PropertyTypeAlias { get; set; }
public string ContentTypeAlias { get; private set; }
public string AddedCompositionAlias { get; private set; }
public string[] PropertyTypeAliases { get; private set; }
public override string Message
{
get
{
return string.Format(
"InvalidCompositionException - ContentType with alias '{0}' was added as a Compsition to ContentType with alias '{1}', " +
"but there was a conflict on the PropertyType alias '{2}'. " +
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.",
AddedCompositionAlias, ContentTypeAlias, PropertyTypeAlias);
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));
}
}
}

View File

@@ -94,13 +94,7 @@ namespace Umbraco.Core.Models
.Select(p => p.Alias)).ToList();
if (conflictingPropertyTypeAliases.Any())
throw new InvalidCompositionException
{
AddedCompositionAlias = contentType.Alias,
ContentTypeAlias = Alias,
PropertyTypeAlias =
string.Join(", ", conflictingPropertyTypeAliases)
};
throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray());
_contentTypeComposition.Add(contentType);
OnPropertyChanged(ContentTypeCompositionSelector);

View File

@@ -308,15 +308,28 @@ namespace Umbraco.Core.Services
}
public void Validate(IContentTypeComposition compo)
/// <summary>
/// Validates the composition, if its invalid a list of property type aliases that were duplicated is returned
/// </summary>
/// <param name="compo"></param>
/// <returns></returns>
public Attempt<string[]> ValidateComposition(IContentTypeComposition compo)
{
using (new WriteLock(Locker))
{
ValidateLocked(compo);
try
{
ValidateLocked(compo);
return Attempt<string[]>.Succeed();
}
catch (InvalidCompositionException ex)
{
return Attempt.Fail(ex.PropertyTypeAliases, ex);
}
}
}
private void ValidateLocked(IContentTypeComposition compositionContentType)
protected void ValidateLocked(IContentTypeComposition compositionContentType)
{
// performs business-level validation of the composition
// should ensure that it is absolutely safe to save the composition
@@ -369,10 +382,8 @@ namespace Umbraco.Core.Services
if (contentTypeDependency == null) continue;
var intersect = contentTypeDependency.PropertyTypes.Select(x => x.Alias.ToLowerInvariant()).Intersect(propertyTypeAliases).ToArray();
if (intersect.Length == 0) continue;
var message = string.Format("The following PropertyType aliases from the current ContentType conflict with existing PropertyType aliases: {0}.",
string.Join(", ", intersect));
throw new Exception(message);
throw new InvalidCompositionException(compositionContentType.Alias, intersect.ToArray());
}
}

View File

@@ -10,6 +10,13 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IContentTypeService : IService
{
/// <summary>
/// Validates the composition, if its invalid a list of property type aliases that were duplicated is returned
/// </summary>
/// <param name="compo"></param>
/// <returns></returns>
Attempt<string[]> ValidateComposition(IContentTypeComposition compo);
Attempt<int> CreateFolder(int parentId, string name, int userId = 0);
/// <summary>

View File

@@ -508,7 +508,7 @@ function umbDataFormatter() {
var realProperties = _.reject(g.properties, function (p) {
//do not include these properties
return p.propertyState === "init";
return p.propertyState === "init" || p.inherited === true;
});
var saveProperties = _.map(realProperties, function (p) {
@@ -518,9 +518,19 @@ function umbDataFormatter() {
saveGroup.properties = saveProperties;
//if this is an inherited group and there are not non-inherited properties on it, then don't send up the data
if (saveGroup.inherited === true && saveProperties.length === 0) {
return null;
}
return saveGroup;
});
//we don't want any null groups
saveModel.groups = _.reject(saveModel.groups, function(g) {
return !g;
});
return saveModel;
},

View File

@@ -96,10 +96,12 @@
<ng-form name="propertyTypeForm">
<div class="control-group -no-margin" ng-if="!sortingMode">
<umb-locked-field locked="locked"
<div class="umb-group-builder__property-meta-alias" ng-if="property.inherited">{{ property.alias }}</div>
<umb-locked-field ng-if="!property.inherited"
locked="locked"
ng-model="property.alias"
placeholder-text="'Alias...'"
server-validation-field="{{'Groups[' + $parent.$index + '].Properties[' + $index + '].Alias'}}">
server-validation-field="{{'Groups[' + $parent.$parent.$parent.$parent.$index + '].Properties[' + $index + '].Alias'}}">
</umb-locked-field>
<div class="umb-group-builder__property-meta-label">
@@ -107,7 +109,7 @@
name="groupName"
umb-auto-resize
required
val-server-field="{{'Groups[' + $parent.$index + '].Properties[' + $index + '].Label'}}"></textarea>
val-server-field="{{'Groups[' + $parent.$parent.$parent.$parent.$index + '].Properties[' + $index + '].Label'}}"></textarea>
<span class="help-inline" val-msg-for="groupName" val-toggle-msg="valServerField"></span>
</div>

View File

@@ -89,7 +89,42 @@ namespace Umbraco.Web.Editors
? Request.CreateResponse(HttpStatusCode.OK, result.Result) //return the id
: Request.CreateValidationErrorResponse(result.Exception.Message);
}
/// <summary>
/// Validates the composition and adds errors to the model state if any are found then throws an error response if there are errors
/// </summary>
/// <param name="contentTypeSave"></param>
/// <param name="composition"></param>
/// <returns></returns>
private void ValidateComposition(ContentTypeSave contentTypeSave, IContentTypeComposition composition)
{
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<ContentTypeDisplay>(composition);
//map the 'save' data on top
display = Mapper.Map(contentTypeSave, display);
display.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(display));
}
}
public ContentTypeDisplay PostSave(ContentTypeSave contentTypeSave)
{
var ctId = Convert.ToInt32(contentTypeSave.Id);
@@ -106,8 +141,6 @@ namespace Umbraco.Web.Editors
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
//TODO: Deal with validation for composition with property and group names/aliases
//filter out empty properties
contentTypeSave.Groups = contentTypeSave.Groups.Where(x => x.Name.IsNullOrWhiteSpace() == false).ToList();
@@ -129,8 +162,11 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
Mapper.Map(contentTypeSave, found);
ctService.Save(found);
//NOTE: this throws an error response if it is not valid
ValidateComposition(contentTypeSave, found);
ctService.Save(found);
display = Mapper.Map<ContentTypeDisplay>(found);
}
else
@@ -166,11 +202,16 @@ namespace Umbraco.Web.Editors
//save as new
var newCt = Mapper.Map<IContentType>(contentTypeSave);
//NOTE: this throws an error response if it is not valid
ValidateComposition(contentTypeSave, newCt);
ctService.Save(newCt);
//we need to save it twice to allow itself under itself.
if (allowItselfAsChild)
{
//NOTE: This will throw if the composition isn't right... but it shouldn't be at this stage
newCt.AddContentType(newCt);
ctService.Save(newCt);
}