ASP.NET Core retorna JSON com código de status

153

Estou procurando a maneira correta de retornar JSON com um código de status HTTP no meu controlador de API Web do .NET Core. Eu uso para usá-lo assim:

public IHttpActionResult GetResourceData()
{
    return this.Content(HttpStatusCode.OK, new { response = "Hello"});
}

Isso estava em um aplicativo MVC 4.6, mas agora com o .NET Core não parece ter isso e IHttpActionResulttenho o ActionResultseguinte:

public ActionResult IsAuthenticated()
{
    return Ok(Json("123"));
}

Mas a resposta do servidor é estranha, como na imagem abaixo:

insira a descrição da imagem aqui

Eu só quero que o controlador da API da Web retorne JSON com um código de status HTTP, como fiz na API da Web 2.

Rossco
fonte
1
Os métodos "ok" retornam 200 como código de status. Os métodos predefinidos cobrem todos os casos comuns. Para retornar 201 (+ cabeçalho com o novo local do recurso), você usa o CreatedAtRoutemétodo etc.
Tseng

Respostas:

191

A versão mais básica que responde com a JsonResulté:

// GET: api/authors
[HttpGet]
public JsonResult Get()
{
    return Json(_authorRepository.List());
}

No entanto, isso não ajudará no seu problema, porque você não pode lidar explicitamente com seu próprio código de resposta.

A maneira de obter controle sobre os resultados do status é retornar um ActionResultque é onde você pode tirar proveito do StatusCodeResulttipo.

por exemplo:

// GET: api/authors/search?namelike=foo
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
    var result = _authorRepository.GetByNameSubstring(namelike);
    if (!result.Any())
    {
        return NotFound(namelike);
    }
    return Ok(result);
}

Observe que os dois exemplos acima vieram de um ótimo guia disponível em Documentação da Microsoft: Formatando dados de resposta


Coisas extras

O problema que me deparo com frequência é que eu queria um controle mais granular sobre minha WebAPI, em vez de apenas ir com a configuração de padrões do modelo "New Project" no VS.

Vamos garantir que você tenha alguns princípios básicos ...

Etapa 1: configurar seu serviço

Para que sua ASP.NET Core WebAPI responda com um Objeto serializado JSON ao longo do controle total do código de status, você deve começar por incluir o AddMvc()serviço em seu ConfigureServicesmétodo normalmente encontrado em Startup.cs.

É importante observar que AddMvc()incluirá automaticamente o Formatador de Entrada / Saída para JSON, além de responder a outros tipos de solicitação.

Se seu projeto requer controle total e você deseja definir estritamente seus serviços, como o comportamento da sua WebAPI com vários tipos de solicitação, incluindo application/jsone não respondendo a outros tipos de solicitação (como uma solicitação padrão do navegador), você pode defini-lo manualmente com o código a seguir:

public void ConfigureServices(IServiceCollection services)
{
    // Build a customized MVC implementation, without using the default AddMvc(), instead use AddMvcCore().
    // https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs

    services
        .AddMvcCore(options =>
        {
            options.RequireHttpsPermanent = true; // does not affect api requests
            options.RespectBrowserAcceptHeader = true; // false by default
            //options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();

            //remove these two below, but added so you know where to place them...
            options.OutputFormatters.Add(new YourCustomOutputFormatter()); 
            options.InputFormatters.Add(new YourCustomInputFormatter());
        })
        //.AddApiExplorer()
        //.AddAuthorization()
        .AddFormatterMappings()
        //.AddCacheTagHelper()
        //.AddDataAnnotations()
        //.AddCors()
        .AddJsonFormatters(); // JSON, or you can build your own custom one (above)
}

Você notará que eu também incluí uma maneira de adicionar seus próprios formatadores de entrada / saída personalizados, caso você queira responder a outro formato de serialização (protobuf, thrift etc.).

O pedaço de código acima é basicamente uma duplicata do AddMvc()método. No entanto, estamos implementando cada serviço "padrão" por conta própria, definindo cada serviço em vez de ir com o serviço pré-enviado com o modelo. Eu adicionei o link do repositório no bloco de código, ou você pode fazer o check-out AddMvc() no repositório do GitHub. .

Observe que existem alguns guias que tentarão resolver isso "desfazendo" os padrões, em vez de simplesmente não implementá-lo ... Se você considerar que agora estamos trabalhando com código aberto, este é um trabalho redundante , código ruim e francamente um velho hábito que desaparecerá em breve.


Etapa 2: Criar um Controlador

Vou mostrar uma pergunta realmente direta apenas para esclarecer sua dúvida.

public class FooController
{
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] Object item)
    {
        if (item == null) return BadRequest();

        var newItem = new Object(); // create the object to return
        if (newItem != null) return Ok(newItem);

        else return NotFound();
    }
}

Etapa 3: verifique seu Content-TypeeAccept

Você precisa garantir que seus cabeçalhos Content-Typee os de Acceptsua solicitação estejam definidos corretamente. No seu caso (JSON), você desejará configurá-lo para ser application/json.

Se você deseja que sua WebAPI responda como JSON como padrão, independentemente do que o cabeçalho da solicitação está especificando, você pode fazer isso de duas maneiras .

Caminho 1 Como mostrado no artigo que eu recomendei anteriormente ( Formatação de dados de resposta ), você pode forçar um formato específico no nível do controlador / ação. Pessoalmente, não gosto dessa abordagem ... mas aqui está a questão:

Forçando um formato específico Se você deseja restringir os formatos de resposta para uma ação específica, pode aplicar o filtro [Produces]. O filtro [Produces] especifica os formatos de resposta para uma ação específica (ou controlador). Como a maioria dos filtros, isso pode ser aplicado no escopo de ação, controlador ou global.

[Produces("application/json")]
public class AuthorsController

O [Produces]filtro forçará todas as ações no AuthorsControllerretorno a respostas formatadas em JSON, mesmo se outros formatadores foram configurados para o aplicativo e o cliente forneceu um Acceptcabeçalho solicitando um formato disponível diferente.

Caminho 2 Meu método preferido é o WebAPI responder a todas as solicitações com o formato solicitado. No entanto, caso ele não aceite o formato solicitado, retorne ao padrão (por exemplo, JSON)

Primeiro, você precisará registrar isso em suas opções (precisamos refazer o comportamento padrão, conforme observado anteriormente)

options.RespectBrowserAcceptHeader = true; // false by default

Por fim, simplesmente reordenando a lista dos formatadores que foram definidos no construtor de serviços, o host padrão será o formatador que você posicionar no topo da lista (ou seja, posição 0).

Mais informações podem ser encontradas nesta entrada do blog .NET Web Development and Tools

Svek
fonte
Muito obrigado pelo esforço que você fez. Sua resposta me inspirou a implementar IActionResultcom o return Ok(new {response = "123"});Cheers!
Rossco 21/02
1
@ Rossco Não há problema. Espero que o restante do código ajude a guiá-lo à medida que seu projeto se desenvolve.
Svek
1
Para estender este tema, eu criei um guia adicional e mais completa para implementação do WebAPI aqui: stackoverflow.com/q/42365275/3645638
Svek
Na configuração: RespectBrowserAcceptHeader = true; Você não está explicando por que está fazendo isso, e normalmente é desnecessário e errado fazê-lo. Os navegadores solicitam html e, portanto, não devem afetar a seleção do formatador de qualquer maneira (que o Chrome infelizmente solicita XML). Em suma, é algo que eu iria manter fora, eo fallback você está especificando já é o comportamento padrão
Yishai Galatzer
@YishaiGalatzer O tema principal dessa parte da minha resposta foi destacar como liberar o middleware padrão entre o cliente e a lógica da API. Na minha opinião, RespectBrowserAcceptHeaderé fundamental ao implementar o uso de um serializador alternativo ou, mais comumente, quando você deseja garantir que seus clientes não estejam enviando solicitações malformadas. Por isso, enfatizei "Se o seu projeto requer controle total e você deseja definir estritamente o seu serviço", observe também a citação em bloco destacada acima dessa declaração.
Svek
57

Você tem métodos predefinidos para os códigos de status mais comuns.

  • Ok(result)retorna 200com resposta
  • CreatedAtRouteretorna 201+ novo URL do recurso
  • NotFound retorna 404
  • BadRequestretorna 400etc.

Veja BaseController.cse Controller.cspara uma lista de todos os métodos.

Mas se você realmente insistir, pode usar StatusCodepara definir um código personalizado, mas não deve, pois isso torna o código menos legível e você terá que repetir o código para definir cabeçalhos (como para CreatedAtRoute).

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}
Tseng
fonte
1
isso me deu uma visão da minha resposta abaixo. Obrigado
Oge Nwike
Este código não está correto para o ASP.NET Core 2.2. Eu apenas tentei isso e ele serializa para JSONo ActionResultcriado pelo Json()método. Não inclui a sequência "123" diretamente.
amedina 29/09/19
1
@amedina: Meu mal, basta remover oe Json(...)passar a corda para # StatusCode
Tseng
Quando você diz "Ok (resultado)" - o que é resultado? É uma sequência de formato JSON ou é um objeto C # (que é automaticamente convertido em sequência JSON?)?
variável
@ variável: sempre um POCO / classe / objeto. Se você quiser retornar uma string, precisará usar "Conteúdo"
Tseng
42

Com o ASP.NET Core 2.0 , a maneira ideal de retornar o objeto Web API(que é unificado ao MVC e usa a mesma classe base Controller) é

public IActionResult Get()
{
    return new OkObjectResult(new Item { Id = 123, Name = "Hero" });
}

Notar que

  1. Retorna com 200 OKcódigo de status (é um Oktipo de ObjectResult)
  2. Faz negociação de conteúdo, ou seja, retornará com base no Acceptcabeçalho da solicitação. Se Accept: application/xmlfor enviado a pedido, retornará como XML. Se nada for enviado, JSONé o padrão.

Se precisar enviar com código de status específico , use ObjectResultou em StatusCodevez disso. Ambos fazem a mesma coisa e suportam a negociação de conteúdo.

return new ObjectResult(new Item { Id = 123, Name = "Hero" }) { StatusCode = 200 };
return StatusCode( 200, new Item { Id = 123, Name = "Hero" });

ou ainda mais refinado com ObjectResult:

 Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection myContentTypes = new Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection { System.Net.Mime.MediaTypeNames.Application.Json };
 String hardCodedJson = "{\"Id\":\"123\",\"DateOfRegistration\":\"2012-10-21T00:00:00+05:30\",\"Status\":0}";
 return new ObjectResult(hardCodedJson) { StatusCode = 200, ContentTypes = myContentTypes };

Se você deseja retornar especificamente como JSON , existem duas maneiras

//GET http://example.com/api/test/asjson
[HttpGet("AsJson")]
public JsonResult GetAsJson()
{
    return Json(new Item { Id = 123, Name = "Hero" });
}

//GET http://example.com/api/test/withproduces
[HttpGet("WithProduces")]
[Produces("application/json")]
public Item GetWithProduces()
{
    return new Item { Id = 123, Name = "Hero" };
}

Notar que

  1. Ambos reforçam JSONde duas maneiras diferentes.
  2. Ambos ignoram a negociação de conteúdo.
  3. O primeiro método impõe JSON com serializador específico Json(object).
  4. O segundo método faz o mesmo usando o Produces()atributo (que é a ResultFilter) comcontentType = application/json

Leia mais sobre eles nos documentos oficiais . Aprenda sobre filtros aqui .

A classe de modelo simples usada nas amostras

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Arghya C
fonte
10
Essa é uma boa resposta, pois se concentra na pergunta e explica alguns aspectos práticos em breve.
Netfed
33

A maneira mais fácil de criar é:

var result = new Item { Id = 123, Name = "Hero" };

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};
Gerald Hughes
fonte
2
Eu acho que isso é melhor do que a resposta do @tseng porque sua solução inclui campos duplicados para códigos de status etc.
Christian Sauer
2
Uma melhoria que você pode fazer é usar os StatusCodes definidos no Microsoft.AspNetCore.Http assim: retornar novo JsonResult (novo {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard
2
Essa deve ser a resposta aceita. Embora existam maneiras de configurar universalmente o json, às vezes precisamos trabalhar com terminais herdados e as configurações podem ser diferentes. Até que possamos parar de apoiar alguns endpoints legados, esta é a melhor forma de ter controle total
pqsk
Microsoft.AspNetCore.Mvc.JsonResult é o nome totalmente qualificado, eu acho. Nenhuma resposta de FQN ou "usando" me deixa louca. :) Assembly Microsoft.AspNetCore.Mvc.Core, Versão = 3.1.0.0, Cultura = neutra, PublicKeyToken = adb9793829ddae60 // C: \ Arquivos de programas \ dotnet \ packs \ Microsoft.AspNetCore.App.Ref \ 3.1.0 \ ref \ netcoreapp3.1 \ Microsoft.AspNetCore.Mvc.Core.dll
granadaCoder
1
Isso funcionou para mim quando eu tinha um tipo forte ("ITem result = new Item" neste exemplo ... Item é um tipo conhecido em tempo de execução)). Veja minha resposta (para esta pergunta) para quando o tipo não é conhecido. (Eu tinha json em um banco de dados .. e o tipo json não era conhecido em tempo de execução). Obrigado Gerald.
granadaCoder 9/03
15

Esta é a minha solução mais fácil:

public IActionResult InfoTag()
{
    return Ok(new {name = "Fabio", age = 42, gender = "M"});
}

ou

public IActionResult InfoTag()
{
    return Json(new {name = "Fabio", age = 42, gender = "M"});
}
Fabio
fonte
4

Em vez de usar códigos de status 404/201 usando enum

     public async Task<IActionResult> Login(string email, string password)
    {
        if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
        { 
            return StatusCode((int)HttpStatusCode.BadRequest, Json("email or password is null")); 
        }

        var user = await _userManager.FindByEmailAsync(email);
        if (user == null)
        {
            return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));

        }
        var passwordSignInResult = await _signInManager.PasswordSignInAsync(user, password, isPersistent: true, lockoutOnFailure: false);
        if (!passwordSignInResult.Succeeded)
        {
            return StatusCode((int)HttpStatusCode.BadRequest, Json("Invalid Login and/or password"));
        }
        return StatusCode((int)HttpStatusCode.OK, Json("Sucess !!!"));
    }
ram dev
fonte
Enum é uma ótima idéia!
precisa saber é o seguinte
2

Respostas impressionantes que encontrei aqui e também tentei esta declaração de retorno ver StatusCode(whatever code you wish)e funcionou !!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });
Oge Nwike
fonte
1
Como este! Boa sugestão!
ticky
0

Consulte o código abaixo, você pode gerenciar vários códigos de status com diferentes tipos JSON

public async Task<HttpResponseMessage> GetAsync()
{
    try
    {
        using (var entities = new DbEntities())
        {
            var resourceModelList = entities.Resources.Select(r=> new ResourceModel{Build Your Resource Model}).ToList();

            if (resourceModelList.Count == 0)
            {
                return this.Request.CreateResponse<string>(HttpStatusCode.NotFound, "No resources found.");
            }

            return this.Request.CreateResponse<List<ResourceModel>>(HttpStatusCode.OK, resourceModelList, "application/json");
        }
    }
    catch (Exception ex)
    {
        return this.Request.CreateResponse<string>(HttpStatusCode.InternalServerError, "Something went wrong.");
    }
}
Suyog
fonte
9
Não. Isso é ruim.
Phillip Copley
0

O que faço em meus aplicativos Asp Net Core Api é criar uma classe que se estende do ObjectResult e fornecer muitos construtores para personalizar o conteúdo e o código de status. Então todas as minhas ações do Controller usam um dos costrutores conforme apropriado. Você pode dar uma olhada na minha implementação em: https://github.com/melardev/AspNetCoreApiPaginatedCrud

e

https://github.com/melardev/ApiAspCoreEcommerce

aqui está como a classe se parece (vá ao meu repositório para obter o código completo):

public class StatusCodeAndDtoWrapper : ObjectResult
{



    public StatusCodeAndDtoWrapper(AppResponse dto, int statusCode = 200) : base(dto)
    {
        StatusCode = statusCode;
    }

    private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, string message) : base(dto)
    {
        StatusCode = statusCode;
        if (dto.FullMessages == null)
            dto.FullMessages = new List<string>(1);
        dto.FullMessages.Add(message);
    }

    private StatusCodeAndDtoWrapper(AppResponse dto, int statusCode, ICollection<string> messages) : base(dto)
    {
        StatusCode = statusCode;
        dto.FullMessages = messages;
    }
}

Observe a base (dto) que você substitui dto pelo seu objeto e você deve estar pronto.

Melardev
fonte
0

Eu fiz isso funcionar. Meu grande problema foi que meu json era uma string (no meu banco de dados ... e não um tipo específico / conhecido).

Ok, finalmente consegui que isso funcionasse.

////[Route("api/[controller]")]
////[ApiController]
////public class MyController: Microsoft.AspNetCore.Mvc.ControllerBase
////{
                    //// public IActionResult MyMethod(string myParam) {

                    string hardCodedJson = "{}";
                    int hardCodedStatusCode = 200;

                    Newtonsoft.Json.Linq.JObject job = Newtonsoft.Json.Linq.JObject.Parse(hardCodedJson);
                    /* "this" comes from your class being a subclass of Microsoft.AspNetCore.Mvc.ControllerBase */
                    Microsoft.AspNetCore.Mvc.ContentResult contRes = this.Content(job.ToString());
                    contRes.StatusCode = hardCodedStatusCode;

                    return contRes;

                    //// } ////end MyMethod
              //// } ////end class

Eu estou no asp.net core 3.1

#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
//C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\3.1.0\ref\netcoreapp3.1\Microsoft.AspNetCore.Mvc.Core.dll

Eu tenho a dica a partir daqui :: https://www.jianshu.com/p/7b3e92c42b61

granadaCoder
fonte