ASP.NET Core gibt JSON mit Statuscode zurück


152

Ich suche nach dem richtigen Weg, um JSON mit einem HTTP-Statuscode in meinem .NET Core Web API-Controller zurückzugeben. Ich benutze es so:

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

Dies war in einer 4.6 MVC-Anwendung, aber jetzt mit .NET Core habe ich anscheinend nicht das, was IHttpActionResultich habe ActionResultund benutze es wie folgt:

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

Aber die Antwort vom Server ist seltsam, wie im Bild unten:

Geben Sie hier die Bildbeschreibung ein

Ich möchte nur, dass der Web-API-Controller JSON mit einem HTTP-Statuscode zurückgibt, wie ich es in Web-API 2 getan habe.


1
Die "ok" -Methoden geben 200 als Statuscode zurück. Die vordefinierten Methoden decken alle gängigen Fälle ab. Um 201 (+ Header mit neuem Ressourcenstandort) zurückzugeben, verwenden Sie CreatedAtRouteMethode usw.
Tseng

Antworten:


190

Die grundlegendste Version, die mit a antwortet, JsonResultist:

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

Dies hilft jedoch nicht bei Ihrem Problem, da Sie Ihren eigenen Antwortcode nicht explizit behandeln können.

Um die Kontrolle über die Statusergebnisse zu erlangen, müssen Sie a zurückgeben ActionResult, wo Sie dann den StatusCodeResultTyp nutzen können.

beispielsweise:

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

Beachten Sie, dass diese beiden obigen Beispiele aus einem großartigen Handbuch stammen, das in der Microsoft-Dokumentation verfügbar ist: Formatieren von Antwortdaten


Extra Zeug

Das Problem, auf das ich häufig stoße, ist, dass ich eine detailliertere Kontrolle über meine WebAPI haben wollte, anstatt nur die Standardkonfiguration aus der Vorlage "Neues Projekt" in VS zu verwenden.

Lassen Sie uns sicherstellen, dass Sie einige der Grundlagen haben ...

Schritt 1: Konfigurieren Sie Ihren Service

Damit Ihre ASP.NET Core-WebAPI mit einem JSON Serialized Object unter vollständiger Kontrolle des Statuscodes antwortet, sollten Sie zunächst sicherstellen, dass Sie den AddMvc()Dienst in Ihre ConfigureServicesnormalerweise in enthaltene Methode aufgenommen haben Startup.cs.

Es ist wichtig zu beachten, dass AddMvc()der Eingabe- / Ausgabeformatierer für JSON automatisch einbezogen wird und auf andere Anforderungstypen reagiert wird.

Wenn für Ihr Projekt die vollständige Kontrolle erforderlich ist und Sie Ihre Dienste genau definieren möchten, z. B. wie sich Ihre WebAPI gegenüber verschiedenen Anforderungstypen verhält, einschließlich application/jsonund nicht auf andere Anforderungstypen (z. B. eine Standardbrowseranforderung), können Sie diese manuell mit dem definieren folgenden Code:

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

Sie werden feststellen, dass ich Ihnen auch eine Möglichkeit hinzugefügt habe, Ihre eigenen benutzerdefinierten Eingabe- / Ausgabeformatierer hinzuzufügen, falls Sie auf ein anderes Serialisierungsformat (Protobuf, Sparsamkeit usw.) reagieren möchten.

Der obige Codeabschnitt ist meistens ein Duplikat der AddMvc()Methode. Wir implementieren jedoch jeden "Standard" -Dienst für sich, indem wir jeden einzelnen Dienst definieren, anstatt den vorab ausgelieferten mit der Vorlage zu verwenden. Ich habe den Repository-Link in den Codeblock eingefügt, oder Sie können AddMvc() aus dem GitHub-Repository auschecken. .

Beachten Sie, dass es einige Anleitungen gibt, die versuchen, dieses Problem zu lösen, indem sie die Standardeinstellungen "rückgängig machen", anstatt sie erst gar nicht zu implementieren. Wenn Sie berücksichtigen, dass wir jetzt mit Open Source arbeiten, ist dies redundante Arbeit , schlechter Code und ehrlich gesagt eine alte Angewohnheit, die bald verschwinden wird.


Schritt 2: Erstellen Sie einen Controller

Ich werde Ihnen eine wirklich einfache zeigen, nur um Ihre Frage zu sortieren.

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

Schritt 3: Überprüfen Sie Ihre Content-TypeundAccept

Sie müssen sicherstellen, dass Ihre Content-Typeund die AcceptHeader in Ihrer Anfrage richtig eingestellt sind. In Ihrem Fall (JSON) möchten Sie es so einrichtenapplication/json .

Wenn Sie möchten, dass Ihre WebAPI standardmäßig als JSON antwortet, können Sie dies auf verschiedene Arten tun, unabhängig davon, was im Anforderungsheader angegeben ist .

Weg 1 Wie in dem zuvor empfohlenen Artikel ( Formatieren von Antwortdaten ) gezeigt, können Sie ein bestimmtes Format auf Controller- / Aktionsebene erzwingen. Ich persönlich mag diesen Ansatz nicht ... aber hier ist er der Vollständigkeit halber:

Erzwingen eines bestimmten Formats Wenn Sie die Antwortformate für eine bestimmte Aktion einschränken möchten, können Sie den Filter [Produces] anwenden. Der Filter [Produces] gibt die Antwortformate für eine bestimmte Aktion (oder einen bestimmten Controller) an. Wie die meisten Filter kann dies auf die Aktion, den Controller oder den globalen Bereich angewendet werden.

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

Der [Produces]Filter erzwingt, dass alle Aktionen innerhalb AuthorsControllervon JSON-formatierte Antworten zurückgeben, selbst wenn andere Formatierer für die Anwendung konfiguriert wurden und der Client einen AcceptHeader bereitgestellt hat , der ein anderes verfügbares Format anfordert.

Weg 2 Meine bevorzugte Methode ist, dass die WebAPI auf alle Anforderungen mit dem angeforderten Format antwortet. Falls das angeforderte Format jedoch nicht akzeptiert wird, greifen Sie zurück einen Standard zurück (z. B. JSON).

Zuerst müssen Sie dies in Ihren Optionen registrieren (wir müssen das Standardverhalten, wie bereits erwähnt, überarbeiten).

options.RespectBrowserAcceptHeader = true; // false by default

Schließlich ordnet der Webhost durch einfaches Neuordnen der Liste der Formatierer, die im Service Builder definiert wurden, standardmäßig den Formatierer an, den Sie oben in der Liste positionieren (dh Position 0).

Weitere Informationen finden Sie in diesem Blogeintrag zu .NET Web Development and Tools


Vielen Dank für die Mühe, die Sie investiert haben. Ihre Antwort hat mich dazu inspiriert, sie IActionResultmit dem return Ok(new {response = "123"});Jubel umzusetzen !
Rossco

1
@ Rossco Kein Problem. Hoffentlich hilft Ihnen der Rest des Codes bei der Entwicklung Ihres Projekts.
Svek

1
Um dieses Thema zu erweitern, habe ich hier eine zusätzliche und umfassendere Anleitung zur Implementierung der WebAPI erstellt: stackoverflow.com/q/42365275/3645638
Svek

Bei Einstellung: RespectBrowserAcceptHeader = true; Sie erklären nicht, warum Sie es tun, und es ist normalerweise unnötig und falsch, dies zu tun. Browser fragen nach HTML, und daher sollten sie die Formatiererauswahl ohnehin nicht beeinflussen (das Chrome tut dies leider, indem sie nach XML fragen). Kurz gesagt, ich würde mich davon fernhalten, und der von Ihnen angegebene Fallback ist bereits das Standardverhalten
Yishai Galatzer,

@YishaiGalatzer Das Hauptthema dieses Teils meiner Antwort war es, hervorzuheben, wie die Standard-Middleware zwischen dem Client und der API-Logik entlastet werden kann. Meiner Meinung nach RespectBrowserAcceptHeaderist dies wichtig, wenn Sie die Verwendung eines alternativen Serializers implementieren oder häufiger, wenn Sie sicherstellen möchten, dass Ihre Clients keine fehlerhaften Anforderungen senden. Daher habe ich betont, "Wenn Ihr Projekt die volle Kontrolle erfordert und Sie Ihren Service genau definieren möchten", und das hervorgehobene Blockzitat auch über dieser Aussage notiert.
Svek

57

Sie haben vordefinierte Methoden für die meisten gängigen Statuscodes.

  • Ok(result)kehrt 200mit Antwort zurück
  • CreatedAtRouteGibt 201+ neue Ressourcen-URL zurück
  • NotFound kehrt zurück 404
  • BadRequestRückgabe 400etc.

Siehe BaseController.csund Controller.csfür eine Liste aller Methoden.

Wenn Sie jedoch wirklich darauf bestehen, dass Sie StatusCodeeinen benutzerdefinierten Code festlegen können, sollten Sie dies jedoch nicht tun, da dies den Code weniger lesbar macht und Sie den Code wiederholen müssen, um Header festzulegen (wie z CreatedAtRoute. B. für ).

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

1
Dies gab mir einen Einblick in meine Antwort unten. Vielen Dank
Oge Nwike

Dieser Code ist für ASP.NET Core 2.2 nicht korrekt. Ich habe es gerade versucht und es serialisiert in JSONdie ActionResultvon der Json()Methode erstellte. Die Zeichenfolge "123" ist nicht direkt enthalten.
Amedina

1
@amedina: Mein schlechtes, entfernen Sie einfach die Json(...)und übergeben Sie die Zeichenfolge an StatusCode
Tseng

Wenn Sie "Ok (Ergebnis)" sagen - was ist das Ergebnis? Handelt es sich um eine Zeichenfolge im JSON-Format oder um ein C # -Objekt (das automatisch in eine JSON-Zeichenfolge konvertiert wird?)?
Variable

@variable: Immer ein POCO / eine Klasse / ein Objekt. Wenn Sie eine Zeichenfolge zurückgeben möchten, müssen Sie stattdessen "Inhalt" verwenden
Tseng

42

Mit ASP.NET Core 2.0 ist der ideale Weg, um ein Objekt zurückzugeben Web API(das mit MVC vereinheitlicht ist und dieselbe Basisklasse verwendet Controller)

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

Beachte das

  1. Es wird mit dem 200 OKStatuscode zurückgegeben (es ist eine OkArt von ObjectResult)
  2. Es führt eine Inhaltsverhandlung durch, dh es wird basierend auf dem Acceptangeforderten Header zurückgegeben. Wenn Accept: application/xmleine Anfrage gesendet wird, wird diese als zurückgegeben XML. Wenn nichts gesendet wird, JSONist dies Standard.

Wenn es mit einem bestimmten Statuscode senden muss , verwenden Sie ObjectResultoder StatusCodestattdessen. Beide machen dasselbe und unterstützen die Aushandlung von Inhalten.

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

oder noch feiner mit 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 };

Wenn Sie speziell als JSON zurückkehren möchten , gibt es mehrere Möglichkeiten

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

Beachte das

  1. Beide erzwingen JSONauf zwei verschiedene Arten.
  2. Beide ignorieren die Inhaltsverhandlung.
  3. Die erste Methode erzwingt JSON mit einem bestimmten Serializer Json(object).
  4. Die zweite Methode macht dasselbe, indem sie das Produces()Attribut (das a ist ResultFilter) mit verwendetcontentType = application/json

Lesen Sie mehr darüber in den offiziellen Dokumenten . Erfahren Sie hier mehr über Filter .

Die einfache Modellklasse, die in den Beispielen verwendet wird

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

10
Dies ist eine gute Antwort, da sie sich auf die Frage konzentriert und einige praktische Aspekte kurz erläutert.
netfed

32

Der einfachste Weg, den ich gefunden habe, ist:

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

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

2
Ich denke, das ist besser als die Antwort von @tseng, weil seine Lösung doppelte Felder für Statuscodes usw. enthält.
Christian Sauer

2
Eine Verbesserung, die Sie vornehmen können, besteht darin, die in Microsoft.AspNetCore.Http definierten StatusCodes wie folgt zu verwenden: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard

2
Dies sollte die akzeptierte Antwort sein. Obwohl es Möglichkeiten gibt, den json universell einzurichten, müssen wir manchmal mit älteren Endpunkten arbeiten, und die Einstellungen können unterschiedlich sein. Bis wir einige ältere Endpunkte nicht mehr unterstützen können, ist dies der ultimative Weg, um die volle Kontrolle zu haben
pqsk

Microsoft.AspNetCore.Mvc.JsonResult ist der vollständig qualifizierte Name, denke ich. Kein FQN oder "Verwenden" von Antworten macht mich verrückt. :) Assembly Microsoft.AspNetCore.Mvc.Core, Version = 3.1.0.0, Kultur = neutral, PublicKeyToken = adb9793829ddae60 // C: \ Programme \ dotnet \ packs \ Microsoft.AspNetCore.App.Ref \ 3.1.0 \ ref \ netcoreapp3.1 \ Microsoft.AspNetCore.Mvc.Core.dll
granadaCoder

1
Dies funktionierte für mich, wenn ich einen starken Typ hatte ("ITem result = new Item" in diesem Beispiel ... Item ist zur Laufzeit ein bekannter Typ)). Siehe meine Antwort (auf diese Frage), wenn der Typ ~ nicht ~ bekannt ist. (Ich hatte json in einer Datenbank ... und der json-Typ war zur Laufzeit nicht bekannt). Danke Gerald.
GranadaCoder

15

Dies ist meine einfachste Lösung:

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

oder

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

4

Anstatt 404/201 Statuscodes mit enum zu verwenden

     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 ist eine großartige Idee!
Bhimbim

2

Tolle Antworten, die ich hier gefunden habe und ich habe auch versucht, diese Rückgabeerklärung zu sehen StatusCode(whatever code you wish)und es hat funktioniert !!!

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

1
Wie dieser! Guter Vorschlag!
Ticky

0

Bitte beziehen Sie sich auf den folgenden Code. Sie können mehrere Statuscodes mit unterschiedlichem JSON-Typ verwalten

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
Nein, das ist schlecht.
Phillip Copley

0

In meinen Asp Net Core Api-Anwendungen erstelle ich eine Klasse, die von ObjectResult ausgeht und viele Konstruktoren zum Anpassen des Inhalts und des Statuscodes bereitstellt. Dann verwenden alle meine Controller-Aktionen einen der Kostenleiter als geeignet. Sie können sich meine Implementierung unter folgender Adresse ansehen: https://github.com/melardev/AspNetCoreApiPaginnedCrud

und

https://github.com/melardev/ApiAspCoreEcommerce

So sieht die Klasse aus (den vollständigen Code finden Sie in meinem Repo):

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

Beachten Sie die Basis (dto), die Sie durch Ihr Objekt ersetzen, und Sie sollten bereit sein, loszulegen.


0

Ich habe das zum Laufen gebracht. Mein großes Problem war, dass mein JSON eine Zeichenfolge war (in meiner Datenbank ... und kein bestimmter / bekannter Typ).

Ok, ich habe das endlich zum Laufen gebracht.

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

Ich bin zufällig auf 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

Ich habe den Hinweis von hier bekommen: https://www.jianshu.com/p/7b3e92c42b61

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.