O corpo da requisição chega como um stream que só pode ser lido para a frente, e quem o lê primeiro normalmente é o único que recebe o conteúdo. Esse único fato decide tudo sobre ler o corpo da requisição em uma Web API ASP.NET Core: onde podemos lê-lo, se o model binding ainda funciona depois e por que uma segunda leitura volta vazia.

Para baixar o código-fonte deste artigo, você pode acessar nosso repositório no GitHub.

Vamos ver quatro lugares para fazer isso: uma ação do controlador, um middleware personalizado, um filtro de ação e um atributo personalizado. Request.EnableBuffering() é o que permite que mais de um deles funcione na mesma requisição.

Como ler o corpo da requisição em uma ação do controlador?

Dentro de uma ação do controlador, Request.Body é o stream bruto da requisição. Ele só pode ser lido para a frente e fica posicionado no final assim que algo o lê, e é por isso que uma segunda leitura volta vazia em vez de lançar uma exceção.

Para lê-lo uma vez, basta um StreamReader. Para lê-lo duas vezes, é preciso Request.EnableBuffering(), que troca o stream por um que pode voltar ao início, mais um Request.Body.Position = 0 entre as duas leituras.

A alternativa é deixar o framework ler o corpo por nós. Antes de a ação rodar, um formatador de entrada desserializa no nosso próprio tipo o parâmetro marcado com [FromBody], então trabalhamos com um objeto, e não com um stream.

As duas formas não se misturam sozinhas. Depois que o model binding lê o corpo para um parâmetro [FromBody], o stream dentro dessa mesma ação fica vazio, a menos que tenha sido armazenado em buffer antes e depois levado de volta ao início.

Então, dentro de uma ação, a escolha é simples: receber o texto bruto ou receber um modelo vinculado. EnableBuffering() existe justamente para quando queremos os dois na mesma ação.

Tudo o que vem a seguir roda em um controlador comum de Web API ASP.NET Core, sem nenhum pacote extra.

Método de extensão ReadAsStringAsync()

Em vez de converter os dados do stream em string diretamente na ação do controlador, podemos implementar um método de extensão. Essa abordagem nos permite usá-lo em várias situações. Ao longo deste artigo, vamos precisar com frequência converter dados de stream em string. Os métodos de extensão são uma solução eficiente para implementar uma vez e usar muitas vezes.

Vamos implementar o método de extensão:

public static class RequestExtensions
{
    public static async Task<string> ReadAsStringAsync(this Stream requestBody, bool leaveOpen = false)
    {
        using StreamReader reader = new(requestBody, leaveOpen: leaveOpen);
        var bodyAsString = await reader.ReadToEndAsync();

        return bodyAsString;
    }
}

Criamos um método de extensão chamado ReadAsStringAsync(). Esse método amplia as funcionalidades do tipo Stream e nos permite converter os dados do stream em string. Para isso, criamos uma instância da classe StreamReader. A classe StreamReader facilita a leitura de caracteres de um stream, seja de um arquivo, seja de um stream de rede.

Para entender melhor StreamReader e StreamWriter, recomendamos o nosso artigo sobre como ler e gravar arquivos com StreamWriter e StreamReader.

Embora o construtor ofereça várias opções, no nosso caso passamos dois parâmetros para o StreamReader. O primeiro é o stream que representa o corpo da requisição, e o segundo é leaveOpen. Esse parâmetro garante que o stream continue aberto mesmo depois que o StreamReader termina o trabalho e é descartado.

Em seguida, chamamos o método ReadToEndAsync() do objeto reader, que retorna a representação em string dos dados do stream.

Leitura como string

Podemos ler o corpo da requisição como string em uma ação do controlador. As nossas ações ficam em um HomeController que adicionamos a um projeto criado com dotnet new webapi --use-controllers. Ele tem os mesmos atributos [ApiController] e [Route("[controller]")] do controlador que já vem no modelo de projeto, então a ação abaixo responde a requisições POST em /Home/read-as-string. Dentro das ações do controlador, o ASP.NET Core oferece a propriedade Request, que dá acesso ao Request.Body. No entanto, esse corpo é do tipo Stream e não conseguimos lê-lo diretamente. Para tratar os dados desse stream como texto, precisamos convertê-los em string:

[HttpPost("read-as-string")]
public async Task<IActionResult> ReadAsString()
{
    var requestBody = await Request.Body.ReadAsStringAsync();

    return Ok($"Request Body As String: {requestBody}");
}

Aqui, acessamos o corpo da requisição chamando o método de extensão ReadAsStringAsync() que implementamos acima. Assim que obtemos o resultado do método de extensão, simplesmente o retornamos da ação para fins de teste.

Uso de EnableBuffering para várias leituras

O que acontece se tentarmos ler o corpo da requisição mais uma vez, ou várias vezes, no exemplo acima?

Vamos verificar:

[HttpPost("read-as-string-multiple")]
public async Task<IActionResult> ReadAsStringMultiple()
{
    var requestBody = await Request.Body.ReadAsStringAsync();
    var requestBodySecond = await Request.Body.ReadAsStringAsync();

    return Ok($"First: {requestBody}, Second:{requestBodySecond}");
}

Agora, vamos enviar a string “CodeMaze” para esse método de ação como corpo da requisição e examinar a resposta:

First: CodeMaze, Second:

A primeira tentativa de leitura funciona, mas a segunda não traz o que esperamos.

Aqui não há uma página do Swagger UI para clicar, porque o modelo de projeto de Web API do .NET 10 vem com Microsoft.AspNetCore.OpenApi e sem o Swashbuckle, então o projeto gera um documento OpenAPI e nenhuma página interativa. Esse documento é gerado a partir de todos os endpoints mapeados, e deixar um endpoint fora do documento OpenAPI gerado é uma tarefa curta à parte.

Quando precisamos ler o corpo da requisição várias vezes, é essencial ativar o armazenamento em buffer. Para isso, podemos usar o método EnableBuffering() do objeto Request:

[HttpPost("read-multiple-enable-buffering")]
public async Task<IActionResult> ReadMultipleEnableBuffering()
{
    Request.EnableBuffering();
    var requestBody = await Request.Body.ReadAsStringAsync(true);

    Request.Body.Position = 0;
    var requestBodySecond = await Request.Body.ReadAsStringAsync();

    return Ok($"First: {requestBody}, Second:{requestBodySecond}");
}

Aqui, chamamos o método Request.EnableBuffering(), que permite ler o corpo da requisição várias vezes. Em seguida, chamamos o método ReadAsStringAsync(). Para garantir que o stream continue aberto para as leituras seguintes, definimos o parâmetro leaveOpen como true. Logo antes da segunda tentativa, redefinimos para zero a posição do corpo da requisição.

Com essas últimas alterações, vamos testar a API com o mesmo corpo de requisição:

First: CodeMaze, Second:CodeMaze

Depois de chamar o método EnableBuffering(), conseguimos obter o corpo da requisição também na segunda tentativa.

Model binding

O ASP.NET Core permite desserializar automaticamente o corpo da requisição em uma classe de modelo predefinida. Essa abordagem simplifica o tratamento de dados estruturados. Para usar esse recurso interessante, aplicamos o atributo [FromBody] na ação, antes do parâmetro do modelo:

[HttpPost("read-from-body")]
public IActionResult ReadFromBody([FromBody] PersonItemDto model)
{
    var message = $"Person Data => Name: {model.Name}, Age: {model.Age}";

    return Ok(message);
}

Aqui, o ASP.NET Core mapeia automaticamente o corpo da requisição recebido para o tipo PersonItemDto. Depois, dentro do corpo da ação, podemos acessar e usar as propriedades do modelo. Se o payload atendeu às nossas regras é outra questão, e verificar se o modelo foi de fato vinculado é a função de ModelState.

Podemos definir o tipo PersonItemDto e as propriedades dele como quisermos, para atender exatamente às necessidades da aplicação:

public record PersonItemDto(string Name, int Age);

Vamos ver mais uma situação antes de encerrar este tópico. Digamos que queremos enviar e ler um parâmetro extra de salário no método de ação. A versão a seguir não funciona, e vale a pena ver por quê:

[HttpPost("read-from-body-multi-param")]
public IActionResult ReadFromBodyMultiParam([FromBody] PersonItemDto model, [FromBody] decimal salary)
{
    var message = $"Person Data => Name: {model.Name}, Age: {model.Age}, Salary: {salary}";

    return Ok(message);
}

Não há erros ao compilar o código. No entanto, em tempo de execução, quando a aplicação tenta rodar, ocorre uma InvalidOperationException na linha app.MapControllers() do arquivo Program.cs:

Action 'ReadingRequestBody.Controllers.HomeController.ReadFromBodyMultiParam (ReadingRequestBody)'
has more than one parameter that was specified or inferred as bound from request body. Only one
parameter per action may be bound from body. Inspect the following parameters, and use
'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route,
and 'FromBodyAttribute' for parameters to be bound from body:

Em termos simples, essa exceção nos avisa que o atributo FromBody só pode aparecer uma vez nos parâmetros de um método de ação. Por isso, é recomendável reunir todos os parâmetros em um único modelo de requisição, recebido como parâmetro.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Por que só um parâmetro pode ser vinculado a partir do corpo da requisição?

Uma ação pode ter no máximo um parâmetro vinculado a partir do corpo da requisição. Com [ApiController], um segundo parâmetro desses provoca InvalidOperationException durante a inicialização da aplicação, na linha app.MapControllers(), antes de qualquer requisição chegar.

O motivo, mais uma vez, é o stream. A documentação de model binding da Microsoft diz isso diretamente: depois que um formatador de entrada lê o stream da requisição, ele não fica mais disponível para ser lido de novo na vinculação de outros parâmetros [FromBody].

O atributo [ApiController] torna mais fácil esbarrar nisso sem querer, porque ele infere o corpo como origem para parâmetros de tipo complexo sem escrevermos [FromBody] em lugar nenhum. Dois parâmetros complexos em uma mesma ação bastam para provocar o erro.

A solução é reunir tudo o que o payload traz em um único tipo e vincular esse tipo. Valores que não fazem parte de fato do payload ficam em outro lugar: [FromRoute] para identificadores no caminho, [FromQuery] para filtros, [FromHeader] para metadados.

A mensagem da exceção cita FromQueryAttribute e FromRouteAttribute pelo nome justamente por esse motivo.

A documentação de model binding da Microsoft é direta sobre isso: “Depois que o stream da requisição é lido por um formatador de entrada, ele não fica mais disponível para ser lido de novo na vinculação de outros parâmetros [FromBody].”

É a inferência que transforma essa regra em armadilha, então vale a pena saber exatamente quando o atributo ApiController escolhe uma origem de vinculação por nós.

Como ler o corpo da requisição em um middleware personalizado?

O middleware vê a requisição antes do model binding e antes de qualquer filtro. Isso faz dele o primeiro lugar em que podemos ler o corpo e o único que pode ativar o armazenamento em buffer para tudo o que vem depois.

É o lugar natural para o trabalho que vale para todas as requisições: registrar payloads em log, auditar, calcular uma assinatura, rejeitar uma requisição de imediato, antes mesmo que o MVC a veja.

Duas linhas tornam isso seguro para o resto do pipeline. Request.EnableBuffering() troca o corpo por um stream que pode voltar ao início, e Request.Body.Position = 0 o leva de volta ao início quando terminamos.

Se pularmos essa volta ao início, todo parâmetro [FromBody] mais adiante no pipeline é vinculado a um stream vazio, sem nenhuma exceção que explique o motivo.

O armazenamento em buffer tem custo. Chamado sem argumentos, EnableBuffering() mantém até 30 KB na memória e grava o que passar disso em um arquivo temporário no disco, então um middleware que armazena em buffer todas as requisições também armazena em buffer todos os arquivos enviados.

Não vamos entrar nos detalhes de middleware aqui. Para relembrar, veja o nosso artigo sobre como criar fluxos de aplicação flexíveis com middleware do ASP.NET Core.

Vejamos como criar um middleware personalizado para ler o corpo da requisição:

public class RequestBodyMiddleware(RequestDelegate next, ILogger<RequestBodyMiddleware> logger)
{
    private const int MaxContentLength = 1024;

    public async Task Invoke(HttpContext context)
    {
        await next(context);
    }
}

Aqui, criamos um middleware personalizado chamado RequestBodyMiddleware. O construtor recebe ILogger<RequestBodyMiddleware>, e não o ILogger não genérico, que o contêiner não consegue resolver sozinho: se o construtor pedir esse tipo, a aplicação lança uma exceção durante a inicialização.

Agora, vamos implementar o método Invoke() para acessar e ler o corpo da requisição:

public async Task Invoke(HttpContext context)
{
    var requestPath = context.Request.Path.Value ?? string.Empty;

    if (requestPath.Contains("read-from-middleware", StringComparison.OrdinalIgnoreCase))
    {
        if (context.Request.ContentLength > MaxContentLength)
        {
            context.Response.StatusCode = StatusCodes.Status413PayloadTooLarge;
            await context.Response.WriteAsync("Request Body Too Large");

            return;
        }

        context.Request.EnableBuffering(bufferThreshold: MaxContentLength, bufferLimit: MaxContentLength);
        var requestBody = await context.Request.Body.ReadAsStringAsync(true);

        logger.LogInformation("Request Body:{@requestBody}", requestBody);
        context.Request.Headers.Append("RequestBodyMiddleware", requestBody);
        context.Items.Add("RequestBody", requestBody);
        context.Request.Body.Position = 0;
    }

    await next(context);
}

Primeiro, examinamos o caminho da requisição para identificar rotas específicas. Depois, convertemos o stream do corpo da requisição em uma string bruta chamando o nosso método de extensão ReadAsStringAsync(). A partir daí, há várias opções para aproveitar o corpo da requisição: registrar o resultado em log, acrescentá-lo aos cabeçalhos da requisição ou passá-lo adiante no pipeline em HttpContext.Items, entre outros usos possíveis. Quando terminamos de processar o corpo da requisição, encaminhamos a requisição para o próximo middleware chamando o delegado next().

Para usar esse middleware, precisamos adicioná-lo no arquivo Program.cs:

var app = builder.Build();
app.UseMiddleware<RequestBodyMiddleware>();

Para ver o que o middleware leu, adicionamos mais uma ação ao controlador, em uma rota que contém read-from-middleware:

[HttpPost("read-from-middleware")]
public IActionResult ReadFromMiddleware()
{
    var requestBody = Request.Headers["RequestBodyMiddleware"];
    var message = $"Request Body From Middleware : {requestBody}";

    return Ok(message);
}

Evite ler corpos de requisição grandes

Lidar com corpos de requisição grandes em uma aplicação web exige cuidado, por causa de possíveis problemas de memória, queda de desempenho e esgotamento de recursos. O risco de ataques de negação de serviço com corpos propositalmente grandes é a parte mais perigosa disso, e a proteção que escrevemos decide se ficamos expostos a ele.

Verificar requestBody.Length depois da leitura não é um limite de tamanho. A essa altura, o payload inteiro já é uma string na memória, que é exatamente o que um ataque com corpos grandes quer. O limite tem de vir antes da leitura, e é por isso que o nosso middleware consulta primeiro o cabeçalho da requisição:

if (context.Request.ContentLength > MaxContentLength)
{
    context.Response.StatusCode = StatusCodes.Status413PayloadTooLarge;
    await context.Response.WriteAsync("Request Body Too Large");

    return;
}

context.Request.EnableBuffering(bufferThreshold: MaxContentLength, bufferLimit: MaxContentLength);

ContentLength vem do cabeçalho da requisição, então a decisão não custa nada. bufferLimit é a última barreira para uma requisição que não informa o tamanho, porque ler além desse limite lança uma IOException em vez de encher a memória ou o disco. Com MaxContentLength em 1.024, um payload de 2.000 bytes volta como Payload Too Large (413), e um de 500 bytes passa com um 200.

O ASP.NET Core já tem limites próprios. O MaxRequestBodySize do Kestrel tem como padrão 30.000.000 bytes, cerca de 28,6 MB, e [RequestSizeLimit] em uma ação ou IHttpMaxRequestBodySizeFeature em um middleware altera esse limite por endpoint ou por requisição.

Como ler o corpo da requisição em um filtro de ação?

Os filtros de ação rodam depois do model binding, e essa ordem decide tudo sobre o que eles conseguem ver. A documentação de filtros da Microsoft coloca os filtros de recurso antes do model binding e os filtros de ação depois dele.

Por isso, um filtro de ação que lê Request.Body em uma ação sem parâmetros recebe o payload inteiro. O mesmo filtro em uma ação com um parâmetro [FromBody] recebe uma string vazia, porque o formatador de entrada já consumiu o stream.

Nada lança exceção. O filtro lê zero bytes e segue em frente, o que faz disso uma das formas mais silenciosas de perder dados em uma Web API.

The Web API Production Checklist, e-book gratuito em inglês

E-book gratuito

Sua Web API está pronta para produção?

33 itens para verificar antes de implantá-la, com a correção de cada um. Um PDF gratuito de 76 páginas para .NET 10.

O e-book está em inglês.

Baixe o checklist gratuito

PDF gratuito. Um único e-mail para enviá-lo. Cancele a inscrição quando quiser.

Duas mudanças tornam isso confiável. Ative o armazenamento em buffer antes, em um middleware ou em um filtro de recurso, e defina Request.Body.Position = 0 no filtro antes da leitura.

Se tudo o que queremos é o corpo antes do model binding, um filtro de recurso é o melhor ponto de extensão. Ele fica no mesmo pipeline do MVC, com acesso aos mesmos metadados de controlador e de ação, mas roda do lado de cá do model binding.

Para saber mais sobre filtros de ação, confira o nosso artigo sobre como escrever e registrar filtros de ação.

Vamos criar um filtro de ação personalizado:

using Microsoft.AspNetCore.Mvc.Filters;

public class ReadRequestBodyActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var requestPath = context.HttpContext.Request.Path.Value ?? string.Empty;

        if (requestPath.Contains("read-from-action-filter", StringComparison.OrdinalIgnoreCase))
        {
            var requestBody = await context.HttpContext.Request.Body.ReadAsStringAsync();
            context.HttpContext.Request.Headers.Append("ReadRequestBodyActionFilter", requestBody);
        }

        await next();
    }
}

Aqui, criamos um filtro de ação personalizado chamado ReadRequestBodyActionFilter, que implementa a interface IAsyncActionFilter. Nesse filtro, definimos o método OnActionExecutionAsync() para tratar a nossa lógica específica. Depois, examinamos o caminho da requisição e extraímos o corpo com o método de extensão ReadAsStringAsync(). Por fim, acrescentamos o corpo aos cabeçalhos da requisição com a chave ReadRequestBodyActionFilter.

Para usar esse filtro de ação, precisamos registrá-lo no arquivo Program.cs:

builder.Services.AddControllers(options =>
{
    options.Filters.Add<ReadRequestBodyActionFilter>();
});

O filtro só lê o corpo em uma rota que contém read-from-action-filter, então adicionamos ali uma ação que retorna o cabeçalho:

[HttpPost("read-from-action-filter")]
public IActionResult ReadFromActionFilter()
{
    var requestBody = Request.Headers["ReadRequestBodyActionFilter"];
    var message = $"Request Body From Action Filter : {requestBody}";

    return Ok(message);
}

Como ler o corpo da requisição com um atributo personalizado?

Para interceptar as requisições recebidas, podemos usar um atributo personalizado em combinação com filtros de ação e modificar o comportamento do pipeline de processamento de requisições.

Vamos criar um atributo personalizado para inspecionar e ler o corpo da requisição:

using Microsoft.AspNetCore.Mvc.Filters;

[AttributeUsage(AttributeTargets.Method)]
public class ReadRequestBodyAttribute : Attribute, IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var requestBody = await context.HttpContext.Request.Body.ReadAsStringAsync();
        context.HttpContext.Request.Headers.Append("ReadRequestBodyAttribute", requestBody);

        await next();
    }
}

Aqui, criamos um atributo personalizado chamado ReadRequestBodyAttribute, que implementa IAsyncActionFilter, e lemos o corpo em OnActionExecutionAsync() com o mesmo método de extensão ReadAsStringAsync() de antes. Depois, acrescentamos o corpo aos cabeçalhos da requisição com a chave ReadRequestBodyAttribute. Tirando a interface de filtro, esse é um atributo personalizado comum.

O momento de execução é idêntico ao da seção anterior, então vale o mesmo cuidado: isso roda depois do model binding e precisa do armazenamento em buffer ativado antes se a ação também vincular um parâmetro a partir do corpo. O que o atributo acrescenta é o escopo. Um filtro de ação registrado em AddControllers() roda em todas as ações e precisa verificar o caminho, enquanto um atributo roda apenas onde é aplicado.

Agora podemos usar o nosso atributo personalizado. Para isso, precisamos aplicá-lo à ação do controlador:

[HttpPost("read-from-attribute")]
[ReadRequestBody]
public IActionResult ReadFromAttribute()
{
    var requestBody = Request.Headers["ReadRequestBodyAttribute"];
    var message = $"Request Body From Attribute : {requestBody}";

    return Ok(message);
}

Aqui, aplicamos o atributo personalizado ReadRequestBody à ação do controlador ReadFromAttribute(). Dentro da ação, lemos o cabeçalho da requisição ReadRequestBodyAttribute e atribuímos o conteúdo dele à resposta da ação.

Qual abordagem usar para ler o corpo da requisição?

Escolha pelo ponto em que o corpo ainda existe e pela abrangência da tarefa.

Middleware para tudo o que vale para todas as requisições: logging, auditoria, limites de tamanho. Ele roda primeiro, é o único lugar que pode ativar o armazenamento em buffer para o pipeline inteiro e pode encerrar uma requisição antes de o MVC começar.

Um filtro de recurso para o mesmo trabalho restrito ao MVC, quando queremos os metadados de controlador e de ação, mas ainda precisamos do corpo antes que o model binding o consuma.

Um filtro de ação ou um atributo personalizado quando a leitura do corpo é um interesse transversal comum a um punhado de ações. Os dois rodam depois do model binding, então os dois precisam do armazenamento em buffer ativado em algum ponto anterior quando a ação vincula o corpo.

A própria ação do controlador quando só essa ação precisa do corpo, e [FromBody] sempre que o payload tiver uma estrutura que valha a pena desserializar.

A ordem por trás de tudo isso é o próprio pipeline de filtros, e vale a pena ver como os filtros são ordenados antes de escolher um ponto de extensão.

Onde lemos o corpoQuando rodaO que vê por padrãoUse para
Middleware personalizadoAntes do model binding e de todos os filtrosO corpo inteiro, em primeira mãoLogging, auditoria, limites de tamanho, tudo o que vale para todas as requisições
Filtro de recurso (IAsyncResourceFilter)Depois da autorização, antes do model bindingO corpo inteiroAtivar o armazenamento em buffer dentro do MVC, em que os metadados de controlador e de ação estão disponíveis
Model binding ([FromBody])Entre os filtros de recurso e os filtros de açãoUm objeto desserializado, e não um streamQualquer payload que tenha uma estrutura
Filtro de ação (IAsyncActionFilter)Depois do model bindingNada, se a ação tiver um parâmetro [FromBody]Trabalho transversal a um grupo de ações
Atributo personalizado que implementa IAsyncActionFilterDepois do model binding, como acimaIgual ao anteriorO mesmo trabalho, restrito a ações específicas em vez de registrado globalmente
A própria ação do controladorPor últimoO corpo, a menos que um parâmetro tenha sido vinculado a partir deleUma única ação que precisa do texto bruto

Conclusão

Neste artigo, vimos vários métodos para ler o corpo da requisição em uma aplicação Web API ASP.NET Core. Obter o corpo da requisição lendo-o nas ações do controlador oferece simplicidade e controle em casos básicos.

Podemos usar um middleware personalizado quando queremos amplos recursos de interceptação global. Isso pode nos permitir registrar o corpo da requisição em log em um único lugar para os endpoints da Web API. Além disso, um middleware personalizado permite manipular com facilidade tanto as requisições quanto as respostas em um só lugar.

Os filtros de ação são bons candidatos para encapsular lógica, o que deixa as ações do controlador mais claras e focadas. Com filtros de ação, abstraímos os detalhes do tratamento do corpo da requisição. Isso permite que a ação do controlador mantenha um foco mais limpo e dedicado no seu objetivo principal.

Também usamos atributos personalizados para um tratamento especializado e declarativo. Isso nos dá controle preciso sobre o processamento dos corpos de requisição. A adequação de cada abordagem depende dos requisitos específicos da aplicação, que vão do controle básico à necessidade de encapsulamento e especialização.

Seja qual for a escolha, o fator decisivo é o mesmo: até que ponto do pipeline o corpo ainda está disponível para leitura.

Testado com .NET 10.0.10.