web-dev-qa-db-fra.com

Validation personnalisée MVC: comparer deux dates

J'ai créé un ValidationAttribute personnalisé qui compare 2 dates et s'assure que la deuxième date est supérieure à la première:

public sealed class IsDateAfter : ValidationAttribute, IClientValidatable
{
    private readonly string testedPropertyName;
    private readonly bool allowEqualDates;

    public IsDateAfter(string testedPropertyName, bool allowEqualDates = false)
    {
        this.testedPropertyName = testedPropertyName;
        this.allowEqualDates = allowEqualDates;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.testedPropertyName);
        if (propertyTestedInfo == null)
        {
            return new ValidationResult(string.Format("unknown property {0}", this.testedPropertyName));
        }

        var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);

        if (value == null || !(value is DateTime))
        {
            return ValidationResult.Success;
        }

        if (propertyTestedValue == null || !(propertyTestedValue is DateTime))
        {
            return ValidationResult.Success;
        }

        // Compare values
        if ((DateTime)value >= (DateTime)propertyTestedValue)
        {
            if (this.allowEqualDates)
            {
                return ValidationResult.Success;
            }
            if ((DateTime)value > (DateTime)propertyTestedValue)
            {
                return ValidationResult.Success;
            }
        }

        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessageString,
            ValidationType = "isdateafter"
        };
        rule.ValidationParameters["propertytested"] = this.testedPropertyName;
        rule.ValidationParameters["allowequaldates"] = this.allowEqualDates;
        yield return rule;
    }

CalendarEntry class: ...

public virtual DateTime StartDate { get; set; }

[IsDateAfter("StartDate", true, ErrorMessage="End date needs to be after start date")]
public virtual DateTime EndDate { get; set; }

VUE:

$.validator.unobtrusive.adapters.add(
    'isdateafter', ['propertytested', 'allowequaldates'], function (options) {
    options.rules['isdateafter'] = options.params;
    options.messages['isdateafter'] = options.message;
});
$.validator.addMethod("isdateafter", function(value, element, params) {
    alert(params.propertytested);
    var startdatevalue = $('input[name="' + params.propertytested + '"]').val();
    if (!value || !startdatevalue) return true;
    return (params.allowequaldates) ? Date.parse(startdatevalue) <= Date.parse(value) : Date.parse(startdatevalue) < Date.parse(value);
}, '');

Cela fonctionne bien lorsque CalendarEntry n'est pas encapsulé dans une autre classe. CEPENDANT, quand j'utilise un modèle de vue comme ceci:

    public class TrainingDateEditViewModel
    {
        #region Properties

        /// <summary>
        /// Gets or sets CalendarEntry.
        /// </summary>
        public CalendarEntry CalendarEntry { get; set; }
....

La validation client ne fonctionne plus car la sortie html produite est la suivante:

<input type="text" value="" name="CalendarEntry.EndDate" id="CalendarEntry_EndDate" data-val-isdateafter-propertytested="StartDate" data-val-isdateafter-allowequaldates="True" data-val-isdateafter="End date needs to be after start date" data-val="true">

Et le

data-val-isdateafter-propertytested="StartDate" and IT SHOULD BE: "CalendarEntry.StartDate".

Comment pourrais-je faire en sorte qu'il sache se lier à la règle "CalendarEntry.StartDate". ValidationParameters ["propertytested"] = this.testedPropertyName; // ICI CE DEVRAIT ÊTRE LE NOM COMPLET ??? COMMENT??

merci

62
ShaneKm

Vous devez modifier votre script côté client pour vérifier le préfixe de l'élément testé et ajouter le préfixe (le cas échéant) à votre sélecteur, comme suit:

$.validator.addMethod("isdateafter", function(value, element, params) {
    var parts = element.name.split(".");
    var prefix = "";
    if (parts.length > 1)
        prefix = parts[0] + ".";
    var startdatevalue = $('input[name="' + prefix + params.propertytested + '"]').val();
    if (!value || !startdatevalue) 
        return true;    
    return (params.allowequaldates) ? Date.parse(startdatevalue) <= Date.parse(value) :
        Date.parse(startdatevalue) < Date.parse(value);
});
31
counsellorben

N'oubliez pas d'inclure le côté client dans ce code. Il m'a fallu des heures pour constater que cela manquait!

(function ($) {

  // your code here..

})(jQuery);
4
MrFlo

Juste pour corriger une petite erreur dans le javascript de counsellorben: le "(params.allowequaldates)" sera interprété comme une chaîne (qui aura la valeur "False" ou "True"), mais cette chaîne sera toujours évaluée à true, permettant ainsi toujours des dates égales. Si vous souhaitez également autoriser plus de niveaux d'imbrication de vos objets que juste 1, vous obtiendrez:

$.validator.addMethod("isdateafter", function(value, element, params) {
    var parts = element.name.split(".");
    var prefix = "";
    for (var i = 0; i < parts.length - 1; i++)
       prefix = parts[i] + ".";
    var startdatevalue = $('input[name="' + prefix + params.propertytested + '"]').val();
    if (!value || !startdatevalue) 
        return true;    
    var allowequal = params.allowequaldates.toLowerCase === "true";
    return allowequal ? Date.parse(startdatevalue) <= Date.parse(value) :
        Date.parse(startdatevalue) < Date.parse(value);
});
1
gerard789

Dans la dernière réponse, il y avait des parenthèses manquantes lors de l'appel à toLowerCase, voici une version mise à jour avec le document prêt et la partie $ .validator.unobtrusive ...-:

$(function () {
    $.validator.addMethod("isdateafter", function(value, element, params) {
        var parts = element.name.split(".");
        var prefix = "";
        for (var i = 0; i < parts.length - 1; i++) {
            prefix = parts[i] + ".";
        }

        var startdatevalue = $('input[name="' + prefix + params.propertytested + '"]').val();

        if (!value || !startdatevalue) return true;    

        var allowequal = params.allowequaldates.toLowerCase() === "true";
        return allowequal ? Date.parse(startdatevalue) <= Date.parse(value) :
            Date.parse(startdatevalue) < Date.parse(value);
    });
    $.validator.unobtrusive.adapters.add('isdateafter', 
        ['propertytested', 'allowequaldates'], 
        function (options) {
            options.rules['isdateafter'] = options.params;
            options.messages['isdateafter'] = options.message;
        });
});
0
Dan Pettersson