ASP.NET MVC: validation personnalisée par DataAnnotation

110

J'ai un modèle avec 4 propriétés qui sont de type string. Je sais que vous pouvez valider la longueur d'une seule propriété en utilisant l'annotation StringLength. Cependant je souhaite valider la longueur des 4 propriétés combinées.

Quelle est la manière MVC de faire cela avec l'annotation de données?

Je pose cette question parce que je suis nouveau dans MVC et que je veux le faire correctement avant de créer ma propre solution.

Danny van der Kraan
la source
2
Avez-vous regardé Fluent Validation? Il gère bien mieux les scénarios complexes que les annotations de données
levelnis
Jetez un œil aux solutions hautement recommandées .... dotnetcurry.com/ShowArticle.aspx?ID=776
Niks
Merci de répondre. Je vais vérifier Fluent Validation, je n'en ai jamais entendu parler. Et Niks, Darin a essentiellement écrit ce que l'article sur le lien que vous avez posté expliquait. Alors, merci ... Des trucs géniaux!
Danny van der Kraan

Réponses:

177

Vous pouvez écrire un attribut de validation personnalisé:

public class CombinedMinLengthAttribute: ValidationAttribute
{
    public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
    {
        this.PropertyNames = propertyNames;
        this.MinLength = minLength;
    }

    public string[] PropertyNames { get; private set; }
    public int MinLength { get; private set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
        var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
        var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
        if (totalLength < this.MinLength)
        {
            return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }
}

puis vous pourriez avoir un modèle de vue et décorer l'une de ses propriétés avec:

public class MyViewModel
{
    [CombinedMinLength(20, "Bar", "Baz", ErrorMessage = "The combined minimum length of the Foo, Bar and Baz properties should be longer than 20")]
    public string Foo { get; set; }
    public string Bar { get; set; }
    public string Baz { get; set; }
}
Darin Dimitrov
la source
4
Merci d'avoir répondu, j'ai accepté votre réponse. Sentez-vous un peu gêné en fait. Vous avez rédigé toute la solution! Hehe. Il suffit de changer la fonction IsValid pour vérifier la longueur maximale. Est-ce donc la solution MVC acceptée pour ces types de problèmes?
Danny van der Kraan
7
@DannyvanderKraan, oui, c'est la manière acceptée. Bien sûr, cela craint tellement que je ne l'utilise jamais et utilise plutôt FluentValidation.NET pour effectuer la validation.
Darin Dimitrov
11
Ici: fluentvalidation.codeplex.com . Vous pourriez venez d' écrire simple validateur pour le modèle de vue qui aurait pu ressembler cette (une seule ligne de code): this.RuleFor(x => x.Foo).Must((x, foo) => x.Foo.Length + x.Bar.Length + x.Baz.Length < 20).WithMessage("The combined minimum length of the Foo, Bar and Baz properties should be longer than 20");. Maintenant, regardez le code dans ma réponse que vous devez écrire avec les annotations de données et dites-moi laquelle vous préférez. Le modèle de validation déclarative est très médiocre par rapport à un modèle impératif.
Darin Dimitrov
1
C'est un peu tard, mais est-ce que quelqu'un sait s'il existe un paramètre différent que vous devez «activer» pour autoriser les annotations de données personnalisées? Je sais comment ajouter un espace de noms pour les js discrets sur le fichier web.config, mais ailleurs?
Jose
1
J'ai cherché ça toute la matinée! Je l'ai implémenté, et malheureusement, quand il IsValidest appelé le validationContextest nul. Une idée de ce que j'ai mal fait? :-(
Grimm The Opiner
95

Modèle auto-validé

Votre modèle doit implémenter une interface IValidatableObject. Mettez votre code de validation en Validateméthode:

public class MyModel : IValidatableObject
{
    public string Title { get; set; }
    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Title == null)
            yield return new ValidationResult("*", new [] { nameof(Title) });

        if (Description == null)
            yield return new ValidationResult("*", new [] { nameof(Description) });
    }
}

Remarque: il s'agit d'une validation côté serveur . Cela ne fonctionne pas du côté client. Votre validation ne sera effectuée qu'après l'envoi du formulaire.

Andrei
la source
Merci d'avoir répondu à Andrei. Bien que votre solution fonctionne aussi, je choisis celle de Darin car elle est plus réutilisable.
Danny van der Kraan
6
yield return new ValidationResult ("Le titre est obligatoire.", "Titre"); ajouterait le nom de la propriété, utile pour regrouper les erreurs de validation à afficher si nécessaire.
Mike Kingscott
5
Notez que cette méthode de validation n'est appelée que lorsque tous les attributs de validation ont passé la validation.
Pedro
3
Cela a bien fonctionné pour moi car ma validation était très spécifique. L'ajout d'un attribut personnalisé aurait été excessif pour moi car la validation n'allait pas être réutilisée.
Steve S du
C'est ce que je recherche. Je vous remercie!
Amol Jadhav le
27

ExpressiveAnnotations vous offre une telle possibilité:

[Required]
[AssertThat("Length(FieldA) + Length(FieldB) + Length(FieldC) + Length(FieldD) > 50")]
public string FieldA { get; set; }
jwaliszko
la source
C'est génial! mes prières ont été exaucées :)
Korayem
Je viens de trouver cette réponse et c'est juste gagné beaucoup de temps. Les annotations expressives sont géniales!
Brad le
10

Pour améliorer la réponse de Darin, elle peut être un peu plus courte:

public class UniqueFileName : ValidationAttribute
{
    private readonly NewsService _newsService = new NewsService();

    public override bool IsValid(object value)
    {
        if (value == null) { return false; }

        var file = (HttpPostedFile) value;

        return _newsService.IsFileNameUnique(file.FileName);
    }
}

Modèle:

[UniqueFileName(ErrorMessage = "This file name is not unique.")]

Notez qu'un message d'erreur est requis, sinon l'erreur sera vide.

Jamie
la source
8

Contexte:

Des validations de modèle sont nécessaires pour garantir que les données reçues que nous recevons sont valides et correctes afin que nous puissions poursuivre le traitement avec ces données. Nous pouvons valider un modèle dans une méthode d'action. Les attributs de validation intégrés sont Compare, Range, RegularExpression, Required, StringLength. Cependant, nous pouvons avoir des scénarios dans lesquels nous avons besoin d'attributs de validation autres que ceux intégrés.

Attributs de validation personnalisés

public class EmployeeModel 
{
    [Required]
    [UniqueEmailAddress]
    public string EmailAddress {get;set;}
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public int OrganizationId {get;set;}
}

Pour créer un attribut de validation personnalisé, vous devrez dériver cette classe de ValidationAttribute.

public class UniqueEmailAddress : ValidationAttribute
{
    private IEmployeeRepository _employeeRepository;
    [Inject]
    public IEmployeeRepository EmployeeRepository
    {
        get { return _employeeRepository; }
        set
        {
            _employeeRepository = value;
        }
    }
    protected override ValidationResult IsValid(object value,
                        ValidationContext validationContext)
    {
        var model = (EmployeeModel)validationContext.ObjectInstance;
        if(model.Field1 == null){
            return new ValidationResult("Field1 is null");
        }
        if(model.Field2 == null){
            return new ValidationResult("Field2 is null");
        }
        if(model.Field3 == null){
            return new ValidationResult("Field3 is null");
        }
        return ValidationResult.Success;
    }
}

J'espère que cela t'aides. À votre santé !

Références

Yasser Shaikh
la source
1

Un peu tard pour répondre, mais pour qui cherche. Vous pouvez facilement le faire en utilisant une propriété supplémentaire avec l'annotation de données:

public string foo { get; set; }
public string bar { get; set; }

[MinLength(20, ErrorMessage = "too short")]
public string foobar 
{ 
    get
    {
        return foo + bar;
    }
}

C'est tout ce que c'est vraiment trop. Si vous voulez vraiment afficher à un endroit spécifique l'erreur de validation, vous pouvez l'ajouter dans votre vue:

@Html.ValidationMessage("foobar", "your combined text is too short")

faire cela dans la vue peut être utile si vous souhaitez effectuer une localisation.

J'espère que cela t'aides!

Leo Muller
la source