Deshabilitar el atributo de validación requerido bajo ciertas circunstancias

138

Me preguntaba si es posible deshabilitar el atributo de validación requerida en ciertas acciones del controlador. Me pregunto esto porque en uno de mis formularios de edición no requiero que el usuario ingrese valores para los campos que ya han especificado previamente. Sin embargo, luego implemento la lógica de que cuando ingresan un valor, utiliza una lógica especial para actualizar el modelo, como el hash de un valor, etc.

¿Alguna sugerencia sobre cómo solucionar este problema?

EDITAR:
Y sí, la validación del cliente es un problema aquí, ya que no les permitirá enviar el formulario sin ingresar un valor.

Alex Hope O'Connor
fuente
3
+1 buena P. Sería bueno mencionar la validación del cliente aquí. Una opción es eliminar por RequiredAttrcompleto y hacer una verificación del lado del servidor cuando sea necesario. Pero esto sería complicado para el cliente
Gedeón
44
Puntos para cualquier persona que también cubre desactivación de ciertos campos de validación del cliente (sin la eliminación de las referencias a la validación de jQuery)
Gedeón
Tal vez me estoy perdiendo su punto, pero si el usuario ya ha especificado los valores de antemano, esos valores ya están presentes y, por lo tanto, pasarán la validación requerida. ¿Querías decir otra cosa?
Erik Funkenbusch
Debido a que estos valores se han cifrado desde entonces, como la contraseña y la respuesta de seguridad, por lo tanto, si ingresan un nuevo valor en el formulario de edición, quiero volver a codificar el nuevo valor antes de la inserción, pero también quiero que la opción se deje en blanco tipo de cosa.
Alex Hope O'Connor
1
@Gideon: vea la respuesta de Adrian Smith: stackoverflow.com/a/9781066/114029
Leniel Maccaferri

Respuestas:

76

Este problema se puede resolver fácilmente mediante el uso de modelos de vista. Los modelos de vista son clases que se adaptan específicamente a las necesidades de una vista determinada. Entonces, por ejemplo, en su caso, podría tener los siguientes modelos de vista:

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

    ... some other properties
}

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

    ... some other properties
}

que se utilizarán en sus acciones de controlador correspondientes:

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

[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
    ...
}
Darin Dimitrov
fuente
No siempre es cierto si tiene un booleano / bit que no es anulable. Pero realmente no hace la diferencia, ya que va a terminar en verdadero o falso. Tenía css para resaltar los campos obligatorios y resaltó falsamente el elemento CheckBoxFor. Mi solución: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
Robert Koch
En la actualización generalmente tenemos (colección FormCollection). ¿podría explicar cómo está utilizando el modelo como parámetro
Raj Kumar
44
No, generalmente no tenemos Update(FormCollection collection), al menos yo nunca. Siempre definir y utilizar una vista de modelo específica: Update(UpdateViewView model).
Darin Dimitrov
Los métodos de sobrecarga con la misma acción HTTP no están permitidos, por lo que me parece que esto no funcionaría. ¿Me estoy perdiendo de algo?
e11s
@edgi, no, no te falta nada. Es un error en mi publicación. El segundo método de acción obviamente debería llamarse Insert. Gracias por señalar esto.
Darin Dimitrov
55

Si solo desea deshabilitar la validación para un solo campo en el lado del cliente, puede anular los atributos de validación de la siguiente manera:

@Html.TexBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})
Adrian Smith
fuente
20
Lo que funcionó para mí a través de jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
Robert Koch
66
Me gusta este enfoque, pero necesitaba volver a analizar los atributos de validación del formulario usando: $ ('formulario'). RemoveData ('unobtrusiveValidation'); $ ('formulario'). removeData ('validador'); $ .validator.unobtrusive.parse ('selector para su formulario');
Yannick Smits
15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- Más fácil de leer IMO.
eth0
Me gustó la mayoría de este enfoque para dejarme dar un control fino de cada campo, pero podría agregar para cancelar ModelState.IsValid cuando envíe datos para guardar por POST. Tal vez causar algún riesgo?
Felipe FMMobile
44
Si desea desactivarlo a través de jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri
40

Sé que esta pregunta ha sido respondida hace mucho tiempo y la respuesta aceptada realmente hará el trabajo. Pero hay una cosa que me molesta: tener que copiar 2 modelos solo para deshabilitar una validación.

Aquí está mi sugerencia:

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 esta manera, no tiene que molestarse con las validaciones del lado del cliente / servidor, el marco se comportará de la manera que se supone que debe hacerlo. Además, si define un [Display]atributo en la clase base, no tiene que redefinirlo en su UpdateModel.

Y aún puede usar estas clases de la misma manera:

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

[HttpPost]
public ActionResult Insert(InsertModel model)
{
    ...
}
PhilDulac
fuente
Me gusta más este enfoque, especialmente si tiene una larga lista de atributos de validación. En su ejemplo, puede agregar más atributos en la clase base para que el beneficio sea más evidente. La única desventaja que puedo ver es que no hay forma de heredar propiedades para anular los atributos. Ejemplo, si tiene una propiedad [Obligatoria] en la clase base, la propiedad heredada también se ve obligada a ser [Obligatoria], a menos que tenga un atributo [Opcional] personalizado.
Yorro
También pensé en algo como esto, aunque tengo un viewModel que tiene un objeto 'Proyecto' que tiene múltiples atributos y solo quiero uno de estos atributos validado bajo ciertas circunstancias. No creo que pueda anular un atributo de un objeto, ¿verdad? ¿algún consejo?
vincent de g
No puede anular el atributo. La clase base solo debe contener atributos comunes para todas las subclases. Luego, sus subclases deberían definir los atributos que necesitan.
PhilDulac
1
La solución más elegante, reutilizable y clara. Las réplicas son malas. El polimorfismo es el camino. +1
T-moty
en mi caso, una clase base tiene un atributo obligatorio y quiero que no sea obligatorio en mi clase principal. ¿Es posible sin tener dos copias del modelo?
Alexey Strakh el
27

Puede eliminar toda la validación de una propiedad con lo siguiente en la acción de su controlador.

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

Comentario de @ Ian sobre MVC5

Lo siguiente todavía es posible

ModelState.Remove("PropertyNameInModel");

Es un poco molesto que pierdas la escritura estática con la API actualizada. Puede lograr algo similar a la forma anterior creando una instancia de ayuda HTML y utilizando métodos NameExtensions .

jps
fuente
Excepto ... no hay un método ModelStateque coincida con esa firma. No en MVC 5, al menos.
Ian Kemp
La pregunta no era cómo eliminar toda la validación. Era cómo eliminar la validación de campo requerida. Es posible que desee hacer esto mientras deja otras reglas de validación en su lugar.
Richard
15

Lado del cliente Para deshabilitar la validación de un formulario, a continuación se ofrecen múltiples opciones basadas en mi investigación. Uno de ellos con suerte trabajaría para usted.

Opción 1

Prefiero esto, y esto funciona perfectamente para mí.

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

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

e invocándolo como

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

opcion 2

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

Opción 3

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

Opcion 4

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

Opción 5

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

Lado del servidor

Cree un atributo y marque su método de acción con ese atributo. Personalice esto para adaptarlo a sus necesidades específicas.

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

Aquí se describe un mejor enfoque Habilitar / Deshabilitar la validación del lado del servidor mvc dinámicamente

int-i
fuente
$ ('selector de entrada'). each (function () {$ (this) .rules ('remove');}); me ayudó
Sachin Pakale
La pregunta era específicamente sobre la eliminación de la validación de campo requerida, no sobre la eliminación de toda la validación. Su respuesta es sobre eliminar toda la validación.
Richard
14

Personalmente, tendería a utilizar el enfoque que mostró Darin Dimitrov en su solución. Esto le permite utilizar el enfoque de anotación de datos con validación Y tener atributos de datos separados en cada ViewModel correspondiente a la tarea en cuestión. Para minimizar la cantidad de trabajo para copiar entre el modelo y el modelo de vista, debe mirar AutoMapper o ValueInjecter . Ambos tienen sus puntos fuertes individuales, así que verifíquelos.

Otro enfoque posible para usted sería derivar su modelo o modelo de vista de IValidatableObject. Esto le da la opción de implementar una función Validar. Al validar, puede devolver una Lista de elementos de ValidationResult o emitir un rendimiento para cada problema que detecte en la validación.

ValidationResult consta de un mensaje de error y una lista de cadenas con los nombres de campo. Los mensajes de error se mostrarán en una ubicación cerca de los campos de entrada.

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;
}
nttakr
fuente
podemos conectar esto en el lado del cliente?
Muhammad Adeel Zahid
La validación del lado del cliente generalmente se realiza solo para campos individuales, realizando comentarios interactivos de validación de campo por campo, para conveniencia del usuario. Dado que el paso de validación de objetos generalmente implica una validación dependiente (múltiples campos y / o condiciones que son externas al objeto en sí), esto no puede realizarse necesariamente en el lado del cliente, incluso si pudiera compilar el código en JavaScript. Si la validación compleja / dependiente en el lado del cliente agrega valor en su caso, necesitará usar la devolución de llamada onsubmit y duplicar la lógica de validación en el lado del cliente.
mindplay.dk
7

Creo que la forma más limpia aquí será deshabilitar la validación del lado del cliente y en el lado del servidor deberá:

  1. ModelState ["SomeField"]. Errores. Borrar (en su controlador o cree un filtro de acción para eliminar errores antes de ejecutar el código del controlador)
  2. Agregue ModelState.AddModelError de su código de controlador cuando detecte una violación de sus problemas detectados.

Parece que incluso un modelo de vista personalizada aquí no resolverá el problema porque el número de esos campos 'pre respondidos' podría variar. Si no lo hacen, un modelo de vista personalizada puede ser la forma más fácil, pero utilizando la técnica anterior puede solucionar sus problemas de validación.

Adam Tuliper - MSFT
fuente
1
Esto es justo lo que necesitaba. Tenía una vista y se producen diferentes acciones en esta vista, por lo que no pude hacerlo con diferentes ViewModels. Esto funciona como un encanto
ggderas
6

esta fue la respuesta de otra persona en los comentarios ... pero debería ser una respuesta real:

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

probado en MVC 6 con un campo que tiene el [Required]atributo

respuesta robada de https://stackoverflow.com/users/73382/rob arriba

Mike_Matthews_II
fuente
1
¿Qué pasa con la validación del lado del servidor?
T-moty
ModelState.Remove, ¿verdad? En cualquier caso, para mí, el problema era que tenía una segunda entidad incluida en mi modelo ... la primaria en la que quería validación, pero la secundaria no necesitaba validación en esa página ... así que, en este escenario , solo JQuery era necesario.
Mike_Matthews_II
Creo que el enlace está roto, así que edítelo. Esta es una respuesta parcial
T-moty
Es extraño que digas que esto funciona en MVC6 (no tengo la opción de probar en MVC6 actualmente) pero no me funciona en MVC4 que estoy usando actualmente.
eaglei22
La pregunta es para MVC / C # - no JS, la respuesta no funcionaría del lado del servidor
mtbennett
2

Estaba teniendo este problema cuando creé una vista de edición para mi modelo y quiero actualizar solo un campo.

Mi solución para una forma más simple es poner los dos campos usando:

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

Comentarios es el campo que solo actualizo en la Vista de edición, que no tiene el Atributo requerido.

ASP.NET MVC 3 Entity

Felipe FMMobile
fuente
1

AFAIK no puede eliminar el atributo en tiempo de ejecución, sino que solo cambia sus valores (es decir: solo lectura verdadero / falso) busque aquí algo similar . Como otra forma de hacer lo que quiere sin jugar con los atributos, iré con un ViewModel para su acción específica para que pueda insertar toda la lógica sin romper la lógica que necesitan otros controladores. Si intenta obtener algún tipo de asistente (un formulario de varios pasos), puede serializar los campos ya compilados y con TempData los llevará a lo largo de sus pasos. (para obtener ayuda en serializar deserializar puede usar futuros MVC )

Iridio
fuente
1

Lo que dijo @Darin es lo que recomendaría también. Sin embargo, agregaría (y en respuesta a uno de los comentarios) que, de hecho, también puede usar este método para tipos primitivos como bit, bool, incluso estructuras como Guid simplemente haciéndolos anulables. Una vez que haga esto, el Requiredatributo funciona como se esperaba.

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
}
Nick Albrecht
fuente
1

A partir de MVC 5, esto se puede lograr fácilmente agregando esto en su global.asax.

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
Iom S.
fuente
1

Estaba buscando una solución donde pueda usar el mismo modelo para una inserción y actualización en la API web. En mi situación, esto es siempre un contenido corporal. Los [Requiered]atributos deben omitirse si se trata de un método de actualización. En mi solución, coloca un atributo [IgnoreRequiredValidations]sobre el método. Esto es lo siguiente:

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

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

    ...

¿Qué más hay que hacer? Se debe crear y agregar un propio BodyModelValidator al inicio. Esto está en la HttpConfiguration y se ve así: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());
        }

        ...

Mi propio BodyModelValidator se deriva del DefaultBodyModelValidator. Y me di cuenta de que tenía que anular el método 'ShallowValidate'. En esta anulación, filtro los validadores de modelo requeridos. Y ahora la clase IgnoreRequiredOrDefaultBodyModelValidator y la clase de atributo 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
    {

    }
}

Fuentes:

Roberto B
fuente
0

Si no desea utilizar otro ViewModel, puede deshabilitar las validaciones de clientes en la vista y también eliminar las validaciones en el servidor para aquellas propiedades que desea ignorar. Consulte esta respuesta para obtener una explicación más profunda https://stackoverflow.com/a/15248790/1128216

Jonathan Morales Vélez
fuente
0

En mi caso, se utilizó el mismo modelo en muchas páginas para fines de reutilización. Entonces, lo que hice fue crear un atributo personalizado que verifica las exclusiones

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

        }
    }
}

y en tu controlador

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

            //do something           

    }

Digamos que el modelo es

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; }
}
Nithin Chandran
fuente
-1

Sí, es posible deshabilitar el atributo requerido. Cree su propio atributo de clase personalizado (código de muestra llamado ChangeableRequired) en extensión desde RequiredAtribute y agregue una Propiedad deshabilitada y anule el método IsValid para verificar si está desbalanceado. Use la reflexión para establecer la propiedad deshabilitada, así:

Atributo personalizado:

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

Actualice su propiedad para usar su nuevo atributo personalizado:

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

donde necesita deshabilitar la propiedad, use la reflexión para establecerla:

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;

Esto se siente bien y limpio?

NB: la validación anterior se deshabilitará mientras su instancia de objeto esté viva \ activa ...

Ernest Gunning
fuente
Después de algunos debates sobre esto, me gustaría mencionar que no recomiendo deshabilitar la validación, sino más bien revisar la regla. Si lo deshabilita, está creando una dependencia para volver a habilitar la regla y sugeriría revisar la regla.
Ernest Gunning
Cambiar un valor estático para desactivar por completo toda la validación de ese campo ejecutado dentro de ese mismo proceso parece una idea terrible.
Jeremy Lakeman