Partilhar via


Associação de Modelos em ASP.NET Core

Note

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Warning

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica o que é a vinculação de modelo, como ela funciona e como personalizar seu comportamento.

O que é a vinculação de modelo

Controladores e Razor páginas trabalham com dados provenientes de solicitações HTTP. Por exemplo, os dados de rota podem fornecer uma chave de registro e os campos de formulário lançados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres para tipos .NET seria tedioso e propenso a erros. A vinculação de modelo automatiza esse processo. O sistema de associação de modelos:

  • 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 propriedades de tipos complexos.

Example

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

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

E o aplicativo recebe uma solicitação com este URL:

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

A vinculaçã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 inteiro chamado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" nos dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booleano chamado dogsOnly.
  • Examina as fontes e encontra "DogsOnly=true" na cadeia de caracteres de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a string "true" em booleano 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 de vinculação de modelo são parâmetros de método que são tipos simples . Os alvos também podem ser as propriedades de um tipo complexo. Depois que cada propriedade é vinculada com êxito, ocorre a validação do modelo para essa propriedade. O registro de quais dados estão vinculados ao modelo, e quaisquer erros de vinculação ou validação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid .

Targets

A associação de modelos tenta encontrar valores para os seguintes tipos de alvos:

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

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para que a vinculação de modelo tenha como alvo essa propriedade.

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

    // ...
}

Atributo [BindProperties]

Pode ser aplicado a um controlador ou PageModel classe para informar a vinculação de modelo para direcionar todas as propriedades públicas da classe:

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

    // ...
}

Vinculação de modelo para solicitações HTTP GET

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

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

Tipos simples e complexos de vinculação de modelo

A vinculação de modelo usa definições específicas para os tipos em que opera. Um tipo simples é convertido a partir de uma única cadeia de caracteres usando TypeConverter ou um TryParse método. Um tipo complexo é convertido a partir de vários valores de entrada. O quadro determina a diferença com base na existência de um TypeConverter ou TryParse. Recomendamos criar um conversor de tipo ou usar TryParse para uma conversão string para SomeType que não exija recursos externos ou múltiplas entradas.

Sources

Por padrão, a vinculação de modelo 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. Dados de rota
  4. Parâmetros de string de consulta
  5. Ficheiros carregados

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

  • Os dados de rota e os valores da cadeia de caracteres de consulta são usados apenas para tipos simples .
  • Os arquivos carregados são vinculados apenas aos tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

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

  • [FromQuery] - Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] - Obtém valores a partir 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.

Estes atributos:

  • São adicionados à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 da 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 a partir do corpo de uma solicitação HTTP. O runtime do ASP.NET Core delega a responsabilidade de ler o corpo a um formatador de entrada. Os formatadores de entrada são explicados mais adiante neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de ligação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido a partir 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; } = null!;

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

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 lêem 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 se aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação é lido por um formatador de entrada, ele não está mais disponível para ser lido novamente para vincular outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de vinculação de modelo pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para vinculação de modelo de outras fontes. Por exemplo, você pode querer dados de cookies ou estado da sessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implemente o IValueProvider.
  • Crie uma classe que implemente o IValueProviderFactory.
  • Registre a classe de fábrica em Program.cs.

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

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

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

Nenhuma fonte para uma propriedade de modelo

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

  • Os tipos simples anuláveis são definidos como null.
  • Os 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, a vinculação de modelo cria uma instância usando o construtor padrão, sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que byte[] as matrizes são definidas como null.

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

Observe que esse [BindRequired] comportamento se aplica à vinculação de modelo a partir de dados de formulário postados, não de dados JSON ou XML em um corpo de solicitação. Os dados do corpo da solicitação são tratados por input formatters.

Erros de conversão de tipos

Se uma origem 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 propriedade de destino é definido como null ou um valor padrão, conforme observado na seção anterior.

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

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

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

    // ...

    return RedirectToPage("./Index");
}

Quando a página é reexibida pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade model foi definida como null ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Se você quiser reexibir 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 é recomendada se você não quiser que erros de conversão de tipo resultem em erros de estado do modelo. Nesse caso, torne a propriedade model uma cadeia de caracteres.

Tipos simples

Consulte Vinculação de modelos de tipos simples e complexos para obter explicações sobre tipos simples e complexos.

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres incluem o seguinte:

Vincular com IParsable<T>.TryParse

A IParsable<TSelf>.TryParse API suporta valores de parâmetros de ação do controlador de vinculação:

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

A classe a seguir DateRange implementa IParsable<TSelf> para dar suporte à vinculaçã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 vinculador de modelos usa o método IParsable<TSelf>.TryParse para vincular o DateRange.

A seguinte ação do controlador usa a DateRange classe para vincular 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 à vinculaçã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 seguinte ação do controlador usa a Locale classe para vincular 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 seguinte ação do controlador usa as DateRange classes e Locale para vincular um intervalo de datas com CultureInfo:

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

Vincular com TryParse

A TryParse API suporta valores de parâmetros de ação do controlador de vinculaçã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 vinculação de parâmetros porque, ao contrário do TryParse, não depende da reflexão.

A seguinte DateRangeTP classe 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)
    {
        var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (range?.Length != 2)
        {
            result = default;
            return false;
        }

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

A seguinte ação do controlador usa a DateRangeTP classe para vincular um intervalo de datas:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP 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 graváveis públicas para vincular. Quando a vinculação de modelo ocorre, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, a associação de modelos procura nas fontes pelo padrão de nomeprefix.property_name. Se nada for encontrado, ele procura apenas property_name sem o prefixo. A decisão de usar o prefixo não é feita para cada propriedade individualmente. Por exemplo, com uma consulta contendo ?Instructor.Id=100&Name=foo, vinculado ao método OnGet(Instructor instructor), o objeto resultante do tipo Instructor contém:

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

Note

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

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

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

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 vinculado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

A vinculação de modelo começa examinando as fontes para a chave instructorToUpdate.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser vinculado for uma propriedade nomeada Instructor do controlador ou PageModel classe:

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo personalizado

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

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar a vinculação de modelos de tipos complexos:

Warning

Esses atributos afetam a vinculação do modelo quando os dados do formulário postado são a fonte dos valores. Eles não afetam os formatadores de entrada, que processam os corpos de solicitações JSON e XML enviadas. Os formatadores de entrada são explicados mais adiante 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 na associação de modelo. [Bind] não afeta os formatadores de entrada.

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

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

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

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

O [Bind] atributo pode ser usado para proteger contra sobrepostagem em cenários de criação . Ele não funciona bem em cenários de edição porque as propriedades excluídas são definidas como null ou um valor padrão em vez de serem deixadas inalteradas. Para proteção contra sobrepostagem, os modelos de exibição são recomendados em vez do atributo [Bind]. Para obter mais informações, consulte Nota de segurança sobre sobrepostagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. 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<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 vinculado ao modelo:

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

    // ...
}

Atributo [BindRequired]

Faz com que a associação de modelo adicione um erro de estado de modelo se não for possível associar a propriedade de um modelo. Aqui está um exemplo:

public class InstructorBindRequired
{
    // ...

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

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

Atributo [BindNever]

Pode ser aplicado a uma propriedade ou a um tipo. Impede que a vinculação de modelo defina a propriedade de um modelo. Quando aplicado a um tipo, o sistema de vinculação de modelo exclui todas as propriedades que o tipo define. Aqui está um exemplo:

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

    // ...
}

Collections

Para destinos que são coleções de tipos simples, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados sem o prefixo. Por exemplo:

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

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Os dados da cadeia de caracteres de formulário ou consulta 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 vincular um parâmetro ou uma propriedade nomeada index ou Index se ela for adjacente a um valor de coleção. A vinculação de modelo tenta usar index como índice para a coleção, o que pode resultar em uma vinculação incorreta. Por exemplo, considere a seguinte ação:

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

    No código anterior, o index parâmetro query string se liga ao index parâmetro method e também é usado para vincular a coleção de produtos. Renomear o index parâmetro ou usar um atributo de vinculação de modelo para configurar a associação evita esse problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • O seguinte formato é suportado apenas em dados de formulário:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos os formatos de exemplo anteriores, a vinculação de modelo 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 assegurar que são numerados sequencialmente a partir de zero. Se existirem lacunas na numeração de índice, todos os itens após a lacuna serão ignorados. Por exemplo, se os subscritos forem 0 e 2 em vez de 0 e 1, o segundo item será ignorado.

Dictionaries

Para Dictionary destinos, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados 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 do formulário postado ou da cadeia de caracteres de consulta podem se parecer com um dos seguintes exemplos:

    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 de exemplo anteriores, a vinculação de modelo passa um dicionário de dois itens para o parâmetro selectedCourses.

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

Vinculação de construtor e tipos de registro

A vinculação de modelo requer que tipos complexos tenham um construtor sem parâmetros. System.Text.Json Tanto a Newtonsoft.Json entrada baseada formatters suporta a 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 pela rede. O ASP.NET Core suporta vinculaçã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

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

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

A estrutura permite vincular e validar tipos de registro:

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

Para que o precedente 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 diferir consoante as maiúsculas e minúsculas.

POCOs sem construtores sem parâmetros

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

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

public class Person {
    public 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 o trabalho de construtores primários

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 registo, metadados de validação e ligação

Para os tipos de registo, são utilizados metadados de validação e de ligação nos parâmetros. 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, existem 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á vincular Name novamente. No entanto, Age é permitido ser atualizado

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

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

  • Trate os valores como cultura invariante.
  • Espere que os URLs sejam invariantes de cultura.

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

Para fazer com que o provedor de valores de rota do ASP.NET Core e o provedor de valores de cadeia de consulta passem 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

Existem alguns tipos de dados especiais que a vinculação de modelo pode manipular.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. IEnumerable<IFormFile> também é suportado para vários ficheiros.

CancellationToken

Opcionalmente, as ações podem vincular um CancellationToken como parâmetro. Isso liga RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é abortada. As ações podem usar esse parâmetro para cancelar operações assíncronas de longa duração que são executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados do formulário postado.

Contributos para as questões

Os dados no corpo da solicitação podem estar em JSON, XML ou algum outro formato. Para analisar esses dados, a vinculação de modelo usa um formatador de entrada configurado para lidar com um tipo de conteúdo específico. Por padrão, o ASP.NET Core inclui formatadores de entrada baseados em JSON para processar dados JSON usando System.Text.Json. Você pode adicionar outros formatters para outros tipos de conteúdo.

O formatador de entrada JSON padrão pode ser configurado usando o AddJsonOptions método:

builder.Services.AddControllers().AddJsonOptions(options =>
{
    // Configure property naming policy (camelCase)
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

    // Add enum converter to serialize enums as strings
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());

    // Configure other JSON options
    options.JsonSerializerOptions.WriteIndented = true;
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});

As opções de configuração comuns incluem:

  • Política de nomenclatura de propriedade - Configurar camelCase ou outras convenções de nomenclatura
  • Enum converters - Gerir a serialização de enum como strings
  • Conversores personalizados - Adicionar lógica de serialização específica para o tipo

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

Para usar os formatadores de entrada XML incorporados, siga estes passos:

Personalizar a vinculação de modelos com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura dos 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 formatador de entrada baseado em System.Text.Json para interpretar um tipo personalizado chamado ObjectId.

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

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

Para personalizar o processo de vinculação de modelo ao usar System.Text.Jsono , 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 tipo ObjectId é configurado com o seu ObjectIdConverter conversor personalizado:

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

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

Excluir tipos especificados da vinculação de modelo

O comportamento dos sistemas de vinculação e validação de modelos é impulsionado pelo ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes a MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar a vinculação ou validação de modelo para tipos especificados.

Para desativar a vinculação de modelo em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Program.cs. Por exemplo, para desativar a vinculação de modelo 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 desativar a validação em propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Program.cs. Por exemplo, para desativar a validação em 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 a vinculação de modelo escrevendo um fichário de modelo personalizado e usando o [ModelBinder] atributo para selecioná-lo para um determinado destino. Saiba mais sobre a vinculação de modelo personalizado.

Vinculação manual do modelo

A vinculação de modelo pode ser invocada manualmente usando o TryUpdateModelAsync método. O método é definido em ambas as ControllerBase classes e PageModel classes. As sobrecargas de método permitem especificar o prefixo e o provedor de valor a serem usados. O método retorna false se a vinculação do modelo falhar. Aqui está 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, da cadeia de caracteres de consulta e dos dados de rota. TryUpdateModelAsync é normalmente:

  • Usado com Razor aplicativos Pages e MVC usando controladores e visualizações para evitar postagens excessivas.
  • Não é utilizado com uma API da Web, a menos que seja consumido a partir de dados de formulário, parâmetros de consulta e dados de rota. Os endpoints da Web API que utilizam JSON usam Input formatters para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome desse atributo segue o padrão de atributos de vinculação de modelo que especificam uma fonte de dados. Mas não se trata de vincular dados de um provedor de valor. Obtem-se uma instância de um tipo do contentor de injeção de dependência. Seu objetivo é fornecer uma alternativa à injeção do construtor para quando você precisa de um serviço somente se um método específico for chamado.

Se uma instância do tipo não estiver registrada no contêiner de injeção de dependência, o aplicativo lançará uma exceção ao tentar vincular 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, certifique-se de que o parâmetro não null está antes de acessá-lo.

Desserialização Json+PipeReader no MVC

A partir do .NET 10, as seguintes áreas funcionais do ASP.NET Core usam sobrecargas de JsonSerializer.DeserializeAsync baseadas em PipeReader em vez de Stream:

  • APIs mínimas (vinculação de parâmetros, corpo da solicitação de leitura)
  • MVC (input formatters, modelo)
  • Os métodos de extensão HttpRequestJsonExtensions para leitura do corpo da solicitação como JSON.

Para a maioria dos aplicativos, uma transição do Stream para o PipeReader oferece melhor desempenho sem exigir alterações no código do aplicativo. Mas se o seu aplicativo tiver um conversor personalizado, o conversor pode não lidar Utf8JsonReader.HasValueSequence corretamente. Se isso não acontecer, o resultado pode ser erros como ArgumentOutOfRangeException ou dados ausentes ao desserializar. Você tem as seguintes opções para fazer seu conversor funcionar sem erros relacionados ao PipeReader.

Opção 1: Solução temporária

A solução rápida é voltar a usar o Stream sem suporte ao PipeReader. Para implementar esta opção, defina o switch AppContext "Microsoft.AspNetCore.UseStreamBasedJsonParsing" como "true". Recomendamos que você faça isso apenas como uma solução temporária e atualize seu conversor para oferecer suporte HasValueSequence o mais rápido possível. A opção pode ser removida no .NET 11. Seu único objetivo era dar aos desenvolvedores tempo para atualizar seus conversores.

Opção 2: Uma solução rápida para implementações de JsonConverter

Para essa correção, você aloca uma matriz do ReadOnlySequence. Este exemplo mostra a aparência do código:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

Opção 3: Uma correção mais complicada, mas com melhor desempenho

Essa correção envolve a configuração de um caminho de código separado para o ReadOnlySequence manipulamento:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Para obter mais informações, veja

Recursos adicionais

Este artigo explica o que é a vinculação de modelo, como ela funciona e como personalizar seu comportamento.

O que é a vinculação de modelo

Controladores e Razor páginas trabalham com dados provenientes de solicitações HTTP. Por exemplo, os dados de rota podem fornecer uma chave de registro e os campos de formulário lançados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres para tipos .NET seria tedioso e propenso a erros. A vinculação de modelo automatiza esse processo. O sistema de associação de modelos:

  • 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 propriedades de tipos complexos.

Example

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

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

E o aplicativo recebe uma solicitação com este URL:

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

A vinculaçã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 inteiro chamado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" nos dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booleano chamado dogsOnly.
  • Examina as fontes e encontra "DogsOnly=true" na cadeia de caracteres de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a string "true" em booleano 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 de vinculação de modelo são parâmetros de método que são tipos simples . Os alvos também podem ser as propriedades de um tipo complexo. Depois que cada propriedade é vinculada com êxito, ocorre a validação do modelo para essa propriedade. O registro de quais dados estão vinculados ao modelo, e quaisquer erros de vinculação ou validação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid .

Targets

A associação de modelos tenta encontrar valores para os seguintes tipos de alvos:

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

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para que a vinculação de modelo tenha como alvo essa propriedade.

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

    // ...
}

Atributo [BindProperties]

Pode ser aplicado a um controlador ou PageModel classe para informar a vinculação de modelo para direcionar todas as propriedades públicas da classe:

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

    // ...
}

Vinculação de modelo para solicitações HTTP GET

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

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

Tipos simples e complexos de vinculação de modelo

A vinculação de modelo usa definições específicas para os tipos em que opera. Um tipo simples é convertido a partir de uma única cadeia de caracteres usando TypeConverter ou um TryParse método. Um tipo complexo é convertido a partir de vários valores de entrada. O quadro determina a diferença com base na existência de um TypeConverter ou TryParse. Recomendamos criar um conversor de tipo ou usar TryParse para uma conversão string para SomeType que não exija recursos externos ou múltiplas entradas.

Sources

Por padrão, a vinculação de modelo 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. Dados de rota
  4. Parâmetros de string de consulta
  5. Ficheiros carregados

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

  • Os dados de rota e os valores da cadeia de caracteres de consulta são usados apenas para tipos simples .
  • Os arquivos carregados são vinculados apenas aos tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

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

  • [FromQuery] - Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] - Obtém valores a partir 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.

Estes atributos:

  • São adicionados à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 da 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 a partir do corpo de uma solicitação HTTP. O runtime do ASP.NET Core delega a responsabilidade de ler o corpo a um formatador de entrada. Os formatadores de entrada são explicados mais adiante neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de ligação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido a partir 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; } = null!;

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

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 lêem 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 se aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação é lido por um formatador de entrada, ele não está mais disponível para ser lido novamente para vincular outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de vinculação de modelo pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para vinculação de modelo de outras fontes. Por exemplo, você pode querer dados de cookies ou estado da sessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implemente o IValueProvider.
  • Crie uma classe que implemente o IValueProviderFactory.
  • Registre a classe de fábrica em Program.cs.

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

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

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

Nenhuma fonte para uma propriedade de modelo

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

  • Os tipos simples anuláveis são definidos como null.
  • Os 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, a vinculação de modelo cria uma instância usando o construtor padrão, sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que byte[] as matrizes são definidas como null.

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

Observe que esse [BindRequired] comportamento se aplica à vinculação de modelo a partir de dados de formulário postados, não a dados JSON ou XML em um corpo de solicitação. Os dados do corpo da solicitação são tratados por input formatters.

Erros de conversão de tipos

Se uma origem 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 propriedade de destino é definido como null ou um valor padrão, conforme observado na seção anterior.

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

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

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

    // ...

    return RedirectToPage("./Index");
}

Quando a página é reexibida pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade model foi definida como null ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Se você quiser reexibir 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 é recomendada se você não quiser que erros de conversão de tipo resultem em erros de estado do modelo. Nesse caso, torne a propriedade model uma cadeia de caracteres.

Tipos simples

Consulte Vinculação de modelos de tipos simples e complexos para obter explicações sobre tipos simples e complexos.

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres incluem o seguinte:

Vincular com IParsable<T>.TryParse

A IParsable<TSelf>.TryParse API suporta valores de parâmetros de ação do controlador de vinculação:

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

A classe a seguir DateRange implementa IParsable<TSelf> para dar suporte à vinculaçã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 vinculador de modelos usa o método IParsable<TSelf>.TryParse para vincular o DateRange.

A seguinte ação do controlador usa a DateRange classe para vincular 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 à vinculaçã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 seguinte ação do controlador usa a Locale classe para vincular 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 seguinte ação do controlador usa as DateRange classes e Locale para vincular um intervalo de datas com CultureInfo:

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

Vincular com TryParse

A TryParse API suporta valores de parâmetros de ação do controlador de vinculaçã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 vinculação de parâmetros porque, ao contrário do TryParse, não depende da reflexão.

A seguinte DateRangeTP classe 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)
    {
        var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (range?.Length != 2)
        {
            result = default;
            return false;
        }

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

A seguinte ação do controlador usa a DateRangeTP classe para vincular um intervalo de datas:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP 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 graváveis públicas para vincular. Quando a vinculação de modelo ocorre, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, a associação de modelos procura nas fontes pelo padrão de nomeprefix.property_name. Se nada for encontrado, ele procura apenas property_name sem o prefixo. A decisão de usar o prefixo não é feita para cada propriedade individualmente. Por exemplo, com uma consulta contendo ?Instructor.Id=100&Name=foo, vinculado ao método OnGet(Instructor instructor), o objeto resultante do tipo Instructor contém:

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

Note

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

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

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

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 vinculado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

A vinculação de modelo começa examinando as fontes para a chave instructorToUpdate.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser vinculado for uma propriedade nomeada Instructor do controlador ou PageModel classe:

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo personalizado

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

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar a vinculação de modelos de tipos complexos:

Warning

Esses atributos afetam a vinculação do modelo quando os dados do formulário postado são a fonte dos valores. Eles não afetam os formatadores de entrada, que processam os corpos de solicitações JSON e XML enviadas. Os formatadores de entrada são explicados mais adiante 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 na associação de modelo. [Bind] não afeta os formatadores de entrada.

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

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

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

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

O [Bind] atributo pode ser usado para proteger contra sobrepostagem em cenários de criação . Ele não funciona bem em cenários de edição porque as propriedades excluídas são definidas como null ou um valor padrão em vez de serem deixadas inalteradas. Para proteção contra sobrepostagem, os modelos de exibição são recomendados em vez do atributo [Bind]. Para obter mais informações, consulte Nota de segurança sobre sobrepostagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. 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 vinculado ao modelo:

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

    // ...
}

Atributo [BindRequired]

Faz com que a associação de modelo adicione um erro de estado de modelo se não for possível associar a propriedade de um modelo. Aqui está um exemplo:

public class InstructorBindRequired
{
    // ...

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

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

Atributo [BindNever]

Pode ser aplicado a uma propriedade ou a um tipo. Impede que a vinculação de modelo defina a propriedade de um modelo. Quando aplicado a um tipo, o sistema de vinculação de modelo exclui todas as propriedades que o tipo define. Aqui está um exemplo:

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

    // ...
}

Collections

Para destinos que são coleções de tipos simples, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados sem o prefixo. Por exemplo:

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

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Os dados da cadeia de caracteres de formulário ou consulta 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 vincular um parâmetro ou uma propriedade nomeada index ou Index se ela for adjacente a um valor de coleção. A vinculação de modelo tenta usar index como índice para a coleção, o que pode resultar em uma vinculação incorreta. Por exemplo, considere a seguinte ação:

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

    No código anterior, o index parâmetro query string se liga ao index parâmetro method e também é usado para vincular a coleção de produtos. Renomear o index parâmetro ou usar um atributo de vinculação de modelo para configurar a associação evita esse problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • O seguinte formato é suportado apenas em dados de formulário:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos os formatos de exemplo anteriores, a vinculação de modelo 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 assegurar que são numerados sequencialmente a partir de zero. Se existirem lacunas na numeração de índice, todos os itens após a lacuna serão ignorados. Por exemplo, se os subscritos forem 0 e 2 em vez de 0 e 1, o segundo item será ignorado.

Dictionaries

Para Dictionary destinos, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados 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 do formulário postado ou da cadeia de caracteres de consulta podem se parecer com um dos seguintes exemplos:

    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 de exemplo anteriores, a vinculação de modelo passa um dicionário de dois itens para o parâmetro selectedCourses.

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

Vinculação de construtor e tipos de registro

A vinculação de modelo requer que tipos complexos tenham um construtor sem parâmetros. System.Text.Json Tanto a Newtonsoft.Json entrada baseada formatters suporta a 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 pela rede. O ASP.NET Core suporta vinculaçã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

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

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

A estrutura permite vincular e validar tipos de registro:

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

Para que o precedente 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 diferir consoante as maiúsculas e minúsculas.

POCOs sem construtores sem parâmetros

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

O código a seguir resulta em uma exceção dizendo 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 o trabalho de construtores primários

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 registo, metadados de validação e ligação

Para os tipos de registo, são utilizados metadados de validação e de ligação nos parâmetros. 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, existem 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á vincular Name novamente. No entanto, Age é permitido ser atualizado

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

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

  • Trate os valores como cultura invariante.
  • Espere que os URLs sejam invariantes de cultura.

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

Para fazer com que o provedor de valores de rota do ASP.NET Core e o provedor de valores de cadeia de consulta passem 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

Existem alguns tipos de dados especiais que a vinculação de modelo pode manipular.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. IEnumerable<IFormFile> também é suportado para vários ficheiros.

CancellationToken

Opcionalmente, as ações podem vincular um CancellationToken como parâmetro. Isso liga RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é abortada. As ações podem usar esse parâmetro para cancelar operações assíncronas de longa duração que são executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados do formulário postado.

Contributos para as questões

Os dados no corpo da solicitação podem estar em JSON, XML ou algum outro formato. Para analisar esses dados, a vinculação de modelo usa um formatador de entrada configurado para lidar com um tipo de conteúdo específico. Por padrão, o ASP.NET Core inclui formatadores de entrada baseados em JSON para lidar com dados JSON. Você pode adicionar outros formatters para outros tipos de conteúdo.

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

Para usar os formatadores de entrada XML incorporados, siga estes passos:

Personalizar a vinculação de modelos com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura dos 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 formatador de entrada baseado em System.Text.Json para interpretar um tipo personalizado chamado ObjectId.

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

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

Para personalizar o processo de vinculação de modelo ao usar System.Text.Jsono , 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 tipo ObjectId é configurado com o seu ObjectIdConverter conversor personalizado:

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

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

Excluir tipos especificados da vinculação de modelo

O comportamento dos sistemas de vinculação e validação de modelos é impulsionado pelo ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes a MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar a vinculação ou validação de modelo para tipos especificados.

Para desativar a vinculação de modelo em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Program.cs. Por exemplo, para desativar a vinculação de modelo 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 desativar a validação em propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Program.cs. Por exemplo, para desativar a validação em 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 a vinculação de modelo escrevendo um fichário de modelo personalizado e usando o [ModelBinder] atributo para selecioná-lo para um determinado destino. Saiba mais sobre a vinculação de modelo personalizado.

Vinculação manual do modelo

A vinculação de modelo pode ser invocada manualmente usando o TryUpdateModelAsync método. O método é definido em ambas as ControllerBase classes e PageModel classes. As sobrecargas de método permitem especificar o prefixo e o provedor de valor a serem usados. O método retorna false se a vinculação do modelo falhar. Aqui está 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, da cadeia de caracteres de consulta e dos dados de rota. TryUpdateModelAsync é normalmente:

  • Usado com Razor aplicativos Pages e MVC usando controladores e visualizações para evitar postagens excessivas.
  • Não é utilizado com uma API da Web, a menos que seja consumido a partir de dados de formulário, parâmetros de consulta e dados de rota. Os endpoints da Web API que utilizam JSON usam Input formatters para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome desse atributo segue o padrão de atributos de vinculação de modelo que especificam uma fonte de dados. Mas não se trata de vincular dados de um provedor de valor. Obtem-se uma instância de um tipo do contentor de injeção de dependência. Seu objetivo é fornecer uma alternativa à injeção do construtor para quando você precisa de um serviço somente se um método específico for chamado.

Se uma instância do tipo não estiver registrada no contêiner de injeção de dependência, o aplicativo lançará uma exceção ao tentar vincular 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, certifique-se de que o parâmetro não null está antes de acessá-lo.

Recursos adicionais

Este artigo explica o que é a vinculação de modelo, como ela funciona e como personalizar seu comportamento.

O que é a vinculação de modelo

Controladores e Razor páginas trabalham com dados provenientes de solicitações HTTP. Por exemplo, os dados de rota podem fornecer uma chave de registro e os campos de formulário lançados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres para tipos .NET seria tedioso e propenso a erros. A vinculação de modelo automatiza esse processo. O sistema de associação de modelos:

  • 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 propriedades de tipos complexos.

Example

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

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

E o aplicativo recebe uma solicitação com este URL:

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

A vinculaçã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 inteiro chamado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" nos dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booleano chamado dogsOnly.
  • Examina as fontes e encontra "DogsOnly=true" na cadeia de caracteres de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a string "true" em booleano 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 de vinculação de modelo são parâmetros de método que são tipos simples. Os alvos também podem ser as propriedades de um tipo complexo. Depois que cada propriedade é vinculada com êxito, ocorre a validação do modelo para essa propriedade. O registro de quais dados estão vinculados ao modelo, e quaisquer erros de vinculação ou validação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid .

Targets

A associação de modelos tenta encontrar valores para os seguintes tipos de alvos:

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

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para que a vinculação de modelo tenha como alvo essa propriedade.

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

    // ...
}

Atributo [BindProperties]

Pode ser aplicado a um controlador ou PageModel classe para informar a vinculação de modelo para direcionar todas as propriedades públicas da classe:

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

    // ...
}

Vinculação de modelo para solicitações HTTP GET

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

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

Sources

Por padrão, a vinculação de modelo 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. Dados de rota
  4. Parâmetros de string de consulta
  5. Ficheiros carregados

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

  • Os dados de rota e os valores da cadeia de caracteres de consulta são usados apenas para tipos simples.
  • Os arquivos carregados são vinculados apenas aos tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

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

  • [FromQuery] - Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] - Obtém valores a partir 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.

Estes atributos:

  • São adicionados à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 da 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 a partir do corpo de uma solicitação HTTP. O runtime do ASP.NET Core delega a responsabilidade de ler o corpo a um formatador de entrada. Os formatadores de entrada são explicados mais adiante neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de ligação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido a partir 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; } = null!;

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

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 lêem 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 se aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação é lido por um formatador de entrada, ele não está mais disponível para ser lido novamente para vincular outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de vinculação de modelo pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para vinculação de modelo de outras fontes. Por exemplo, você pode querer dados de cookies ou estado da sessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implemente o IValueProvider.
  • Crie uma classe que implemente o IValueProviderFactory.
  • Registre a classe de fábrica em Program.cs.

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

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

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

Nenhuma fonte para uma propriedade de modelo

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

  • Os tipos simples anuláveis são definidos como null.
  • Os 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, a vinculação de modelo cria uma instância usando o construtor padrão, sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que byte[] as matrizes são definidas como null.

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

Observe que esse [BindRequired] comportamento se aplica à vinculação de modelo a partir de dados de formulário postados, não a dados JSON ou XML em um corpo de solicitação. Os dados do corpo da solicitação são tratados por input formatters.

Erros de conversão de tipos

Se uma origem 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 propriedade de destino é definido como null ou um valor padrão, conforme observado na seção anterior.

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

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

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

    // ...

    return RedirectToPage("./Index");
}

Quando a página é reexibida pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade model foi definida como null ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Se você quiser reexibir 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 é recomendada se você não quiser que erros de conversão de tipo resultem em erros de estado do modelo. Nesse caso, torne a propriedade model uma cadeia de caracteres.

Tipos simples

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres incluem o seguinte:

Tipos complexos

Um tipo complexo deve ter um construtor padrão público e propriedades graváveis públicas para vincular. Quando a vinculação de modelo ocorre, a classe é instanciada usando o construtor padrão público.

Para cada propriedade do tipo complexo, a associação de modelos procura nas fontes pelo padrão de nomeprefix.property_name. Se nada for encontrado, ele procura apenas property_name sem o prefixo. A decisão de usar o prefixo não é feita para cada propriedade individualmente. Por exemplo, com uma consulta contendo ?Instructor.Id=100&Name=foo, vinculado ao método OnGet(Instructor instructor), o objeto resultante do tipo Instructor contém:

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

Note

Os links de documentação para a fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

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

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

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 vinculado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

A vinculação de modelo começa examinando as fontes para a chave instructorToUpdate.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser vinculado for uma propriedade nomeada Instructor do controlador ou PageModel classe:

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo personalizado

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

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar a vinculação de modelos de tipos complexos:

Warning

Esses atributos afetam a vinculação do modelo quando os dados do formulário postado são a fonte dos valores. Eles não afetam os formatadores de entrada, que processam os corpos de solicitações JSON e XML enviadas. Os formatadores de entrada são explicados mais adiante 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 na associação de modelo. [Bind] não afeta os formatadores de entrada.

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

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

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

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

O [Bind] atributo pode ser usado para proteger contra sobrepostagem em cenários de criação . Ele não funciona bem em cenários de edição porque as propriedades excluídas são definidas como null ou um valor padrão em vez de serem deixadas inalteradas. Para proteção contra sobrepostagem, os modelos de exibição são recomendados em vez do atributo [Bind]. Para obter mais informações, consulte Nota de segurança sobre sobrepostagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. 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 vinculado ao modelo:

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

    // ...
}

Atributo [BindRequired]

Faz com que a associação de modelo adicione um erro de estado de modelo se não for possível associar a propriedade de um modelo. Aqui está um exemplo:

public class InstructorBindRequired
{
    // ...

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

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

Atributo [BindNever]

Pode ser aplicado a uma propriedade ou a um tipo. Impede que a vinculação de modelo defina a propriedade de um modelo. Quando aplicado a um tipo, o sistema de vinculação de modelo exclui todas as propriedades que o tipo define. Aqui está um exemplo:

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

    // ...
}

Collections

Para destinos que são coleções de tipos simples, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados sem o prefixo. Por exemplo:

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

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Os dados da cadeia de caracteres de formulário ou consulta 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 vincular um parâmetro ou uma propriedade nomeada index ou Index se ela for adjacente a um valor de coleção. A vinculação de modelo tenta usar index como índice para a coleção, o que pode resultar em uma vinculação incorreta. Por exemplo, considere a seguinte ação:

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

    No código anterior, o index parâmetro query string se liga ao index parâmetro method e também é usado para vincular a coleção de produtos. Renomear o index parâmetro ou usar um atributo de vinculação de modelo para configurar a associação evita esse problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • O seguinte formato é suportado apenas em dados de formulário:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos os formatos de exemplo anteriores, a vinculação de modelo 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 assegurar que são numerados sequencialmente a partir de zero. Se existirem lacunas na numeração de índice, todos os itens após a lacuna serão ignorados. Por exemplo, se os subscritos forem 0 e 2 em vez de 0 e 1, o segundo item será ignorado.

Dictionaries

Para Dictionary destinos, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados 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 do formulário postado ou da cadeia de caracteres de consulta podem se parecer com um dos seguintes exemplos:

    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 de exemplo anteriores, a vinculação de modelo passa um dicionário de dois itens para o parâmetro selectedCourses.

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

Vinculação de construtor e tipos de registro

A vinculação de modelo requer que tipos complexos tenham um construtor sem parâmetros. System.Text.Json Tanto a Newtonsoft.Json entrada baseada formatters suporta a 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 pela rede. O ASP.NET Core suporta vinculaçã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

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

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

A estrutura permite vincular e validar tipos de registro:

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

Para que o precedente 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 diferir consoante as maiúsculas e minúsculas.

POCOs sem construtores sem parâmetros

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

O código a seguir resulta em uma exceção dizendo 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 o trabalho de construtores primários

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 registo, metadados de validação e ligação

Para os tipos de registo, são utilizados metadados de validação e de ligação nos parâmetros. 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, existem 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á vincular Name novamente. No entanto, Age é permitido ser atualizado

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

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

  • Trate os valores como cultura invariante.
  • Espere que os URLs sejam invariantes de cultura.

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

Para fazer com que o provedor de valores de rota do ASP.NET Core e o provedor de valores de cadeia de consulta passem 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

Existem alguns tipos de dados especiais que a vinculação de modelo pode manipular.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. IEnumerable<IFormFile> também é suportado para vários ficheiros.

CancellationToken

Opcionalmente, as ações podem vincular um CancellationToken como parâmetro. Isso liga RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é abortada. As ações podem usar esse parâmetro para cancelar operações assíncronas de longa duração que são executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados do formulário postado.

Contributos para as questões

Os dados no corpo da solicitação podem estar em JSON, XML ou algum outro formato. Para analisar esses dados, a vinculação de modelo usa um formatador de entrada configurado para lidar com um tipo de conteúdo específico. Por padrão, o ASP.NET Core inclui formatadores de entrada baseados em JSON para lidar com dados JSON. Você pode adicionar outros formatters para outros tipos de conteúdo.

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

Para usar os formatadores de entrada XML incorporados, siga estes passos:

Personalizar a vinculação de modelos com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura dos 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 formatador de entrada baseado em System.Text.Json para interpretar um tipo personalizado chamado ObjectId.

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

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

Para personalizar o processo de vinculação de modelo ao usar System.Text.Jsono , 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 tipo ObjectId é configurado com o seu ObjectIdConverter conversor personalizado:

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

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

Excluir tipos especificados da vinculação de modelo

O comportamento dos sistemas de vinculação e validação de modelos é impulsionado pelo ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes a MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar a vinculação ou validação de modelo para tipos especificados.

Para desativar a vinculação de modelo em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Program.cs. Por exemplo, para desativar a vinculação de modelo 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 desativar a validação em propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Program.cs. Por exemplo, para desativar a validação em 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 a vinculação de modelo escrevendo um fichário de modelo personalizado e usando o [ModelBinder] atributo para selecioná-lo para um determinado destino. Saiba mais sobre a vinculação de modelo personalizado.

Vinculação manual do modelo

A vinculação de modelo pode ser invocada manualmente usando o TryUpdateModelAsync método. O método é definido em ambas as ControllerBase classes e PageModel classes. As sobrecargas de método permitem especificar o prefixo e o provedor de valor a serem usados. O método retorna false se a vinculação do modelo falhar. Aqui está 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, da cadeia de caracteres de consulta e dos dados de rota. TryUpdateModelAsync é normalmente:

  • Usado com Razor aplicativos Pages e MVC usando controladores e visualizações para evitar postagens excessivas.
  • Não é utilizado com uma API da Web, a menos que seja consumido a partir de dados de formulário, parâmetros de consulta e dados de rota. Os endpoints da Web API que utilizam JSON usam Input formatters para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome desse atributo segue o padrão de atributos de vinculação de modelo que especificam uma fonte de dados. Mas não se trata de vincular dados de um provedor de valor. Obtem-se uma instância de um tipo do contentor de injeção de dependência. Seu objetivo é fornecer uma alternativa à injeção do construtor para quando você precisa de um serviço somente se um método específico for chamado.

Se uma instância do tipo não estiver registrada no contêiner de injeção de dependência, o aplicativo lançará uma exceção ao tentar vincular 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, certifique-se de que o parâmetro não null está antes de acessá-lo.

Recursos adicionais

Este artigo explica o que é a vinculação de modelo, como ela funciona e como personalizar seu comportamento.

Ver ou descarregar o código de exemplo (como descarregar).

O que é a vinculação de modelo

Controladores e Razor páginas trabalham com dados provenientes de solicitações HTTP. Por exemplo, os dados de rota podem fornecer uma chave de registro e os campos de formulário lançados podem fornecer valores para as propriedades do modelo. Escrever código para recuperar cada um desses valores e convertê-los de cadeias de caracteres para tipos .NET seria tedioso e propenso a erros. A vinculação de modelo automatiza esse processo. O sistema de associação de modelos:

  • 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 propriedades de tipos complexos.

Example

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

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

E o aplicativo recebe uma solicitação com este URL:

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

A vinculaçã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 inteiro chamado id.
  • Examina as fontes disponíveis na solicitação HTTP e localiza id = "2" nos dados de rota.
  • Converte a cadeia de caracteres "2" em inteiro 2.
  • Localiza o próximo parâmetro de GetById, um booleano chamado dogsOnly.
  • Examina as fontes e encontra "DogsOnly=true" na cadeia de caracteres de consulta. A correspondência de nomes não diferencia maiúsculas de minúsculas.
  • Converte a string "true" em booleano 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 de vinculação de modelo são parâmetros de método que são tipos simples. Os alvos também podem ser as propriedades de um tipo complexo. Depois que cada propriedade é vinculada com êxito, ocorre a validação do modelo para essa propriedade. O registro de quais dados estão vinculados ao modelo, e quaisquer erros de vinculação ou validação, é armazenado em ControllerBase.ModelState ou PageModel.ModelState. Para descobrir se esse processo foi bem-sucedido, o aplicativo verifica o sinalizador ModelState.IsValid .

Targets

A associação de modelos tenta encontrar valores para os seguintes tipos de alvos:

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

Atributo [BindProperty]

Pode ser aplicado a uma propriedade pública de um controlador ou classe PageModel para que a vinculação de modelo tenha como alvo essa propriedade.

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

Atributo [BindProperties]

Disponível no ASP.NET Core 2.1 ou posterior. Pode ser aplicado a um controlador ou PageModel classe para informar a vinculação de modelo para direcionar todas as propriedades públicas da classe:

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

Vinculação de modelo para solicitações HTTP GET

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

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

Sources

Por padrão, a vinculação de modelo 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. Dados de rota
  4. Parâmetros de string de consulta
  5. Ficheiros carregados

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

  • Os dados de rota e os valores da cadeia de caracteres de consulta são usados apenas para tipos simples.
  • Os arquivos carregados são vinculados apenas aos tipos de destino que implementam IFormFile ou IEnumerable<IFormFile>.

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

  • [FromQuery] - Obtém valores da cadeia de caracteres de consulta.
  • [FromRoute] - Obtém valores a partir 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.

Estes atributos:

  • São adicionados às propriedades do modelo individualmente (não à classe do 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 da 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 a partir do corpo de uma solicitação HTTP. O runtime do ASP.NET Core delega a responsabilidade de ler o corpo a um formatador de entrada. Os formatadores de entrada são explicados mais adiante neste artigo.

Quando [FromBody] é aplicado a um parâmetro de tipo complexo, todos os atributos de origem de ligação aplicados às suas propriedades são ignorados. Por exemplo, a ação a seguir Create especifica que seu pet parâmetro é preenchido a partir 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 lêem 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 se aplique [FromBody] a mais de um parâmetro por método de ação. Depois que o fluxo de solicitação é lido por um formatador de entrada, ele não está mais disponível para ser lido novamente para vincular outros [FromBody] parâmetros.

Fontes adicionais

Os dados de origem são fornecidos ao sistema de vinculação de modelo pelos provedores de valor. Você pode escrever e registrar provedores de valor personalizados que obtêm dados para vinculação de modelo de outras fontes. Por exemplo, você pode querer dados de cookies ou estado da sessão. Para obter dados de uma nova fonte:

  • Crie uma classe que implemente o IValueProvider.
  • Crie uma classe que implemente o IValueProviderFactory.
  • Registre a classe de fábrica 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 registo 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 da lista, ligue Insert(0, new CookieValueProviderFactory()) em vez de Add.

Nenhuma fonte para uma propriedade de modelo

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

  • Os tipos simples anuláveis são definidos como null.
  • Os 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, a vinculação de modelo cria uma instância usando o construtor padrão, sem definir propriedades.
  • As matrizes são definidas como Array.Empty<T>(), exceto que byte[] as matrizes são definidas como null.

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

Observe que esse [BindRequired] comportamento se aplica à vinculação de modelo a partir de dados de formulário postados, não a dados JSON ou XML em um corpo de solicitação. Os dados do corpo da solicitação são tratados por input formatters.

Erros de conversão de tipos

Se uma origem 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 propriedade de destino é definido como null ou um valor padrão, conforme observado na seção anterior.

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

Em uma página Razor, redisponha 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 deteta a maioria dos dados incorretos que, de outra forma, seriam enviados para um Razor formulário do Pages. Essa validação dificulta o acionamento do código destacado anterior. O aplicativo de exemplo inclui um botão Enviar com data inválida que coloca dados incorretos no campo Data de contratação e envia o formulário. Este botão mostra como o código para reexibir a página funciona quando ocorrem erros de conversão de dados.

Quando a página é reexibida pelo código anterior, a entrada inválida não é mostrada no campo de formulário. Isso ocorre porque a propriedade model foi definida como null ou um valor padrão. A entrada inválida aparece em uma mensagem de erro. Mas se você quiser reexibir 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 é recomendada se você não quiser que erros de conversão de tipo resultem em erros de estado do modelo. Nesse caso, torne a propriedade model uma cadeia de caracteres.

Tipos simples

Os tipos simples em que o associador de modelos pode converter cadeias de caracteres incluem o seguinte:

Tipos complexos

Um tipo complexo deve ter um construtor padrão público e propriedades graváveis públicas para vincular. Quando a vinculação de modelo ocorre, 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 prefix.property_name. Se nada for encontrado, ele procura apenas property_name sem o prefixo.

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

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

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 vinculado for um parâmetro chamado instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

A vinculação de modelo começa examinando as fontes para a chave instructorToUpdate.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo = nome da propriedade

Se o modelo a ser vinculado for uma propriedade nomeada Instructor do controlador ou PageModel classe:

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Prefixo personalizado

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

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

A vinculação de modelo começa examinando as fontes para a chave Instructor.ID. Se isso não for encontrado, ele procura ID sem um prefixo.

Atributos para destinos de tipo complexo

Vários atributos internos estão disponíveis para controlar a vinculação de modelos de tipos complexos:

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

Warning

Esses atributos afetam a vinculação do modelo quando os dados do formulário postado são a fonte dos valores. Eles não afetam os formatadores de entrada, que processam os corpos de solicitações JSON e XML enviadas. Os formatadores de entrada são explicados mais adiante 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 na associação de modelo. [Bind] não afeta os formatadores de entrada.

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

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

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

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

O [Bind] atributo pode ser usado para proteger contra sobrepostagem em cenários de criação . Ele não funciona bem em cenários de edição porque as propriedades excluídas são definidas como null ou um valor padrão em vez de serem deixadas inalteradas. Para proteção contra sobrepostagem, os modelos de exibição são recomendados em vez do atributo [Bind]. Para obter mais informações, consulte Nota de segurança sobre sobrepostagem.

Atributo [ModelBinder]

ModelBinderAttribute pode ser aplicado a tipos, propriedades ou parâmetros. 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 vinculado ao modelo:

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

    public string Name { get; set; }
}

Atributo [BindRequired]

Só pode ser aplicado às propriedades do modelo, não aos parâmetros do método. Faz com que a associação de modelo adicione um erro de estado de modelo se não for possível associar a propriedade de um modelo. Aqui está 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; }

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

Atributo [BindNever]

Só pode ser aplicado às propriedades do modelo, não aos parâmetros do método. Impede que a vinculação de modelo defina a propriedade de um modelo. Aqui está um exemplo:

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

Collections

Para destinos que são coleções de tipos simples, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados sem o prefixo. Por exemplo:

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

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Os dados da cadeia de caracteres de formulário ou consulta 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 vincular um parâmetro ou uma propriedade nomeada index ou Index se ela for adjacente a um valor de coleção. A vinculação de modelo tenta usar index como índice para a coleção, o que pode resultar em uma vinculação incorreta. Por exemplo, considere a seguinte ação:

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

    No código anterior, o index parâmetro query string se liga ao index parâmetro method e também é usado para vincular a coleção de produtos. Renomear o index parâmetro ou usar um atributo de vinculação de modelo para configurar a associação evita esse problema:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • O seguinte formato é suportado apenas em dados de formulário:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Para todos os formatos de exemplo anteriores, a vinculação de modelo 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 assegurar que são numerados sequencialmente a partir de zero. Se existirem lacunas na numeração de índice, todos os itens após a lacuna serão ignorados. Por exemplo, se os subscritos forem 0 e 2 em vez de 0 e 1, o segundo item será ignorado.

Dictionaries

Para Dictionary destinos, a vinculação de modelo procura correspondências para parameter_name ou property_name. Se nenhuma correspondência for encontrada, ele procurará um dos formatos suportados 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 do formulário postado ou da cadeia de caracteres de consulta podem se parecer com um dos seguintes exemplos:

    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 de exemplo anteriores, a vinculação de modelo passa um dicionário de dois itens para o parâmetro selectedCourses.

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

Vinculação de construtor e tipos de registro

A vinculação de modelo requer que tipos complexos tenham um construtor sem parâmetros. System.Text.Json Tanto a Newtonsoft.Json entrada baseada formatters suporta a desserialização de classes que não têm um construtor sem parâmetros.

O C# 9 introduz tipos de registro, que são uma ótima maneira de representar dados de forma sucinta na rede. ASP.NET Core adiciona suporte para vinculaçã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

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

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

A estrutura permite vincular e validar tipos de registro:

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

Para que o precedente 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 diferir consoante as maiúsculas e minúsculas.

POCOs sem construtores sem parâmetros

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

O código a seguir resulta em uma exceção dizendo 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 o trabalho de construtores primários

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 registo, metadados de validação e ligação

Para os tipos de registo, são utilizados metadados de validação e de ligação nos parâmetros. 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, existem 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á vincular Name novamente. No entanto, Age é permitido ser atualizado

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

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

  • Trate os valores como cultura invariante.
  • Espere que os URLs sejam invariantes de cultura.

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

Para fazer com que o provedor de valores de rota do ASP.NET Core e o provedor de valores de cadeia de consulta passem 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

Existem alguns tipos de dados especiais que a vinculação de modelo pode manipular.

IFormFile e IFormFileCollection

Um arquivo carregado incluído na solicitação HTTP. IEnumerable<IFormFile> também é suportado para vários ficheiros.

CancellationToken

Opcionalmente, as ações podem vincular um CancellationToken como parâmetro. Isso liga RequestAborted que sinaliza quando a conexão subjacente à solicitação HTTP é abortada. As ações podem usar esse parâmetro para cancelar operações assíncronas de longa duração que são executadas como parte das ações do controlador.

FormCollection

Usado para recuperar todos os valores dos dados do formulário postado.

Contributos para as questões

Os dados no corpo da solicitação podem estar em JSON, XML ou algum outro formato. Para analisar esses dados, a vinculação de modelo usa um formatador de entrada configurado para lidar com um tipo de conteúdo específico. Por padrão, o ASP.NET Core inclui formatadores de entrada baseados em JSON para lidar com dados JSON. Você pode adicionar outros formatters para outros tipos de conteúdo.

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

Para usar os formatadores de entrada XML incorporados, siga estes passos:

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

  • Em Startup.ConfigureServices, ligue 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 Consumes atributo a classes de controlador ou 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, consulte Introdução à serialização XML.

Personalizar a vinculação de modelos com formatadores de entrada

Um formatador de entrada assume total responsabilidade pela leitura dos 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 formatador de entrada baseado em System.Text.Json para interpretar um tipo personalizado chamado ObjectId.

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

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

Para personalizar o processo de vinculação de modelo ao usar System.Text.Jsono , 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 tipo ObjectId é configurado com o seu ObjectIdConverter 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 da vinculação de modelo

O comportamento dos sistemas de vinculação e validação de modelos é impulsionado pelo ModelMetadata. Você pode personalizar ModelMetadata adicionando um provedor de detalhes a MvcOptions.ModelMetadataDetailsProviders. Provedores de detalhes internos estão disponíveis para desabilitar a vinculação ou validação de modelo para tipos especificados.

Para desativar a vinculação de modelo em todos os modelos de um tipo especificado, adicione um ExcludeBindingMetadataProvider em Startup.ConfigureServices. Por exemplo, para desativar a vinculação de modelo 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 desativar a validação em propriedades de um tipo especificado, adicione um SuppressChildValidationMetadataProvider em Startup.ConfigureServices. Por exemplo, para desativar a validação em 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 a vinculação de modelo escrevendo um fichário de modelo personalizado e usando o [ModelBinder] atributo para selecioná-lo para um determinado destino. Saiba mais sobre a vinculação de modelo personalizado.

Vinculação manual do modelo

A vinculação de modelo pode ser invocada manualmente usando o TryUpdateModelAsync método. O método é definido em ambas as ControllerBase classes e PageModel classes. As sobrecargas de método permitem especificar o prefixo e o provedor de valor a serem usados. O método retorna false se a vinculação do modelo falhar. Aqui está 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, da cadeia de caracteres de consulta e dos dados de rota. TryUpdateModelAsync é normalmente:

  • Usado com Razor aplicativos Pages e MVC usando controladores e visualizações para evitar postagens excessivas.
  • Não é utilizado com uma API da Web, a menos que seja consumido a partir de dados de formulário, parâmetros de consulta e dados de rota. Os endpoints da Web API que utilizam JSON usam Input formatters para desserializar o corpo da solicitação em um objeto.

Para obter mais informações, consulte TryUpdateModelAsync.

Atributo [FromServices]

O nome desse atributo segue o padrão de atributos de vinculação de modelo que especificam uma fonte de dados. Mas não se trata de vincular dados de um provedor de valor. Obtem-se uma instância de um tipo do contentor de injeção de dependência. Seu objetivo é fornecer uma alternativa à injeção do construtor para quando você precisa de um serviço somente se um método específico for chamado.

Recursos adicionais