validação de modelo no ASP.NET Core MVC e Razor páginas

Por Kirk Larkin

este artigo explica como validar a entrada do usuário em um aplicativo ASP.NET Core MVC ou Razor Pages.

Exiba ou baixe o código de exemplo (como baixar).

Estado do modelo

O estado do modelo representa erros que vêm de dois subsistemas: model binding e validação de modelo. Os erros originados na Associação de modelo geralmente são erros de conversão de dados. Por exemplo, um "x" é inserido em um campo de número inteiro. A validação do modelo ocorre após a associação de modelo e relata erros em que os dados não estão em conformidade com as regras de negócio. Por exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5.

A associação de modelo e a validação de modelo ocorrem antes da execução de uma ação de controlador ou um Razor método de manipulador de páginas. Nos aplicativos Web, é responsabilidade do aplicativo inspecionar ModelState.IsValid e reagir adequadamente. Geralmente, os aplicativos Web reexibem a página com uma mensagem de erro:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Os controladores de API Web não precisarão verificar ModelState.IsValid se eles tiverem o atributo [ApiController]. Nesse caso, uma resposta HTTP 400 automática contendo detalhes do erro é retornada quando o estado do modelo é inválido. Para obter mais informações, veja Respostas automáticas HTTP 400.

Executar validação novamente

A validação é automática, mas talvez seja necessário repeti-la manualmente. Por exemplo, você pode calcular um valor para uma propriedade e executar novamente a validação após a configuração da propriedade com o valor computado. Para executar a validação novamente, chame ModelStateDictionary.ClearValidationState para limpar a validação específica para o modelo que está sendo validado seguido por TryValidateModel :

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Atributos de validação

Os atributos de validação permitem que você especifique regras de validação para propriedades do modelo. O exemplo a seguir do aplicativo de exemplo mostra uma classe de modelo que é anotada com atributos de validação. O atributo [ClassicMovie] é um atributo de validação personalizado e os outros são atributos internos. Não mostrado é [ClassicMovieWithClientValidator] . [ClassicMovieWithClientValidator] mostra uma maneira alternativa de implementar um atributo personalizado.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Atributos internos

Aqui estão alguns dos atributos de validação internos:

  • [ValidateNever]: ValidateNeverAttribute Indica que uma propriedade ou parâmetro deve ser excluído da validação.
  • [CreditCard]: Valida que a propriedade tem um formato de cartão de crédito. Requer métodos adicionais de validação jQuery.
  • [Compare]: Valida que duas propriedades em um modelo correspondem.
  • [EmailAddress]: Valida que a propriedade tem um formato de email.
  • [Phone]: Valida que a propriedade tem um formato de número de telefone.
  • [Range]: Valida que o valor da propriedade cai em um intervalo especificado.
  • [RegularExpression]: Valida que o valor da propriedade corresponde a uma expressão regular especificada.
  • [Required]: Valida que o campo não é nulo. Consulte o [Required] atributo para obter detalhes sobre o comportamento desse atributo.
  • [StringLength]: Valida que um valor de propriedade de cadeia de caracteres não excede um limite de comprimento especificado.
  • [Url]: Valida que a propriedade tem um formato de URL.
  • [Remote]: Valida a entrada no cliente chamando um método de ação no servidor. Consulte o [Remote] atributo para obter detalhes sobre o comportamento desse atributo.

Uma lista completa de atributos de validação pode ser encontrada no namespace System.ComponentModel.DataAnnotations.

Mensagens de erro

Os atributos de validação permitem que você especifique a mensagem de erro a ser exibido para uma entrada inválida. Por exemplo:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Internamente, a chamada de atributos String.Format com um espaço reservado para o nome do campo e, às vezes, espaços reservados adicionais. Por exemplo:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Quando aplicada a uma propriedade Name, a mensagem de erro criada pelo código anterior seria "Comprimento do nome ter entre 6 e 8.".

Para descobrir quais parâmetros são passados para String.Format no caso de uma mensagem de erro de um atributo específico, consulte o código-fonte de DataAnnotations.

Tipos de referência não anuláveis e atributo [Required]

O sistema de validação trata parâmetros não anuláveis ou propriedades associadas como se tivessem um [Required] atributo. Ao habilitar Nullable contextos, o MVC inicia implicitamente a validação de propriedades ou parâmetros não anuláveis como se eles tivessem sido atribuídos com o [Required] atributo. Considere o seguinte código:

public class Person
{
    public string Name { get; set; }
}

Se o aplicativo foi criado com <Nullable>enable</Nullable> , um valor ausente para Name em uma postagem JSON ou de formulário resultará em um erro de validação. Use um tipo de referência anulável para permitir que valores nulos ou ausentes sejam especificados para a Name Propriedade:

public class Person
{
    public string? Name { get; set; }
}

Esse comportamento pode ser desabilitado com a configuração SuppressImplicitRequiredAttributeForNonNullableReferenceTypes em Startup.ConfigureServices :

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Validação de [Required] no servidor

No servidor, um valor obrigatório será considerado ausente se a propriedade for nula. Um campo não anulável é sempre válido e a mensagem de [Required] erro do atributo nunca é exibida.

No entanto, o model binding para uma propriedade que não permite valor nulo pode falhar, resultando em uma mensagem de erro como The value '' is invalid. Para especificar uma mensagem de erro personalizada para a validação de tipos não anuláveis do lado do servidor, você tem as seguintes opções:

  • Tornar o campo anulável (por exemplo, decimal? em vez de decimal). Permite <T> valor nulo tipos de valor são tratados como tipos anuláveis padrão.

  • Especifique a mensagem de erro padrão a ser usada pelo model binding, conforme mostrado no exemplo a seguir:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    Para obter mais informações sobre erros de model binding para os quais você pode definir mensagens padrão, consulte DefaultModelBindingMessageProvider.

Validação de [Required] no cliente

Cadeias de caracteres e tipos que não permitem valor nulo são tratados de maneira diferente no cliente em comparação com servidor. No cliente:

  • Um valor será considerado presente apenas se a entrada for inserida para ele. Portanto, a validação do lado do cliente lida com tipos que não permitem valor nulo da mesma maneira que com tipos que permitem valor nulo.
  • O espaço em branco em um campo de cadeia de caracteres é considerado uma entrada válida pelo método required do jQuery Validation. A validação do lado do servidor considerará um campo de cadeia de caracteres necessário inválido somente se o espaço em branco for inserido.

Conforme observado anteriormente, os tipos que não permitem valor nulo são tratados como se tivessem um atributo [Required]. Isso significa que você obtém a validação do lado do cliente mesmo que não aplique o atributo [Required]. Mas se não for usar o atributo, você obterá uma mensagem de erro padrão. Para especificar uma mensagem de erro personalizada, use o atributo.

Atributo [Remote]

O atributo [Remote] implementa a validação do lado do cliente, exigindo a chamada de um método no servidor para determinar se a entrada do campo é válida. Por exemplo, o aplicativo pode precisar verificar se um nome de usuário já está em uso.

Para implementar a validação remota:

  1. Crie um método de ação para JavaScript para chamar. O método remoto de validação do jQuery espera uma resposta JSON:

    • true significa que os dados de entrada são válidos.
    • false, undefined ou null significa que a entrada é inválida. Exiba a mensagem de erro padrão.
    • Qualquer outra cadeia de caracteres significa que a entrada é inválida. Exiba a cadeia de caracteres como uma mensagem de erro personalizada.

    Aqui está um exemplo de um método de ação que retorna uma mensagem de erro personalizada:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Na classe de modelo, anote a propriedade com um atributo [Remote] que aponta para o método de ação de validação, conforme mostrado no exemplo a seguir:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

    O atributo [Remote] está no namespace Microsoft.AspNetCore.Mvc.

Campos adicionais

A propriedade AdditionalFields do atributo [Remote] permite validar combinações de campos em relação aos dados no servidor. Por exemplo, se o modelo User tinha as propriedades FirstName e LastName, pode ser interessante verificar se nenhum usuário existente já tem esse par de nomes. O exemplo a seguir mostra como usar AdditionalFields:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields pode ser definido explicitamente para as cadeias de caracteres "FirstName" e "LastName", mas o uso do operador nameof simplifica a refatoração posterior. O método de ação para essa validação deve aceitar ambos os firstName lastName argumentos e:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Quando o usuário insere o primeiro nome ou o sobrenome, o JavaScript faz uma chamada remota para ver se esse par de nomes foi usado.

Para validar dois ou mais campos adicionais, forneça-os como uma lista delimitada por vírgulas. Por exemplo, para adicionar uma propriedade MiddleName ao modelo, defina o atributo [Remote], conforme mostrado no seguinte exemplo:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, como todos os argumentos de atributo, deve ser uma expressão de constante. Portanto, não use uma cadeia de caracteres interpolada nem chame Join para inicializar AdditionalFields.

Alternativas aos atributos internos

Se precisar de validação não fornecida por atributos internos, você poderá:

Atributos personalizados

Para cenários não compatíveis com os atributos de validação internos, você pode criar atributos de validação personalizados. Crie uma classe que herda de ValidationAttribute e substitua o método IsValid.

O método IsValid aceita um objeto chamado valor, que é a entrada a ser validada. Uma sobrecarga também aceita um objeto ValidationContext, que fornece informações adicionais, como a instância do modelo criada pelo model binding.

O exemplo a seguir valida se a data de lançamento de um filme no gênero Clássico não é posterior a um ano especificado. O [ClassicMovie] atributo:

  • Só é executado no servidor.
  • Para filmes clássicos, o valida a data de lançamento:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

A variável movie no exemplo anterior representa um objeto Movie que contém os dados do envio do formulário. Quando a validação falha, um ValidationResult com uma mensagem erro será retornado.

IValidatableObject

O exemplo anterior funciona apenas com tipos Movie. Outra opção para validação de nível de classe é implementar IValidatableObject na classe de modelo, conforme mostrado no exemplo a seguir:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validação de nó de nível superior

Os nós de nível superior incluem:

  • Parâmetros de ação
  • Propriedades do controlador
  • Parâmetros do manipulador de página
  • Propriedades do modelo de página

Os nós de nível superior associados ao modelo são validados além de validar as propriedades do modelo. No exemplo a seguir do aplicativo de exemplo, o método VerifyPhone usa o RegularExpressionAttribute para validar o parâmetro de ação phone:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Os nós de nível superior podem usar BindRequiredAttribute com atributos de validação. No exemplo a seguir do aplicativo de exemplo, o método CheckAge especifica que o parâmetro age deve ser associado da cadeia de caracteres de consulta quando o formulário é enviado:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Na página Verificar Idade (CheckAge.cshtml), há dois formulários. O primeiro formulário envia um Age valor de 99 como um parâmetro de cadeia de caracteres de consulta: https://localhost:5001/Users/CheckAge?Age=99 .

Quando um parâmetro age formatado corretamente da cadeia de caracteres de consulta é enviado, o formulário é validado.

O segundo formulário na página Verificar Idade envia o valor Age no corpo da solicitação e a validação falha. A associação falha porque o parâmetro age deve vir de uma cadeia de caracteres de consulta.

Máximo de erros

A validação para quando o número máximo de erros é atingido (200 por padrão). Você pode configurar esse número com o seguinte código no Startup.ConfigureServices:

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

Recursão máxima

ValidationVisitor percorre o grafo de objeto do modelo que está sendo validado. Para modelos que são de profundidade ou são recursivos infinitamente, a validação pode resultar em estouro de pilha. O MvcOptions.MaxValidationDepth fornece uma maneira de interromper a validação antes que a recursão visitante exceda uma profundidade configurada. O valor padrão de MvcOptions.MaxValidationDepth é 32.

Curto-circuito automático

A validação sofrerá curto-circuito (será ignorada) automaticamente se o grafo do modelo não exigir validação. Objetos em que o runtime ignora a validação para inclusão de coleções de primitivos (como byte[], string[] e Dictionary<string, string>) e grafos de objetos complexos que não têm um validador.

Desabilitar validação

Para desabilitar a validação:

  1. Crie uma implementação de IObjectModelValidator que não marque nenhum campo como inválido.

    public class NullObjectModelValidator : IObjectModelValidator
    {
        public void Validate(ActionContext actionContext,
            ValidationStateDictionary validationState, string prefix, object model)
        {
    
        }
    }
    
  2. Adicione o seguinte código ao Startup.ConfigureServices para substituir a implementação IObjectModelValidator padrão no contêiner de injeção de dependência.

    services.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
    

Talvez você ainda veja erros de estado de modelo que se originam do model binding.

Validação do lado do cliente

A validação do lado do cliente impede o envio até que o formulário seja válido. O botão Enviar executa o JavaScript que envia o formulário ou exibe mensagens de erro.

A validação do lado do cliente evita uma viagem de ida e volta desnecessária ao servidor quando há erros de entrada em um formulário. O script a seguir faz referência nas validações de suporte _Layout.cshtml e _ValidationScriptsPartial.cshtml no lado do cliente:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>

O script de validação não invasiva da jQuery é uma biblioteca de front-end da Microsoft personalizada que se baseia no conhecido plug-in de validação do jQuery . Sem o jQuery Unobtrusive Validation, você teria que codificar a mesma lógica de validação em dois locais: uma vez nos atributos de validação do lado do servidor nas propriedades do modelo e, em seguida, novamente nos scripts do lado do cliente. Em vez disso, os Auxiliares de Marca e os Auxiliares HTML usam os atributos de validação e os metadados de tipo das propriedades do modelo para renderizar atributos data- de HTML 5 para os elementos de formulário que precisam de validação. a validação não invasiva do jQuery analisa os data- atributos e passa a lógica para a validação do jQuery, efetivamente "copiando" a lógica de validação do lado do servidor para o cliente. Você pode exibir erros de validação no cliente usando os auxiliares de marca conforme mostrado aqui:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Os auxiliares de marcação anteriores renderizam o seguinte HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Observe que os atributos data- na saída HTML correspondem aos atributos de validação da propriedade Movie.ReleaseDate. O atributo data-val-required conterá uma mensagem de erro a ser exibida se o usuário não preencher o campo de data de lançamento. a validação não invasiva do jQuery passa esse valor para o método de validação do jQuery necessário () , que, em seguida, exibe essa mensagem no elemento que o acompanha <span> .

A validação de tipo de dados é baseada no tipo .NET de uma propriedade, a menos que seja substituída por um atributo [DataType]. Os navegadores têm suas próprias mensagens de erro padrão, mas o pacote de validação do jQuery Validation Unobtrusive pode substituir essas mensagens. Os atributos [DataType] e as subclasses como [EmailAddress] permitem que você especifique a mensagem de erro.

Validação não invasiva

para obter informações sobre a validação discreta, consulte este GitHub problema.

Adicionar validação a formulários dinâmicos

a validação não invasiva do jQuery passa a lógica de validação e parâmetros para validação do jQuery quando a página é carregada pela primeira vez. Portanto, a validação não funciona automaticamente em formulários gerados dinamicamente. Para habilitar a validação é necessário instruir o jQuery Unobtrusive Validation a analisar o formulário dinâmico imediatamente depois de criá-lo. Por exemplo, o código a seguir define a validação do lado do cliente em um formulário adicionado por meio do AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

O método $.validator.unobtrusive.parse() aceita um seletor do jQuery para um argumento seu. Esse método instrui o jQuery Unobtrusive Validation a analisar os atributos data- de formulários nesse seletor. Os valores desses atributos são passados para o plug-in de validação jQuery.

Adicionar validação a controles dinâmicos

O método $.validator.unobtrusive.parse() funciona em um formulário inteiro, não em controles individuais gerados dinamicamente, como <input> e <select/>. Para uma nova análise do formulário, remova os dados de validação que foram adicionados ao formulário mesmo quando foi analisado anteriormente, conforme mostrado no exemplo a seguir:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Validação personalizada do lado do cliente

A validação personalizada do lado do cliente é feita pela geração de data- atributos HTML que funcionam com um adaptador de validação do jQuery personalizado. O seguinte código do adaptador de exemplo foi escrito para os atributos [ClassicMovie] e [ClassicMovieWithClientValidator] que foram apresentados no início deste artigo:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Para obter informações sobre como escrever adaptadores, consulte a documentação de validação do jQuery.

O uso de um adaptador para um determinado campo é disparado por atributos data- que:

  • Marcam o campo como sujeito à validação do sinalizador (data-val="true").
  • Identifique o nome de uma regra de validação e o texto da mensagem de erro (por exemplo, data-val-rulename="Error message.").
  • Forneça os parâmetros adicionais que o validador precisa (por exemplo, data-val-rulename-param1="value").

A exemplo a seguir mostra os atributos data- para o atributo ClassicMovie do aplicativo de exemplo:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Conforme observado anteriormente, os Auxiliares de Marca e Auxiliares de HTML usam informações de atributos de validação para renderizar atributos data-. Há duas opções para escrever código que resulte na criação de atributos HTML data- personalizados:

  • Criar uma classe que deriva de AttributeAdapterBase<TAttribute> e uma classe que implementa IValidationAttributeAdapterProvider e registrar o atributo e o adaptador na DI. Este método segue a entidade de segurança de responsabilidade única em que o código de validação relacionado ao cliente e ao servidor fica em classes separadas. O adaptador também tem a vantagem de que, uma vez que ele é registrado na DI, outros serviços da DI ficam disponíveis para ele, se necessário.
  • Implementar IClientModelValidator em na classe ValidationAttribute. Esse método poderá ser adequado se o atributo não fizer nenhuma validação do lado do servidor e não precisar de um serviço da DI.

AttributeAdapter para validação do lado do cliente

Esse método de renderização de atributos data- em HTML é usado pelo atributo ClassicMovie no aplicativo de exemplo. Para adicionar a validação do cliente usando esse método:

  1. Crie uma classe de adaptador de atributo para o atributo de validação personalizado. Derive a classe de AttributeAdapterBase<TAttribute> . Criar um método AddValidation que adiciona atributos data- à saída renderizada, conforme mostrado neste exemplo:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. Crie uma classe de provedor de adaptador que implementa IValidationAttributeAdapterProvider. No método GetAttributeAdapter, passe o atributo personalizado para o construtor do adaptador, conforme mostrado neste exemplo:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Registre o provedor de adaptador para DI em Startup.ConfigureServices:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator para validação do lado do cliente

Esse método de renderização de atributos data- em HTML é usado pelo atributo ClassicMovieWithClientValidator no aplicativo de exemplo. Para adicionar a validação do cliente usando esse método:

  • No atributo de validação personalizado, implemente a interface IClientModelValidator e crie um método AddValidation. No método AddValidation, adicione atributos data- para validação, conforme mostrado no exemplo a seguir:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Desabilitar validação do lado do cliente

O código a seguir desabilita a validação do cliente em Razor páginas:

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Outras opções para desabilitar a validação do lado do cliente:

  • Comente a referência para _ValidationScriptsPartial em todos os arquivos . cshtml .
  • Remova o conteúdo do arquivo Pages\Shared _ ValidationScriptsPartial. cshtml .

A abordagem anterior não impedirá a validação do lado do cliente da ASP.NET Core Identity Razor biblioteca de classes. Para obter mais informações, consulte Scaffold Identity em ASP.NET Core projetos.

Recursos adicionais

este artigo explica como validar a entrada do usuário em um aplicativo ASP.NET Core MVC ou Razor Pages.

Exiba ou baixe o código de exemplo (como baixar).

Estado do modelo

O estado do modelo representa erros que vêm de dois subsistemas: model binding e validação de modelo. Erros que se originam de model binding geralmente são erros de conversão de dados (por exemplo, um "x" é inserido em um campo que espera um inteiro). A validação do modelo ocorre após o model binding e relata os erros em que os dados não estão em conformidade com as regras de negócio (por exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5).

A associação de modelo e a validação ocorrem antes da execução de uma ação de controlador ou um Razor método de manipulador de páginas. Nos aplicativos Web, é responsabilidade do aplicativo inspecionar ModelState.IsValid e reagir adequadamente. Geralmente, os aplicativos Web reexibem a página com uma mensagem de erro:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Os controladores de API Web não precisarão verificar ModelState.IsValid se eles tiverem o atributo [ApiController]. Nesse caso, uma resposta HTTP 400 automática contendo detalhes do erro é retornada quando o estado do modelo é inválido. Para obter mais informações, veja Respostas automáticas HTTP 400.

Executar validação novamente

A validação é automática, mas talvez seja necessário repeti-la manualmente. Por exemplo, você pode calcular um valor para uma propriedade e executar novamente a validação após a configuração da propriedade com o valor computado. Para reexecutar a validação, chame o método TryValidateModel, conforme mostrado aqui:

var movie = new Movie
{
    Title = title,
    Genre = genre,
    ReleaseDate = modifiedReleaseDate,
    Description = description,
    Price = price,
    Preorder = preorder,
};

TryValidateModel(movie);

if (ModelState.IsValid)
{
    _context.AddMovie(movie);
    _context.SaveChanges();

    return RedirectToAction(actionName: nameof(Index));
}

return View(movie);

Atributos de validação

Os atributos de validação permitem que você especifique regras de validação para propriedades do modelo. O exemplo a seguir do aplicativo de exemplo mostra uma classe de modelo que é anotada com atributos de validação. O atributo [ClassicMovie] é um atributo de validação personalizado e os outros são atributos internos. Não mostrado é [ClassicMovie2] , que mostra uma maneira alternativa de implementar um atributo personalizado.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    [Required]
    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Atributos internos

Os atributos de validação internos incluem:

  • [CreditCard]: Valida que a propriedade tem um formato de cartão de crédito.
  • [Compare]: Valida que duas propriedades em um modelo correspondem. Por exemplo, o arquivo Register. cshtml. cs usa [Compare] para validar a correspondência de duas senhas inseridas. Scaffold Identity para ver o código de registro.
  • [EmailAddress]: Valida que a propriedade tem um formato de email.
  • [Phone]: Valida que a propriedade tem um formato de número de telefone.
  • [Range]: Valida que o valor da propriedade cai em um intervalo especificado.
  • [RegularExpression]: Valida que o valor da propriedade corresponde a uma expressão regular especificada.
  • [Required]: Valida que o campo não é nulo. Consulte o [Required] atributo para obter detalhes sobre o comportamento desse atributo.
  • [StringLength]: Valida que um valor de propriedade de cadeia de caracteres não excede um limite de comprimento especificado.
  • [Url]: Valida que a propriedade tem um formato de URL.
  • [Remote]: Valida a entrada no cliente chamando um método de ação no servidor. Consulte o [Remote] atributo para obter detalhes sobre o comportamento desse atributo.

Ao usar o [RegularExpression] atributo com validação do lado do cliente, o Regex é executado em JavaScript no cliente. Isso significa que o comportamento de correspondência ECMAScript será usado. Saiba mais neste tópico do GitHub.

Uma lista completa de atributos de validação pode ser encontrada no namespace System.ComponentModel.DataAnnotations.

Mensagens de erro

Os atributos de validação permitem que você especifique a mensagem de erro a ser exibido para uma entrada inválida. Por exemplo:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Internamente, a chamada de atributos String.Format com um espaço reservado para o nome do campo e, às vezes, espaços reservados adicionais. Por exemplo:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Quando aplicada a uma propriedade Name, a mensagem de erro criada pelo código anterior seria "Comprimento do nome ter entre 6 e 8.".

Para descobrir quais parâmetros são passados para String.Format no caso de uma mensagem de erro de um atributo específico, consulte o código-fonte de DataAnnotations.

Atributo [Required]

Por padrão, o sistema de validação trata parâmetros não anuláveis ou propriedades como se tivessem um atributo [Required]. Tipos de valor como decimal e int são não anuláveis.

Validação de [Required] no servidor

No servidor, um valor obrigatório será considerado ausente se a propriedade for nula. Um campo não anulável é sempre válido e a mensagem de erro do atributo [Required] nunca é exibida.

No entanto, o model binding para uma propriedade que não permite valor nulo pode falhar, resultando em uma mensagem de erro como The value '' is invalid. Para especificar uma mensagem de erro personalizada para a validação de tipos não anuláveis do lado do servidor, você tem as seguintes opções:

  • Tornar o campo anulável (por exemplo, decimal? em vez de decimal). Permite <T> valor nulo tipos de valor são tratados como tipos anuláveis padrão.

  • Especifique a mensagem de erro padrão a ser usada pelo model binding, conforme mostrado no exemplo a seguir:

    services.AddMvc(options => 
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                (_) => "The field is required.");
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddSingleton
        <IValidationAttributeAdapterProvider, 
         CustomValidationAttributeAdapterProvider>();
    

    Para obter mais informações sobre erros de model binding para os quais você pode definir mensagens padrão, consulte DefaultModelBindingMessageProvider.

Validação de [Required] no cliente

Cadeias de caracteres e tipos que não permitem valor nulo são tratados de maneira diferente no cliente em comparação com servidor. No cliente:

  • Um valor será considerado presente apenas se a entrada for inserida para ele. Portanto, a validação do lado do cliente lida com tipos que não permitem valor nulo da mesma maneira que com tipos que permitem valor nulo.
  • O espaço em branco em um campo de cadeia de caracteres é considerado uma entrada válida pelo método required do jQuery Validation. A validação do lado do servidor considerará um campo de cadeia de caracteres necessário inválido somente se o espaço em branco for inserido.

Conforme observado anteriormente, os tipos que não permitem valor nulo são tratados como se tivessem um atributo [Required]. Isso significa que você obtém a validação do lado do cliente mesmo que não aplique o atributo [Required]. Mas se não for usar o atributo, você obterá uma mensagem de erro padrão. Para especificar uma mensagem de erro personalizada, use o atributo.

Atributo [Remote]

O atributo [Remote] implementa a validação do lado do cliente, exigindo a chamada de um método no servidor para determinar se a entrada do campo é válida. Por exemplo, o aplicativo pode precisar verificar se um nome de usuário já está em uso.

Para implementar a validação remota:

  1. Crie um método de ação para JavaScript para chamar. O método remoto do jQuery Validate espera uma resposta JSON:

    • "true" significa que os dados de entrada são válidos.
    • "false", undefined ou null significa que a entrada é inválida. Exiba a mensagem de erro padrão.
    • Qualquer outra cadeia de caracteres significa que a entrada é inválida. Exiba a cadeia de caracteres como uma mensagem de erro personalizada.

    Aqui está um exemplo de um método de ação que retorna uma mensagem de erro personalizada:

    [AcceptVerbs("Get", "Post")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userRepository.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Na classe de modelo, anote a propriedade com um atributo [Remote] que aponta para o método de ação de validação, conforme mostrado no exemplo a seguir:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

    O atributo [Remote] está no namespace Microsoft.AspNetCore.Mvc. Instale o pacote NuGet Microsoft.AspNetCore.Mvc.ViewFeatures se você não estiver usando o metapacote Microsoft.AspNetCore.App ou Microsoft.AspNetCore.All.

Campos adicionais

A propriedade AdditionalFields do atributo [Remote] permite validar combinações de campos em relação aos dados no servidor. Por exemplo, se o modelo User tinha as propriedades FirstName e LastName, pode ser interessante verificar se nenhum usuário existente já tem esse par de nomes. O exemplo a seguir mostra como usar AdditionalFields:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
public string LastName { get; set; }

AdditionalFields pode ser definido explicitamente para as cadeias de caracteres "FirstName" e "LastName" , mas o uso do operador nameof simplifica a refatoração posterior. O método de ação para essa validação deve aceitar os argumentos de primeiro nome e de sobrenome:

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userRepository.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Quando o usuário insere o primeiro nome ou o sobrenome, o JavaScript faz uma chamada remota para ver se esse par de nomes foi usado.

Para validar dois ou mais campos adicionais, forneça-os como uma lista delimitada por vírgulas. Por exemplo, para adicionar uma propriedade MiddleName ao modelo, defina o atributo [Remote], conforme mostrado no seguinte exemplo:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, como todos os argumentos de atributo, deve ser uma expressão de constante. Portanto, não use uma cadeia de caracteres interpolada nem chame Join para inicializar AdditionalFields.

Alternativas aos atributos internos

Se precisar de validação não fornecida por atributos internos, você poderá:

Atributos personalizados

Para cenários não compatíveis com os atributos de validação internos, você pode criar atributos de validação personalizados. Crie uma classe que herda de ValidationAttribute e substitua o método IsValid.

O método IsValid aceita um objeto chamado valor, que é a entrada a ser validada. Uma sobrecarga também aceita um objeto ValidationContext, que fornece informações adicionais, como a instância do modelo criada pelo model binding.

O exemplo a seguir valida se a data de lançamento de um filme no gênero Clássico não é posterior a um ano especificado. O atributo [ClassicMovie2] verificará o gênero primeiro e continuará apenas se for Clássico. Para filmes identificados como Classics, ele verifica a data de lançamento para certificar-se de que ele não é posterior ao limite passado para o construtor de atributo.

public class ClassicMovieAttribute : ValidationAttribute
{
    private int _year;

    public ClassicMovieAttribute(int year)
    {
        _year = year;
    }

    protected override ValidationResult IsValid(
        object value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > _year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }

    public int Year => _year;

    public string GetErrorMessage()
    {
        return $"Classic movies must have a release year no later than {_year}.";
    }
}

A variável movie no exemplo anterior representa um objeto Movie que contém os dados do envio do formulário. O método IsValid verifica a data e o gênero. Após a validação bem-sucedida, o IsValid retorna um código ValidationResult.Success. Quando a validação falha, um ValidationResult com uma mensagem erro será retornado.

IValidatableObject

O exemplo anterior funciona apenas com tipos Movie. Outra opção para validação de nível de classe é implementar IValidatableObject na classe de modelo, conforme mostrado no exemplo a seguir:

public class MovieIValidatable : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [Required]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    [Required]
    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year earlier than {_classicYear}.",
                new[] { "ReleaseDate" });
        }
    }
}

Validação de nó de nível superior

Os nós de nível superior incluem:

  • Parâmetros de ação
  • Propriedades do controlador
  • Parâmetros do manipulador de página
  • Propriedades do modelo de página

Os nós de nível superior associados ao modelo são validados além de validar as propriedades do modelo. No exemplo a seguir do aplicativo de exemplo, o método VerifyPhone usa o RegularExpressionAttribute para validar o parâmetro de ação phone:

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Os nós de nível superior podem usar BindRequiredAttribute com atributos de validação. No exemplo a seguir do aplicativo de exemplo, o método CheckAge especifica que o parâmetro age deve ser associado da cadeia de caracteres de consulta quando o formulário é enviado:

[HttpPost]
public IActionResult CheckAge(
    [BindRequired, FromQuery] int age)
{

Na página Verificar Idade (CheckAge.cshtml), há dois formulários. O primeiro formulário envia um valor Age de 99 como uma cadeia de caracteres de consulta: https://localhost:5001/Users/CheckAge?Age=99.

Quando um parâmetro age formatado corretamente da cadeia de caracteres de consulta é enviado, o formulário é validado.

O segundo formulário na página Verificar Idade envia o valor Age no corpo da solicitação e a validação falha. A associação falha porque o parâmetro age deve vir de uma cadeia de caracteres de consulta.

Ao executar com CompatibilityVersion.Version_2_1 ou posterior, a validação do nó de nível superior é habilitada por padrão. Caso contrário, a validação do nó de nível superior ficará desabilitada. A opção padrão pode ser substituída pela configuração da propriedade AllowValidatingTopLevelNodes em (Startup.ConfigureServices), conforme mostrado aqui:

services.AddMvc(options => 
    {
        options.MaxModelValidationErrors = 50;
        options.AllowValidatingTopLevelNodes = false;
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Máximo de erros

A validação para quando o número máximo de erros é atingido (200 por padrão). Você pode configurar esse número com o seguinte código no Startup.ConfigureServices:

services.AddMvc(options => 
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            (_) => "The field is required.");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton
    <IValidationAttributeAdapterProvider, 
     CustomValidationAttributeAdapterProvider>();

Recursão máxima

ValidationVisitor percorre o grafo de objeto do modelo que está sendo validado. Para modelos que são muito profundos ou que são infinitamente recursivos, a validação pode resultar em estouro de pilha. O MvcOptions.MaxValidationDepth fornece uma maneira de interromper a validação antes que a recursão visitante exceda uma profundidade configurada. O valor padrão de MvcOptions.MaxValidationDepth é 32 ao executar com CompatibilityVersion.Version_2_2 ou posterior. Para versões anteriores, o valor é nulo, o que significa que não há nenhuma restrição de profundidade.

Curto-circuito automático

A validação sofrerá curto-circuito (será ignorada) automaticamente se o grafo do modelo não exigir validação. Objetos em que o runtime ignora a validação para inclusão de coleções de primitivos (como byte[], string[] e Dictionary<string, string>) e grafos de objetos complexos que não têm um validador.

Desabilitar validação

Para desabilitar a validação:

  1. Crie uma implementação de IObjectModelValidator que não marque nenhum campo como inválido.

    public class NullObjectModelValidator : IObjectModelValidator
    {
        public void Validate(
            ActionContext actionContext,
            ValidationStateDictionary validationState,
            string prefix,
            object model)
        {
        }
    }
    
  2. Adicione o seguinte código ao Startup.ConfigureServices para substituir a implementação IObjectModelValidator padrão no contêiner de injeção de dependência.

    // There is only one `IObjectModelValidator` object,
    // so AddSingleton replaces the default one.
    services.AddSingleton<IObjectModelValidator>(new NullObjectModelValidator());
    

Talvez você ainda veja erros de estado de modelo que se originam do model binding.

Validação do lado do cliente

A validação do lado do cliente impede o envio até que o formulário seja válido. O botão Enviar executa o JavaScript que envia o formulário ou exibe mensagens de erro.

A validação do lado do cliente evita uma viagem de ida e volta desnecessária ao servidor quando há erros de entrada em um formulário. O script a seguir faz referência nas validações de suporte _Layout.cshtml e _ValidationScriptsPartial.cshtml no lado do cliente:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>

O script do jQuery Unobtrusive Validation é uma biblioteca de front-end personalizada da Microsoft que se baseia no popular plug-in jQuery Validate. Sem o jQuery Unobtrusive Validation, você teria que codificar a mesma lógica de validação em dois locais: uma vez nos atributos de validação do lado do servidor nas propriedades do modelo e, em seguida, novamente nos scripts do lado do cliente. Em vez disso, os Auxiliares de Marca e os Auxiliares HTML usam os atributos de validação e os metadados de tipo das propriedades do modelo para renderizar atributos data- de HTML 5 para os elementos de formulário que precisam de validação. O jQuery Unobtrusive Validation analisa os atributos data- e passa a lógica para o jQuery Validate, "copiando" efetivamente a lógica de validação do lado do servidor para o cliente. Você pode exibir erros de validação no cliente usando os auxiliares de marca conforme mostrado aqui:

<div class="form-group">
    <label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="ReleaseDate" class="form-control" />
        <span asp-validation-for="ReleaseDate" class="text-danger"></span>
    </div>
</div>

Os auxiliares de marca acima renderizam o HTML a seguir.

<form action="/Movies/Create" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <div class="text-danger"></div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
            <div class="col-md-10">
                <input class="form-control" type="datetime"
                data-val="true" data-val-required="The ReleaseDate field is required."
                id="ReleaseDate" name="ReleaseDate" value="">
                <span class="text-danger field-validation-valid"
                data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
            </div>
        </div>
    </div>
</form>

Observe que os atributos data- na saída HTML correspondem aos atributos de validação da propriedade ReleaseDate. O atributo data-val-required conterá uma mensagem de erro a ser exibida se o usuário não preencher o campo de data de lançamento. a validação não invasiva do jQuery passa esse valor para o método de validação do jQuery requerido () , que, em seguida, exibe essa mensagem no elemento que o acompanha <span> .

A validação de tipo de dados é baseada no tipo .NET de uma propriedade, a menos que seja substituída por um atributo [DataType]. Os navegadores têm suas próprias mensagens de erro padrão, mas o pacote de validação do jQuery Validation Unobtrusive pode substituir essas mensagens. Os atributos [DataType] e as subclasses como [EmailAddress] permitem que você especifique a mensagem de erro.

Adicionar validação a formulários dinâmicos

O jQuery Unobtrusive Validation passa parâmetros e a lógica de validação para o jQuery Validate quando a página é carregada pela primeira vez. Portanto, a validação não funciona automaticamente em formulários gerados dinamicamente. Para habilitar a validação é necessário instruir o jQuery Unobtrusive Validation a analisar o formulário dinâmico imediatamente depois de criá-lo. Por exemplo, o código a seguir define a validação do lado do cliente em um formulário adicionado por meio do AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

O método $.validator.unobtrusive.parse() aceita um seletor do jQuery para um argumento seu. Esse método instrui o jQuery Unobtrusive Validation a analisar os atributos data- de formulários nesse seletor. Depois, os valores desses atributos são passados para o plug-in do jQuery Validate.

Adicionar validação a controles dinâmicos

O método $.validator.unobtrusive.parse() funciona em um formulário inteiro, não em controles individuais gerados dinamicamente, como <input> e <select/>. Para uma nova análise do formulário, remova os dados de validação que foram adicionados ao formulário mesmo quando foi analisado anteriormente, conforme mostrado no exemplo a seguir:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validate
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Validação personalizada do lado do cliente

A validação personalizada do lado do cliente é feita por meio da geração de atributos de HTML data- que funcionam com um adaptador do jQuery Validate personalizado. O seguinte código do adaptador de exemplo foi escrito para os atributos ClassicMovie e ClassicMovie2 que foram apresentados no início deste artigo:

$.validator.addMethod('classicmovie',
    function (value, element, params) {
        // Get element value. Classic genre has value '0'.
        var genre = $(params[0]).val(),
            year = params[1],
            date = new Date(value);
        if (genre && genre.length > 0 && genre[0] === '0') {
            // Since this is a classic movie, invalid if release date is after given year.
            return date.getUTCFullYear() <= year;
        }

        return true;
    });

$.validator.unobtrusive.adapters.add('classicmovie',
    ['year'],
    function (options) {
        var element = $(options.form).find('select#Genre')[0];
        options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
        options.messages['classicmovie'] = options.message;
    });

Para obter informações sobre como escrever adaptadores, consulte a documentação do jQuery Validate.

O uso de um adaptador para um determinado campo é disparado por atributos data- que:

  • Marcam o campo como sujeito à validação do sinalizador (data-val="true").
  • Identifique o nome de uma regra de validação e o texto da mensagem de erro (por exemplo, data-val-rulename="Error message.").
  • Forneça os parâmetros adicionais que o validador precisa (por exemplo, data-val-rulename-parm1="value").

A exemplo a seguir mostra os atributos data- para o atributo ClassicMovie do aplicativo de exemplo:

<input class="form-control" type="datetime"
    data-val="true"
    data-val-classicmovie1="Classic movies must have a release year earlier than 1960."
    data-val-classicmovie1-year="1960"
    data-val-required="The ReleaseDate field is required."
    id="ReleaseDate" name="ReleaseDate" value="">

Conforme observado anteriormente, os Auxiliares de Marca e Auxiliares de HTML usam informações de atributos de validação para renderizar atributos data-. Há duas opções para escrever código que resulte na criação de atributos HTML data- personalizados:

  • Criar uma classe que deriva de AttributeAdapterBase<TAttribute> e uma classe que implementa IValidationAttributeAdapterProvider e registrar o atributo e o adaptador na DI. Este método segue a entidade de segurança de responsabilidade única em que o código de validação relacionado ao cliente e ao servidor fica em classes separadas. O adaptador também tem a vantagem de que, uma vez que ele é registrado na DI, outros serviços da DI ficam disponíveis para ele, se necessário.
  • Implementar IClientModelValidator em na classe ValidationAttribute. Esse método poderá ser adequado se o atributo não fizer nenhuma validação do lado do servidor e não precisar de um serviço da DI.

AttributeAdapter para validação do lado do cliente

Esse método de renderização de atributos data- em HTML é usado pelo atributo ClassicMovie no aplicativo de exemplo. Para adicionar a validação do cliente usando esse método:

  1. Crie uma classe de adaptador de atributo para o atributo de validação personalizado. Derive a classe de AttributeAdapterBase<TAttribute> . Criar um método AddValidation que adiciona atributos data- à saída renderizada, conforme mostrado neste exemplo:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        private int _year;
    
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute, IStringLocalizer stringLocalizer) : base (attribute, stringLocalizer)
        {
            _year = attribute.Year;
        }
        public override void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
        {
            return Attribute.GetErrorMessage();
        }
    }
    
  2. Crie uma classe de provedor de adaptador que implementa IValidationAttributeAdapterProvider. No método GetAttributeAdapter, passe o atributo personalizado para o construtor do adaptador, conforme mostrado neste exemplo:

    public class CustomValidationAttributeAdapterProvider :
        IValidationAttributeAdapterProvider
    {
        IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
            else
            {
                return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
            }
        }
    }
    
  3. Registre o provedor de adaptador para DI em Startup.ConfigureServices:

    services.AddMvc(options => 
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                (_) => "The field is required.");
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddSingleton
        <IValidationAttributeAdapterProvider, 
         CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator para validação do lado do cliente

Esse método de renderização de atributos data- em HTML é usado pelo atributo ClassicMovie2 no aplicativo de exemplo. Para adicionar a validação do cliente usando esse método:

  • No atributo de validação personalizado, implemente a interface IClientModelValidator e crie um método AddValidation. No método AddValidation, adicione atributos data- para validação, conforme mostrado no exemplo a seguir:

    
    public class ClassicMovie2Attribute : ValidationAttribute, IClientModelValidator
    {
        private int _year;
    
        public ClassicMovie2Attribute(int year)
        {
            _year = year;
        }
    
        protected override ValidationResult IsValid(
            object value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > _year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = _year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
        protected string GetErrorMessage()
        {
            return $"Classic movies must have a release year no later than {_year} [from attribute 2].";
        }
    }
    

Desabilitar validação do lado do cliente

O código a seguir desabilita a validação de cliente nas exibições do MVC:

services.AddMvc().AddViewOptions(options =>
{
    if (_env.IsDevelopment())
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    }
});

E em Razor páginas:

services.Configure<HtmlHelperOptions>(o => o.ClientValidationEnabled = false);

Outra opção para desabilitar a validação do cliente é comentar a referência ao _ValidationScriptsPartial em seu arquivo .cshtml.

Recursos adicionais