V15: Serverside Media Picker Validation (#18429)

* Add TypedJsonValidator to avoid duplicate serialization

* Add allowed type validator

* Validate multiple media toggle

* Add startnode validator

* Fix tests

* Add validation tests

* Apply suggestions from code review

Co-authored-by: Andy Butland <abutland73@gmail.com>

* Add XML docs

* Remove unnecessary obsolete constructor

* Avoid multiple checks

* Use value instead of specific member names

* Remove test

* Optimize StartNodeValidator

* Clarify Validates_Allowed_Type

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Mole
2025-02-25 13:26:16 +01:00
committed by GitHub
parent 790c451df1
commit 74eb66ef86
8 changed files with 453 additions and 49 deletions

View File

@@ -113,6 +113,9 @@ Mange hilsner fra Umbraco robotten
<key alias="entriesShort"><![CDATA[Minimum %0% element(er), tilføj <strong>%1%</strong> mere.]]></key>
<key alias="entriesExceed"><![CDATA[Maksimum %0% element(er), <strong>%1%</strong> for mange.]]></key>
<key alias="entriesAreasMismatch">Ét eller flere områder lever ikke op til kravene for antal indholdselementer.</key>
<key alias="invalidMediaType">Den valgte medie type er ugyldig.</key>
<key alias="multipleMediaNotAllowed">Det er kun tilladt at vælge ét medie.</key>
<key alias="invalidStartNode">Valgt medie kommer fra en ugyldig mappe.</key>
</area>
<area alias="recycleBin">
<key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key>
@@ -124,4 +127,4 @@ Mange hilsner fra Umbraco robotten
<key alias="FileWriting">Filskrivning</key>
<key alias="MediaFolderCreation">Mediemappeoprettelse</key>
</area>
</language>
</language>

View File

@@ -387,6 +387,9 @@
<key alias="duplicateUserGroupName">User group name '%0%' is already taken</key>
<key alias="duplicateMemberGroupName">Member group name '%0%' is already taken</key>
<key alias="duplicateUsername">Username '%0%' is already taken</key>
<key alias="invalidMediaType">The chosen media type is invalid.</key>
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
</area>
<area alias="healthcheck">
<!-- The following keys get these tokens passed in:

View File

@@ -388,6 +388,9 @@
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
<key alias="invalidMediaType">The chosen media type is invalid.</key>
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
</area>
<area alias="healthcheck">
<!-- The following keys get these tokens passed in:

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Core.Models.Validation;
namespace Umbraco.Cms.Core.PropertyEditors.Validation;
/// <summary>
/// A specific validator used for JSON based value editors, to avoid doing multiple deserialization.
/// <remarks>Is used together with <see cref="TypedJsonValidatorRunner{TValue,TConfiguration}"/></remarks>
/// </summary>
/// <typeparam name="TValue">The type of the value consumed by the validator.</typeparam>
/// <typeparam name="TConfiguration">The type of the configuration consumed by validator.</typeparam>
public interface ITypedJsonValidator<TValue, TConfiguration>
{
public abstract IEnumerable<ValidationResult> Validate(
TValue? value,
TConfiguration? configuration,
string? valueType,
PropertyValidationContext validationContext);
}

View File

@@ -0,0 +1,51 @@
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Core.Models.Validation;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Core.PropertyEditors.Validation;
/// <summary>
/// <para>
/// An aggregate validator for JSON based value editors, to avoid doing multiple deserialization.
/// </para>
/// <para>
/// Will deserialize once, and cast the configuration once, and pass those values to each <see cref="ITypedJsonValidator{TValue,TConfiguration}"/>, aggregating the results.
/// </para>
/// </summary>
/// <typeparam name="TValue">The type of the expected value.</typeparam>
/// <typeparam name="TConfiguration">The type of the expected configuration</typeparam>
public class TypedJsonValidatorRunner<TValue, TConfiguration> : IValueValidator
where TValue : class
{
private readonly IJsonSerializer _jsonSerializer;
private readonly ITypedJsonValidator<TValue, TConfiguration>[] _validators;
public TypedJsonValidatorRunner(IJsonSerializer jsonSerializer, params ITypedJsonValidator<TValue, TConfiguration>[] validators)
{
_jsonSerializer = jsonSerializer;
_validators = validators;
}
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration,
PropertyValidationContext validationContext)
{
var validationResults = new List<ValidationResult>();
if (dataTypeConfiguration is not TConfiguration configuration)
{
return validationResults;
}
if (value is null || _jsonSerializer.TryDeserialize(value, out TValue? deserializedValue) is false)
{
return validationResults;
}
foreach (ITypedJsonValidator<TValue, TConfiguration> validator in _validators)
{
validationResults.AddRange(validator.Validate(deserializedValue, configuration, valueType, validationContext));
}
return validationResults;
}
}