Desativar o atributo de validação necessário em determinadas circunstâncias

138

Eu queria saber se é possível desativar o atributo de validação necessária em determinadas ações do controlador. Estou pensando isso, porque em um dos meus formulários de edição não exijo que o usuário insira valores para os campos que eles já especificaram anteriormente. No entanto, implemento a lógica de que, quando eles inserem um valor, ela usa alguma lógica especial para atualizar o modelo, como hash de um valor etc.

Alguma sugestão de como contornar esse problema?

EDIT:
E sim, a validação do cliente é um problema aqui, pois não permitirá que eles enviem o formulário sem inserir um valor.

Alex Hope O'Connor
fonte
3
+1 em Q bom. Seria bom mencionar aqui a validação do cliente. Uma opção é remover RequiredAttrcompletamente e fazer uma verificação no servidor quando necessário. Mas isso seria complicado no cliente
Gideon
4
Pontos para qualquer um que abrange também a desativação certos campos de validação do cliente (sem remover as referências a validação jQuery)
Gideon
Talvez eu esteja perdendo o seu argumento, mas se o usuário já tiver especificado os valores antecipadamente, esses valores já estarão presentes e, portanto, serão aprovados na validação necessária. Você quis dizer outra coisa?
Erik Funkenbusch 20/03/11
Como esses valores foram hash, como senha e resposta de segurança, por isso, se eles digitarem um novo valor no formulário de edição, desejo re-hash do novo valor antes da inserção, mas também quero que a opção seja deixada em branco tipo de coisa.
Alex esperança O'Connor
1
@gideon: veja a resposta de Adrian Smith: stackoverflow.com/a/9781066/114029
Leniel Maccaferri

Respostas:

76

Esse problema pode ser facilmente resolvido usando os modelos de exibição. Os modelos de exibição são classes especificamente adaptadas às necessidades de uma determinada exibição. Por exemplo, no seu caso, você pode ter os seguintes modelos de exibição:

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

    ... some other properties
}

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

    ... some other properties
}

que será usado em suas ações correspondentes do controlador:

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

[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
    ...
}
Darin Dimitrov
fonte
Nem sempre é verdade se você tiver um booleano / bit que não seja anulável. Mas realmente não faz diferença, já que isso vai acabar sendo verdadeiro ou falso. Eu tinha css para destacar os campos obrigatórios e falsamente destacava o item CheckBoxFor. Minha solução: $ ("# IsValueTrue"). RemoveAttr ("data-val-required");
31812 Robert Koch
Na atualização, geralmente temos (coleção FormCollection). você poderia explicar como você está usando o modelo como parâmetro
Raj Kumar
4
Não, normalmente não temos Update(FormCollection collection), pelo menos eu nunca. Eu sempre definir e utilizar um modelo de visão específica: Update(UpdateViewView model).
precisa
Métodos de sobrecarga com a mesma ação HTTP não são permitidos; portanto, para mim, parece que isso não funcionaria. Estou esquecendo de algo?
e11s
@edgi, não, você não está perdendo nada. É um erro no meu post. O segundo método de ação deve obviamente ser chamado Insert. Obrigado por apontar isso.
Darin Dimitrov
55

Se você quiser apenas desativar a validação para um único campo no lado do cliente, poderá substituir os atributos de validação da seguinte maneira:

@Html.TexBoxFor(model => model.SomeValue, 
                new Dictionary<string, object> { { "data-val", false }})
Adrian Smith
fonte
20
O que funcionou para mim via jQuery: $ ("# SomeValue"). RemoveAttr ("data-val-required");
22412 Robert Koch
6
Eu gosto dessa abordagem, mas precisava analisar novamente os atributos de validação do formulário usando: $ ('form'). RemoveData ('unobtrusiveValidation'); $ ('form'). removeData ('validator'); $ .validator.unobtrusive.parse ('seletor para o seu formulário');
Yannick Smits
15
@Html.TexBoxFor(model => model.SomeValue, new { data_val = false })- mais fácil de ler IMO.
Eth0
Gostei da maior parte dessa abordagem para deixar um controle fino de cada campo, mas você pode adicionar para cancelar ModelState.IsValid ao enviar dados para salvar pelo POST. Talvez cause algum risco?
Felipe FMMobile
4
Se você quiser desativá-lo através do jQuery:$(".search select").attr('data-val', false);
Leniel Maccaferri
40

Eu sei que esta pergunta foi respondida há muito tempo e a resposta aceita realmente fará o trabalho. Mas há uma coisa que me incomoda: ter que copiar dois modelos apenas para desativar uma validação.

Aqui está a minha sugestão:

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

Dessa forma, você não precisa se preocupar com as validações do lado do cliente / servidor, a estrutura se comportará da maneira que deveria. Além disso, se você definir um [Display]atributo na classe base, não precisará redefini-lo no seu UpdateModel.

E você ainda pode usar essas classes da mesma maneira:

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

[HttpPost]
public ActionResult Insert(InsertModel model)
{
    ...
}
PhilDulac
fonte
Eu gosto mais dessa abordagem, especialmente se você tiver uma longa lista de atributos de validação. No seu exemplo, você pode adicionar mais atributos na classe base para ter o benefício mais aparente. A única desvantagem que vejo é que não há como herdar propriedades para substituir os atributos. Por exemplo, se você possui uma propriedade [Necessário] na classe base, a propriedade herdada também é obrigada a ser [Necessário], a menos que você tenha um atributo [Opcional] personalizado.
Yorro
Eu pensei em algo assim também, embora eu tenha um viewModel que tenha um objeto 'Project' que tenha vários atributos e eu só queira um desses atributos validados em determinadas circunstâncias. Eu não acho que posso substituir um atributo de um objeto certo? algum conselho?
vincent de g
Você não pode substituir o atributo. A classe base deve conter apenas atributos comuns para todas as subclasses. Então, suas subclasses devem definir os atributos de que precisam.
9339 PhilDulac
1
A solução mais elegante, reutilizável e clara. As replicações são ruins. Polimorfismo é o caminho. +1
T-moty
no meu caso, uma classe base tem um atributo obrigatório e quero torná-lo não obrigatório na minha classe pai. É possível sem ter duas cópias do modelo?
Alexey Strakh
27

Você pode remover toda a validação de uma propriedade com o seguinte na ação do seu controlador.

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

@ Comentário de Ian sobre MVC5

Ainda é possível o seguinte

ModelState.Remove("PropertyNameInModel");

É irritante que você perca a digitação estática com a API atualizada. Você pode conseguir algo semelhante à maneira antiga, criando uma instância do auxiliar HTML e usando os métodos NameExtensions .

jps
fonte
Exceto ... não existe um método ModelStateque corresponda a essa assinatura. Não no MVC 5, pelo menos.
Ian Kemp
A questão não era como remover toda a validação. Era como remover a validação de campo obrigatória. Você pode fazer isso enquanto deixa outras regras de validação em vigor.
Richard
15

Lado do cliente Para desativar a validação de um formulário, várias opções baseadas em minha pesquisa são fornecidas abaixo. Espero que um deles funcione para você.

Opção 1

Eu prefiro isso, e isso funciona perfeitamente para mim.

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

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

e invocando como

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

opção 2

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

Opção 3

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

Opção 4

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

Opção 5

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

Lado do servidor

Crie um atributo e marque seu método de ação com esse atributo. Personalize isso para se adaptar às suas necessidades 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();
        }
    }
}

Uma abordagem melhor foi descrita aqui Ativar / Desativar validação do lado do servidor mvc dinamicamente

int-i
fonte
$ ('seletor de entrada'). each (function () {$ (this) .rules ('remove');}); me ajudou
Sachin Pakale 13/10
A pergunta era especificamente sobre a remoção da validação de campo obrigatória, não sobre a remoção de toda a validação. Sua resposta é sobre a remoção de toda validação.
Richard
14

Pessoalmente, eu tenderia a usar a abordagem que Darin Dimitrov mostrou em sua solução. Isso libera você para poder usar a abordagem de anotação de dados com validação E ter atributos de dados separados em cada ViewModel correspondente à tarefa em questão. Para minimizar a quantidade de trabalho para copiar entre o modelo e o viewmodel, consulte o AutoMapper ou o ValueInjecter . Ambos têm seus pontos fortes individuais, então verifique os dois.

Outra abordagem possível para você seria derivar seu viewmodel ou modelo de IValidatableObject. Isso fornece a opção de implementar uma função Validar. Na validação, você pode retornar uma lista de elementos ValidationResult ou emitir um retorno de rendimento para cada problema detectado na validação.

O ValidationResult consiste em uma mensagem de erro e uma lista de cadeias com os nomes de campo. As mensagens de erro serão mostradas em um local próximo ao (s) campo (s) 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
fonte
podemos ligar isso no lado do cliente?
Muhammad Adeel Zahid
a validação do lado do cliente geralmente é feita apenas para campos individuais, realizando feedback interativo de validação campo a campo, como uma conveniência para o usuário. Como a etapa de validação do objeto geralmente envolve a validação dependente (vários campos e / ou condições externas ao próprio objeto), isso não pode ser necessariamente executado no lado do cliente, mesmo se você pudesse compilar o código para JavaScript. Se a validação complexa / dependente no lado do cliente agregar valor no seu caso, você precisará usar o onsubmit-callback e duplicar a lógica de validação no lado do cliente.
mindplay.dk
7

A maneira mais limpa aqui, acredito, será desabilitar a validação do lado do cliente e, no lado do servidor, você precisará:

  1. ModelState ["SomeField"]. Errors.Clear (no seu controlador ou crie um filtro de ação para remover erros antes que o código do controlador seja executado)
  2. Adicione ModelState.AddModelError do código do seu controlador quando detectar uma violação dos problemas detectados.

Parece que mesmo um modelo de visualização personalizado aqui não resolve o problema porque o número desses campos 'pré-respondidos' pode variar. Se não o fizerem, um modelo de visualização personalizado pode ser a maneira mais fácil, mas usando a técnica acima, você poderá solucionar seus problemas de validação.

Adam Tuliper - MSFT
fonte
1
Isso é exatamente o que eu precisava. Eu tinha uma view e ações diferentes ocorriam nessa view, então não consegui fazê-lo com ViewModels diferentes. Isso funciona como um encanto
ggderas
6

essa foi a resposta de outra pessoa nos comentários ... mas deve ser uma resposta real:

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

testado no MVC 6 com um campo com o [Required]atributo

resposta roubada de https://stackoverflow.com/users/73382/rob acima

Mike_Matthews_II
fonte
1
E a validação do lado do servidor?
T-MOTY
ModelState.Remove, certo? De qualquer forma, para mim, o problema era que eu tinha uma segunda entidade sendo incluída no meu modelo ... a principal na qual eu queria validação, mas a secundária não precisava de validação nessa página ... então, nesse cenário , apenas o JQuery era necessário.
Mike_Matthews_II 15/06
Eu acho que o link está quebrado, então edite. Esta é uma resposta parcial
T-moty
É estranho que você diga que isso funciona no MVC6 (atualmente não tenho opção para testar no MVC6), mas não funciona para mim no MVC4 que estou usando no momento.
precisa saber é o seguinte
A pergunta é para MVC / C # - não JS, a resposta seria não do lado do servidor trabalho
mtbennett
2

Eu estava com esse problema ao criar uma visualização de edição para o meu modelo e quero atualizar apenas um campo.

Minha solução para uma maneira mais simples é colocar os dois campos usando:

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

Comentários é o campo que eu atualizo apenas na Visualização de Edição, que não possui Atributo Requerido.

Entidade do ASP.NET MVC 3

Felipe FMMobile
fonte
1

AFAIK, você não pode remover o atributo em tempo de execução, mas apenas alterar seus valores (ou seja: readonly true / false) procure aqui algo semelhante . Como outra maneira de fazer o que você deseja, sem mexer nos atributos, irei com um ViewModel para sua ação específica, para que você possa inserir toda a lógica sem interromper a lógica necessária para outros controladores. Se você tentar obter algum tipo de assistente (um formulário de várias etapas), poderá serializar os campos já compilados e, com o TempData, acompanhá-los nas etapas. (para obter ajuda na serialização de desserialização, você pode usar futuros do MVC )

Iridio
fonte
1

O que @Darin disse é o que eu recomendaria também. No entanto, eu acrescentaria a ele (e em resposta a um dos comentários) que você também pode usar esse método para tipos primitivos como bit, bool e até estruturas como Guid, simplesmente tornando-os anuláveis. Depois de fazer isso, o Requiredatributo funcionará conforme o esperado.

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
fonte
1

A partir do MVC 5, isso pode ser facilmente alcançado adicionando-o ao seu global.asax.

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
Yom S.
fonte
1

Eu estava procurando uma solução em que eu possa usar o mesmo modelo para inserir e atualizar na API da Web. Na minha situação, isso é sempre um conteúdo corporal. Os [Requiered]atributos devem ser ignorados se for um método de atualização. Na minha solução, você coloca um atributo [IgnoreRequiredValidations]acima do método. É o seguinte:

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

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

    ...

O que mais precisa ser feito? Um próprio BodyModelValidator deve ser criado e adicionado na inicialização. Isso está no HttpConfiguration e fica assim: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());
        }

        ...

Meu próprio BodyModelValidator é derivado do DefaultBodyModelValidator. E eu descobri que tive que substituir o método 'ShallowValidate'. Nesta substituição, filtrei os validadores de modelo exigidos. E agora a classe IgnoreRequiredOrDefaultBodyModelValidator e a classe 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
    {

    }
}

Fontes:

Roberto B
fonte
0

Se você não quiser usar outro ViewModel, poderá desativar as validações do cliente na exibição e também remover as validações no servidor para as propriedades que deseja ignorar. Verifique esta resposta para obter uma explicação mais profunda https://stackoverflow.com/a/15248790/1128216

Jonathan Morales Vélez
fonte
0

No meu caso, o mesmo modelo foi usado em muitas páginas para fins de reutilização. Então, o que eu fiz foi criar um atributo personalizado que verifica exclusões

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 no seu controlador

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

            //do something           

    }

Digamos que o modelo é

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
fonte
-1

Sim, é possível desativar o Atributo necessário. Crie seu próprio atributo de classe personalizada (código de exemplo chamado ChangeableRequired) na extensão de RequiredAtribute e adicione uma Disabled Property e substitua o método IsValid para verificar se está desbalanceado. Use a reflexão para definir o poperty desativado, da seguinte maneira:

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

Atualize sua propriedade para usar seu novo atributo personalizado:

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

onde você precisa desativar a propriedade, use a reflexão para defini-la:

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;

Isso é agradável e limpo?

Nota: a validação acima será desativada enquanto a instância do seu objeto estiver ativa \ ativa ...

Ernest Gunning
fonte
Após alguns debates sobre isso, gostaria de mencionar que não recomendo desativar a validação, mas revisar a regra. Se você desabilitá-lo, você cria uma dependência para reativar a regra e eu sugiro que reveja a regra.
Ernest Gunning
Alterar um valor estático para desativar completamente toda a validação desse campo executado dentro do mesmo processo parece uma péssima idéia.
Jeremy Lakeman