Disabilitare l'attributo di convalida richiesto in determinate circostanze


138

Mi chiedevo se è possibile disabilitare l'attributo di convalida richiesto in determinate azioni del controller. Mi chiedo questo perché su uno dei miei moduli di modifica non ho bisogno che l'utente inserisca valori per i campi che hanno già specificato in precedenza. Tuttavia implemento quindi la logica che quando immettono un valore utilizza una logica speciale per aggiornare il modello, come l'hashing di un valore, ecc.

Qualche suggerimento su come aggirare questo problema?

EDIT:
E sì, la convalida del client è un problema qui, in quanto non consentirà loro di inviare il modulo senza immettere un valore.


3
+1 buono Q. Sarebbe bene menzionare la convalida del cliente qui. Un'opzione è rimuovere RequiredAttrcompletamente e fare un controllo sul lato server quando è necessario. Ma questo sarebbe difficile per il cliente
Gideon,

4
Punti per chiunque copra anche la disabilitazione di determinati campi dalla convalida del client (nessuna rimozione dei riferimenti alla convalida jquery)
gideon

Forse mi manca il tuo punto, ma se l'utente ha già specificato i valori in anticipo, tali valori sono già presenti e quindi passeranno la convalida richiesta. Intendevi qualcos'altro?
Erik Funkenbusch,

Poiché da allora questi valori sono stati sottoposti a hash, come password e risposta di sicurezza, quindi se immettono un nuovo valore nel modulo di modifica, voglio ricodificare il nuovo valore prima dell'inserimento, ma voglio anche lasciare l'opzione vuota genere di cose.
Alex Hope O'Connor,

1
@gideon: vedi Adrian Smith risposta: stackoverflow.com/a/9781066/114029
Leniel Maccaferri

Risposte:


76

Questo problema può essere facilmente risolto utilizzando i modelli di visualizzazione. I modelli di vista sono classi specificamente adattate alle esigenze di una determinata vista. Ad esempio, nel tuo caso potresti avere i seguenti modelli di visualizzazione:

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

    ... some other properties
}

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

    ... some other properties
}

che verrà utilizzato nelle rispettive azioni del controller:

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

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

Non sempre vero se hai un valore booleano / bit che non è nullable. Ma davvero non fa differenza poiché finirà per essere vero o falso. Ho avuto CSS per evidenziare i campi obbligatori e ha evidenziato erroneamente l'elemento CheckBoxFor. La mia soluzione: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
Robert Koch,

In aggiornamento generalmente abbiamo (collezione FormCollection). potresti spiegare come stai usando il modello come parametro
Raj Kumar,

4
No, di solito non ce l'abbiamo Update(FormCollection collection), almeno non lo faccio mai. Ho sempre definire e utilizzare una vista modello specifico: Update(UpdateViewView model).
Darin Dimitrov,

I metodi di sovraccarico con la stessa azione HTTP non sono consentiti, quindi a me sembra che non funzionerebbe. Mi sto perdendo qualcosa?
e11s

@edgi, no, non ti stai perdendo nulla. È un errore nel mio post. Il secondo metodo di azione dovrebbe ovviamente essere chiamato Insert. Grazie per averlo segnalato.
Darin Dimitrov,

55

Se si desidera solo disabilitare la convalida per un singolo campo nel lato client, è possibile sovrascrivere gli attributi di convalida come segue:

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

20
Cosa ha funzionato per me tramite jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
Robert Koch,

6
Mi piace questo approccio ma avevo bisogno di analizzare nuovamente gli attributi di validazione del modulo usando: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ( 'forma') REMOVEDATA ( 'validatore.'); $ .validator.unobtrusive.parse ('selettore per il tuo modulo');
Yannick Smits

15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- più facile da leggere IMO.
eth0,

Mi è piaciuto gran parte di questo approccio per lasciarmi dare un controllo fine di ogni campo, ma è possibile aggiungere per annullare ModelState.IsValid quando si inviano i dati per salvare tramite POST. Forse causare qualche rischio?
Felipe FMMobile

4
Se si desidera disabilitarlo tramite jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri,

40

So che questa domanda ha avuto risposta molto tempo fa e la risposta accettata farà davvero il lavoro. Ma c'è una cosa che mi preoccupa: dover copiare 2 modelli solo per disabilitare una convalida.

Ecco il mio suggerimento:

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

In questo modo, non devi preoccuparti delle convalide lato client / server, il framework si comporterà come dovrebbe. Inoltre, se si definisce un [Display]attributo sulla classe base, non è necessario ridefinirlo nel proprio UpdateModel.

E puoi ancora usare queste classi allo stesso modo:

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

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

Mi piace meglio questo approccio, soprattutto se hai una lunga lista di attributi di validazione. Nel tuo esempio, puoi aggiungere più attributi nella classe base per avere il vantaggio più evidente. L'unico svantaggio che posso vedere è che non c'è modo per ereditare le proprietà per sovrascrivere gli attributi. Esempio, se si dispone di una proprietà [Obbligatorio] sulla classe base, la proprietà ereditaria viene forzata anche su [Obbligatorio], a meno che non si disponga di un attributo [Opzionale] personalizzato.
Yorro,

Ho pensato anche a qualcosa del genere, anche se ho un viewModel che ha un oggetto "Progetto" che ha più attributi e voglio solo uno di questi attributi convalidato in determinate circostanze. Non penso di poter semplicemente sostituire un attributo di un oggetto giusto? qualche consiglio?
vincent de g,

Non è possibile ignorare l'attributo. La classe base dovrebbe contenere solo attributi comuni per tutte le sottoclassi. Quindi, le tue sottoclassi dovrebbero definire gli attributi di cui hanno bisogno.
PhilDulac,

1
La soluzione più elegante, riutilizzabile e chiara. Le repliche sono cattive. Il polimorfismo è la strada. +1
T-moty,

nel mio caso una classe base ha un attributo richiesto e voglio renderlo non richiesto nella mia classe genitore. È possibile senza avere due copie del modello?
Alexey Strakh,

27

È possibile rimuovere tutta la convalida da una proprietà con quanto segue nell'azione del controller.

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

@ Commento di Ian su MVC5

Quanto segue è ancora possibile

ModelState.Remove("PropertyNameInModel");

È un po 'fastidioso perdere la digitazione statica con l'API aggiornata. Puoi ottenere qualcosa di simile al vecchio modo creando un'istanza di helper HTML e usando i metodi NameExtensions .


Tranne ... non esiste un metodo ModelStateche corrisponda a quella firma. Almeno non in MVC 5.
Ian Kemp,

La domanda non era come rimuovere tutta la convalida. È stato come rimuovere la convalida del campo richiesta. È possibile che si desideri eseguire questa operazione lasciando in vigore altre regole di convalida.
Richard,

15

Lato client Per disabilitare la convalida di un modulo, di seguito vengono fornite più opzioni basate sulla mia ricerca. Si spera che uno di loro lavorerebbe per te.

opzione 1

Preferisco questo e questo funziona perfettamente per me.

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

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

e invocandolo come

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

opzione 2

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

Opzione 3

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

Opzione 4

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

Opzione 5

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

Lato server

Crea un attributo e segna il tuo metodo di azione con quell'attributo. Personalizza questo per adattarsi alle tue esigenze specifiche.

[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();
        }
    }
}

Qui è stato descritto un approccio migliore Abilitare / disabilitare la convalida lato server mvc in modo dinamico


$ ('selettore di input'). each (function () {$ (this) .rules ('remove');}); mi ha aiutato
Sachin Pakale,

La domanda riguardava in particolare la rimozione della convalida del campo richiesta, non la rimozione di tutta la convalida. La tua risposta riguarda la rimozione di tutta la convalida.
Richard,

14

Personalmente tenderei ad usare l'approccio che Darin Dimitrov ha mostrato nella sua soluzione. Ciò consente di utilizzare l'approccio di annotazione dei dati con convalida E di disporre di attributi di dati separati su ciascun ViewModel corrispondente all'attività in corso. Per ridurre al minimo la quantità di lavoro per la copia tra modello e viewmodel dovresti guardare AutoMapper o ValueInjecter . Entrambi hanno i loro punti di forza individuali, quindi controllali entrambi.

Un altro possibile approccio per te sarebbe derivare il tuo modello o modello di visualizzazione da IValidatableObject. Questo ti dà la possibilità di implementare una funzione Convalida. In validate è possibile restituire un elenco di elementi ValidationResult o emettere un rendimento per ogni problema rilevato in validazione.

ValidationResult è costituito da un messaggio di errore e da un elenco di stringhe con i nomi dei campi. I messaggi di errore verranno visualizzati in una posizione vicino ai campi di input.

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

possiamo agganciarlo sul lato client?
Muhammad Adeel Zahid,

la convalida lato client viene generalmente eseguita solo per singoli campi, eseguendo feedback interattivi di convalida campo per campo, per comodità dell'utente. Poiché il passaggio di convalida dell'oggetto generalmente implica una convalida dipendente (più campi e / o condizioni esterne all'oggetto stesso), ciò non può essere necessariamente eseguito sul lato client, anche se è possibile compilare il codice in JavaScript. Se la convalida complessa / dipendente sul lato client aggiunge valore nel tuo caso, dovrai utilizzare onsubmit-callback e duplicare la logica di convalida sul lato client.
mindplay.dk

7

Credo che il modo più pulito qui disabiliterà la convalida lato client e sul lato server dovrai:

  1. ModelState ["SomeField"]. Errors.Clear (nel controller o creare un filtro azioni per rimuovere gli errori prima dell'esecuzione del codice del controller)
  2. Aggiungi ModelState.AddModelError dal codice del controller quando rilevi una violazione dei problemi rilevati.

Sembra che anche un modello di visualizzazione personalizzato qui non risolva il problema perché il numero di questi campi "pre-risposta" potrebbe variare. In caso contrario, un modello di visualizzazione personalizzato potrebbe effettivamente essere il modo più semplice, ma utilizzando la tecnica sopra è possibile aggirare i problemi di convalida.


1
Questo è proprio quello di cui avevo bisogno. Ho avuto una vista e diverse azioni si verificano in questa vista, quindi non sono riuscito a farlo con ViewModels differenti. Funziona come un incantesimo
ggderas

6

questa era la risposta di qualcun altro nei commenti ... ma dovrebbe essere una vera risposta:

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

testato su MVC 6 con un campo con l' [Required]attributo

risposta rubata da https://stackoverflow.com/users/73382/rob sopra


1
Che dire della convalida lato server?
T-moty,

ModelState.Remove, giusto? Ad ogni modo, per me, il problema era che avevo una seconda entità inclusa nel mio modello ... la primaria su cui volevo validare, ma la secondaria non aveva bisogno di validazione su quella pagina ... quindi, in questo scenario , era necessario solo JQuery.
Mike_Matthews_II

Penso che il collegamento sia interrotto, quindi modifica. Questa è una risposta parziale
T-moty,

È strano che tu dica che funziona in MVC6 (al momento non ho l'opzione per testare su MVC6) ma non funziona per me su MVC4 che sto attualmente utilizzando.
eaglei22,

La domanda è per MVC / C # - non JS, la risposta non funzionerebbe sul lato server
mtbennett

2

Ho riscontrato questo problema durante la creazione di una vista di modifica per il mio modello e voglio aggiornare solo un campo.

La mia soluzione per un modo più semplice è mettere i due campi usando:

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

Commenti è il campo che aggiorno solo in Vista di modifica, che non ha Attributo richiesto.

ASP.NET MVC 3 Entity


1

AFAIK non puoi rimuovere l'attributo in fase di runtime, ma solo modificarne i valori (es: readonly true / false) cerca qui qualcosa di simile . Come altro modo di fare quello che vuoi senza fare confusione con gli attributi, andrò con un ViewModel per la tua azione specifica in modo da poter inserire tutta la logica senza rompere la logica richiesta da altri controller. Se si tenta di ottenere una sorta di procedura guidata (un modulo a più passaggi) è possibile invece serializzare i campi già compilati e con TempData portarli lungo i passi. (per assistenza nella serializzazione della deserializzazione è possibile utilizzare i futures MVC )


1

Quello che ha detto @Darin è anche quello che consiglierei. Tuttavia aggiungerei ad esso (e in risposta a uno dei commenti) che in realtà puoi anche usare questo metodo per tipi primitivi come bit, bool, persino strutture come Guid semplicemente rendendoli nulli. Una volta fatto questo, l' Requiredattributo funziona come previsto.

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

A partire da MVC 5 questo può essere facilmente ottenuto aggiungendo questo nel tuo global.asax.

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

1

Stavo cercando una soluzione in cui poter utilizzare lo stesso modello per un inserimento e un aggiornamento in API Web. Nella mia situazione è sempre un contenuto corporeo. Gli [Requiered]attributi devono essere ignorati se si tratta di un metodo di aggiornamento. Nella mia soluzione, metti un attributo [IgnoreRequiredValidations]sopra il metodo. Questo è il seguente:

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

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

    ...

Cos'altro bisogna fare? Un proprio BodyModelValidator deve essere creato e aggiunto all'avvio. Questo è in HttpConfiguration e si presenta così: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());
        }

        ...

Il mio BodyModelValidator è derivato da DefaultBodyModelValidator. E ho capito che dovevo scavalcare il metodo 'ShallowValidate'. In questa sostituzione ho filtrato i validatori del modello richiesti. E ora la classe IgnoreRequiredOrDefaultBodyModelValidator e la classe di attributo 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
    {

    }
}

fonti:



0

Nel mio caso lo stesso modello è stato utilizzato in molte pagine per scopi di riutilizzabilità. Quindi quello che ho fatto è stato creare un attributo personalizzato che verifica le esclusioni

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);

        }
    }
}

e nel tuo controller

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

            //do something           

    }

Di 'che il modello è

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

Sì, è possibile disabilitare l'attributo richiesto. Crea il tuo attributo di classe personalizzato (codice di esempio chiamato ChangeableRequired) per estenderlo da RequiredAtribute e aggiungere una proprietà disabilitata e sovrascrivere il metodo IsValid per verificare se è sbilanciato. Usa la riflessione per impostare la popolazione disabilitata, in questo modo:

Attributo personalizzato:

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);
       }
    }
}

Aggiorna la tua proprietà per utilizzare il nuovo attributo personalizzato:

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

dove devi disabilitare la proprietà usa reflection per impostarla:

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;

È bello e pulito?

NB: la convalida sopra sarà disabilitata mentre l'istanza dell'oggetto è attiva \ attiva ...


Dopo alcuni dibattiti su questo argomento vorrei ricordare che non consiglio di disabilitare la convalida ma piuttosto di rivedere la regola. Se lo disabiliti, stai creando una dipendenza per riattivare la regola e ti suggerirei di rivedere la regola.
Ernest Gunning,

Modificare un valore statico per disabilitare completamente tutta la validazione di quel campo eseguito all'interno dello stesso processo sembra un'idea terribile.
Jeremy Lakeman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.