ASP.NET Core devuelve JSON con código de estado

153

Estoy buscando la forma correcta de devolver JSON con un código de estado HTTP en mi controlador de API web .NET Core. Solía ​​usarlo así:

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

Esto fue en una aplicación MVC 4.6 pero ahora con .NET Core que no parecen tener este IHttpActionResulttengo ActionResulty el uso de la siguiente manera:

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

Pero la respuesta del servidor es extraña, como en la imagen a continuación:

ingrese la descripción de la imagen aquí

Solo quiero que el controlador de API web devuelva JSON con un código de estado HTTP como lo hice en Web API 2.

Rossco
fuente
1
Los métodos "ok" devuelven 200 como código de estado. Los métodos predefinidos cubren todos los casos comunes. Volver a 201 (+ cabecera con nueva ubicación de recursos), se utiliza CreatedAtRouteel método etc
Tseng

Respuestas:

191

La versión más básica que responde con un JsonResultes:

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

Sin embargo, esto no ayudará con su problema porque no puede manejar explícitamente su propio código de respuesta.

La forma de obtener el control sobre los resultados de estado es que debe devolver uno ActionResultque es donde puede aprovechar el StatusCodeResulttipo.

por ejemplo:

// 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);
}

Tenga en cuenta que ambos ejemplos anteriores provienen de una excelente guía disponible de la documentación de Microsoft: Formateo de datos de respuesta


Cosas extra

El problema que encuentro con bastante frecuencia es que quería un control más granular sobre mi WebAPI en lugar de simplemente ir con la configuración predeterminada de la plantilla "Nuevo proyecto" en VS.

Asegurémonos de tener algunos de los conceptos básicos ...

Paso 1: configura tu servicio

Para que su ASP.NET Core WebAPI responda con un objeto serializado JSON junto con el control total del código de estado, debe comenzar asegurándose de haber incluido el AddMvc()servicio en su ConfigureServicesmétodo que generalmente se encuentra en Startup.cs.

Es importante tener en cuenta que AddMvc()incluirá automáticamente el formateador de entrada / salida para JSON junto con la respuesta a otros tipos de solicitud.

Si su proyecto requiere control total y desea definir estrictamente sus servicios, por ejemplo, cómo se comportará su WebAPI frente a varios tipos de solicitud, incluidos application/jsony no responder a otros tipos de solicitud (como una solicitud de navegador estándar), puede definirlo manualmente con el siguiente código:

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)
}

Notarás que también he incluido una forma para que agregues tus propios formateadores de entrada / salida personalizados, en el caso de que quieras responder a otro formato de serialización (protobuf, ahorro, etc.).

El fragmento de código anterior es principalmente un duplicado del AddMvc()método. Sin embargo, estamos implementando cada servicio "predeterminado" por nuestra cuenta definiendo todos y cada uno de los servicios en lugar de ir con el preenviado con la plantilla. He agregado el enlace del repositorio en el bloque de código, o puede consultarlo AddMvc() desde el repositorio de GitHub. .

Tenga en cuenta que hay algunas guías que tratarán de resolver esto "deshaciendo" los valores predeterminados, en lugar de simplemente no implementarlo en primer lugar ... Si considera que ahora estamos trabajando con Open Source, este es un trabajo redundante , mal código y, francamente, un viejo hábito que desaparecerá pronto.


Paso 2: crear un controlador

Te voy a mostrar una muy sencilla para resolver tu pregunta.

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();
    }
}

Paso 3: comprueba tu Content-TypeyAccept

Debe asegurarse de que su Content-Typey los Acceptencabezados en su solicitud estén configurados correctamente. En su caso (JSON), querrá configurarlo para que sea application/json.

Si desea que su WebAPI responda como JSON de forma predeterminada, independientemente de lo que especifique el encabezado de la solicitud, puede hacerlo de dos maneras .

Forma 1 Como se muestra en el artículo que recomendé anteriormente ( Formatear datos de respuesta ), podría forzar un formato particular en el nivel Controlador / Acción. Personalmente no me gusta este enfoque ... pero aquí está para completar:

Forzar un formato particular Si desea restringir los formatos de respuesta para una acción específica, puede aplicar el filtro [Produce]. El filtro [Produce] especifica los formatos de respuesta para una acción específica (o controlador). Como la mayoría de los filtros, esto se puede aplicar en la acción, el controlador o el alcance global.

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

El [Produces]filtro forzará todas las acciones dentro del AuthorsControllerpara devolver respuestas con formato JSON, incluso si se configuraron otros formateadores para la aplicación y el cliente proporcionó un Acceptencabezado solicitando un formato diferente y disponible.

Modo 2 Mi método preferido es que WebAPI responda a todas las solicitudes con el formato solicitado. Sin embargo, en el caso de que no acepte el formato solicitado, recurra a un valor predeterminado (es decir, JSON)

Primero, deberá registrar eso en sus opciones (necesitamos modificar el comportamiento predeterminado, como se señaló anteriormente)

options.RespectBrowserAcceptHeader = true; // false by default

Finalmente, simplemente reordenando la lista de los formateadores que se definieron en el creador de servicios, el proveedor de alojamiento web adoptará el formateador que coloque en la parte superior de la lista (es decir, la posición 0).

Se puede encontrar más información en esta entrada de Blog de herramientas y desarrollo web .NET

Svek
fuente
Muchas gracias por el esfuerzo que pusiste. ¡Tu respuesta me inspiró a implementar IActionResultcon return Ok(new {response = "123"});Cheers!
Rossco
1
@Rossco No hay problema. Esperemos que el resto del código lo guíe a medida que se desarrolla su proyecto.
Svek
1
Para ampliar este tema, creé una guía adicional y más completa para implementar el WebAPI aquí: stackoverflow.com/q/42365275/3645638
Svek
En la configuración: RespectBrowserAcceptHeader = true; No está explicando por qué lo está haciendo, y generalmente es innecesario y está mal hacerlo. Los navegadores solicitan html y, por lo tanto, no deberían afectar la selección del formateador de ninguna manera (desafortunadamente, Chrome lo hace al pedir XML). En resumen, es algo que evitaría, y el respaldo que está especificando ya es el comportamiento predeterminado
Yishai Galatzer
@YishaiGalatzer El tema principal de esa parte de mi respuesta fue resaltar cómo descargar el middleware predeterminado entre el cliente y la lógica API. En mi opinión, RespectBrowserAcceptHeaderes fundamental cuando se implementa el uso de un serializador alternativo o, más comúnmente, cuando se quiere asegurar de que sus clientes no envíen solicitudes con formato incorrecto. Por lo tanto, hice hincapié en "Si su proyecto requiere control total y desea definir estrictamente su servicio" y tenga en cuenta la cita de bloque resaltada encima de esa declaración también.
Svek
57

Tiene métodos predefinidos para los códigos de estado más comunes.

  • Ok(result)vuelve 200con respuesta
  • CreatedAtRoutedevuelve 201+ nueva URL de recurso
  • NotFound devoluciones 404
  • BadRequestdevoluciones 400etc.

Consulte BaseController.csy Controller.cspara obtener una lista de todos los métodos.

Pero si realmente insiste, puede usarlo StatusCodepara configurar un código personalizado, pero realmente no debería hacerlo, ya que hace que el código sea menos legible y tendrá que repetir el código para configurar encabezados (como para CreatedAtRoute).

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}
Tseng
fuente
1
Esto me dio una idea de mi respuesta a continuación. Gracias
Oge Nwike
Este código no es correcto para ASP.NET Core 2.2. Acabo de probarlo y se serializa en JSONel ActionResultcreado por el Json()método. No incluye la cadena "123" directamente.
amedina
1
@amedina: Mi mal, simplemente quite el Json(...)y pase la cadena a StatusCode
Tseng
Cuando dices "Ok (resultado)", ¿cuál es el resultado? ¿Es una cadena de formato JSON o es un objeto C # (que se convierte automáticamente en una cadena JSON)?
variable
@variable: siempre un POCO / clase / objeto. Si desea devolver una cadena, debe usar "Contenido" en su lugar
Tseng
42

Con ASP.NET Core 2.0 , la forma ideal de devolver objetos Web API(que está unificada con MVC y usa la misma clase base Controller) es

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

Darse cuenta de

  1. Regresa con el 200 OKcódigo de estado (es un Oktipo de ObjectResult)
  2. Hace negociación de contenido, es decir, regresará en función del Acceptencabezado en la solicitud. Si Accept: application/xmlse envía en solicitud, regresará como XML. Si no se envía nada, JSONes el valor predeterminado.

Si necesita enviar con un código de estado específico , use ObjectResulto en su StatusCodelugar. Ambos hacen lo mismo y admiten la negociación de contenido.

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

o incluso más de grano fino con 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 };

Si desea regresar específicamente como JSON , hay dos formas

//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" };
}

Darse cuenta de

  1. Ambos se aplican JSONde dos maneras diferentes.
  2. Ambos ignoran la negociación de contenido.
  3. El primer método impone JSON con un serializador específico Json(object).
  4. El segundo método hace lo mismo al usar el Produces()atributo (que es a ResultFilter) concontentType = application/json

Lea más sobre ellos en los documentos oficiales . Aprende sobre los filtros aquí .

La clase de modelo simple que se utiliza en las muestras.

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Arghya C
fuente
10
Esta es una buena respuesta porque se centra en la pregunta y explica brevemente algunos aspectos prácticos.
netfed
33

La forma más fácil que se me ocurrió es:

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

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};
Gerald Hughes
fuente
2
Creo que esto es mejor que la respuesta de @tseng porque su solución incluye campos duplicados para códigos de estado, etc.
Christian Sauer
2
Una mejora que puede hacer es utilizar los StatusCodes definidos en Microsoft.AspNetCore.Http de esta manera: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard
2
Esta debería ser la respuesta aceptada. Aunque hay formas de configurar universalmente el json, a veces tenemos que trabajar con puntos finales heredados y la configuración puede ser diferente. Hasta que podamos dejar de admitir algunos puntos finales heredados, esta es la mejor manera de tener control total
pqsk
Microsoft.AspNetCore.Mvc.JsonResult es el nombre completo que creo. No hay respuestas FQN o "usar" me vuelven loco. :) Asamblea Microsoft.AspNetCore.Mvc.Core, Versión = 3.1.0.0, Cultura = neutral, PublicKeyToken = adb9793829ddae60 // C: \ Archivos de programa \ dotnet \ packs \ Microsoft.AspNetCore.App.Ref \ 3.1.0 \ ref \ netcoreapp3.1 \ Microsoft.AspNetCore.Mvc.Core.dll
granadaCoder
1
Esto funcionó para mí cuando tenía un tipo fuerte ("ITem result = new Item" en este ejemplo ... Item es un tipo conocido en tiempo de ejecución)). Vea mi respuesta (a esta pregunta) para saber cuándo se desconoce el tipo. (Tenía json en una base de datos ... y el tipo json no se conocía en tiempo de ejecución). Gracias Gerald
granadaCoder
15

Esta es mi solución más fácil:

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

o

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

En lugar de usar códigos de estado 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
fuente
¡Enum es una gran idea!
Bhimbim
2

¡¡¡Impresionantes respuestas que encontré aquí y también probé esta declaración de devolución StatusCode(whatever code you wish), ¡¡¡y funcionó !!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });
Oge Nwike
fuente
1
¡Como éste! ¡Buena sugerencia!
ticky
0

Consulte el código a continuación, puede administrar múltiples códigos de estado con 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
fuente
9
No. Esto es malo.
Phillip Copley
0

Lo que hago en mis aplicaciones Asp Net Core Api es crear una clase que se extiende desde ObjectResult y proporcionar muchos constructores para personalizar el contenido y el código de estado. Entonces, todas mis acciones de Controlador usan uno de los costructores como corresponda. Puede ver mi implementación en: https://github.com/melardev/AspNetCoreApiPaginatedCrud

y

https://github.com/melardev/ApiAspCoreEcommerce

así es como se ve la clase (vaya a mi repositorio para obtener el 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 la base (dto) que reemplaza dto por su objeto y debería estar listo.

Melardev
fuente
0

Tengo esto para trabajar. Mi gran problema fue que mi json era una cadena (en mi base de datos ... y no un tipo específico / conocido).

Ok, finalmente conseguí que esto funcione.

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

Estoy en 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

Tengo la pista de aquí :: https://www.jianshu.com/p/7b3e92c42b61

granadaCoder
fuente