ASP.NET Core restituisce JSON con codice di stato


153

Sto cercando il modo corretto di restituire JSON con un codice di stato HTTP nel mio controller API Web .NET Core. Lo uso in questo modo:

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

Questo era in un'applicazione 4.6 MVC ma ora con .NET Core non mi sembra di IHttpActionResultaverlo ActionResulte di usarlo in questo modo:

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

Ma la risposta dal server è strana, come nell'immagine qui sotto:

inserisci qui la descrizione dell'immagine

Voglio solo che il controller API Web restituisca JSON con un codice di stato HTTP come ho fatto in API Web 2.


1
I metodi "ok" restituiscono 200 come codice di stato. I metodi predefiniti coprono tutti i casi comuni. Per restituire 201 (+ intestazione con nuova posizione della risorsa), usi il CreatedAtRoutemetodo ecc.
Tseng,

Risposte:


191

La versione più semplice che risponde con a JsonResultè:

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

Tuttavia, questo non ti aiuterà a risolvere il problema perché non puoi gestire esplicitamente il tuo codice di risposta.

Il modo per ottenere il controllo sui risultati dello stato è che è necessario restituire un oggetto in ActionResultcui è possibile sfruttare il StatusCodeResulttipo.

per esempio:

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

Si noti che entrambi questi esempi sopra riportati provengono da un'ottima guida disponibile nella documentazione Microsoft: formattazione dei dati di risposta


Cose extra

Il problema che ho riscontrato abbastanza spesso è che volevo un controllo più granulare sul mio WebAPI piuttosto che semplicemente andare con la configurazione predefinita dal modello "Nuovo progetto" in VS.

Assicuriamoci di avere alcune delle nozioni di base giù ...

Passaggio 1: configura il tuo servizio

Per fare in modo che la tua ASP.NET Core WebAPI risponda con un oggetto serializzato JSON con il pieno controllo del codice di stato, devi iniziare assicurandoti di aver incluso il AddMvc()servizio nel tuo ConfigureServicesmetodo di solito presente Startup.cs.

È importante notare che AddMvc()includerà automaticamente il formattatore di input / output per JSON insieme alla risposta ad altri tipi di richieste.

Se il tuo progetto richiede il pieno controllo e desideri definire rigorosamente i tuoi servizi, ad esempio come il tuo WebAPI si comporterà a vari tipi di richiesta, incluso application/jsone non risponderà ad altri tipi di richiesta (come una richiesta standard del browser), puoi definirlo manualmente con codice seguente:

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

Noterai che ho anche incluso un modo per aggiungere i tuoi formattatori di input / output personalizzati, nel caso in cui potresti voler rispondere a un altro formato di serializzazione (protobuf, parsimonia, ecc.).

Il pezzo di codice sopra è principalmente un duplicato del AddMvc()metodo. Tuttavia, stiamo implementando da soli ogni servizio "predefinito" definendo ogni singolo servizio invece di andare con quello pre-spedito con il modello. Ho aggiunto il link al repository nel blocco di codice, oppure puoi controllare AddMvc() dal repository GitHub. .

Nota che ci sono alcune guide che proveranno a risolvere questo problema "annullando" le impostazioni predefinite, piuttosto che non implementarle in primo luogo ... Se consideri che ora stiamo lavorando con Open Source, questo è un lavoro ridondante , codice errato e francamente una vecchia abitudine che scomparirà presto.


Passaggio 2: creare un controller

Te lo mostrerò davvero semplice solo per risolvere la tua domanda.

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

Passaggio 3: controlla il tuo Content-TypeeAccept

Devi assicurarti che tu Content-Typee le Acceptintestazioni nella tua richiesta siano impostate correttamente. Nel tuo caso (JSON), vorrai configurarlo per essere application/json.

Se si desidera che WebAPI risponda come JSON come impostazione predefinita, indipendentemente da ciò che l'intestazione della richiesta specifica, è possibile farlo in un paio di modi .

Modo 1 Come mostrato nell'articolo che ho raccomandato in precedenza ( Formattazione dei dati di risposta ) è possibile forzare un formato particolare a livello di controller / azione. Personalmente non mi piace questo approccio ... ma qui è per completezza:

Forzare un formato particolare Se desideri limitare i formati di risposta per un'azione specifica che puoi, puoi applicare il filtro [Produce]. Il filtro [Produce] specifica i formati di risposta per un'azione specifica (o controller). Come la maggior parte dei filtri, questo può essere applicato all'azione, al controller o all'ambito globale.

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

Il [Produces]filtro imporrà a tutte le azioni all'interno AuthorsControllerdi restituire risposte in formato JSON, anche se altri formattatori sono stati configurati per l'applicazione e il client ha fornito Acceptun'intestazione che richiede un formato diverso e disponibile.

Modo 2 Il mio metodo preferito è che WebAPI risponda a tutte le richieste con il formato richiesto. Tuttavia, nel caso in cui non accetti il ​​formato richiesto, ricorrere a un valore predefinito (es. JSON)

Innanzitutto, devi registrarlo nelle tue opzioni (dobbiamo rielaborare il comportamento predefinito, come notato in precedenza)

options.RespectBrowserAcceptHeader = true; // false by default

Infine, semplicemente riordinando l'elenco dei formatter definiti nel builder di servizi, l'host web passerà automaticamente al formatter che si posiziona in cima all'elenco (ovvero posizione 0).

Ulteriori informazioni sono disponibili in questo post sul blog Strumenti e sviluppo Web .NET


Grazie mille per lo sforzo che hai fatto. La tua risposta mi ha ispirato ad attuare IActionResultcon gli return Ok(new {response = "123"});applausi!
Rossco,

1
@Rossco Nessun problema. Spero che il resto del codice ti guiderà nello sviluppo del tuo progetto.
Svek,

1
Per estendere questo argomento, ho creato una guida supplementare e più completa per l'attuazione del WebAPI qui: stackoverflow.com/q/42365275/3645638
Svek

Su impostazione: RespectBrowserAcceptHeader = true; Non stai spiegando perché lo stai facendo, ed è in genere inutile e sbagliato farlo. I browser richiedono html e quindi non dovrebbero in alcun modo influire sulla selezione dei formatter (che Chrome purtroppo fa richiedendo XML). In breve è qualcosa che vorrei tenere fuori, e il fallback che stai specificando è già il comportamento predefinito
Yishai Galatzer,

@YishaiGalatzer Il tema principale di quella parte della mia risposta è stato quello di evidenziare come sgravare il middleware predefinito tra il client e la logica dell'API. A mio avviso, RespectBrowserAcceptHeaderè fondamentale quando si implementa l'uso di un serializzatore alternativo o più comunemente, quando si desidera assicurarsi che i propri clienti non invino richieste non valide. Quindi, ho sottolineato "Se il tuo progetto richiede il pieno controllo e vuoi definire rigorosamente il tuo servizio" e annota anche la citazione di blocco evidenziata sopra quella dichiarazione.
Svek,

57

Hai metodi predefiniti per i codici di stato più comuni.

  • Ok(result)ritorna 200con risposta
  • CreatedAtRouterestituisce 201+ nuovo URL risorsa
  • NotFound ritorna 404
  • BadRequestritorni 400ecc.

Vedi BaseController.cse Controller.csper un elenco di tutti i metodi.

Ma se insisti davvero che puoi usare StatusCodeper impostare un codice personalizzato, ma non dovresti davvero perché rende il codice meno leggibile e dovrai ripetere il codice per impostare le intestazioni (come per CreatedAtRoute).

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}

1
questo mi ha dato un'idea della mia risposta di seguito. Grazie
Oge Nwike

Questo codice non è corretto per ASP.NET Core 2.2. Ho appena ho provato ed è serializza JSONnel ActionResultcreato dal Json()metodo. Non include direttamente la stringa "123".
amedina,

1
@amedina: Mio male, basta rimuovere il Json(...)e passare la stringa a StatusCode
Tseng

Quando dici "Ok (risultato)" - che cos'è il risultato? È una stringa in formato JSON o è un oggetto C # (che viene automaticamente convertito in stringa JSON?)?
variabile dal

@variable: sempre un POCO / class / object. Se vuoi restituire una stringa, devi invece usare "Contenuto"
Tseng

43

Con ASP.NET Core 2.0 , il modo ideale per restituire l'oggetto Web API(che è unificato con MVC e utilizza la stessa classe di base Controller) è

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

Notare che

  1. Restituisce con il 200 OKcodice di stato (è un Oktipo di ObjectResult)
  2. Fa la negoziazione dei contenuti, cioè tornerà in base all'intestazione Acceptnella richiesta. Se Accept: application/xmlviene inviato su richiesta, verrà restituito come XML. Se non viene inviato nulla, JSONè l'impostazione predefinita.

Se deve essere inviato con un codice di stato specifico , utilizzare ObjectResulto StatusCodeinvece. Entrambi fanno la stessa cosa e supportano la negoziazione dei contenuti.

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

o ancora più fine 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 };

Se vuoi tornare specificamente come JSON , ci sono un paio di modi

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

Notare che

  1. Entrambi si applicano JSONin due modi diversi.
  2. Entrambi ignorano la negoziazione del contenuto.
  3. Il primo metodo applica JSON con serializzatore specifico Json(object).
  4. Il secondo metodo fa lo stesso usando l' Produces()attributo (che è a ResultFilter) concontentType = application/json

Maggiori informazioni su di loro nei documenti ufficiali . Ulteriori informazioni sui filtri qui .

La classe del modello semplice utilizzata negli esempi

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}

10
Questa è una buona risposta perché si concentra sulla domanda e spiega brevemente alcuni aspetti pratici.
Netfed

33

Il modo più semplice che mi è venuto in mente è:

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

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};

2
Penso che sia meglio della risposta di @tseng perché la sua soluzione include campi duplicati per i codici di stato, ecc.
Christian Sauer,

2
Un miglioramento che puoi apportare è utilizzare gli StatusCodes definiti in Microsoft.AspNetCore. Http in questo modo: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard,

2
Questa dovrebbe essere la risposta accettata. Sebbene esistano modi per configurare universalmente il json, a volte dobbiamo lavorare con endpoint legacy e le impostazioni possono essere diverse. Fino a quando non potremo smettere di supportare alcuni endpoint legacy, questo è il modo migliore per avere il pieno controllo
pqsk,

Microsoft.AspNetCore.Mvc.JsonResult è il nome completo che penso. Nessuna risposta FQN o "utilizzo" mi fa impazzire. :) Assembly Microsoft.AspNetCore.Mvc.Core, Version = 3.1.0.0, Culture = neutral, PublicKeyToken = adb9793829ddae60 // C: \ Programmi \ dotnet \ packs \ Microsoft.AspNetCore.App.Ref \ 3.1.0 \ ref \ netcoreapp3.1 \ Microsoft.AspNetCore.Mvc.Core.dll
granadaCoder

1
Questo ha funzionato per me quando avevo un tipo forte ("Risultato ITem = nuovo elemento" in questo esempio ... L'articolo è un tipo noto in fase di esecuzione)). Vedi la mia risposta (a questa domanda) per quando il tipo è ~ non ~ noto. (Avevo json in un db..e il tipo json non era noto in fase di esecuzione). Grazie Gerald.
granadaCoder

15

Questa è la mia soluzione più semplice:

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

4

Invece di usare i codici di stato 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 !!!"));
    }

Enum è un'ottima idea!
Bhimbim,

2

Fantastiche risposte che ho trovato qui e ho anche provato questa dichiarazione di ritorno, vedi StatusCode(whatever code you wish)e ha funzionato !!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });

1
Come questo! Buon consiglio!
ticky

0

Si prega di fare riferimento al codice di seguito, è possibile gestire più codici di stato con diverso tipo 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.");
    }
}

9
No. Questo è male.
Phillip Copley,

0

Quello che faccio nelle mie applicazioni Asp Net Core Api è creare una classe che si estende da ObjectResult e fornire molti costruttori per personalizzare il contenuto e il codice di stato. Quindi tutte le azioni del mio controller usano uno dei costruttori come appropriato. Puoi dare un'occhiata alla mia implementazione su: https://github.com/melardev/AspNetCoreApiPaginatedCrud

e

https://github.com/melardev/ApiAspCoreEcommerce

ecco come appare la classe (vai al mio repository per il codice 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;
    }
}

Nota la base (dto) che sostituisci dto con il tuo oggetto e dovresti essere a posto.


0

Ho fatto funzionare tutto questo. Il mio grosso problema era che il mio json era una stringa (nel mio database ... e non un tipo specifico / conosciuto).

Ok, finalmente ho fatto funzionare tutto questo.

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

Mi capita di essere su 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

Ho ricevuto il suggerimento da qui :: https://www.jianshu.com/p/7b3e92c42b61

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.