WIP Getting the content controller a bit further, now need to pull apart the content binder

This commit is contained in:
Shannon
2018-07-31 17:50:24 +10:00
parent 122c18e6d7
commit 9241e1fcda
8 changed files with 49 additions and 115 deletions

View File

@@ -376,30 +376,7 @@ namespace Umbraco.Core.Models
#endregion
#region Validation
/// <inheritdoc />
public bool IsValid(string culture = "*")
{
culture = culture.NullOrWhiteSpaceAsNull();
if (culture == null)
{
if (Name.IsNullOrWhiteSpace()) return false;
return ValidateProperties(null).Length == 0;
}
if (culture != "*")
{
var name = GetCultureName(culture);
if (name.IsNullOrWhiteSpace()) return false;
return ValidateProperties(culture).Length == 0;
}
// 'all cultures'
// those that have a name are ok, those without a name... we don't validate
return AvailableCultures.All(c => ValidateProperties(c).Length == 0);
}
/// <inheritdoc />
public virtual Property[] ValidateProperties(string culture = "*")
{

View File

@@ -137,14 +137,7 @@ namespace Umbraco.Core.Models
void CopyFrom(IContent other, string culture = "*");
// fixme validate published cultures?
/// <summary>
/// Checks if the content and property values are valid in order to be persisted.
/// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
bool IsValid(string culture = "*");
/// <summary>
/// Validates the content item's properties.
/// </summary>

View File

@@ -1458,7 +1458,6 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="resendInviteHeader">Invite user</key>
<key alias="resendInviteSuccess">Invitation has been re-sent to %0%</key>
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
<key alias="contentCultureValidationError">Validation failed for language '%0%'</key>
<key alias="contentCultureUnexpectedValidationError">Unexpected validation failed for language '%0%'</key>
<key alias="documentTypeExportedSuccess">Document type was exported to file</key>
<key alias="documentTypeExportedError">An error occurred while exporting the document type</key>

View File

@@ -1,49 +1,28 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding.Binders;
using System.Web.Http.Validation;
using System.Web.ModelBinding;
using System.Web.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Editors;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi.Filters;
using IModelBinder = System.Web.Http.ModelBinding.IModelBinder;
using ModelBindingContext = System.Web.Http.ModelBinding.ModelBindingContext;
using ModelMetadata = System.Web.Http.Metadata.ModelMetadata;
using ModelMetadataProvider = System.Web.Http.Metadata.ModelMetadataProvider;
using MutableObjectModelBinder = System.Web.Http.ModelBinding.Binders.MutableObjectModelBinder;
using Task = System.Threading.Tasks.Task;
namespace Umbraco.Web.Editors.Binders
{
/// <inheritdoc />
{
/// <summary>
/// Binds the content model to the controller action for the posted multi-part Post
/// </summary>
internal abstract class ContentItemBaseBinder<TPersisted, TModelSave> : IModelBinder
where TPersisted : class, IContentBase
//where TModelSave : ContentBaseItemSave<TPersisted>
where TPersisted : class, IContentBase
where TModelSave : IContentSave<TPersisted>
{
protected Core.Logging.ILogger Logger { get; }
protected ServiceContext Services { get; }

View File

@@ -17,6 +17,7 @@ using Umbraco.Web.WebApi.Filters;
namespace Umbraco.Web.Editors.Binders
{
/// <inheritdoc />
internal class ContentItemBinder : ContentItemBaseBinder<IContent, ContentItemSave>
{
public ContentItemBinder() : this(Current.Logger, Current.Services, Current.UmbracoContextAccessor)

View File

@@ -19,7 +19,6 @@ using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Web.PublishedCache;
@@ -29,9 +28,9 @@ using Umbraco.Web.Models;
using Umbraco.Web.WebServices;
using Umbraco.Web._Legacy.Actions;
using Constants = Umbraco.Core.Constants;
using ContentVariation = Umbraco.Core.Models.ContentVariation;
using Language = Umbraco.Web.Models.ContentEditing.Language;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.Editors.Binders;
using Umbraco.Web.Editors.Filters;
namespace Umbraco.Web.Editors
@@ -653,7 +652,7 @@ namespace Umbraco.Web.Editors
{
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the modelstate to the outgoing object and throw a validation message
var forDisplay = MapToDisplay(contentItem.PersistedContent, contentItem.Culture);
var forDisplay = MapToDisplay(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
@@ -693,7 +692,7 @@ namespace Umbraco.Web.Editors
}
//get the updated model
var display = MapToDisplay(contentItem.PersistedContent, contentItem.Culture);
var display = MapToDisplay(contentItem.PersistedContent);
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
HandleInvalidModelState(display);
@@ -757,6 +756,8 @@ namespace Umbraco.Web.Editors
/// </remarks>
private void PublishInternal(ContentItemSave contentItem, ref PublishResult publishStatus, ref bool wasCancelled)
{
if (publishStatus == null) throw new ArgumentNullException(nameof(publishStatus));
if (!contentItem.PersistedContent.ContentType.VariesByCulture())
{
//its invariant, proceed normally
@@ -767,47 +768,35 @@ namespace Umbraco.Web.Editors
{
var canPublish = true;
//All variants in this collection should have a culture if we get here! but we'll double check and filter here
var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList();
//check if we are publishing other variants and validate them
var allLangs = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase);
var otherVariantsToValidate = contentItem.PublishVariations.Where(x => !x.Culture.InvariantEquals(contentItem.Culture)).ToList();
//validate any mandatory variants that are not in the list
var mandatoryLangs = Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLangs.Values)
.Where(x => otherVariantsToValidate.All(v => !v.Culture.InvariantEquals(x.IsoCode))) //don't include variants above
.Where(x => !x.IsoCode.InvariantEquals(contentItem.Culture)) //don't include the current variant
.Where(x => x.Mandatory);
var mandatoryLangs = Mapper.Map<IEnumerable<ILanguage>, IEnumerable<Language>>(allLangs.Values).Where(x => x.Mandatory);
foreach (var lang in mandatoryLangs)
{
//cannot continue publishing since a required language that is not currently being published isn't published
if (!contentItem.PersistedContent.IsCulturePublished(lang.IsoCode))
//Check if a mandatory language is missing from being published
//TODO: This logic is wrong, we need to also check if this language doesn't already have a published version
if (cultureVariants.Any(x => x.Culture == lang.IsoCode && !x.Publish))
{
var errMsg = Services.TextService.Localize("speechBubbles/contentReqCulturePublishError", new[] { allLangs[lang.IsoCode].CultureName });
ModelState.AddModelError("publish_variant_" + lang.IsoCode + "_", errMsg);
canPublish = false;
}
}
if (canPublish)
{
//validate all other variants to be published
foreach (var publishVariation in otherVariantsToValidate)
{
//validate the content item and the culture property values, we don't need to validate any invariant property values here because they will have
//been validated in the post.
var valid = contentItem.PersistedContent.IsValid(publishVariation.Culture);
if (!valid)
//cannot continue publishing since a required language that is not currently being published isn't published
if (!contentItem.PersistedContent.IsCulturePublished(lang.IsoCode))
{
var errMsg = Services.TextService.Localize("speechBubbles/contentCultureValidationError", new[] { allLangs[publishVariation.Culture].CultureName });
ModelState.AddModelError("publish_variant_" + publishVariation.Culture + "_", errMsg);
var errMsg = Services.TextService.Localize("speechBubbles/contentReqCulturePublishError", new[] { allLangs[lang.IsoCode].CultureName });
ModelState.AddModelError("publish_variant_" + lang.IsoCode + "_", errMsg);
canPublish = false;
}
}
}
if (canPublish)
{
//try to publish all the values on the model
canPublish = PublishCulture(contentItem, otherVariantsToValidate, allLangs);
canPublish = PublishCulture(contentItem.PersistedContent, cultureVariants, allLangs);
}
if (canPublish)
@@ -827,25 +816,22 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// This will call TryPublishValues on the content item for each culture that needs to be published including the invariant culture
/// This will call PublishCulture on the content item for each culture that needs to be published including the invariant culture
/// </summary>
/// <param name="contentItem"></param>
/// <param name="otherVariantsToValidate"></param>
/// <param name="persistentContent"></param>
/// <param name="cultureVariants"></param>
/// <param name="allLangs"></param>
/// <returns></returns>
private bool PublishCulture(ContentItemSave contentItem, IEnumerable<ContentVariationPublish> otherVariantsToValidate, IDictionary<string, ILanguage> allLangs)
private bool PublishCulture(IContent persistentContent, IEnumerable<ContentVariantSave> cultureVariants, IDictionary<string, ILanguage> allLangs)
{
var culturesToPublish = new List<string> { contentItem.Culture };
culturesToPublish.AddRange(otherVariantsToValidate.Select(x => x.Culture));
foreach(var culture in culturesToPublish)
foreach(var variant in cultureVariants.Where(x => x.Publish))
{
// publishing any culture, implies the invariant culture
var valid = contentItem.PersistedContent.PublishCulture(culture);
var valid = persistentContent.PublishCulture(variant.Culture);
if (!valid)
{
var errMsg = Services.TextService.Localize("speechBubbles/contentCultureUnexpectedValidationError", new[] { allLangs[culture].CultureName });
ModelState.AddModelError("publish_variant_" + culture + "_", errMsg);
var errMsg = Services.TextService.Localize("speechBubbles/contentCultureUnexpectedValidationError", new[] { allLangs[variant.Culture].CultureName });
ModelState.AddModelError("publish_variant_" + variant.Culture + "_", errMsg);
return false;
}
}
@@ -1220,8 +1206,11 @@ namespace Umbraco.Web.Editors
/// <param name="contentSave"></param>
private void MapPropertyValues(ContentItemSave contentSave)
{
//set the names for the variants
foreach(var variant in contentSave.Variants)
//inline method to determine if a property type varies
bool Varies(Property property) => property.PropertyType.VariesByCulture();
//loop through each variant, set the correct name and property values
foreach (var variant in contentSave.Variants)
{
//Don't update the name if it is empty
if (!variant.Name.IsNullOrWhiteSpace())
@@ -1237,6 +1226,12 @@ namespace Umbraco.Web.Editors
contentSave.PersistedContent.Name = variant.Name;
}
}
//for each variant, map the property values
MapPropertyValues<IContent, ContentItemSave>(
contentSave,
(save, property) => Varies(property) ? property.GetValue(variant.Culture) : property.GetValue(), //get prop val
(save, property, v) => { if (Varies(property)) property.SetValue(v, variant.Culture); else property.SetValue(v); }); //set prop val
}
//TODO: We need to support 'send to publish'
@@ -1262,13 +1257,6 @@ namespace Umbraco.Web.Editors
contentSave.PersistedContent.Template = template;
}
}
bool Varies(Property property) => property.PropertyType.VariesByCulture();
MapPropertyValues<IContent, ContentItemSave>(
contentSave,
(save, property) => Varies(property) ? property.GetValue(save.Culture) : property.GetValue(), //get prop val
(save, property, v) => { if (Varies(property)) property.SetValue(v, save.Culture); else property.SetValue(v); }); //set prop val
}
/// <summary>

View File

@@ -44,12 +44,12 @@ namespace Umbraco.Web.Editors
/// <param name="contentItem"></param>
/// <param name="getPropertyValue"></param>
/// <param name="savePropertyValue"></param>
protected void MapPropertyValues<TPersisted, TSaved>(
internal void MapPropertyValues<TPersisted, TSaved>(
TSaved contentItem,
Func<TSaved, Property, object> getPropertyValue,
Action<TSaved, Property, object> savePropertyValue)
where TPersisted : IContentBase
where TSaved : ContentBaseSave<TPersisted>
where TSaved : IContentSave<TPersisted>
{
// map the property values
foreach (var propertyDto in contentItem.ContentDto.Properties)
@@ -69,6 +69,7 @@ namespace Umbraco.Web.Editors
// get the property
var property = contentItem.PersistedContent.Properties[propertyDto.Alias];
//TODO: Need to update this API to support variants and/or basically any sort of 'key'
// prepare files, if any
var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == propertyDto.Alias).ToArray();
foreach (var file in files)
@@ -99,9 +100,7 @@ namespace Umbraco.Web.Editors
}
}
protected void HandleInvalidModelState<T, TPersisted>(ContentItemDisplayBase<T, TPersisted> display)
where TPersisted : IContentBase
where T : ContentPropertyBasic
protected void HandleInvalidModelState(IErrorModel display)
{
//lasty, if it is not valid, add the modelstate to the outgoing object and throw a 403
if (!ModelState.IsValid)

View File

@@ -213,8 +213,6 @@
<Compile Include="Models\ContentEditing\ContentRedirectUrl.cs" />
<Compile Include="Models\ContentEditing\ContentVariantSave.cs" />
<Compile Include="Models\ContentEditing\ContentVariationDisplay.cs" />
<Compile Include="Models\ContentEditing\ContentVariation.cs" />
<Compile Include="Models\ContentEditing\ContentVariationPublish.cs" />
<Compile Include="Models\ContentEditing\DomainDisplay.cs" />
<Compile Include="Models\ContentEditing\DomainSave.cs" />
<Compile Include="Models\ContentEditing\CreatedDocumentTypeCollectionResult.cs" />