web-dev-qa-db-fra.com

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

Je me demandais s'il était possible de désactiver l'attribut de validation requise dans certaines actions du contrôleur. Je me pose la question parce que sur l'un de mes formulaires de modification, je ne demande pas à l'utilisateur de saisir des valeurs pour les champs qu'il a déjà spécifiés auparavant. Cependant, j'implémente ensuite une logique selon laquelle, lorsqu'ils saisissent une valeur, il utilise une logique spéciale pour mettre à jour le modèle, tel que le hachage d'une valeur, etc.

Des suggestions sur la façon de contourner ce problème?

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

122
Alex Hope O'Connor

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. Ainsi, 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)
{
    ...
}
74
Darin Dimitrov

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 }})
55
Adrian Smith

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: devoir copier 2 modèles uniquement 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 supposé. De même, si vous définissez un attribut [Display] sur la classe de base, vous n'avez pas à le redéfinir dans votre 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)
{
    ...
}
39
PhilDulac

Vous pouvez supprimer toute validation d'une propriété avec les éléments suivants dans l'action de votre contrôleur.

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

@ Ian's commentaire concernant MVC5

Ce qui suit est toujours possible

ModelState.Remove("PropertyNameInModel");

Peu ennuyeux que vous perdiez le typage statique avec l'API mise à jour. Vous pouvez obtenir quelque chose de similaire à l'ancien en créant une instance de l'assistant HTML et en utilisant méthodes NameExtensions .

19
jps

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'entre eux travaillerait pour vous.

Option 1

Je préfère ceci 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');
});

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

14
int-i

Personnellement, j'aurais tendance à utiliser l'approche que Darin Dimitrov a montrée dans sa solution. Cela vous libère pour pouvoir utiliser l'approche annotation de données avec validation ET avoir des attributs de données distincts sur chaque ViewModel correspondant à la tâche à exécuter. Pour minimiser le travail de copie entre model et viewmodel, vous devez utiliser 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 modèle ou modèle de vue 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 consiste en un message d'erreur et une liste de chaînes avec les noms de champs. Les messages d'erreur seront affichés à un emplacement proche du (des) champ (s) de saisie.

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;
}
14
nttakr

À mon avis, la méthode la plus propre consiste à désactiver la validation côté client. Du côté du serveur, vous devez:

  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 des problèmes détectés.

Même un modèle de vue personnalisé ne résoudra pas le problème ici car le nombre de champs 'pré-répondu' peut varier. S'ils ne le font pas, un modèle d'affichage 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.

7

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

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

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

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

5
Mike_Matthews_II

Je rencontrais ce problème lorsque je créais une vue Edition pour mon modèle et je voulais mettre à jour un seul champ.

Ma solution pour une 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)%>

Commentaires est le champ que je mets à jour uniquement dans la vue Edition, pour lequel l'attribut requis n'est pas requis.

Entité ASP.NET MVC 3

2
Felipe FMMobile

Autant que je sache, vous ne pouvez pas supprimer d'attribut au moment de l'exécution, mais seulement modifier leurs valeurs (par exemple: readonly true/false) cherchez ici quelque chose de similaire . Une autre façon de faire ce que vous voulez sans vous occuper d'attributs est d'utiliser un ViewModel pour votre action spécifique afin que vous puissiez insérer toute la logique sans rompre la logique nécessaire aux autres contrôleurs. Si vous essayez d’obtenir une sorte d’assistant (un formulaire à étapes multiples), vous pouvez sérialiser les champs déjà compilés et avec TempData, amenez-les avec vos étapes. (pour obtenir de l'aide dans la désérialisation de la sérialisation, vous pouvez utiliser MVC futures )

1
Iridio

Je recherchais une solution permettant d'utiliser le même modèle pour une insertion et une mise à jour dans une API Web. Dans ma situation, est-ce toujours un contenu corporel? Les attributs [Requiered] 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 configuration HttpC 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 cette substitution, je filtre les validateurs de modèle requis. Et maintenant, la classe IgnoreRequiredOrDefaultBodyModelValidator et la classe 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:

1
Roberto B

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 tels que bit, bool, même les structures telles que Guid, en les rendant simplement nulles. Une fois cette opération effectuée, l'attribut Required 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
Nick Albrecht

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

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
1
Yom S.

Si vous ne souhaitez pas utiliser un autre ViewModel, vous pouvez désactiver les validations de client sur la vue et également supprimer les validations sur le serveur pour les propriétés que vous souhaitez ignorer. S'il vous plaît vérifier cette réponse pour une explication plus profonde https://stackoverflow.com/a/15248790/1128216

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 était que j'ai 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           

    }

Dites 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; }
}
0
Nithin Chandran