ASP .NET Core  - Retornando JSON Result com Status Code customizado


  Neste artigo veremos como controlar o formato das respostas e ver como retornar um JSON Result com código de status personalizado com a ajuda de formatadores ou diretamente das Actions.

A ASP.NET Core tem suporte para formatar dados de resposta. Os dados de resposta podem ser formatados usando formatos específicos ou em resposta ao formato solicitado pelo cliente.

Alguns tipos de resultado de ação são específicos a um formato específico, como JsonResult e ContentResult. As ações podem retornar resultados formatados em um formato específico, independentemente das preferências do cliente. Por exemplo:

1-  Ao retornar JsonResult estamos retornando dados formatados para JSON;
2 -
Retornar ContentResult ou uma string retorna dados strings formatados com texto sem formatação;

Como a ASP .NET decide o formato do Response ?

Vamos iniciar dando uma olhada em como um resultado JSON é realmente produzido pela ASP.NET Core, o que nos ajudará a entender o mecanismo por trás da formatação de resposta.

Quando configuramos a ASP.NET Core com services.AddControllers, isso adiciona os OutputFormatters integrados, que são usados para gravar um objeto no fluxo de saída:

Microsoft.AspNetCore.Mvc.Formatters.HttpNoContentOutputFormatter
Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter
Microsoft.AspNetCore.Mvc.Formatters.StreamOutputFormatter
Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter


Esses formatadores de saída são o que permite que uma Action retorne qualquer valor de retorno do objeto. O formatador é selecionado aqui por meio do processo denominado negociação de conteúdo, que ocorre quando o cliente especifica um cabeçalho Accept.


Vejamos isso com um exemplo. Vamos criar um projeto WEB API usando o seguinte comando:
  dotnet new webapi --no-https --auth=None

A seguir vamos alterar o formato de saída adicionando o formatador de saída XML à lista de formatadores suportados:

        ...
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers().AddXmlSerializerFormatters();

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "jsonformat", Version = "v1" });
            });
        }
        ...

Vamos executar o projeto com o comando: dotnet run

E a seguir vamos emitir o seguinte comando a partir do projeto em uma janela de comandos:

curl -v -H "Accept: application/xml" http://localhost:5000/WeatherForecast

Teremos o resultado abaixo:

Note que a resposta esta no formato XML.

Vamos alterar o valor do cabeçalho Accept para application/json emitindo o seguinte comando:

curl -v -H "Accept: application/json" http://localhost:5000/WeatherForecast

Teremos o seguinte resultado:

Assim, conforme o valor do header Accept teremos o retorno como XML ou JSON.

Além de retornar POCOs (Plain Old CLR Objects) e deixar a negociação de conteúdo decidir qual formatador de saída escolher, você também pode retornar um IActionResult, que define um contrato que representa o resultado de um método Action, a partir da Action do controlador que pode permitir que tenha controle direto sobre o tipo de retorno.

Por exemplo, a implementação JsonResult de IActionResult retorna dados formatados como JSON, independentemente do cabeçalho Accept.

Podemos verificar isso alterando o método código do método HpttGet do controlador WeatherForecast:

...
        [HttpGet]
        public IActionResult Get()
        {
            var rng = new Random();
            var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

             return new JsonResult(result);
        }
...

Para provar isso emita o comando abaixo em um terminal de comandos na pasta do projeto:

curl -v -H "Accept: application/xml" http://localhost:5000/WeatherForecast

O resultado é visto abaixo:

Note que mesmo definindo o Accept como application/xml o formato do response é JSON.

Alterando o Código de Status de Resposta

Agora, vamos ver como podemos alterar o código de status de resposta. Com todos os casos que mencionamos acima, podemos simplesmente definir Response.StatusCode antes de retornar o resultado para o código de status apropriado com o qual queremos responder.

Vamos alterar o código do método HttpGet do controlador WeatherForecast:

...
        [HttpGet]
        public IActionResult Get()
        {
            var rng = new Random();
            var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

             Response.StatusCode = StatusCodes.Status400BadRequest;
             return new JsonResult(result);
        }
...

A seguir vamos executar o projeto com : dotnet run

E emitir o seguinte comando :

curl -v -H "Accept: application/json" http://localhost:5000/WeatherForecast

Veja o resultado a seguir:

Além dessa maneira simples de definir o código de status, também temos alguns métodos auxiliares no objeto ControllerBase, que nos dá a capacidade de moldar uma resposta.

Por exemplo, o método Conflict cria um ConflictObjectResult que produz uma resposta Status409Conflict.

Assim, alterando o código do método Get conforme abaixo:

...
        [HttpGet]
        public IActionResult Get()
        {
            var rng = new Random();
            var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();

            return Conflit(result);
        }
...

Executando o projeto e emitindo o comando conforme abaixo temos o resultado exibido:

Nesse caso, não importa que definimos Response.StatusCode dentro da Action, ele seria substituído pelo método auxiliar Conflict.

E estamos conversados...

"Confessai as vossas culpas uns aos outros, e orai uns pelos outros, para que sareis. A oração feita por um justo pode muito em seus efeitos."
Tiago 5:16

Referências:


José Carlos Macoratti