web-dev-qa-db-fra.com

Attribut de validation conditionnelle RequiredIf

Je cherchais des conseils sur la meilleure façon de mettre en œuvre un attribut de validation qui fait ce qui suit.

Modèle

public class MyInputModel 
{
    [Required]
    public int Id {get;set;}

    public string MyProperty1 {get;set;}
    public string MyProperty2 {get;set;}
    public bool MyProperty3 {get;set;}

}

Je veux avoir au moins prop1 prop2 prop3 avec une valeur et si prop3 est la seule valeur remplie, elle ne devrait pas être fausse. Comment pourrais-je écrire un ou plusieurs attributs de validation pour cela?

Merci pour toute aide!

51
zSynopsis

J'ai eu le même problème hier, mais je l'ai fait d'une manière très propre qui fonctionne à la fois pour la validation côté client et côté serveur.

Condition: En fonction de la valeur d'une autre propriété dans le modèle, vous souhaitez rendre une autre propriété requise. Voici le code

public class RequiredIfAttribute : RequiredAttribute
{
  private String PropertyName { get; set; }
  private Object DesiredValue { get; set; }

  public RequiredIfAttribute(String propertyName, Object desiredvalue)
  {
    PropertyName = propertyName;
    DesiredValue = desiredvalue;
  }

  protected override ValidationResult IsValid(object value, ValidationContext context)
  {
    Object instance = context.ObjectInstance;
    Type type = instance.GetType();
    Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
    if (proprtyvalue.ToString() == DesiredValue.ToString())
    {
      ValidationResult result = base.IsValid(value, context);
      return result;
    }
    return ValidationResult.Success;
  }
}

PropertyName est la propriété sur laquelle vous souhaitez effectuer votre condition
DesiredValue est la valeur particulière du PropertyName (propriété) pour laquelle votre autre propriété doit être validée

Disons que vous disposez des éléments suivants:

public enum UserType
{
  Admin,
  Regular
}

public class User
{
  public UserType UserType {get;set;}

  [RequiredIf("UserType",UserType.Admin,
              ErrorMessageResourceName="PasswordRequired", 
              ErrorMessageResourceType = typeof(ResourceString))
  public string Password
  { get; set; }
}

Enfin, mais non le moindre, enregistrez l'adaptateur pour votre attribut afin qu'il puisse faire une validation côté client (je l'ai mis dans global.asax, Application_Start)

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),
                                                      typeof(RequiredAttributeAdapter));

ÉDITÉ

Certaines personnes se plaignaient que le côté client tire quoi qu'il arrive ou que cela ne fonctionne pas. J'ai donc modifié le code ci-dessus pour effectuer également une validation conditionnelle côté client avec javascript. Dans ce cas, vous n'avez pas besoin d'enregistrer l'adaptateur

 public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    {
        private String PropertyName { get; set; }
        private Object DesiredValue { get; set; }
        private readonly RequiredAttribute _innerAttribute;

        public RequiredIfAttribute(String propertyName, Object desiredvalue)
        {
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            _innerAttribute = new RequiredAttribute();
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);

            if (dependentValue.ToString() == DesiredValue.ToString())
            {
                if (!_innerAttribute.IsValid(value))
                {
                    return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
                }
            }
            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = ErrorMessageString,
                ValidationType = "requiredif",
            };
            rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
            rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;

            yield return rule;
        }
    }

Et enfin le javascript (le regrouper et le rendre ... le mettre dans son propre fichier script)

$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options) {
    options.rules['requiredif'] = options.params;
    options.messages['requiredif'] = options.message;
});

$.validator.addMethod('requiredif', function (value, element, parameters) {
    var desiredvalue = parameters.desiredvalue;
    desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
    var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
    var actualvalue = {}
    if (controlType == "checkbox" || controlType == "radio") {
        var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
        actualvalue = control.val();
    } else {
        actualvalue = $("#" + parameters.dependentproperty).val();
    }
    if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
        var isValid = $.validator.methods.required.call(this, value, element, parameters);
        return isValid;
    }
    return true;
});

Vous avez évidemment besoin que la requête de validation discrète soit incluse comme exigence

86
Dan Hunex

Je sais que le sujet a été posé il y a quelque temps, mais récemment, j'avais rencontré un problème similaire et en ai trouvé un autre, mais à mon avis, une solution plus complète. J'ai décidé de mettre en œuvre un mécanisme qui fournit des attributs conditionnels pour calculer les résultats de validation en fonction d'autres valeurs de propriétés et des relations entre elles, qui sont définies dans des expressions logiques.

En l'utilisant, vous pouvez obtenir le résultat que vous avez demandé de la manière suivante:

[RequiredIf("MyProperty2 == null && MyProperty3 == false")]
public string MyProperty1 { get; set; }

[RequiredIf("MyProperty1 == null && MyProperty3 == false")]
public string MyProperty2 { get; set; }

[AssertThat("MyProperty1 != null || MyProperty2 != null || MyProperty3 == true")]
public bool MyProperty3 { get; set; }

Plus d'informations sur la bibliothèque ExpressiveAnnotations peuvent être trouvées ici . Il devrait simplifier de nombreux cas de validation déclarative sans qu'il soit nécessaire d'écrire des attributs spécifiques au cas supplémentaires ou d'utiliser une méthode impérative de validation à l'intérieur des contrôleurs.

30
jwaliszko

Je l'ai fait fonctionner sur ASP.NET MVC 5

J'ai vu beaucoup de gens intéressés et souffrant de ce code et je sais que c'est vraiment déroutant et perturbant pour la première fois.

Remarques

  • travaillé sur MVC 5 côté serveur et côté client: D
  • Je n'ai pas du tout installé la bibliothèque "ExpressiveAnnotations".
  • Je prends le code d'origine de "@ Dan Hunex", le trouver ci-dessus

Conseils pour corriger cette erreur

"Le type System.Web.Mvc.RequiredAttributeAdapter doit avoir un constructeur public qui accepte trois paramètres de types System.Web.Mvc.ModelMetadata, System.Web.Mvc.ControllerContext et ExpressiveAnnotations.Attributes.RequiredIfAttribute Nom du paramètre: adapterType"

Astuce # 1: assurez-vous que vous héritez de 'ValidationAttribute' pas de 'RequiredAttribute'

 public class RequiredIfAttribute : ValidationAttribute, IClientValidatable { ...}

Astuce n ° 2: OR supprimez cette ligne entière de 'Global.asax', ce n'est pas du tout nécessaire dans la nouvelle version de le code (après modification par @Dan_Hunex), et oui cette ligne était un must dans l'ancienne version ...

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredAttributeAdapter));

Conseils pour faire fonctionner la partie du code Javascript

1- mettre le code dans un nouveau fichier js (ex: requiredIfValidator.js)

2- déforme le code dans un $ (document) .ready (function () {........});

3- inclure notre fichier js après avoir inclus les bibliothèques de validation JQuery, donc ça ressemble à ceci maintenant:

@Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>

4- Modifier le code C #

de

rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);

à

rule.ValidationParameters["dependentproperty"] = PropertyName;

et de

if (dependentValue.ToString() == DesiredValue.ToString())

à

if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())

Mon code entier est opérationnel

Global.asax

Rien à ajouter ici, gardez-le propre

requiredIfValidator.js

créer ce fichier dans ~/content ou dans le dossier ~/scripts

    $.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'desiredvalue'], function (options)
{
    options.rules['requiredif'] = options.params;
    options.messages['requiredif'] = options.message;
});


$(document).ready(function ()
{

    $.validator.addMethod('requiredif', function (value, element, parameters) {
        var desiredvalue = parameters.desiredvalue;
        desiredvalue = (desiredvalue == null ? '' : desiredvalue).toString();
        var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
        var actualvalue = {}
        if (controlType == "checkbox" || controlType == "radio") {
            var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
            actualvalue = control.val();
        } else {
            actualvalue = $("#" + parameters.dependentproperty).val();
        }
        if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
            var isValid = $.validator.methods.required.call(this, value, element, parameters);
            return isValid;
        }
        return true;
    });
});

_ Layout.cshtml ou la vue

@Scripts.Render("~/bundles/jqueryval")
<script src="~/Content/JS/requiredIfValidator.js"></script>

classe RequiredIfAttribute.cs

créez-le quelque part dans votre projet, par exemple dans ~/models/customValidation /

    using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Your_Project_Name.Models.CustomValidation
{
    public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
    {
        private String PropertyName { get; set; }
        private Object DesiredValue { get; set; }
        private readonly RequiredAttribute _innerAttribute;

        public RequiredIfAttribute(String propertyName, Object desiredvalue)
        {
            PropertyName = propertyName;
            DesiredValue = desiredvalue;
            _innerAttribute = new RequiredAttribute();
        }

        protected override ValidationResult IsValid(object value, ValidationContext context)
        {
            var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);

            if (dependentValue != null && dependentValue.ToString() == DesiredValue.ToString())
            {
                if (!_innerAttribute.IsValid(value))
                {
                    return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
                }
            }
            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = ErrorMessageString,
                ValidationType = "requiredif",
            };
            rule.ValidationParameters["dependentproperty"] = PropertyName;
            rule.ValidationParameters["desiredvalue"] = DesiredValue is bool ? DesiredValue.ToString().ToLower() : DesiredValue;

            yield return rule;
        }
    }
}

Le modèle

    using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using Your_Project_Name.Models.CustomValidation;

namespace Your_Project_Name.Models.ViewModels
{

    public class CreateOpenActivity
    {
        public Nullable<int> ORG_BY_CD { get; set; }

        [RequiredIf("ORG_BY_CD", "5", ErrorMessage = "Coordinator ID is required")] // This means: IF 'ORG_BY_CD' is equal 5 (for the example)  > make 'COR_CI_ID_NUM' required and apply its all validation / data annotations
        [RegularExpression("[0-9]+", ErrorMessage = "Enter Numbers Only")]
        [MaxLength(9, ErrorMessage = "Enter a valid ID Number")]
        [MinLength(9, ErrorMessage = "Enter a valid ID Number")]
        public string COR_CI_ID_NUM { get; set; }
    }
}

La vue

Rien à noter ici en fait ...

    @model Your_Project_Name.Models.ViewModels.CreateOpenActivity
@{
    ViewBag.Title = "Testing";
}

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>CreateOpenActivity</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.ORG_BY_CD, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ORG_BY_CD, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ORG_BY_CD, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.COR_CI_ID_NUM, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.COR_CI_ID_NUM, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.COR_CI_ID_NUM, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

Je peux télécharger un exemple de projet pour cela plus tard ...

J'espère que cela vous a été utile

Merci

8
Adel Mourad

En développant les notes d'Adel Mourad et de Dan Hunex, j'ai modifié le code pour fournir un exemple qui n'accepte que des valeurs qui ne pas correspondent à la valeur donnée.

J'ai également constaté que je n'avais pas besoin du JavaScript.

J'ai ajouté la classe suivante à mon dossier Modèles:

public class RequiredIfNotAttribute : ValidationAttribute, IClientValidatable
{
    private String PropertyName { get; set; }
    private Object InvalidValue { get; set; }
    private readonly RequiredAttribute _innerAttribute;

    public RequiredIfNotAttribute(String propertyName, Object invalidValue)
    {
        PropertyName = propertyName;
        InvalidValue = invalidValue;
        _innerAttribute = new RequiredAttribute();
    }

    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        var dependentValue = context.ObjectInstance.GetType().GetProperty(PropertyName).GetValue(context.ObjectInstance, null);

        if (dependentValue.ToString() != InvalidValue.ToString())
        {
            if (!_innerAttribute.IsValid(value))
            {
                return new ValidationResult(FormatErrorMessage(context.DisplayName), new[] { context.MemberName });
            }
        }
        return ValidationResult.Success;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessageString,
            ValidationType = "requiredifnot",
        };
        rule.ValidationParameters["dependentproperty"] = (context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
        rule.ValidationParameters["invalidvalue"] = InvalidValue is bool ? InvalidValue.ToString().ToLower() : InvalidValue;

        yield return rule;
    }

Je n'ai pas eu besoin de modifier ma vue, mais j'ai modifié les propriétés de mon modèle:

    [RequiredIfNot("Id", 0, ErrorMessage = "Please select a Source")]
    public string TemplateGTSource { get; set; }

    public string TemplateGTMedium
    {
        get
        {
            return "Email";
        }
    }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Campaign")]
    public string TemplateGTCampaign { get; set; }

    [RequiredIfNot("Id", 0, ErrorMessage = "Please enter a Term")]
    public string TemplateGTTerm { get; set; }

J'espère que cela t'aides!

2
Amy Gray

Si vous essayez d'utiliser "ModelState.Remove" ou "ModelState [" Prop "]. Errors.Clear ()" le stil "ModelState.IsValid" renvoie false.

Pourquoi ne pas simplement supprimer l'annotation "Obligatoire" par défaut du modèle et effectuer votre validation personnalisée avant l'action "ModelState.IsValid" sur l'action "Post" du contrôleur? Comme ça:

if (!String.IsNullOrEmpty(yourClass.Property1) && String.IsNullOrEmpty(yourClass.dependantProperty))            
            ModelState.AddModelError("dependantProperty", "It´s necessary to select some 'dependant'.");

La principale différence avec les autres solutions ici est que celle-ci réutilise la logique dans RequiredAttribute côté serveur et utilise la méthode de validation de requireddepends propriété côté client:

public class RequiredIf : RequiredAttribute, IClientValidatable
{
    public string OtherProperty { get; private set; }
    public object OtherPropertyValue { get; private set; }

    public RequiredIf(string otherProperty, object otherPropertyValue)
    {
        OtherProperty = otherProperty;
        OtherPropertyValue = otherPropertyValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        PropertyInfo otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherProperty);
        if (otherPropertyInfo == null)
        {
            return new ValidationResult($"Unknown property {OtherProperty}");
        }

        object otherValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
        if (Equals(OtherPropertyValue, otherValue)) // if other property has the configured value
            return base.IsValid(value, validationContext);

        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
        rule.ValidationType = "requiredif"; // data-val-requiredif
        rule.ValidationParameters.Add("other", OtherProperty); // data-val-requiredif-other
        rule.ValidationParameters.Add("otherval", OtherPropertyValue); // data-val-requiredif-otherval

        yield return rule;
    }
}

$.validator.unobtrusive.adapters.add("requiredif", ["other", "otherval"], function (options) {
    var value = {
        depends: function () {
            var element = $(options.form).find(":input[name='" + options.params.other + "']")[0];
            return element && $(element).val() == options.params.otherval;
        }
    }
    options.rules["required"] = value;
    options.messages["required"] = options.message;
});
0
glut