Désactiver l'attribut de validation obligatoire dans certaines circonstances


138

Je me demandais s'il était possible de désactiver l'attribut de validation requis dans certaines actions du contrôleur. Je me demande cela car sur l'un de mes formulaires d'édition, je n'oblige pas l'utilisateur à saisir des valeurs pour les champs qu'il a déjà spécifiés précédemment. Cependant, j'implémente ensuite une logique selon laquelle lorsqu'ils entrent une valeur, ils utilisent une logique spéciale pour mettre à jour le modèle, comme le hachage d'une valeur, etc.

Des suggestions pour contourner ce problème?

EDIT:
Et oui, la validation du client est un problème ici, car elle ne leur permettra pas de soumettre le formulaire sans entrer une valeur.


3
+1 bien Q. Il serait bon de mentionner la validation du client ici. Une option consiste à supprimer RequiredAttrcomplètement le fichier et à effectuer une vérification côté serveur lorsque vous en avez besoin. Mais ce serait délicat pour le client
gideon

4
Points pour tous ceux qui couvrent également la désactivation de certains champs de la validation client (pas de suppression des références à la validation jquery)
Gideon

Peut-être que je manque votre point, mais si l'utilisateur a déjà spécifié les valeurs au préalable, ces valeurs sont déjà présentes et passeront donc la validation requise. Vouliez-vous dire autre chose?
Erik Funkenbusch

Parce que ces valeurs ont depuis été hachées, telles que le mot de passe et la réponse de sécurité, donc si elles entrent une nouvelle valeur dans le formulaire d'édition, je veux hacher à nouveau la nouvelle valeur avant l'insertion, mais je veux également que l'option soit laissée vide genre de chose.
Alex Hope O'Connor

1
@gideon: voir la réponse d'Adrian Smith: stackoverflow.com/a/9781066/114029
Leniel Maccaferri

Réponses:


76

Ce problème peut être facilement résolu en utilisant des modèles de vue. Les modèles de vue sont des classes spécifiquement adaptées aux besoins d'une vue donnée. Par exemple, dans votre cas, vous pourriez avoir les modèles de vue suivants:

public UpdateViewView
{
    [Required]
    public string Id { get; set; }

    ... some other properties
}

public class InsertViewModel
{
    public string Id { get; set; }

    ... some other properties
}

qui seront utilisés dans leurs actions de contrôleur correspondantes:

[HttpPost]
public ActionResult Update(UpdateViewView model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
    ...
}

Pas toujours vrai si vous avez un booléen / bit qui n'est pas nullable. Mais cela ne fait vraiment aucune différence puisque cela finira par être vrai ou faux. J'ai eu css pour mettre en évidence les champs obligatoires et il a faussement mis en évidence l'élément CheckBoxFor. Ma solution: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
Robert Koch

Dans la mise à jour, nous avons généralement (collection FormCollection). pourriez-vous s'il vous plaît expliquer comment vous utilisez le modèle comme paramètre
Raj Kumar

4
Non, nous n'avons généralement pas Update(FormCollection collection), du moins je ne le fais jamais. Je définis et toujours utiliser un modèle de vue spécifique: Update(UpdateViewView model).
Darin Dimitrov

Les méthodes de surcharge avec la même action HTTP ne sont pas autorisées, il me semble donc que cela ne fonctionnerait pas. Est-ce que je manque quelque chose?
e11s

@edgi, non, rien ne vous manque. C'est une erreur dans mon post. La deuxième méthode d'action doit évidemment être appelée Insert. Merci de l'avoir signalé.
Darin Dimitrov

55

Si vous souhaitez simplement désactiver la validation pour un seul champ côté client, vous pouvez remplacer les attributs de validation comme suit:

@Html.TexBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})

20
Ce qui a fonctionné pour moi via jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
Robert Koch

6
J'aime cette approche mais j'avais besoin de ré-analyser les attributs de validation du formulaire en utilisant: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ('formulaire'). removeData ('validateur'); $ .validator.unobtrusive.parse ('sélecteur pour votre formulaire');
Yannick Smits

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- plus facile à lire IMO.
eth0

J'ai aimé la plupart de cette approche pour me laisser donner un contrôle fin de chaque champ, mais vous pouvez ajouter pour annuler ModelState.IsValid lors de l'envoi de données à enregistrer par POST. Peut-être causer un risque?
Felipe FMMobile

4
Si vous souhaitez le désactiver via jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri

40

Je sais que cette question a reçu une réponse il y a longtemps et que la réponse acceptée fera réellement le travail. Mais il y a une chose qui me dérange: ne devoir copier que 2 modèles pour désactiver une validation.

Voici ma suggestion:

public class InsertModel
{
    [Display(...)]
    public virtual string ID { get; set; }

    ...Other properties
}

public class UpdateModel : InsertModel
{
    [Required]
    public override string ID
    {
        get { return base.ID; }
        set { base.ID = value; }
    }
}

De cette façon, vous n'avez pas à vous soucier des validations côté client / serveur, le framework se comportera comme il est censé le faire. De plus, si vous définissez un [Display]attribut sur la classe de base, vous n'avez pas à le redéfinir dans votre fichier UpdateModel.

Et vous pouvez toujours utiliser ces classes de la même manière:

[HttpPost]
public ActionResult Update(UpdateModel model)
{
    ...
}

[HttpPost]
public ActionResult Insert(InsertModel model)
{
    ...
}

J'aime mieux cette approche, surtout si vous avez une longue liste d'attributs de validation. Dans votre exemple, vous pouvez ajouter plus d'attributs dans la classe de base pour rendre l'avantage plus apparent. Le seul inconvénient que je peux voir est qu'il n'y a aucun moyen pour hériter des propriétés de remplacer les attributs. Par exemple, si vous avez une propriété [Obligatoire] sur la classe de base, la propriété héritée est également forcée d'être [Obligatoire], sauf si vous avez un attribut personnalisé [Facultatif].
Yorro

J'ai pensé à quelque chose comme ça aussi, même si j'ai un viewModel qui a un objet «Projet» qui a plusieurs attributs et je veux seulement qu'un de ces attributs soit validé dans certaines circonstances. Je ne pense pas que je puisse simplement remplacer un attribut d'un objet, n'est-ce pas? aucun conseil?
vincent de g

Vous ne pouvez pas remplacer l'attribut. La classe de base ne doit contenir que des attributs communs pour toutes les sous-classes. Ensuite, vos sous-classes doivent définir les attributs dont elles ont besoin.
PhilDulac

1
Solution la plus élégante, réutilisable et transparente. Les réplications sont mauvaises. Le polymorphisme est le chemin. +1
T-moty

dans mon cas, une classe de base a un attribut obligatoire et je veux le rendre non obligatoire dans ma classe parent. Est-ce possible sans avoir deux exemplaires du modèle?
Alexey Strakh

27

Vous pouvez supprimer toute validation d'une propriété avec ce qui suit dans votre action de contrôleur.

ModelState.Remove<ViewModel>(x => x.SomeProperty);

Commentaire de @ Ian concernant MVC5

Ce qui suit est toujours possible

ModelState.Remove("PropertyNameInModel");

Peu ennuyeux que vous perdiez la saisie statique avec l'API mise à jour. Vous pouvez réaliser quelque chose de similaire à l'ancienne méthode en créant une instance de l'aide HTML et en utilisant les méthodes NameExtensions .


Sauf ... il n'y a pas de méthode ModelStatequi correspond à cette signature. Pas dans MVC 5, du moins.
Ian Kemp

La question n'était pas de savoir comment supprimer toute validation. C'était comment supprimer la validation de champ obligatoire. Vous voudrez peut-être faire cela tout en laissant d'autres règles de validation en place.
Richard

15

Côté client Pour désactiver la validation d'un formulaire, plusieurs options basées sur mes recherches sont données ci-dessous. J'espère que l'un d'eux fonctionnera pour vous.

Option 1

Je préfère cela, et cela fonctionne parfaitement pour moi.

(function ($) {
    $.fn.turnOffValidation = function (form) {
        var settings = form.validate().settings;

        for (var ruleIndex in settings.rules) {
            delete settings.rules[ruleIndex];
        }
    };
})(jQuery); 

et l'invoquant comme

$('#btn').click(function () {
    $(this).turnOffValidation(jQuery('#myForm'));
});

Option 2

$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");

Option 3

var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";

Option 4

 $("form").get(0).submit();
 jQuery('#createForm').unbind('submit').submit();

Option 5

$('input selector').each(function () {
    $(this).rules('remove');
});

Du côté serveur

Créez un attribut et marquez votre méthode d'action avec cet attribut. Personnalisez-le pour l'adapter à vos besoins spécifiques.

[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var modelState = filterContext.Controller.ViewData.ModelState;

        foreach (var modelValue in modelState.Values)
        {
            modelValue.Errors.Clear();
        }
    }
}

Une meilleure approche a été décrite ici Activer / Désactiver dynamiquement la validation côté serveur mvc


$ ('sélecteur d'entrée'). each (function () {$ (this) .rules ('remove');}); m'a aidé
Sachin Pakale

La question portait spécifiquement sur la suppression de la validation de champ obligatoire, et non sur la suppression de toute la validation. Votre réponse consiste à supprimer toute validation.
Richard

14

Personnellement, j'aurais tendance à utiliser l'approche de Darin Dimitrov dans sa solution. Cela vous permet de pouvoir utiliser l'approche d'annotation de données avec validation ET d'avoir des attributs de données séparés sur chaque ViewModel correspondant à la tâche à accomplir. Pour minimiser la quantité de travail pour la copie entre le modèle et le modèle de vue, vous devez examiner AutoMapper ou ValueInjecter . Les deux ont leurs points forts individuels, alors vérifiez-les tous les deux.

Une autre approche possible pour vous serait de dériver votre viewmodel ou model de IValidatableObject. Cela vous donne la possibilité d'implémenter une fonction Valider. Dans validate, vous pouvez renvoyer une liste d'éléments ValidationResult ou émettre un retour de rendement pour chaque problème détecté lors de la validation.

Le ValidationResult se compose d'un message d'erreur et d'une liste de chaînes avec les noms de champ. Les messages d'erreur seront affichés à un emplacement près du ou des champs d'entrée.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
  if( NumberField < 0 )
  {
    yield return new ValidationResult( 
        "Don't input a negative number", 
        new[] { "NumberField" } );
  }

  if( NumberField > 100 )
  {
    yield return new ValidationResult( 
        "Don't input a number > 100", 
        new[] { "NumberField" } );
  }

  yield break;
}

pouvons-nous accrocher cela du côté client?
Muhammad Adeel Zahid

La validation côté client est généralement effectuée pour les champs individuels uniquement, en effectuant un retour de validation interactif champ par champ, par souci de commodité pour l'utilisateur. Étant donné que l'étape de validation d'objet implique généralement une validation dépendante (plusieurs champs et / ou conditions externes à l'objet lui-même), cela ne peut pas nécessairement être effectué côté client, même si vous pouvez compiler le code en JavaScript. Si la validation complexe / dépendante côté client ajoute de la valeur dans votre cas, vous devrez utiliser le rappel onsubmit et dupliquer la logique de validation côté client.
mindplay.dk

7

Le moyen le plus propre ici, je pense, va désactiver votre validation côté client et du côté serveur, vous devrez:

  1. ModelState ["SomeField"]. Errors.Clear (dans votre contrôleur ou créez un filtre d'action pour supprimer les erreurs avant l'exécution du code du contrôleur)
  2. Ajoutez ModelState.AddModelError à partir du code de votre contrôleur lorsque vous détectez une violation de vos problèmes détectés.

Il semble que même un modèle de vue personnalisé ici ne résoudra pas le problème car le nombre de ces champs «pré-répondus» pourrait varier. Si ce n'est pas le cas, un modèle de vue personnalisé peut en effet être le moyen le plus simple, mais en utilisant la technique ci-dessus, vous pouvez contourner vos problèmes de validation.


1
C'est exactement ce dont j'avais besoin. J'avais une vue et différentes actions se produisent dans cette vue, donc je ne pouvais pas le faire avec différents ViewModels. Cela fonctionne comme un charme
ggderas

6

c'était la réponse de quelqu'un d'autre dans les commentaires ... mais cela devrait être une vraie réponse:

$("#SomeValue").removeAttr("data-val-required")

testé sur MVC 6 avec un champ ayant l' [Required]attribut

réponse volée sur https://stackoverflow.com/users/73382/rob ci-dessus


1
Qu'en est-il de la validation côté serveur?
T-moty le

ModelState.Remove, non? En tout cas, pour moi, le problème était que j'avais une deuxième entité incluse dans mon modèle ... la principale sur laquelle je voulais une validation, mais la secondaire n'avait pas besoin de validation sur cette page ... donc, dans ce scénario , seul le JQuery était nécessaire.
Mike_Matthews_II

Je pense que le lien est rompu, veuillez donc le modifier. Ceci est une réponse partielle
T-moty

C'est bizarre que vous disiez que cela fonctionne dans MVC6 (je n'ai pas d'option pour tester sur MVC6 actuellement) mais ne fonctionne pas pour moi sur MVC4 que j'utilise actuellement.
eaglei22

La question est pour MVC / C # - pas JS, la réponse ne fonctionnerait pas côté serveur
mtbennett

2

J'avais ce problème lors de la création d'une vue d'édition pour mon modèle et je souhaite mettre à jour un seul champ.

Ma solution pour la manière la plus simple est de mettre les deux champs en utilisant:

 <%: Html.HiddenFor(model => model.ID) %>
 <%: Html.HiddenFor(model => model.Name)%>
 <%: Html.HiddenFor(model => model.Content)%>
 <%: Html.TextAreaFor(model => model.Comments)%>

Les commentaires sont le champ que je mets à jour uniquement dans la vue d'édition, qui n'a pas d'attribut requis.

Entité ASP.NET MVC 3


1

AFAIK vous ne pouvez pas supprimer l'attribut au moment de l'exécution, mais seulement changer leurs valeurs (c'est-à-dire: lecture seule vrai / faux) cherchez ici quelque chose de similaire . Comme autre façon de faire ce que vous voulez sans déranger les attributs, j'irai avec un ViewModel pour votre action spécifique afin que vous puissiez insérer toute la logique sans casser la logique nécessaire aux autres contrôleurs. Si vous essayez d'obtenir une sorte d'assistant (un formulaire à plusieurs étapes), vous pouvez à la place sérialiser les champs déjà compilés et avec TempData les amener le long de vos étapes. (pour obtenir de l'aide sur la sérialisation de la désérialisation, vous pouvez utiliser les futurs MVC )


1

Ce que @Darin a dit est ce que je recommanderais également. Cependant, j'ajouterais (et en réponse à l'un des commentaires) que vous pouvez en fait également utiliser cette méthode pour les types primitifs comme bit, bool, même des structures comme Guid en les rendant simplement nullables. Une fois que vous faites cela, l' Requiredattribut fonctionne comme prévu.

public UpdateViewView
{
    [Required]
    public Guid? Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public int? Age { get; set; }
    [Required]
    public bool? IsApproved { get; set; }
    //... some other properties
}

1

À partir de MVC 5, cela peut être facilement réalisé en ajoutant ceci dans votre fichier global.asax.

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

1

Je cherchais une solution où je peux utiliser le même modèle pour une insertion et une mise à jour dans l'API Web. Dans ma situation, est-ce toujours un contenu corporel. Les [Requiered]attributs doivent être ignorés s'il s'agit d'une méthode de mise à jour. Dans ma solution, vous placez un attribut [IgnoreRequiredValidations]au-dessus de la méthode. C'est comme suit:

public class WebServiceController : ApiController
{
    [HttpPost]
    public IHttpActionResult Insert(SameModel model)
    {
        ...
    }

    [HttpPut]
    [IgnoreRequiredValidations]
    public IHttpActionResult Update(SameModel model)
    {
        ...
    }

    ...

Que faut-il faire d'autre? Un propre BodyModelValidator doit être créé et ajouté au démarrage. Ceci est dans la HttpConfiguration et ressemble à ceci:config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());

using Owin;
using your_namespace.Web.Http.Validation;

[assembly: OwinStartup(typeof(your_namespace.Startup))]

namespace your_namespace
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Configuration(app, new HttpConfiguration());
        }

        public void Configuration(IAppBuilder app, HttpConfiguration config)
        {
            config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
        }

        ...

Mon propre BodyModelValidator est dérivé de DefaultBodyModelValidator. Et je me suis rendu compte que je devais ignorer la méthode «ShallowValidate». Dans ce remplacement, je filtre les validateurs de modèle requis. Et maintenant, la classe IgnoreRequiredOrDefaultBodyModelValidator et la classe d'attribut IgnoreRequiredValidations:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;

namespace your_namespace.Web.Http.Validation
{
    public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
    {
        private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;

        static IgnoreRequiredOrDefaultBodyModelValidator()
        {
            _ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
        }

        protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
        {
            var actionContext = validationContext.ActionContext;

            if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
                validators = validators.Where(v => !v.IsRequired);          

            return base.ShallowValidate(metadata, validationContext, container, validators);
        }

        #region RequiredValidationsIsIgnored
        private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
        {
            bool ignore;

            if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
                _ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));

            return ignore;
        }

        private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
        {
            if (actionDescriptor == null)
                return false;

            return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
        } 
        #endregion
    }

    [AttributeUsage(AttributeTargets.Method, Inherited = true)]
    public class IgnoreRequiredValidationsAttribute : Attribute
    {

    }
}

Sources:



0

Dans mon cas, le même modèle a été utilisé dans de nombreuses pages à des fins de réutilisation. Donc ce que j'ai fait, c'est d'avoir créé un attribut personnalisé qui vérifie les exclusions

public class ValidateAttribute : ActionFilterAttribute
{
    public string Exclude { get; set; }
    public string Base { get; set; }
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!string.IsNullOrWhiteSpace(this.Exclude))
        {
            string[] excludes = this.Exclude.Split(',');
            foreach (var exclude in excludes)
            {
                actionContext.ModelState.Remove(Base + "." + exclude);
            }
        }
        if (actionContext.ModelState.IsValid == false)
        {
            var mediaType = new MediaTypeHeaderValue("application/json");
            var error = actionContext.ModelState;

            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);

        }
    }
}

et dans votre contrôleur

[Validate(Base= "person",Exclude ="Age,Name")]
    public async Task<IHttpActionResult> Save(User person)
    {

            //do something           

    }

Disons que le modèle est

public class User
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Range(18,99)]
    public string Age { get; set; }
    [MaxLength(250)]
    public string Address { get; set; }
}

-1

Oui, il est possible de désactiver l'attribut requis. Créez votre propre attribut de classe personnalisé (exemple de code appelé ChangeableRequired) à partir de RequiredAtribute et ajoutez une propriété désactivée et remplacez la méthode IsValid pour vérifier si elle est déséquilibrée. Utilisez la réflexion pour définir la propriété désactivée, comme ceci:

Attribut personnalisé:

namespace System.ComponentModel.DataAnnotations
{
    public class ChangeableRequired : RequiredAttribute
    {
       public bool Disabled { get; set; }

       public override bool IsValid(object value)
       {
          if (Disabled)
          {
            return true;
          }

          return base.IsValid(value);
       }
    }
}

Mettez à jour votre propriété pour utiliser votre nouvel attribut personnalisé:

 class Forex
 {
 ....
    [ChangeableRequired]
    public decimal? ExchangeRate {get;set;}
 ....
 }

où vous devez désactiver la propriété, utilisez la réflexion pour la définir:

Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib =  (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];

// Set Attribute to true to Disable
attrib.Disabled = true;

C'est agréable et propre?

NB: La validation ci-dessus sera désactivée tant que votre instance d'objet est vivante \ active ...


Après quelques débats à ce sujet, je voudrais mentionner que je ne recommande pas de désactiver la validation, mais plutôt de revoir la règle. Si vous le désactivez, vous créez une dépendance pour réactiver la règle et je suggérerais de revoir la règle.
Ernest Gunning

Changer une valeur statique pour désactiver complètement toute validation de ce champ exécutée dans ce même processus semble être une idée terrible.
Jeremy Lakeman
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.