Model binding no ASP.NET Core

Este artigo explica o que é model binding, como ele funciona e como personalizar seu comportamento.

O que é o model binding

Controladores e Razor páginas funcionam com dados provenientes de solicitações HTTP. Por exemplo, dados de rota podem fornecer uma chave de registro e campos de formulário postados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres em tipos .NET seria uma tarefa entediante e propensa a erro. O model binding automatiza esse processo. O sistema de model binding:

  • Recupera dados de várias fontes, como dados de rota, campos de formulário e cadeias de caracteres de consulta.
  • Fornece os dados para controladores e Razor páginas em parâmetros de método e propriedades públicas.
  • Converte dados de cadeia de caracteres em tipos .NET.
  • Atualiza as propriedades de tipos complexos.

Exemplo

Suponha que você tenha o seguinte método de ação:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

E o aplicativo receba uma solicitação com esta URL:

https://contoso.com/api/pets/2?DogsOnly=true

A associação de modelo passa pelas seguintes etapas depois que o sistema de roteamento seleciona o método de ação:

  • Localiza o primeiro parâmetro de GetById, um número inteiro denominado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" em dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booliano chamado dogsOnly.
  • Examina as fontes e localiza "DogsOnly=true" na cadeia de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a cadeia de caracteres "true" no booliano true.

A estrutura então chama o método GetById, passando 2 para o parâmetro id e true para o parâmetro dogsOnly.

No exemplo anterior, os destinos do model binding são parâmetros de método que são tipos simples. Destinos também podem ser as propriedades de um tipo complexo. Depois de cada propriedade ser associada com êxito, a validação do modelo ocorre para essa propriedade. O registro de quais dados estão associados ao modelo, além de quaisquer erros de validação ou de associação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid.

Destinos

O model binding tenta encontrar valores para os seguintes tipos de destinos:

  • Parâmetros do método de ação do controlador para o qual uma solicitação é roteada.
  • Parâmetros do método do manipulador Pages para o Razor qual uma solicitação é roteada.
  • Propriedades públicas de um controlador ou classe PageModel, se especificadas por atributos.

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para fazer o model binding ter essa propriedade como destino:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atributo [BindProperties]

Pode ser aplicado a um controlador ou classe PageModel para informar o model binding para ter todas as propriedades públicas da classe como destino:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Model binding para solicitações HTTP GET

Por padrão, as propriedades não são vinculadas para solicitações HTTP GET. Normalmente, tudo o que você precisa para uma solicitação GET é um parâmetro de ID de registro. A ID do registro é usada para pesquisar o item no banco de dados. Portanto, não é necessário associar uma propriedade que contém uma instância do modelo. Em cenários em que você deseja associar propriedades a dados de solicitações GET, defina a propriedade SupportsGet como true:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Origens

Por padrão, o model binding obtém dados na forma de pares chave-valor das seguintes fontes em uma solicitação HTTP:

  1. Campos de formulário
  2. O corpo da solicitação (para controladores que têm o atributo [ApiController]).
  3. Rotear dados
  4. Parâmetros de cadeia de caracteres de consulta
  5. Arquivos carregados

Para cada parâmetro ou propriedade de destino, as fontes são verificadas na ordem indicada na lista anterior. Há algumas exceções:

  • Os valores de cadeia de caracteres de consulta e dados de rota são usados apenas para tipos simples.
  • Arquivos carregados são associados apenas a tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

Se a origem padrão não estiver correta, use um dos seguintes atributos para especificar a origem:

  • [FromQuery] – Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] – Obtém valores de dados de rota.
  • [FromForm] – Obtém valores de campos de formulário postados.
  • [FromBody] – Obtém valores do corpo da solicitação.
  • [FromHeader] – Obtém valores de cabeçalhos HTTP.

Esses atributos:

  • São adicionadas às propriedades do modelo individualmente e não à classe de modelo, como no exemplo a seguir:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcionalmente, aceite um valor de nome de modelo no construtor. Essa opção é fornecida caso o nome da propriedade não corresponda ao valor na solicitação. Por exemplo, o valor na solicitação pode ser um cabeçalho com um hífen em seu nome, como no exemplo a seguir:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atributo [FromBody]

Aplique o [FromBody] atributo a um parâmetro para preencher suas propriedades do corpo de uma solicitação HTTP. O runtime ASP.NET Core delega a responsabilidade de ler o corpo para um formatador de entrada. O formatadores de entrada são explicados posteriormente neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de associação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido do corpo:

public ActionResult<Pet> Create([FromBody] Pet pet)

A Pet classe especifica que sua Breed propriedade é populada de um parâmetro de cadeia de caracteres de consulta:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

No exemplo anterior:

  • O [FromQuery] atributo é ignorado.
  • A Breed propriedade não é preenchida de um parâmetro de cadeia de caracteres de consulta.

Os formatadores de entrada leem apenas o corpo e não entendem os atributos de origem de associação. Se um valor adequado for encontrado no corpo, esse valor será usado para preencher a Breed propriedade.

Não aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação for lido por um formatador de entrada, ele não estará mais disponível para ser lido novamente para associar outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de model binding pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para model binding de outras fontes. Por exemplo, talvez você queira dados de s ou estado de cookiesessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implementa IValueProvider.
  • Crie uma classe que implementa IValueProviderFactory.
  • Registre a classe de alocador em Program.cs.

O exemplo inclui um provedor de valores e um exemplo de fábrica que obtém valores de cookies. Registrar fábricas de provedores de valores personalizados em Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

O código anterior coloca o provedor de valor personalizado após todos os provedores de valores internos. Para torná-lo o primeiro na lista, chame Insert(0, new CookieValueProviderFactory()), em vez de Add.

Nenhuma fonte para uma propriedade de modelo

Por padrão, um erro de estado de modelo não será criado se nenhum valor for encontrado para uma propriedade de modelo. A propriedade é definida como null ou um valor padrão:

  • Tipos simples anuláveis definidos como null.
  • Tipos de valor não anuláveis são definidos como default(T). Por exemplo, um parâmetro int id é definido como 0.
  • Para tipos complexos, o model binding cria uma instância usando o construtor padrão sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que matrizes byte[] são definidas como null.

Se o estado do modelo deve ser invalidado quando nada for encontrado em campos de formulário para uma propriedade de modelo, use o [BindRequired] atributo.

Observe que esse [BindRequired] comportamento se aplica à associação de modelo de dados de formulário postados, não a JSdados ON ou XML em um corpo da solicitação. Dados do corpo da solicitação são tratados pelos formatadores de entrada.

Erros de conversão de tipos

Se uma fonte for encontrada, mas não puder ser convertida no tipo de destino, o estado do modelo será sinalizado como inválido. O parâmetro ou a propriedade de destino é definido como nulo ou um valor padrão, conforme observado na seção anterior.

Em um controlador de API que tem o atributo [ApiController], um estado de modelo inválido resulta em uma resposta HTTP 400 automática.

Em uma Razor página, redimize a página com uma mensagem de erro:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Quando a página é reproduçada pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade do modelo foi definida como nulo ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Se você quiser redicionar os dados incorretos no campo de formulário, considere tornar a propriedade do modelo uma cadeia de caracteres e fazer a conversão de dados manualmente.

A mesma estratégia será recomendada se você não desejar que erros de conversão de tipo resultem em erros de estado de modelo. Nesse caso, torne a propriedade de modelo uma cadeia de caracteres.

Tipos simples

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres de origem incluem os seguintes:

Associar com IParsable<T>.TryParse

A IParsable<TSelf>.TryParse API dá suporte a valores de parâmetro de ação do controlador de associação:

public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);

A classe a seguir DateRange implementa IParsable<TSelf> para dar suporte à associação de um intervalo de datas:

public class DateRange : IParsable<DateRange>
{
    public DateOnly? From { get; init; }
    public DateOnly? To { get; init; }

    public static DateRange Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse(string? value,
                                IFormatProvider? provider, out DateRange dateRange)
    {
        var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries 
                                       | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && DateOnly.TryParse(segments[0], provider, out var fromDate)
            && DateOnly.TryParse(segments[1], provider, out var toDate))
        {
            dateRange = new DateRange { From = fromDate, To = toDate };
            return true;
        }

        dateRange = new DateRange { From = default, To = default };
        return false;
    }
}

O código anterior:

  • Converte uma cadeia de caracteres que representa duas datas em um DateRange objeto
  • O associador de modelo usa o IParsable<TSelf>.TryParse método para associar o DateRange.

A ação do controlador a seguir usa a DateRange classe para associar um intervalo de datas:

// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

A classe a seguir Locale implementa IParsable<TSelf> para dar suporte à associação a CultureInfo:

public class Locale : CultureInfo, IParsable<Locale>
{
    public Locale(string culture) : base(culture)
    {
    }

    public static Locale Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider, out Locale locale)
    {
        if (value is null)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
        
        try
        {
            locale = new Locale(value);
            return true;
        }
        catch (CultureNotFoundException)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
    }
}

A ação do controlador a seguir usa a Locale classe para associar uma CultureInfo cadeia de caracteres:

// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View(weatherForecasts);
}

A ação do controlador a seguir usa as classes e Locale as DateRange classes para associar um intervalo de datas aCultureInfo:

// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
    {
        ModelState.TryAddModelError(nameof(range),
            $"Invalid date range: {range} for locale {locale.DisplayName}");

        return View("Error", ModelState.Values.SelectMany(v => v.Errors));
    }

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
                     && DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

O aplicativo de exemplo de API no GitHub mostra o exemplo anterior para um controlador de API.

Associar com TryParse

A TryParse API dá suporte a valores de parâmetro de ação do controlador de associação:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

IParsable<T>.TryParse é a abordagem recomendada para a associação de parâmetros porque, ao contrário TryParse, ela não depende da reflexão.

A classe a seguir DateRangeTP implementa TryParse:

public class DateRangeTP
{
    public DateOnly? From { get; }
    public DateOnly? To { get; }

    public DateRangeTP(string from, string to)
    {
        if (string.IsNullOrEmpty(from))
            throw new ArgumentNullException(nameof(from));
        if (string.IsNullOrEmpty(to))
            throw new ArgumentNullException(nameof(to));

        From = DateOnly.Parse(from);
        To = DateOnly.Parse(to);
    }

    public static bool TryParse(string? value, out DateRangeTP? result)
    {
        if (string.IsNullOrEmpty(value) || value.Split('-').Length != 2)
        {
            result = default;
            return false;
        }

        var range = value.Split(',');
        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

A ação do controlador a seguir usa a DateRDateRangeTPange classe para associar um intervalo de datas:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRange range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Tipos complexos

Um tipo complexo deve ter um construtor padrão público e propriedades públicas graváveis para associar. Quando ocorre model binding, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, a associação de modelo examina as fontes para o padrão de nomeprefix.property_name. Se nada for encontrado, ela procurará apenas property_name sem o prefixo. A decisão de usar o prefixo não é tomada por propriedade. Por exemplo, com uma consulta contendo ?Instructor.Id=100&Name=foo, associado ao método OnGet(Instructor instructor), o objeto resultante do tipo Instructor contém:

  • Id definido como 100.
  • Name definido como null. A associação de modelo espera Instructor.Name porque Instructor.Id foi usada no parâmetro de consulta anterior.

Para associação a um parâmetro, o prefixo é o nome do parâmetro. Para associação a uma propriedade pública PageModel, o prefixo é o nome da propriedade pública. Alguns atributos têm uma propriedade Prefix que permite substituir o uso padrão do nome da propriedade ou do parâmetro.

Por exemplo, suponha que o tipo complexo seja a seguinte classe Instructor:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Prefixo = nome do parâmetro

Se o modelo a ser associado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave instructorToUpdate.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser associado for uma propriedade chamada Instructor do controlador ou da classe PageModel:

[BindProperty]
public Instructor Instructor { get; set; }

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo personalizado

Se o modelo a ser associado for um parâmetro denominado instructorToUpdate e um atributo Bind especificar Instructor como o prefixo:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar o model binding de tipos complexos:

Aviso

Esses atributos afetam o model binding quando os dados de formulário postados são a origem dos valores. Eles não afetam os formatadores de entrada, que processam corpos de solicitação ON e XML postados JS. O formatadores de entrada são explicados posteriormente neste artigo.

Atributo [Bind]

Pode ser aplicado a uma classe ou a um parâmetro de método. Especifica quais propriedades de um modelo devem ser incluídas no model binding. [Bind]não afeta os formatadores de entrada.

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando qualquer método de ação ou o manipulador é chamado:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando o método OnPost é chamado:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

O atributo [Bind] pode ser usado para proteção contra o excesso de postagem cenários de criar. Ele não funciona bem em cenários de edição, pois as propriedades excluídas são definidas como nulas ou um valor padrão, em vez de serem deixadas inalteradas. Para defesa contra o excesso de postagem, são recomendados modelos de exibição, em vez do atributo [Bind]. Para obter mais informações, veja Observação de segurança sobre o excesso de postagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. Ele permite especificar o tipo de associador de modelo usado para associar a instância ou o tipo específico. Por exemplo:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

O [ModelBinder] atributo também pode ser usado para alterar o nome de uma propriedade ou parâmetro quando está sendo associado ao modelo:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atributo [BindRequired]

Faz com que o model binding adicione um erro de estado de modelo se a associação não puder ocorrer para a propriedade de um modelo. Veja um exemplo:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Veja também a discussão sobre o atributo [Required] em Validação do modelo.

Atributo [BindNever]

Pode ser aplicado a uma propriedade ou um tipo. Impede que o model binding configure a propriedade de um modelo. Quando aplicado a um tipo, o sistema de associação de modelo exclui todas as propriedades definidas pelo tipo. Veja um exemplo:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Coleções

Para destinos que são coleções de tipos simples, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro a ser associado seja uma matriz chamada selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dados de cadeia de caracteres de consulta ou formulário podem estar em um dos seguintes formatos:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

Evite associar um parâmetro ou uma propriedade nomeada index ou Index se estiver adjacente a um valor de coleção. A associação de modelo tenta usar index como índice para a coleção, o que pode resultar em associação incorreta. Por exemplo, considere a seguinte ação:

public IActionResult Post(string index, List<Product> products)

No código anterior, o parâmetro de index cadeia de caracteres de consulta se associa ao parâmetro do index método e também é usado para associar a coleção de produtos. Renomear o index parâmetro ou usar um atributo de associação de modelo para configurar a associação evita esse problema:

public IActionResult Post(string productIndex, List<Product> products)
selectedCourses[]=1050&selectedCourses[]=2000
  • Para todos os formatos do exemplo anterior, o model binding passa uma matriz de dois itens para o parâmetro selectedCourses:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Formatos de dados que usam números subscritos (... [0]... [1]...) devem garantir que eles estejam numerados em sequência, começando com zero. Se houver quaisquer intervalos na numeração de subscrito, todos os itens após o intervalo serão ignorados. Por exemplo, se os subscritos forem 0 e 2, em vez de 0 e 1, o segundo item será ignorado.

Dicionários

Para destinos Dictionary, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro de destino seja um Dictionary<int, string> chamado selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Os dados de cadeia de caracteres de consulta ou formulário postados podem se parecer com um dos exemplos a seguir:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Para todos os formatos do exemplo anterior, o model binding passa um dicionário de dois itens para o parâmetro selectedCourses:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Tipos de associação e registro do construtor

A associação de modelo requer que tipos complexos tenham um construtor sem parâmetros. Os System.Text.Json formatadores de entrada baseados e Newtonsoft.Json baseados dão suporte à desserialização de classes que não têm um construtor sem parâmetros.

Os tipos de registro são uma ótima maneira de representar dados de forma sucinta na rede. ASP.NET Core dá suporte à associação de modelo e à validação de tipos de registro com um único construtor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Ao validar tipos de registro, o runtime procura metadados de associação e validação especificamente em parâmetros e não em propriedades.

A estrutura permite associar e validar tipos de registro:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Para que o anterior funcione, o tipo deve:

  • Ser um tipo de registro.
  • Tenha exatamente um construtor público.
  • Contém parâmetros que têm uma propriedade com o mesmo nome e tipo. Os nomes não devem diferir por caso.

POCOs sem construtores sem parâmetros

POCOs que não têm construtores sem parâmetros não podem ser associados.

O código a seguir resulta em uma exceção informando que o tipo deve ter um construtor sem parâmetros:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Tipos de registro com construtores criados manualmente

Tipos de registro com construtores criados manualmente que se parecem com construtores primários funcionam

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Tipos de registro, validação e metadados de associação

Para tipos de registro, metadados de validação e associação em parâmetros são usados. Todos os metadados nas propriedades são ignorados

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validação e metadados

A validação usa metadados no parâmetro, mas usa a propriedade para ler o valor. No caso comum com construtores primários, os dois seriam idênticos. No entanto, há maneiras de derrotá-lo:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel não atualiza parâmetros em um tipo de registro

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Nesse caso, o MVC não tentará associar Name novamente. No entanto, Age é permitido ser atualizado

Comportamento de globalização de dados de rota de associação de modelo e cadeias de caracteres de consulta

O provedor de valor de rota ASP.NET Core e o provedor de valor da cadeia de caracteres de consulta:

  • Tratar valores como cultura invariável.
  • Espere que as URLs sejam invariáveis à cultura.

Por outro lado, os valores provenientes de dados de formulário passam por uma conversão sensível à cultura. Isso é por design para que as URLs sejam compartilháveis entre as localidades.

Para tornar o provedor de valor de rota ASP.NET Core e o provedor de valor de cadeia de caracteres de consulta passarem por uma conversão sensível à cultura:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipos de dados especiais

Há alguns tipos de dados especiais com o model binding pode lidar.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. Também compatível com IEnumerable<IFormFile> para vários arquivos.

CancellationToken

As ações podem, opcionalmente, associar um CancellationToken parâmetro. Isso associa RequestAborted isso sinaliza quando a conexão subjacente à solicitação HTTP é anulada. As ações podem usar esse parâmetro para cancelar operações assíncronas de execução longa que são executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados de formulário postados.

Formatadores de entrada

Os dados no corpo da solicitação podem estar em JSON, XML ou em algum outro formato. Para analisar esses dados, o model binding usa um formatador de entrada configurado para lidar com um determinado tipo de conteúdo. Por padrão, ASP.NET Core inclui JSformatadores de entrada baseados em ON para manipular JSdados ON. Você pode adicionar outros formatadores para outros tipos de conteúdo.

O ASP.NET Core seleciona formatadores de entrada com base no atributo Consome. Se nenhum atributo estiver presente, ele usará o cabeçalho Content-Type.

Para usar os formatadores de entrada XML internos:

Personalizar a associação de modelo com formatores de entrada

Um formatador de entrada assume total responsabilidade pela leitura de dados do corpo da solicitação. Para personalizar esse processo, configure as APIs usadas pelo formatador de entrada. Esta seção descreve como personalizar o System.Text.Jsonformatador de entrada baseado para entender um tipo personalizado chamado ObjectId.

Considere o seguinte modelo, que contém uma propriedade personalizada ObjectId chamada Id:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Para personalizar o processo de associação de modelo ao usar System.Text.Json, crie uma classe derivada de JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Para usar um conversor personalizado, aplique o JsonConverterAttribute atributo ao tipo. No exemplo a seguir, o ObjectId tipo é configurado como ObjectIdConverter conversor personalizado:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Para obter mais informações, consulte Como escrever conversores personalizados.

Excluir tipos especificados do model binding

O comportamento dos sistemas de associação e validação do modelo é controlado por ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar o model binding ou a validação para tipos especificados.

Para desabilitar o model binding em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Program.cs. Por exemplo, para desabilitar o model binding em todos os modelos do tipo System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Para desabilitar a validação nas propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Program.cs. Por exemplo, para desabilitar a validação nas propriedades do tipo System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Associadores de modelos personalizados

Você pode estender o model binding escrevendo um associador de modelos personalizado e usando o atributo [ModelBinder] para selecioná-la para um determinado destino. Saiba mais sobre o model binding personalizado.

Model binding manual

O model binding pode ser invocado manualmente usando o método TryUpdateModelAsync. O método é definido nas classes ControllerBase e PageModel. Sobrecargas de método permitem que você especifique o provedor de valor e prefixo a ser usado. O método retornará false se o model binding falhar. Veja um exemplo:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync usa provedores de valor para obter dados do corpo do formulário, cadeia de caracteres de consulta e dados de rota. TryUpdateModelAsync normalmente:

  • Usado com Razor páginas e aplicativos MVC usando controladores e exibições para impedir a postagem excessiva.
  • Não é usado com uma API Web, a menos que seja consumido de dados de formulário, cadeias de caracteres de consulta e dados de rota. Os pontos de extremidade da API Web que consomem JSON usam formatadores de entrada para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome do atributo segue o padrão dos atributos de model binding que especificam uma fonte de dados. Porém, não se trata de associar dados de um provedor de valor. Ele obtém uma instância de um tipo do contêiner de injeção de dependência. Sua finalidade é oferecer uma alternativa à injeção de construtor para quando você precisa de um serviço somente se um determinado método for chamado.

Se uma instância do tipo não estiver registrada no contêiner de injeção de dependência, o aplicativo gerará uma exceção ao tentar associar o parâmetro. Para tornar o parâmetro opcional, use uma das seguintes abordagens:

  • Torne o parâmetro anulável.
  • Defina um valor padrão para o parâmetro.

Para parâmetros anuláveis, verifique se o parâmetro não null é antes de acessá-lo.

Recursos adicionais

Este artigo explica o que é model binding, como ele funciona e como personalizar seu comportamento.

O que é o model binding

Controladores e Razor páginas trabalham com dados provenientes de solicitações HTTP. Por exemplo, dados de rota podem fornecer uma chave de registro e campos de formulário postados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres em tipos .NET seria uma tarefa entediante e propensa a erro. O model binding automatiza esse processo. O sistema de model binding:

  • Recupera dados de várias fontes, como dados de rota, campos de formulário e cadeias de caracteres de consulta.
  • Fornece os dados para controladores e Razor páginas em parâmetros de método e propriedades públicas.
  • Converte dados de cadeia de caracteres em tipos .NET.
  • Atualiza as propriedades de tipos complexos.

Exemplo

Suponha que você tenha o seguinte método de ação:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

E o aplicativo receba uma solicitação com esta URL:

https://contoso.com/api/pets/2?DogsOnly=true

A associação de modelo passa pelas seguintes etapas depois que o sistema de roteamento seleciona o método de ação:

  • Localiza o primeiro parâmetro de GetById, um número inteiro denominado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" em dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booliano chamado dogsOnly.
  • Examina as fontes e localiza "DogsOnly=true" na cadeia de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a cadeia de caracteres "true" no booliano true.

A estrutura então chama o método GetById, passando 2 para o parâmetro id e true para o parâmetro dogsOnly.

No exemplo anterior, os destinos do model binding são parâmetros de método que são tipos simples. Destinos também podem ser as propriedades de um tipo complexo. Depois de cada propriedade ser associada com êxito, a validação do modelo ocorre para essa propriedade. O registro de quais dados estão associados ao modelo, além de quaisquer erros de validação ou de associação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid.

Destinos

O model binding tenta encontrar valores para os seguintes tipos de destinos:

  • Parâmetros do método de ação do controlador para o qual uma solicitação é roteada.
  • Parâmetros do método do manipulador Pages para o Razor qual uma solicitação é roteada.
  • Propriedades públicas de um controlador ou classe PageModel, se especificadas por atributos.

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para fazer o model binding ter essa propriedade como destino:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atributo [BindProperties]

Pode ser aplicado a um controlador ou classe PageModel para informar o model binding para ter todas as propriedades públicas da classe como destino:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Model binding para solicitações HTTP GET

Por padrão, as propriedades não são vinculadas para solicitações HTTP GET. Normalmente, tudo o que você precisa para uma solicitação GET é um parâmetro de ID de registro. A ID do registro é usada para pesquisar o item no banco de dados. Portanto, não é necessário associar uma propriedade que contém uma instância do modelo. Em cenários em que você deseja associar propriedades a dados de solicitações GET, defina a propriedade SupportsGet como true:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Origens

Por padrão, o model binding obtém dados na forma de pares chave-valor das seguintes fontes em uma solicitação HTTP:

  1. Campos de formulário
  2. O corpo da solicitação (para controladores que têm o atributo [ApiController]).
  3. Rotear dados
  4. Parâmetros de cadeia de caracteres de consulta
  5. Arquivos carregados

Para cada parâmetro ou propriedade de destino, as fontes são verificadas na ordem indicada na lista anterior. Há algumas exceções:

  • Os valores de cadeia de caracteres de consulta e dados de rota são usados apenas para tipos simples.
  • Arquivos carregados são associados apenas a tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

Se a origem padrão não estiver correta, use um dos seguintes atributos para especificar a origem:

  • [FromQuery] – Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] – Obtém valores de dados de rota.
  • [FromForm] - Obtém valores de campos de formulário postados.
  • [FromBody] – Obtém valores do corpo da solicitação.
  • [FromHeader] – Obtém valores de cabeçalhos HTTP.

Esses atributos:

  • São adicionadas às propriedades do modelo individualmente e não à classe de modelo, como no exemplo a seguir:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Opcionalmente, aceite um valor de nome de modelo no construtor. Essa opção é fornecida caso o nome da propriedade não corresponda ao valor na solicitação. Por exemplo, o valor na solicitação pode ser um cabeçalho com um hífen em seu nome, como no exemplo a seguir:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atributo [FromBody]

Aplique o [FromBody] atributo a um parâmetro para preencher suas propriedades do corpo de uma solicitação HTTP. O runtime ASP.NET Core delega a responsabilidade de ler o corpo para um formatador de entrada. O formatadores de entrada são explicados posteriormente neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de associação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido do corpo:

public ActionResult<Pet> Create([FromBody] Pet pet)

A Pet classe especifica que sua Breed propriedade é populada de um parâmetro de cadeia de caracteres de consulta:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

No exemplo anterior:

  • O [FromQuery] atributo é ignorado.
  • A Breed propriedade não é preenchida de um parâmetro de cadeia de caracteres de consulta.

Os formatadores de entrada leem apenas o corpo e não entendem os atributos de origem de associação. Se um valor adequado for encontrado no corpo, esse valor será usado para preencher a Breed propriedade.

Não aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação for lido por um formatador de entrada, ele não estará mais disponível para ser lido novamente para associar outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de model binding pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para model binding de outras fontes. Por exemplo, talvez você queira dados de s ou estado de cookiesessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implementa IValueProvider.
  • Crie uma classe que implementa IValueProviderFactory.
  • Registre a classe de alocador em Program.cs.

O exemplo inclui um provedor de valores e um exemplo de fábrica que obtém valores de cookies. Registrar fábricas de provedores de valores personalizados em Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

O código anterior coloca o provedor de valor personalizado após todos os provedores de valores internos. Para torná-lo o primeiro na lista, chame Insert(0, new CookieValueProviderFactory()), em vez de Add.

Nenhuma fonte para uma propriedade de modelo

Por padrão, um erro de estado de modelo não será criado se nenhum valor for encontrado para uma propriedade de modelo. A propriedade é definida como null ou um valor padrão:

  • Tipos simples anuláveis definidos como null.
  • Tipos de valor não anuláveis são definidos como default(T). Por exemplo, um parâmetro int id é definido como 0.
  • Para tipos complexos, o model binding cria uma instância usando o construtor padrão sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que matrizes byte[] são definidas como null.

Se o estado do modelo deve ser invalidado quando nada for encontrado em campos de formulário para uma propriedade de modelo, use o [BindRequired] atributo.

Observe que esse [BindRequired] comportamento se aplica à associação de modelo de dados de formulário postados, não a JSdados ON ou XML em um corpo da solicitação. Dados do corpo da solicitação são tratados pelos formatadores de entrada.

Erros de conversão de tipos

Se uma fonte for encontrada, mas não puder ser convertida no tipo de destino, o estado do modelo será sinalizado como inválido. O parâmetro ou a propriedade de destino é definido como nulo ou um valor padrão, conforme observado na seção anterior.

Em um controlador de API que tem o atributo [ApiController], um estado de modelo inválido resulta em uma resposta HTTP 400 automática.

Em uma Razor página, reprodução redisplay da página com uma mensagem de erro:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Quando a página é reproduçada pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade do modelo foi definida como nulo ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Se você quiser redicionar os dados inválidas no campo de formulário, considere tornar a propriedade do modelo uma cadeia de caracteres e fazer a conversão de dados manualmente.

A mesma estratégia será recomendada se você não desejar que erros de conversão de tipo resultem em erros de estado de modelo. Nesse caso, torne a propriedade de modelo uma cadeia de caracteres.

Tipos simples

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres de origem incluem os seguintes:

Tipos complexos

Um tipo complexo deve ter um construtor padrão público e propriedades públicas graváveis para associar. Quando ocorre model binding, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, a associação de modelo examina as fontes para o padrão de nomeprefix.property_name. Se nada for encontrado, ela procurará apenas property_name sem o prefixo. A decisão de usar o prefixo não é tomada por propriedade. Por exemplo, com uma consulta que ?Instructor.Id=100&Name=foocontém, associado ao método OnGet(Instructor instructor), o objeto de tipo Instructor resultante contém:

  • Id definido como 100.
  • Name definido como null. A associação de modelo espera Instructor.Name porque Instructor.Id foi usada no parâmetro de consulta anterior.

Para associação a um parâmetro, o prefixo é o nome do parâmetro. Para associação a uma propriedade pública PageModel, o prefixo é o nome da propriedade pública. Alguns atributos têm uma propriedade Prefix que permite substituir o uso padrão do nome da propriedade ou do parâmetro.

Por exemplo, suponha que o tipo complexo seja a seguinte classe Instructor:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Prefixo = nome do parâmetro

Se o modelo a ser associado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave instructorToUpdate.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser associado for uma propriedade chamada Instructor do controlador ou da classe PageModel:

[BindProperty]
public Instructor Instructor { get; set; }

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo personalizado

Se o modelo a ser associado for um parâmetro denominado instructorToUpdate e um atributo Bind especificar Instructor como o prefixo:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar o model binding de tipos complexos:

Aviso

Esses atributos afetam o model binding quando os dados de formulário postados são a origem dos valores. Eles não afetam os formatadores de entrada, que processam corpos de solicitação ON e XML postados JS. O formatadores de entrada são explicados posteriormente neste artigo.

Atributo [Bind]

Pode ser aplicado a uma classe ou a um parâmetro de método. Especifica quais propriedades de um modelo devem ser incluídas no model binding. [Bind]não afeta os formatadores de entrada.

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando qualquer método de ação ou o manipulador é chamado:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando o método OnPost é chamado:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

O atributo [Bind] pode ser usado para proteção contra o excesso de postagem cenários de criar. Ele não funciona bem em cenários de edição, pois as propriedades excluídas são definidas como nulas ou um valor padrão, em vez de serem deixadas inalteradas. Para defesa contra o excesso de postagem, são recomendados modelos de exibição, em vez do atributo [Bind]. Para obter mais informações, veja Observação de segurança sobre o excesso de postagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. Ele permite especificar o tipo de associador de modelo usado para associar a instância ou tipo específico. Por exemplo:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

O [ModelBinder] atributo também pode ser usado para alterar o nome de uma propriedade ou parâmetro quando ele está sendo associado ao modelo:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atributo [BindRequired]

Faz com que o model binding adicione um erro de estado de modelo se a associação não puder ocorrer para a propriedade de um modelo. Veja um exemplo:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Veja também a discussão sobre o atributo [Required] em Validação do modelo.

Atributo [BindNever]

Pode ser aplicado a uma propriedade ou um tipo. Impede que o model binding configure a propriedade de um modelo. Quando aplicado a um tipo, o sistema de associação de modelo exclui todas as propriedades definidas pelo tipo. Veja um exemplo:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Coleções

Para destinos que são coleções de tipos simples, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro a ser associado seja uma matriz chamada selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dados de cadeia de caracteres de consulta ou formulário podem estar em um dos seguintes formatos:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

Evite associar um parâmetro ou uma propriedade nomeada index ou Index se ele for adjacente a um valor de coleção. A associação de modelo tenta usar index como o índice para a coleção, o que pode resultar em associação incorreta. Por exemplo, considere a seguinte ação:

public IActionResult Post(string index, List<Product> products)

No código anterior, o parâmetro de index cadeia de caracteres de consulta associa-se ao parâmetro de index método e também é usado para associar a coleção de produtos. Renomear o index parâmetro ou usar um atributo de associação de modelo para configurar a associação evita esse problema:

public IActionResult Post(string productIndex, List<Product> products)
selectedCourses[]=1050&selectedCourses[]=2000
  • Para todos os formatos do exemplo anterior, o model binding passa uma matriz de dois itens para o parâmetro selectedCourses:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Formatos de dados que usam números subscritos (... [0]... [1]...) devem garantir que eles estejam numerados em sequência, começando com zero. Se houver quaisquer intervalos na numeração de subscrito, todos os itens após o intervalo serão ignorados. Por exemplo, se os subscritos forem 0 e 2, em vez de 0 e 1, o segundo item será ignorado.

Dicionários

Para destinos Dictionary, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro de destino seja um Dictionary<int, string> chamado selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Os dados de cadeia de caracteres de consulta ou formulário postados podem se parecer com um dos exemplos a seguir:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Para todos os formatos do exemplo anterior, o model binding passa um dicionário de dois itens para o parâmetro selectedCourses:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Tipos de associação e registro do construtor

A associação de modelo requer que tipos complexos tenham um construtor sem parâmetros. Os System.Text.Json formatadores de entrada baseados e Newtonsoft.Json baseados dão suporte à desserialização de classes que não têm um construtor sem parâmetros.

Os tipos de registro são uma ótima maneira de representar de forma sucinta os dados na rede. ASP.NET Core dá suporte à associação de modelos e à validação de tipos de registro com um único construtor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Ao validar tipos de registro, o runtime pesquisa metadados de associação e validação especificamente em parâmetros e não em propriedades.

A estrutura permite associar e validar tipos de registro:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Para que o anterior funcione, o tipo deve:

  • Seja um tipo de registro.
  • Tenha exatamente um construtor público.
  • Contêm parâmetros que têm uma propriedade com o mesmo nome e tipo. Os nomes não devem ser diferentes por caso.

POCOs sem construtores sem parâmetros

POCOs que não têm construtores sem parâmetros não podem ser associados.

O código a seguir resulta em uma exceção informando que o tipo deve ter um construtor sem parâmetros:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Tipos de registro com construtores criados manualmente

Tipos de registro com construtores criados manualmente que se parecem com construtores primários funcionam

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Tipos de registro, validação e metadados de associação

Para tipos de registro, os metadados de validação e associação em parâmetros são usados. Todos os metadados nas propriedades são ignorados

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validação e metadados

A validação usa metadados no parâmetro, mas usa a propriedade para ler o valor. No caso comum com construtores primários, os dois seriam idênticos. No entanto, há maneiras de derrotá-lo:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel não atualiza parâmetros em um tipo de registro

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Nesse caso, o MVC não tentará associar Name novamente. No entanto, Age é permitido ser atualizado

Comportamento de globalização de dados de rota de associação de modelo e cadeias de caracteres de consulta

O provedor de valor de rota ASP.NET Core e o provedor de valor da cadeia de consulta:

  • Tratar valores como cultura invariável.
  • Espere que as URLs sejam invariáveis à cultura.

Por outro lado, os valores provenientes dos dados do formulário passam por uma conversão sensível à cultura. Isso é feito por design para que as URLs sejam compartilháveis entre localidades.

Para tornar o provedor de valores de rota ASP.NET Core e o provedor de valor da cadeia de consulta passarem por uma conversão sensível à cultura:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipos de dados especiais

Há alguns tipos de dados especiais com o model binding pode lidar.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. Também compatível com IEnumerable<IFormFile> para vários arquivos.

CancellationToken

As ações podem, opcionalmente, associar um CancellationToken parâmetro como um parâmetro. Isso associa RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é anulada. As ações podem usar esse parâmetro para cancelar operações assíncronas de execução longa executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados de formulário postados.

Formatadores de entrada

Os dados no corpo da solicitação podem estar em JSON, XML ou em algum outro formato. Para analisar esses dados, o model binding usa um formatador de entrada configurado para lidar com um determinado tipo de conteúdo. Por padrão, ASP.NET Core inclui JSformatadores de entrada baseados em ON para lidar com JSdados ON. Você pode adicionar outros formatadores para outros tipos de conteúdo.

O ASP.NET Core seleciona formatadores de entrada com base no atributo Consome. Se nenhum atributo estiver presente, ele usará o cabeçalho Content-Type.

Para usar os formatadores de entrada XML internos:

Personalizar a associação de modelo com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura de dados do corpo da solicitação. Para personalizar esse processo, configure as APIs usadas pelo formatador de entrada. Esta seção descreve como personalizar o System.Text.Jsonformatador de entrada baseado para entender um tipo personalizado chamado ObjectId.

Considere o modelo a seguir, que contém uma propriedade personalizada ObjectId chamada Id:

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Para personalizar o processo de associação de modelo ao usar System.Text.Json, crie uma classe derivada de JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Para usar um conversor personalizado, aplique o JsonConverterAttribute atributo ao tipo. No exemplo a seguir, o ObjectId tipo é configurado como ObjectIdConverter seu conversor personalizado:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Para obter mais informações, consulte Como escrever conversores personalizados.

Excluir tipos especificados do model binding

O comportamento dos sistemas de associação e validação do modelo é controlado por ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar o model binding ou a validação para tipos especificados.

Para desabilitar o model binding em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Program.cs. Por exemplo, para desabilitar o model binding em todos os modelos do tipo System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Para desabilitar a validação nas propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Program.cs. Por exemplo, para desabilitar a validação nas propriedades do tipo System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Associadores de modelos personalizados

Você pode estender o model binding escrevendo um associador de modelos personalizado e usando o atributo [ModelBinder] para selecioná-la para um determinado destino. Saiba mais sobre o model binding personalizado.

Model binding manual

O model binding pode ser invocado manualmente usando o método TryUpdateModelAsync. O método é definido nas classes ControllerBase e PageModel. Sobrecargas de método permitem que você especifique o provedor de valor e prefixo a ser usado. O método retornará false se o model binding falhar. Veja um exemplo:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync usa provedores de valor para obter dados do corpo do formulário, cadeia de caracteres de consulta e dados de rota. TryUpdateModelAsync normalmente é:

  • Usado com Razor páginas e aplicativos MVC usando controladores e exibições para evitar a postagem excessiva.
  • Não usado com uma API Web, a menos que seja consumido de dados de formulário, cadeias de caracteres de consulta e dados de rota. Os pontos de extremidade da API Web que consomem JSON usam formatadores de entrada para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome do atributo segue o padrão dos atributos de model binding que especificam uma fonte de dados. Porém, não se trata de associar dados de um provedor de valor. Ele obtém uma instância de um tipo do contêiner de injeção de dependência. Sua finalidade é oferecer uma alternativa à injeção de construtor para quando você precisa de um serviço somente se um determinado método for chamado.

Se uma instância do tipo não for registrada no contêiner de injeção de dependência, o aplicativo gerará uma exceção ao tentar associar o parâmetro. Para tornar o parâmetro opcional, use uma das seguintes abordagens:

  • Torne o parâmetro anulável.
  • Defina um valor padrão para o parâmetro.

Para parâmetros anuláveis, verifique se o parâmetro não null é antes de acessá-lo.

Recursos adicionais

Este artigo explica o que é model binding, como ele funciona e como personalizar seu comportamento.

Exibir ou baixar um código de exemplo (como baixar).

O que é o model binding

Controladores e Razor páginas funcionam com dados provenientes de solicitações HTTP. Por exemplo, dados de rota podem fornecer uma chave de registro e campos de formulário postados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres em tipos .NET seria uma tarefa entediante e propensa a erro. O model binding automatiza esse processo. O sistema de model binding:

  • Recupera dados de várias fontes, como dados de rota, campos de formulário e cadeias de caracteres de consulta.
  • Fornece os dados para controladores e Razor páginas em parâmetros de método e propriedades públicas.
  • Converte dados de cadeia de caracteres em tipos .NET.
  • Atualiza as propriedades de tipos complexos.

Exemplo

Suponha que você tenha o seguinte método de ação:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

E o aplicativo receba uma solicitação com esta URL:

http://contoso.com/api/pets/2?DogsOnly=true

A associação de modelo passa pelas seguintes etapas depois que o sistema de roteamento seleciona o método de ação:

  • Localiza o primeiro parâmetro de GetById, um número inteiro denominado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" em dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booliano chamado dogsOnly.
  • Examina as fontes e localiza "DogsOnly=true" na cadeia de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a cadeia de caracteres "true" no booliano true.

A estrutura então chama o método GetById, passando 2 para o parâmetro id e true para o parâmetro dogsOnly.

No exemplo anterior, os destinos do model binding são parâmetros de método que são tipos simples. Destinos também podem ser as propriedades de um tipo complexo. Depois de cada propriedade ser associada com êxito, a validação do modelo ocorre para essa propriedade. O registro de quais dados estão associados ao modelo, além de quaisquer erros de validação ou de associação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid.

Destinos

O model binding tenta encontrar valores para os seguintes tipos de destinos:

  • Parâmetros do método de ação do controlador para o qual uma solicitação é roteada.
  • Parâmetros do método do manipulador Pages para o Razor qual uma solicitação é roteada.
  • Propriedades públicas de um controlador ou classe PageModel, se especificadas por atributos.

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para fazer o model binding ter essa propriedade como destino:

public class EditModel : InstructorsPageModel
{
    [BindProperty]
    public Instructor Instructor { get; set; }

Atributo [BindProperties]

Disponível no ASP.NET Core 2.1 e posteriores. Pode ser aplicado a um controlador ou classe PageModel para informar o model binding para ter todas as propriedades públicas da classe como destino:

[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
    public Instructor Instructor { get; set; }

Model binding para solicitações HTTP GET

Por padrão, as propriedades não são vinculadas para solicitações HTTP GET. Normalmente, tudo o que você precisa para uma solicitação GET é um parâmetro de ID de registro. A ID do registro é usada para pesquisar o item no banco de dados. Portanto, não é necessário associar uma propriedade que contém uma instância do modelo. Em cenários em que você deseja associar propriedades a dados de solicitações GET, defina a propriedade SupportsGet como true:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }

Origens

Por padrão, o model binding obtém dados na forma de pares chave-valor das seguintes fontes em uma solicitação HTTP:

  1. Campos de formulário
  2. O corpo da solicitação (para controladores que têm o atributo [ApiController]).
  3. Rotear dados
  4. Parâmetros de cadeia de caracteres de consulta
  5. Arquivos carregados

Para cada parâmetro ou propriedade de destino, as fontes são verificadas na ordem indicada na lista anterior. Há algumas exceções:

  • Os valores de cadeia de caracteres de consulta e dados de rota são usados apenas para tipos simples.
  • Arquivos carregados são associados apenas a tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

Se a origem padrão não estiver correta, use um dos seguintes atributos para especificar a origem:

  • [FromQuery] – Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] – Obtém valores de dados de rota.
  • [FromForm] - Obtém valores de campos de formulário postados.
  • [FromBody] – Obtém valores do corpo da solicitação.
  • [FromHeader] – Obtém valores de cabeçalhos HTTP.

Esses atributos:

  • São adicionados às propriedades de modelo individualmente (não à classe de modelo), como no exemplo a seguir:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Opcionalmente, aceite um valor de nome de modelo no construtor. Essa opção é fornecida caso o nome da propriedade não corresponda ao valor na solicitação. Por exemplo, o valor na solicitação pode ser um cabeçalho com um hífen em seu nome, como no exemplo a seguir:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atributo [FromBody]

Aplique o [FromBody] atributo a um parâmetro para preencher suas propriedades do corpo de uma solicitação HTTP. O runtime ASP.NET Core delega a responsabilidade de ler o corpo para um formatador de entrada. O formatadores de entrada são explicados posteriormente neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de associação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido do corpo:

public ActionResult<Pet> Create([FromBody] Pet pet)

A Pet classe especifica que sua Breed propriedade é preenchida a partir de um parâmetro de cadeia de caracteres de consulta:

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

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; }
}

No exemplo anterior:

  • O [FromQuery] atributo é ignorado.
  • A Breed propriedade não é preenchida a partir de um parâmetro de cadeia de caracteres de consulta.

Os formatadores de entrada leem apenas o corpo e não entendem os atributos de origem de associação. Se um valor adequado for encontrado no corpo, esse valor será usado para preencher a Breed propriedade.

Não aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação for lido por um formatador de entrada, ele não estará mais disponível para ser lido novamente para associar outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de model binding pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para model binding de outras fontes. Por exemplo, talvez você queira dados de s ou estado de cookiesessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implementa IValueProvider.
  • Crie uma classe que implementa IValueProviderFactory.
  • Registre a classe de alocador em Startup.ConfigureServices.

O aplicativo de exemplo inclui um provedor de valor e um exemplo de fábrica que obtém valores de cookies. Aqui está o código de registro em Startup.ConfigureServices:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

O código mostrado coloca o provedor de valor personalizado depois de todos os provedores de valor internos. Para torná-lo o primeiro na lista, chame Insert(0, new CookieValueProviderFactory()), em vez de Add.

Nenhuma fonte para uma propriedade de modelo

Por padrão, um erro de estado de modelo não será criado se nenhum valor for encontrado para uma propriedade de modelo. A propriedade é definida como null ou um valor padrão:

  • Tipos simples anuláveis definidos como null.
  • Tipos de valor não anuláveis são definidos como default(T). Por exemplo, um parâmetro int id é definido como 0.
  • Para tipos complexos, o model binding cria uma instância usando o construtor padrão sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que matrizes byte[] são definidas como null.

Se o estado do modelo deve ser invalidado quando nada for encontrado em campos de formulário para uma propriedade de modelo, use o [BindRequired] atributo.

Observe que esse [BindRequired] comportamento se aplica à associação de modelo de dados de formulário postados, não a JSdados ON ou XML em um corpo da solicitação. Dados do corpo da solicitação são tratados pelos formatadores de entrada.

Erros de conversão de tipos

Se uma fonte for encontrada, mas não puder ser convertida no tipo de destino, o estado do modelo será sinalizado como inválido. O parâmetro ou a propriedade de destino é definido como nulo ou um valor padrão, conforme observado na seção anterior.

Em um controlador de API que tem o atributo [ApiController], um estado de modelo inválido resulta em uma resposta HTTP 400 automática.

Em uma Razor página, redimize a página com uma mensagem de erro:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _instructorsInMemoryStore.Add(Instructor);
    return RedirectToPage("./Index");
}

A validação do lado do cliente captura a maioria dos dados inválidos que, de outra forma, seriam enviados para um Razor formulário pages. Essa validação torna difícil disparar o código realçado anterior. O aplicativo de exemplo inclui um botão Enviar com Data Inválida que coloca os dados inválidos no campo Data de Contratação e envia o formulário. Esse botão mostra como o código para exibir novamente a página funciona quando ocorrem erros de conversão de dados.

Quando a página é exibida novamente pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade do modelo foi definida como nulo ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Porém, se você quiser exibir novamente os dados inválidos no campo de formulário, considere tornar a propriedade do modelo uma cadeia de caracteres e fazer a conversão de dados manualmente.

A mesma estratégia será recomendada se você não desejar que erros de conversão de tipo resultem em erros de estado de modelo. Nesse caso, torne a propriedade de modelo uma cadeia de caracteres.

Tipos simples

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres de origem incluem os seguintes:

Tipos complexos

Um tipo complexo deve ter um construtor padrão público e propriedades públicas graváveis para associar. Quando ocorre model binding, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, o model binding examina as fontes em busca do nome padrão prefix.property_name. Se nada for encontrado, ela procurará apenas property_name sem o prefixo.

Para associação a um parâmetro, o prefixo é o nome do parâmetro. Para associação a uma propriedade pública PageModel, o prefixo é o nome da propriedade pública. Alguns atributos têm uma propriedade Prefix que permite substituir o uso padrão do nome da propriedade ou do parâmetro.

Por exemplo, suponha que o tipo complexo seja a seguinte classe Instructor:

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Prefixo = nome do parâmetro

Se o modelo a ser associado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave instructorToUpdate.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser associado for uma propriedade chamada Instructor do controlador ou da classe PageModel:

[BindProperty]
public Instructor Instructor { get; set; }

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Prefixo personalizado

Se o modelo a ser associado for um parâmetro denominado instructorToUpdate e um atributo Bind especificar Instructor como o prefixo:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

O model binding começa examinando as fontes para a chave Instructor.ID. Se ela não for encontrada, procurará ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar o model binding de tipos complexos:

  • [Bind]
  • [BindRequired]
  • [BindNever]

Aviso

Esses atributos afetam o model binding quando os dados de formulário postados são a origem dos valores. Eles não afetam os formatadores de entrada, que processam corpos de solicitação ON e XML postados JS. O formatadores de entrada são explicados posteriormente neste artigo.

Atributo [Bind]

Pode ser aplicado a uma classe ou a um parâmetro de método. Especifica quais propriedades de um modelo devem ser incluídas no model binding. [Bind]não afeta os formatadores de entrada.

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando qualquer método de ação ou o manipulador é chamado:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

No exemplo a seguir, somente as propriedades especificadas do modelo Instructor são associadas quando o método OnPost é chamado:

[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

O atributo [Bind] pode ser usado para proteção contra o excesso de postagem cenários de criar. Ele não funciona bem em cenários de edição, pois as propriedades excluídas são definidas como nulas ou um valor padrão, em vez de serem deixadas inalteradas. Para defesa contra o excesso de postagem, são recomendados modelos de exibição, em vez do atributo [Bind]. Para obter mais informações, veja Observação de segurança sobre o excesso de postagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. Ele permite especificar o tipo de associador de modelo usado para associar a instância ou o tipo específico. Por exemplo:

[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

O [ModelBinder] atributo também pode ser usado para alterar o nome de uma propriedade ou parâmetro quando está sendo associado ao modelo:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    public string Name { get; set; }
}

Atributo [BindRequired]

Somente pode ser aplicado às propriedades de modelo, não aos parâmetros do método. Faz com que o model binding adicione um erro de estado de modelo se a associação não puder ocorrer para a propriedade de um modelo. Veja um exemplo:

public class InstructorWithCollection
{
    public int ID { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Hire Date")]
    [BindRequired]
    public DateTime HireDate { get; set; }

Veja também a discussão sobre o atributo [Required] em Validação do modelo.

Atributo [BindNever]

Somente pode ser aplicado às propriedades de modelo, não aos parâmetros do método. Impede que o model binding configure a propriedade de um modelo. Veja um exemplo:

public class InstructorWithDictionary
{
    [BindNever]
    public int ID { get; set; }

Coleções

Para destinos que são coleções de tipos simples, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro a ser associado seja uma matriz chamada selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Dados de cadeia de caracteres de consulta ou formulário podem estar em um dos seguintes formatos:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Evite associar um parâmetro ou uma propriedade nomeada index ou Index se estiver adjacente a um valor de coleção. A associação de modelo tenta usar index como índice para a coleção, o que pode resultar em associação incorreta. Por exemplo, considere a seguinte ação:

    public IActionResult Post(string index, List<Product> products)
    

    No código anterior, o parâmetro de index cadeia de caracteres de consulta se associa ao parâmetro do index método e também é usado para associar a coleção de produtos. Renomear o index parâmetro ou usar um atributo de associação de modelo para configurar a associação evita esse problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • O formato a seguir é compatível apenas com os dados de formulário:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos os formatos do exemplo anterior, o model binding passa uma matriz de dois itens para o parâmetro selectedCourses:

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Formatos de dados que usam números subscritos (... [0]... [1]...) devem garantir que eles estejam numerados em sequência, começando com zero. Se houver quaisquer intervalos na numeração de subscrito, todos os itens após o intervalo serão ignorados. Por exemplo, se os subscritos forem 0 e 2, em vez de 0 e 1, o segundo item será ignorado.

Dicionários

Para destinos Dictionary, o model binding procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, procurará um dos formatos compatíveis sem o prefixo. Por exemplo:

  • Suponha que o parâmetro de destino seja um Dictionary<int, string> chamado selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Os dados de cadeia de caracteres de consulta ou formulário postados podem se parecer com um dos exemplos a seguir:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Para todos os formatos do exemplo anterior, o model binding passa um dicionário de dois itens para o parâmetro selectedCourses:

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Tipos de associação e registro do construtor

A associação de modelo requer que tipos complexos tenham um construtor sem parâmetros. Os System.Text.Json formatadores de entrada baseados e Newtonsoft.Json baseados dão suporte à desserialização de classes que não têm um construtor sem parâmetros.

O C# 9 apresenta tipos de registro, que são uma ótima maneira de representar dados de forma sucinta na rede. ASP.NET Core adiciona suporte para associação de modelo e validação de tipos de registro com um único construtor:

public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
       ...
   }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
...
Age: <input asp-for="Age" />

Ao validar tipos de registro, o runtime procura metadados de associação e validação especificamente em parâmetros e não em propriedades.

A estrutura permite associar e validar tipos de registro:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Para que o anterior funcione, o tipo deve:

  • Ser um tipo de registro.
  • Tenha exatamente um construtor público.
  • Contém parâmetros que têm uma propriedade com o mesmo nome e tipo. Os nomes não devem diferir por caso.

POCOs sem construtores sem parâmetros

POCOs que não têm construtores sem parâmetros não podem ser associados.

O código a seguir resulta em uma exceção informando que o tipo deve ter um construtor sem parâmetros:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
   public Person(string Name) : this (Name, 0);
}

Tipos de registro com construtores criados manualmente

Tipos de registro com construtores criados manualmente que se parecem com construtores primários funcionam

public record Person
{
   public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);

   public string Name { get; set; }
   public int Age { get; set; }
}

Tipos de registro, validação e metadados de associação

Para tipos de registro, metadados de validação e associação em parâmetros são usados. Todos os metadados nas propriedades são ignorados

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validação e metadados

A validação usa metadados no parâmetro, mas usa a propriedade para ler o valor. No caso comum com construtores primários, os dois seriam idênticos. No entanto, há maneiras de derrotá-lo:

public record Person([Required] string Name)
{
   private readonly string _name;
   public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}

TryUpdateModel não atualiza parâmetros em um tipo de registro

public record Person(string Name)
{
   public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Nesse caso, o MVC não tentará associar Name novamente. No entanto, Age é permitido ser atualizado

Comportamento de globalização de dados de rota de associação de modelo e cadeias de caracteres de consulta

O provedor de valor de rota ASP.NET Core e o provedor de valor da cadeia de caracteres de consulta:

  • Tratar valores como cultura invariável.
  • Espere que as URLs sejam invariáveis à cultura.

Por outro lado, os valores provenientes dos dados do formulário passam por uma conversão sensível à cultura. Isso é feito por design para que as URLs sejam compartilháveis entre localidades.

Para tornar o provedor de valores de rota ASP.NET Core e o provedor de valor da cadeia de consulta passarem por uma conversão sensível à cultura:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        var index = options.ValueProviderFactories.IndexOf(
            options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
        options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
    });
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query != null && query.Count > 0)
        {
            var valueProvider = new QueryStringValueProvider(
                BindingSource.Query,
                query,
                CultureInfo.CurrentCulture);

            context.ValueProviders.Add(valueProvider);
        }

        return Task.CompletedTask;
    }
}

Tipos de dados especiais

Há alguns tipos de dados especiais com o model binding pode lidar.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. Também compatível com IEnumerable<IFormFile> para vários arquivos.

CancellationToken

As ações podem, opcionalmente, associar um CancellationToken parâmetro como um parâmetro. Isso associa RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é anulada. As ações podem usar esse parâmetro para cancelar operações assíncronas de execução longa executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados de formulário postados.

Formatadores de entrada

Os dados no corpo da solicitação podem estar em JSON, XML ou em algum outro formato. Para analisar esses dados, o model binding usa um formatador de entrada configurado para lidar com um determinado tipo de conteúdo. Por padrão, ASP.NET Core inclui JSformatadores de entrada baseados em ON para lidar com JSdados ON. Você pode adicionar outros formatadores para outros tipos de conteúdo.

O ASP.NET Core seleciona formatadores de entrada com base no atributo Consome. Se nenhum atributo estiver presente, ele usará o cabeçalho Content-Type.

Para usar os formatadores de entrada XML internos:

  • Instale o pacote do NuGet Microsoft.AspNetCore.Mvc.Formatters.Xml.

  • Em Startup.ConfigureServices, chame AddXmlSerializerFormatters ou AddXmlDataContractSerializerFormatters.

    services.AddRazorPages()
        .AddMvcOptions(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters();
    
  • Aplique o atributo Consumes às classes de controlador ou aos métodos de ação que devem esperar XML no corpo da solicitação.

    [HttpPost]
    [Consumes("application/xml")]
    public ActionResult<Pet> Create(Pet pet)
    

    Para obter mais informações, veja Introdução à serialização XML.

Personalizar a associação de modelo com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura de dados do corpo da solicitação. Para personalizar esse processo, configure as APIs usadas pelo formatador de entrada. Esta seção descreve como personalizar o System.Text.Jsonformatador de entrada baseado para entender um tipo personalizado chamado ObjectId.

Considere o modelo a seguir, que contém uma propriedade personalizada ObjectId chamada Id:

public class ModelWithObjectId
{
    public ObjectId Id { get; set; }
}

Para personalizar o processo de associação de modelo ao usar System.Text.Json, crie uma classe derivada de JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
    }

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(value.Id);
    }
}

Para usar um conversor personalizado, aplique o JsonConverterAttribute atributo ao tipo. No exemplo a seguir, o ObjectId tipo é configurado como ObjectIdConverter seu conversor personalizado:

[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
    public ObjectId(int id) =>
        Id = id;

    public int Id { get; }
}

Para obter mais informações, consulte Como escrever conversores personalizados.

Excluir tipos especificados do model binding

O comportamento dos sistemas de associação e validação do modelo é controlado por ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar o model binding ou a validação para tipos especificados.

Para desabilitar o model binding em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Startup.ConfigureServices. Por exemplo, para desabilitar o model binding em todos os modelos do tipo System.Version:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Para desabilitar a validação nas propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Startup.ConfigureServices. Por exemplo, para desabilitar a validação nas propriedades do tipo System.Guid:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Associadores de modelos personalizados

Você pode estender o model binding escrevendo um associador de modelos personalizado e usando o atributo [ModelBinder] para selecioná-la para um determinado destino. Saiba mais sobre o model binding personalizado.

Model binding manual

O model binding pode ser invocado manualmente usando o método TryUpdateModelAsync. O método é definido nas classes ControllerBase e PageModel. Sobrecargas de método permitem que você especifique o provedor de valor e prefixo a ser usado. O método retornará false se o model binding falhar. Veja um exemplo:

if (await TryUpdateModelAsync<InstructorWithCollection>(
    newInstructor,
    "Instructor",
    i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
    _instructorsInMemoryStore.Add(newInstructor);
    return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();

TryUpdateModelAsync usa provedores de valor para obter dados do corpo do formulário, cadeia de caracteres de consulta e dados de rota. TryUpdateModelAsync normalmente é:

  • Usado com Razor páginas e aplicativos MVC usando controladores e exibições para evitar a postagem excessiva.
  • Não usado com uma API Web, a menos que seja consumido de dados de formulário, cadeias de caracteres de consulta e dados de rota. Os pontos de extremidade da API Web que consomem JSON usam formatadores de entrada para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome do atributo segue o padrão dos atributos de model binding que especificam uma fonte de dados. Porém, não se trata de associar dados de um provedor de valor. Ele obtém uma instância de um tipo do contêiner de injeção de dependência. Sua finalidade é oferecer uma alternativa à injeção de construtor para quando você precisa de um serviço somente se um determinado método for chamado.

Recursos adicionais