Comment fournir des avertissements lors de la validation dans ASP.NET MVC?

Parfois, l'entrée des utilisateurs n'est pas strictement invalide mais peut être considérée comme problématique.

Par exemple:

  • Un utilisateur saisit une longue phrase dans un champ Name seule ligne. Il aurait probablement dû utiliser le champ Description place .
  • Un utilisateur entre un Name qui est très similaire à celui d'une entité existante. Peut-être est-il en train de saisir la même entité mais ne s'est pas rendu compte qu'elle existe déjà, ou certains utilisateurs simultanés y entrent.

Certains d'entre eux peuvent facilement être vérifiés côté client, certains nécessitent des contrôles côté serveur.

Quelle est la meilleure façon , peut-être quelque chose d'similaire à la validation DataAnnotations , pour fournir des avertissements à l'utilisateur dans de tels cas? La clé ici est que l'utilisateur doit pouvoir annuler l'avertissement et soumettre le formulaire (ou soumettre de nouveau le formulaire, selon la mise en œuvre).

La solution la plus viable qui vient à l'esprit est de créer un attribut semblable à un CustomValidationAttribute , qui peut faire un appel AJAX et afficher un texte d'avertissement mais n'affecte pas le ModelState . L'utilisation prévue est la suivante:

 [WarningOnFieldLength(MaxLength = 150)] [WarningOnPossibleDuplicate()] public string Name { get; set; } 

Dans la vue:

 @Html.EditorFor(model => model.Name) @Html.WarningMessageFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) 

Alors, des idées?

Conception générale

Pour commencer, je crois que vous devriez suivre en quelque sorte si l'utilisateur choisit d'ignorer les avertissements. Une façon simple et transparente de le faire est d'avoir une case à cocher Ignorer les avertissements , que l'utilisateur devrait vérifier avant de soumettre. Une autre option est de les faire soumettre le formulaire deux fois et d'ignorer les avertissements sur le second soumettre; Alors vous auriez probablement besoin d'un champ caché IgnoreWarnings . Il pourrait y avoir d'autres conceptions, mais pour des raisons de simplicité, je vais suivre la première option.

Bref, l'approche consiste à créer

  • Un attribut d'annotation de données personnalisé pour tous les modèles de vues prenant en charge le type de validation d'avertissement;
  • Une classe de base connue dont les modèles de vue hériteront;
  • Nous devrons dupliquer la logique en JavaScript pour chaque attribut personnalisé.

Veuillez noter que le code ci-dessous illustre l'approche et je dois assumer beaucoup de choses sans connaître le contexte complet.

Modèle de vue

Dans ce scénario, il est préférable de séparer un modèle de vue d'un modèle réel qui est une bonne idée de toute façon. Une approche possible est d'avoir une classe de base pour tous les modèles de vision qui prennent en charge les avertissements:

 public abstract class BaseViewModel { public bool IgnoreWarnings { get; set; } } 

La raison principale pour laquelle un modèle doit être séparé est qu'il n'y a pas beaucoup de sens dans le stockage de la propriété IgnoreWarnings dans votre base de données.

Votre modèle de vue dérivé sera alors le suivant:

 public class YourViewModel : BaseViewModel { [Required] [StringLengthWarning(MaximumLength = 5, ErrorMessage = "Your Warning Message")] public string YourProperty { get; set; } } 

StringLengthWarning est un attribut personnalisé d'annotation de données pour la validation du serveur et du client. Il supporte juste la longueur maximale et peut facilement être étendu avec toutes les autres propriétés nécessaires.

Attribut d'annotation des données

Le noyau de l'attribut est IsValid(value, validationContext méthode IsValid(value, validationContext .

 [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public class StringLengthWarningAttribute : ValidationAttribute, IClientValidatable { public int MaximumLength { get; set; } public override bool IsValid(object value) { return true; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var model = validationContext.ObjectInstance as BaseViewModel; var str = value as string; if (!model.IgnoreWarnings && (string.IsNullOrWhiteSpace(str) || str.Length > MaximumLength)) return new ValidationResult(ErrorMessage); return base.IsValid(value, validationContext); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new StringLengthWarningValidationRule(MaximumLength, ErrorMessage); } } 

L'attribut implémente IClientValidatable et utilise une règle de validation client personnalisée:

 public class StringLengthWarningValidationRule : ModelClientValidationRule { public StringLengthWarningValidationRule(int maximumLength, string errorMessage) { ErrorMessage = errorMessage; ValidationType = "stringlengthwarning"; ValidationParameters.Add("maximumlength", maximumLength); ValidationParameters.Add("ignorewarningsfield", "IgnoreWarnings"); } } 

JavaScript côté client

Enfin, pour que cela fonctionne, vous aurez besoin du JavaScript suivant référencé à partir de votre vue:

 $(function () { $.validator.addMethod('stringlengthwarning', function (value, element, params) { var maximumlength = params['maximumlength']; var ignorewarningsfield = params['ignorewarningsfield']; var ctl = $("#" + ignorewarningsfield); if (ctl == null || ctl.is(':checked')) return true; return value.length <= maximumlength; }); $.validator.unobtrusive.adapters.add("stringlengthwarning", ["maximumlength", "ignorewarningsfield"], function (options) { var value = { maximumlength: options.params.maximumlength, ignorewarningsfield: options.params.ignorewarningsfield }; options.rules["stringlengthwarning"] = value; if (options.message) { options.messages["stringlengthwarning"] = options.message; } }); }(jQuery)); 

Le JavaScript pose certaines hypothèses que vous pourriez vouloir réexaminer (le nom de la boîte à lettres, etc.).

MISE À JOUR: Helpers HTML

Pour afficher les messages de validation séparément pour les erreurs et les avertissements, quelques aides seront nécessaires. La classe suivante fournit un exemple:

 public static class MessageHelpers { public static MvcHtmlString WarningMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) { if (htmlHelper.ViewData.ModelState["IgnoreWarnings"] != null) return htmlHelper.ValidationMessageFor(expression); return MvcHtmlString.Empty; } public static MvcHtmlString ErrorMessageFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) { if (htmlHelper.ViewData.ModelState["IgnoreWarnings"] == null) return htmlHelper.ValidationMessageFor(expression); return MvcHtmlString.Empty; } } 

Dans la vue, ils peuvent être utilisés comme d'habitude:

  @Html.EditorFor(model => model.YourProperty) @Html.ErrorMessageFor(model => model.YourProperty) @Html.WarningMessageFor(model => model.YourProperty) 

Juste un commentaire rapide sur la mise en œuvre possible de re-soumission que vous avez mentionnée …

Pour "voutais-tu faire ça?" Type de validation, du point de vue de l'utilisateur, avoir à soumettre de nouveau un formulaire basé sur l'hypothèse qu'ils ont commis une erreur pourrait être très ennuyant. Je ne mettrais en œuvre cette "pseudo-validation" du côté client avec javascript et (j'espère rapidement) des appels ajax si vous devez frapper le serveur.

Je tenterais également d'afficher les avertissements sur les événements de flou / change de l'entrée afin qu'ils s'affichent avant que l'utilisateur ne voie le soumettre. Peut-être pas pratique dans toutes les situations, mais je pensais juste que je lancerais là-bas.

Vous pouvez utiliser la fonction depends de la validation jquery pour simplifier votre vie.

Ex.

 @Html.LabelFor(m => m.UserName) @Html.TextBoxFor(m => m.UserName) @Html.ValidationMessageFor(m => m.UserName) <label>Ignore Warnings</label> <input id="ignore-warnings" type="checkbox" /> <script> $(function () { $("#UserName").rules("add", { minlength: { param: 6, depends: function (element) { return !$("#ignore-warnings").attr('checked'); } }, // server side remote validation for duplicate check remote: { param: '/account/duplicate', depends: function (element) { return !$("#ignore-warnings").attr('checked'); } } }); }); </script> 

Ce n'est qu'un croquis d'une solution possible. Il existe de nombreux exemples d'ajout d'attributs personnalisés (y compris ci-dessus), donc je saute ce bit.

Il est possible d'ajouter l'utilisation de l'ignorance dans la fonction de validateur jQuery .

Ensuite, utilisez

 $("form").validate({ ignore: ".warning-only" }); 

Et utilisez le validateur du côté client pour ajouter la classe «avertissement seulement» après un premier passage dans le validateur. Cela devrait permettre au formulaire d'être envoyé au serveur.

Comme je l'ai dit, juste un croquis, mais c'est quelque chose que j'ai fait des recherches pour l'utilisation de la fureur.

Voici un moyen de faire un avertissement sans écrire de code serveur. Ajoutez la classe "ignore-validation" aux éléments non valides souhaités sur le formulaire submit et, dans votre méthode de validation personnalisée, retournez "true" si l'élément a cette classe (si elle a la classe, cela signifie que le formulaire a été envoyé une fois). Vous devrez également supprimer la classe "ignorer-validation" de #IdOfInput sur flou ou changer, selon le type de contrôle, ce bit de code n'est pas représenté ici:

 <script type="text/javascript"> $.validator.addMethod('isValidCustomMethod', function (value, element) { if($(element).hasClass('ignore-validation')){ return true; } var isValid = false; //your code to do validation would actually go here return isValid; }); $(document).ready(function () { $('#IdOfInput').rules('add', { isValidCustomMethod: true, messages: { isValidCustomMethod: 'Your warning message here'} }); $('form').submit(function () { $(this).validate().invalidElements().each(function () { if($(this).attr('id')=='IdOfInput'){ $(this).addClass('ignore-validation'); } }); }); }); </script>