Validação de modelo no ASP.NET Core MVC e Razor Pages

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

Exibir ou baixar um 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 do model binding 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 o model binding e relata os erros em que os dados não estão em conformidade com as regras de negócios. Por exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5.

O model binding e a validação do modelo ocorrem antes da execução de uma ação do controlador ou de um método de manipulador do Razor Pages. Nos aplicativos Web, é responsabilidade do aplicativo inspecionar ModelState.IsValid e reagir adequadamente. Normalmente, os aplicativos Web reproduzem a página com uma mensagem de erro, conforme mostrado no exemplo do Razor Pages a seguir:

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

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

    return RedirectToPage("./Index");
}

Para o ASP.NET Core MVC com controladores e exibições, o exemplo a seguir mostra como verificar ModelState.IsValid dentro de uma ação do controlador:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

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

    return RedirectToAction(nameof(Index));
}

Os controladores de API Web não precisarão verificar ModelState.IsValid se eles tiverem o atributo [ApiController]. Nesse caso, uma resposta automática HTTP 400 contendo detalhes do erro será retornada quando o estado do modelo for 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 novamente a validação, chame ModelStateDictionary.ClearValidationState para limpar a validação específica do 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. O [ClassicMovieWithClientValidator] não está exibido e 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; } = null!;

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

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

    [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]: indica que uma propriedade ou parâmetro deve ser excluído da validação.
  • [CreditCard]: valida se a propriedade tem um formato de cartão de crédito. Requer métodos adicionais do jQuery Validation.
  • [Compare]: valida se duas propriedades em um modelo são correspondentes.
  • [EmailAddress]: valida se a propriedade tem um formato de email.
  • [Phone]: valida se a propriedade tem um formato de número de telefone.
  • [Range]: valida se o valor da propriedade está dentro de um intervalo especificado.
  • [RegularExpression]: valida se o valor da propriedade corresponde à expressão regular especificada.
  • [Required]: valida se o campo não é nulo. Confira atributo [Required] para obter detalhes sobre o comportamento desse atributo.
  • [StringLength]: valida se um valor da propriedade de cadeia de caracteres não excede um limite de comprimento especificado.
  • [Url]: valida se a propriedade tem um formato de URL.
  • [Remote]: valida a entrada no cliente chamando um método de ação no servidor. Confira atributo [Remote] 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.

Use nomes de propriedade JSON em erros de validação

Por padrão, quando ocorre um erro de validação, a validação de modelo produz um ModelStateDictionary com o nome da propriedade como a chave de erro. Alguns aplicativos, como aplicativos de página única, se beneficiam do uso de nomes de propriedade JSON para erros de validação gerados de APIs Web. O código abaixo configura a validação para usar os nomes de SystemTextJsonValidationMetadataProvider a fim de usar nomes de propriedade JSON:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

O código abaixo configura a validação para usar NewtonsoftJsonValidationMetadataProvider a fim de usar o nome da propriedade JSON ao usar Json.NET:

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obter um exemplo da política para usar camel-casing, confira Program.cs no GitHub.

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 limitadas como se tivessem um atributo [Required(AllowEmptyStrings = true)]. Ao habilitar contextosNullable, o MVC inicia implicitamente a validação de propriedades ou parâmetros não anuláveis como se tivessem sido atribuídos com o atributo [Required(AllowEmptyStrings = true)]. 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 de formulário ou JSON resultará em um erro de validação. Isso pode parecer contraditório, já que o atributo [Required(AllowEmptyStrings = true)] está implícito, mas esse é o comportamento esperado porque as cadeias de caracteres vazias são convertidas em nulas por padrão. Use um tipo de referência anulável para permitir que valores nulos ou ausentes sejam especificados na propriedade Name:

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

Esse comportamento pode ser desabilitado configurando SuppressImplicitRequiredAttributeForNonNullableReferenceTypes em Program.cs:

builder.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 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). Tipos de valor Nullable<T> são tratados como tipos que permitem valor nulo padrão.

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

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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(AllowEmptyStrings = true)]. Isso significa que você obtém a validação do lado do cliente mesmo que não aplique o atributo [Required(AllowEmptyStrings = true)]. 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 Validation 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; } = null!;
    

A validação do lado do servidor também precisa ser implementada para clientes que desabilitaram o JavaScript.

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; } = null!;

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

AdditionalFields pode ser definido de forma explícita com 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 firstName e lastName:

[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 atributo [ClassicMovie]:

  • Só é executado no servidor.
  • Para Filmes clássicos, 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; } = null!;

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

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

    [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 personalizada

O código a seguir mostra como adicionar um erro de modelo depois de examinar o modelo:

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

O código a seguir implementa o teste de validação em um controlador:

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

O código a seguir verifica se o número de telefone e o email são exclusivos:

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

O código a seguir implementa o teste de validação em um controlador:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

Normalmente, a verificação de um número de telefone ou email exclusivo também é feita com a validação remota.

ValidationResult

Considere a seguinte ValidateNameAttribute personalizada:

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

No código a seguir, o atributo personalizado [ValidateName] é aplicado:

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

Quando o modelo contém zz, um novo ValidationResult é retornado.

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 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 Program.cs:

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

builder.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 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.

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.

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. As referências de script a seguir em _Layout.cshtml e _ValidationScriptsPartial.cshtml oferecem suporte à validação no lado do cliente:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.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 Validation, "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="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 acima renderizam o HTML a seguir:

<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. O jQuery Unobtrusive Validation passa esse valor para o método required() do jQuery Validation, que, por sua vez, exibe essa mensagem no elemento <span> complementar.

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. Atributos e subclasses [DataType], como [EmailAddress], permitem que você especifique a mensagem de erro.

Validação discreta

Para obter informações sobre validação discreta, confira este problema do GitHub.

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 Validation 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 Validation.

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 por meio da geração de atributos de HTML data- que funcionam com um adaptador do jQuery Validation 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 do jQuery Validation.

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 do aplicativo de exemploClassicMovie:

<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:

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 Program.cs:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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 static 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 de cliente no Razor Pages:

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

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

  • Comente a referência a _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 de ASP.NET Core IdentityRazor Biblioteca de Classes. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core.

Detalhes do problema

Os Detalhes do problema não são o único formato de resposta a descrever um erro de API HTTP; no entanto, eles geralmente são usados para relatar erros para APIs HTTP.

O serviço de detalhes do problema implementa a interface IProblemDetailsService, que dá suporte à criação de detalhes do problema no ASP.NET Core. O método de extensão AddProblemDetails em IServiceCollection regista a implementação IProblemDetailsService padrão.

Em aplicativos ASP.NET Core, o middleware abaixo gera respostas HTTP de detalhes do problema quando AddProblemDetails é chamado, exceto quando o cabeçalho HTTP da solicitação Accept não inclui um dos tipos de conteúdo com suporte pelo IProblemDetailsWriter registrado (padrão: application/json):

Recursos adicionais

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

Exibir ou baixar um 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 do model binding 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 o model binding e relata os erros em que os dados não estão em conformidade com as regras de negócios. Por exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5.

O model binding e a validação do modelo ocorrem antes da execução de uma ação do controlador ou de um método de manipulador do Razor Pages. Nos aplicativos Web, é responsabilidade do aplicativo inspecionar ModelState.IsValid e reagir adequadamente. Normalmente, os aplicativos Web reproduzem a página com uma mensagem de erro, conforme mostrado no exemplo do Razor Pages a seguir:

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

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

    return RedirectToPage("./Index");
}

Para o ASP.NET Core MVC com controladores e exibições, o exemplo a seguir mostra como verificar ModelState.IsValid dentro de uma ação do controlador:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

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

    return RedirectToAction(nameof(Index));
}

Os controladores de API Web não precisarão verificar ModelState.IsValid se eles tiverem o atributo [ApiController]. Nesse caso, uma resposta automática HTTP 400 contendo detalhes do erro será retornada quando o estado do modelo for 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 novamente a validação, chame ModelStateDictionary.ClearValidationState para limpar a validação específica do 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. O [ClassicMovieWithClientValidator] não está exibido e 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; } = null!;

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

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

    [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]: indica que uma propriedade ou parâmetro deve ser excluído da validação.
  • [CreditCard]: valida se a propriedade tem um formato de cartão de crédito. Requer métodos adicionais do jQuery Validation.
  • [Compare]: valida se duas propriedades em um modelo são correspondentes.
  • [EmailAddress]: valida se a propriedade tem um formato de email.
  • [Phone]: valida se a propriedade tem um formato de número de telefone.
  • [Range]: valida se o valor da propriedade está dentro de um intervalo especificado.
  • [RegularExpression]: valida se o valor da propriedade corresponde à expressão regular especificada.
  • [Required]: valida se o campo não é nulo. Confira atributo [Required] para obter detalhes sobre o comportamento desse atributo.
  • [StringLength]: valida se um valor da propriedade de cadeia de caracteres não excede um limite de comprimento especificado.
  • [Url]: valida se a propriedade tem um formato de URL.
  • [Remote]: valida a entrada no cliente chamando um método de ação no servidor. Confira atributo [Remote] 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.

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

O sistema de validação trata parâmetros não anuláveis ou propriedades limitadas como se tivessem um atributo [Required(AllowEmptyStrings = true)]. Ao habilitar contextos Nullable, o MVC inicia implicitamente a validação de propriedades em tipos não-genéricos ou parâmetros não anuláveis como se tivessem sido atribuídos com o atributo [Required(AllowEmptyStrings = true)]. 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 de formulário ou JSON resultará em um erro de validação. Use um tipo de referência anulável para permitir que valores nulos ou ausentes sejam especificados na propriedade Name:

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

Esse comportamento pode ser desabilitado configurando SuppressImplicitRequiredAttributeForNonNullableReferenceTypes em Program.cs:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Propriedades não anuláveis em tipos genéricos e atributo [Required]

As propriedades não anuláveis em tipos genéricos devem incluir o atributo [Required] quando o tipo é necessário. No código a seguir, TestRequired não é necessário:

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

No código a seguir, TestRequired é explicitamente marcado como necessário:

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

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). Tipos de valor Nullable<T> são tratados como tipos que permitem valor nulo padrão.

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

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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(AllowEmptyStrings = true)]. Isso significa que você obtém a validação do lado do cliente mesmo que não aplique o atributo [Required(AllowEmptyStrings = true)]. 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 Validation 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; } = null!;
    

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; } = null!;

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

AdditionalFields pode ser definido de forma explícita com 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 firstName e lastName:

[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 atributo [ClassicMovie]:

  • Só é executado no servidor.
  • Para Filmes clássicos, 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; } = null!;

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

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

    [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 valor Age 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 Program.cs:

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

builder.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 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.

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.

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. As referências de script a seguir em _Layout.cshtml e _ValidationScriptsPartial.cshtml oferecem suporte à validação no lado do cliente:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.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 Validation, "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="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 acima renderizam o HTML a seguir:

<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. O jQuery Unobtrusive Validation passa esse valor para o método required() do jQuery Validation, que, por sua vez, exibe essa mensagem no elemento <span> complementar.

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. Atributos e subclasses [DataType], como [EmailAddress], permitem que você especifique a mensagem de erro.

Validação discreta

Para obter informações sobre validação discreta, confira este problema do GitHub.

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 Validation 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 Validation.

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 por meio da geração de atributos de HTML data- que funcionam com um adaptador do jQuery Validation 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 do jQuery Validation.

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 do aplicativo de exemploClassicMovie:

<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:

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 Program.cs:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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 static 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 de cliente no Razor Pages:

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

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

  • Comente a referência a _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 de ASP.NET Core IdentityRazor Biblioteca de Classes. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core.

Recursos adicionais

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

Exibir ou baixar um 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 do model binding 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 o model binding e relata os erros em que os dados não estão em conformidade com as regras de negócios. Por exemplo, um 0 é inserido em um campo que espera uma classificação entre 1 e 5.

O model binding e a validação do modelo ocorrem antes da execução de uma ação do controlador ou de um método de manipulador do Razor Pages. 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 automática HTTP 400 contendo detalhes do erro será retornada quando o estado do modelo for 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 novamente a validação, chame ModelStateDictionary.ClearValidationState para limpar a validação específica do 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. O [ClassicMovieWithClientValidator] não está exibido e 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]: indica que uma propriedade ou parâmetro deve ser excluído da validação.
  • [CreditCard]: valida se a propriedade tem um formato de cartão de crédito. Requer métodos adicionais do jQuery Validation.
  • [Compare]: valida se duas propriedades em um modelo são correspondentes.
  • [EmailAddress]: valida se a propriedade tem um formato de email.
  • [Phone]: valida se a propriedade tem um formato de número de telefone.
  • [Range]: valida se o valor da propriedade está dentro de um intervalo especificado.
  • [RegularExpression]: valida se o valor da propriedade corresponde à expressão regular especificada.
  • [Required]: valida se o campo não é nulo. Confira atributo [Required] para obter detalhes sobre o comportamento desse atributo.
  • [StringLength]: valida se um valor da propriedade de cadeia de caracteres não excede um limite de comprimento especificado.
  • [Url]: valida se a propriedade tem um formato de URL.
  • [Remote]: valida a entrada no cliente chamando um método de ação no servidor. Confira atributo [Remote] 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 limitadas como se tivessem um atributo [Required(AllowEmptyStrings = true)]. Ao habilitar contextosNullable, o MVC inicia implicitamente a validação de propriedades ou parâmetros não anuláveis como se tivessem sido atribuídos com o atributo [Required(AllowEmptyStrings = true)]. 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 de formulário ou JSON resultará em um erro de validação. Use um tipo de referência anulável para permitir que valores nulos ou ausentes sejam especificados na propriedade Name:

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

Esse comportamento pode ser desabilitado configurando 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 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). Tipos de valor Nullable<T> são tratados como tipos que permitem valor nulo 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(AllowEmptyStrings = true)]. Isso significa que você obtém a validação do lado do cliente mesmo que não aplique o atributo [Required(AllowEmptyStrings = true)]. 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 Validation 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; }
    

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 de forma explícita com 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 firstName e lastName:

[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 atributo [ClassicMovie]:

  • Só é executado no servidor.
  • Para Filmes clássicos, 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 valor Age 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 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.

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.

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. As referências de script a seguir em _Layout.cshtml e _ValidationScriptsPartial.cshtml oferecem suporte à validação no lado do cliente:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.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 Validation, "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="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 acima renderizam o HTML a seguir:

<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. O jQuery Unobtrusive Validation passa esse valor para o método required() do jQuery Validation, que, por sua vez, exibe essa mensagem no elemento <span> complementar.

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. Atributos e subclasses [DataType], como [EmailAddress], permitem que você especifique a mensagem de erro.

Validação discreta

Para obter informações sobre validação discreta, confira este problema do GitHub.

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 Validation 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 Validation.

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 por meio da geração de atributos de HTML data- que funcionam com um adaptador do jQuery Validation 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 do jQuery Validation.

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 do aplicativo de exemploClassicMovie:

<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:

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 de cliente no Razor Pages:

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

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

  • Comente a referência a _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 de ASP.NET Core IdentityRazor Biblioteca de Classes. Para obter mais informações, veja Scaffold Identity em projetos ASP.NET Core.

Recursos adicionais