ASP.NET Core zwraca kod JSON z kodem stanu

153

Szukam prawidłowego sposobu zwracania kodu JSON z kodem stanu HTTP w moim kontrolerze interfejsu API sieci Web .NET Core. Używam tego w ten sposób:

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

To było w aplikacji 4.6 MVC, ale teraz z .NET Core wydaje mi się, że nie mam tego, IHttpActionResultco mam ActionResulti używam w ten sposób:

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

Ale odpowiedź z serwera jest dziwna, jak na poniższym obrazku:

wprowadź opis obrazu tutaj

Chcę tylko, aby kontroler interfejsu API sieci Web zwracał kod JSON z kodem stanu HTTP, tak jak w przypadku interfejsu API sieci Web 2.

Rossco
źródło
1
Metody „ok” zwracają 200 jako kod stanu. Predefiniowane metody obejmują wszystkie typowe przypadki. Aby zwrócić 201 (+ nagłówek z nową lokalizacją zasobu), użyj CreatedAtRoutemetody itp.
Tseng,

Odpowiedzi:

191

Najbardziej podstawowa wersja odpowiadająca za pomocą JsonResultto:

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

Jednak to nie pomoże w rozwiązaniu problemu, ponieważ nie możesz jawnie zająć się własnym kodem odpowiedzi.

Aby uzyskać kontrolę nad wynikami statusu, musisz zwrócić a, w ActionResultktórym możesz następnie skorzystać z tego StatusCodeResulttypu.

na przykład:

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

Zauważ, że oba powyższe przykłady pochodzą ze świetnego przewodnika dostępnego w Microsoft Documentation: Formatting Response Data


Dodatkowe rzeczy

Problem, z którym często się spotykam, polega na tym, że chciałem mieć bardziej szczegółową kontrolę nad moim interfejsem WebAPI, a nie tylko korzystać z domyślnej konfiguracji z szablonu „Nowy projekt” w VS.

Upewnijmy się, że znasz podstawy ...

Krok 1: Skonfiguruj usługę

Aby Twoje ASP.NET Core WebAPI odpowiadało za pomocą obiektu serializowanego JSON wraz z pełną kontrolą nad kodem stanu, należy zacząć od upewnienia się, że AddMvc()usługa została uwzględniona w ConfigureServicesmetodzie zwykle znajdującej się w Startup.cs.

Ważne jest, aby pamiętać, że AddMvc()automatycznie uwzględni on formatowanie danych wejściowych / wyjściowych dla formatu JSON wraz z odpowiadaniem na inne typy żądań.

Jeśli Twój projekt wymaga pełnej kontroli i chcesz ściśle zdefiniować swoje usługi, na przykład sposób, w jaki Twoje WebAPI będzie zachowywać się w przypadku różnych typów żądań, w tym application/jsoni nie odpowiadać na inne typy żądań (takie jak standardowe żądania przeglądarki), możesz zdefiniować je ręcznie za pomocą następujący kod:

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

Zauważysz, że zamieściłem również sposób dodawania własnych niestandardowych elementów formatujących wejścia / wyjścia, na wypadek, gdybyś chciał odpowiedzieć na inny format serializacji (protobuf, oszczędność itp.).

Fragment powyższego kodu jest w większości duplikatem AddMvc()metody. Jednak każdą usługę „domyślną” wdrażamy samodzielnie, definiując każdą usługę, zamiast korzystać z usługi wstępnie dostarczonej z szablonem. Dodałem link do repozytorium w bloku kodu lub możesz wyewidencjonować AddMvc() z repozytorium GitHub. .

Zwróć uwagę, że istnieje kilka poradników, które spróbują rozwiązać ten problem przez „cofanie” wartości domyślnych, zamiast po prostu ich nie implementować w pierwszej kolejności… Jeśli weźmiesz pod uwagę fakt, że pracujemy teraz z Open Source, jest to praca zbędna , zły kod i szczerze mówiąc stary nawyk, który wkrótce zniknie.


Krok 2: Utwórz kontroler

Pokażę ci naprawdę proste, żeby uporządkować twoje pytanie.

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

Krok 3: Sprawdź Content-TypeiAccept

Musisz upewnić się, że Twoje Content-Typei Acceptnagłówki w żądaniu są ustawione poprawnie. W twoim przypadku (JSON), będziesz chciał ustawić to na application/json.

Jeśli chcesz, aby Twoje WebAPI odpowiadało domyślnie jako JSON, niezależnie od tego, co określa nagłówek żądania, możesz to zrobić na kilka sposobów .

Sposób 1 Jak pokazano w artykule, który zaleciłem wcześniej ( Formatowanie danych odpowiedzi ), możesz wymusić określony format na poziomie kontrolera / akcji. Osobiście nie podoba mi się to podejście ... ale tutaj chodzi o kompletność:

Wymuszanie określonego formatu Jeśli chcesz ograniczyć formaty odpowiedzi dla określonej akcji, możesz zastosować filtr [Produces]. Filtr [Produces] określa formaty odpowiedzi dla określonej akcji (lub kontrolera). Podobnie jak większość filtrów, można to zastosować w działaniu, kontrolerze lub zasięgu globalnym.

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

[Produces]Filtr zmusi wszystkich działań w ramach AuthorsControllerpowrócić JSON-sformatowanych odpowiedzi, nawet jeśli inne formatujących zostały skonfigurowane dla aplikacji, a klient dostarczył Acceptnagłówek żąda innego, dostępnego formatu.

Sposób 2 Moją preferowaną metodą jest odpowiadanie przez WebAPI na wszystkie żądania w żądanym formacie. Jednak w przypadku, gdy nie akceptuje żądanego formatu, wróć do domyślnego (tj. JSON)

Najpierw musisz to zarejestrować w opcjach (musimy przerobić domyślne zachowanie, jak wspomniano wcześniej)

options.RespectBrowserAcceptHeader = true; // false by default

Wreszcie, po prostu zmieniając kolejność na liście elementów formatujących, które zostały zdefiniowane w narzędziu do tworzenia usług, host sieciowy domyślnie ustawi program formatujący, który umieścisz na górze listy (tj. Pozycja 0).

Więcej informacji można znaleźć w tym wpisie blogu .NET Web Development and Tools

Svek
źródło
Wielkie dzięki za wysiłek, który włożyłeś. Twoja odpowiedź zainspirowała mnie do wdrożenia IActionResultz return Ok(new {response = "123"});Pozdrowieniami!
Rossco,
1
@ Rossco Nie ma problemu. Miejmy nadzieję, że pozostała część kodu pomoże Ci w rozwoju Twojego projektu.
Svek
1
Aby rozszerzyć ten temat, utworzyłem dodatkowy i bardziej kompletny przewodnik po implementacji interfejsu WebAPI tutaj: stackoverflow.com/q/42365275/3645638
Svek
Przy ustawianiu: RespectBrowserAcceptHeader = true; Nie wyjaśniasz, dlaczego to robisz, i zazwyczaj jest to niepotrzebne i niewłaściwe. Przeglądarki proszą o html, a zatem i tak nie powinny wpływać na wybór programu formatującego (ten chrome niestety robi, prosząc o XML). Krótko mówiąc, jest to coś, czego chciałbym uniknąć, a
wskazana przez ciebie opcja
@YishaiGalatzer Głównym tematem tej części mojej odpowiedzi było podkreślenie, jak odciążyć domyślne oprogramowanie pośredniczące między klientem a logiką API. Moim zdaniem RespectBrowserAcceptHeaderma to kluczowe znaczenie przy implementacji korzystania z alternatywnego serializatora lub częściej, gdy chcesz mieć pewność, że Twoi klienci nie wysyłają źle sformułowanych żądań. Dlatego też podkreśliłem „Jeśli Twój projekt wymaga pełnej kontroli i chcesz ściśle określić swoją usługę” i zwróć uwagę na podświetlony cytat blokowy nad tym stwierdzeniem.
Svek
57

Masz predefiniowane metody dla większości typowych kodów stanu.

  • Ok(result)wraca 200z odpowiedzią
  • CreatedAtRoutezwraca 201+ nowy adres URL zasobu
  • NotFound zwroty 404
  • BadRequestzwroty 400itp.

Zobacz BaseController.csi Controller.cslistę wszystkich metod.

Ale jeśli naprawdę nalegasz, możesz użyć StatusCodedo ustawienia niestandardowego kodu, ale naprawdę nie powinieneś, ponieważ sprawia to, że kod jest mniej czytelny i będziesz musiał powtórzyć kod, aby ustawić nagłówki (jak dla CreatedAtRoute).

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}
Tseng
źródło
1
to dało mi wgląd w moją odpowiedź poniżej. Dziękuję
Oge Nwike
Ten kod nie jest poprawny dla ASP.NET Core 2,2. Właśnie go wypróbowałem i serializuje się JSONdo ActionResultutworzonego za pomocą Json()metody. Nie zawiera bezpośrednio ciągu „123”.
amedina
1
@amedina: Moja wina, po prostu usuń Json(...)i przekaż ciąg do StatusCode
Tseng
Kiedy mówisz „Ok (wynik)” - jaki jest wynik? Czy jest to ciąg formatu JSON, czy jest to obiekt C # (który jest automatycznie konwertowany na ciąg JSON?)?
zmienna
@variable: zawsze POCO / klasa / obiekt. Jeśli chcesz zwrócić ciąg znaków, musisz zamiast tego użyć „Content”
Tseng
43

W przypadku ASP.NET Core 2.0 idealnym sposobem na zwrócenie obiektu z Web API(ujednoliconego z MVC i używającego tej samej klasy bazowej Controller) jest

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

Zauważ, że

  1. Wraca z 200 OKkodem statusu (jest to Oktyp ObjectResult)
  2. Prowadzi negocjację treści, tj. Zwróci na podstawie Acceptnagłówka w żądaniu. Jeśli Accept: application/xmlzostanie wysłane w żądaniu, wróci jako XML. Jeśli nic nie jest wysyłane, JSONjest to ustawienie domyślne.

Jeśli trzeba wysłać z określonym kodem statusu , użyj ObjectResultlub StatusCode. Obie robią to samo i obsługują negocjacje treści.

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

lub jeszcze bardziej drobnoziarnisty dzięki 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 };

Jeśli chcesz powrócić jako JSON , istnieje kilka sposobów

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

Zauważ, że

  1. Oba egzekwuje JSONna dwa różne sposoby.
  2. Obie ignorują negocjacje treści.
  3. Pierwsza metoda wymusza JSON z określonym serializatorem Json(object).
  4. Druga metoda robi to samo, używając Produces()atrybutu (który jest ResultFilter) withcontentType = application/json

Przeczytaj więcej o nich w oficjalnej dokumentacji . Dowiedz się o filtrach tutaj .

Prosta klasa modelu używana w przykładach

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Arghya C
źródło
10
To dobra odpowiedź, ponieważ skupia się na pytaniu i krótko wyjaśnia kilka praktycznych aspektów.
netfed
33

Najłatwiejszy sposób jaki wymyśliłem to:

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

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};
Gerald Hughes
źródło
2
Myślę, że to lepsze niż odpowiedź od @tseng, ponieważ jego rozwiązanie zawiera zduplikowane pola dla kodów stanu itp.
Christian Sauer
2
Jednym z ulepszeń, które możesz wprowadzić, jest użycie kodów stanu zdefiniowanych w Microsoft.AspNetCore.Http w następujący sposób: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard
2
To powinna być akceptowana odpowiedź. Chociaż istnieją sposoby na uniwersalną konfigurację json, czasami musimy pracować ze starszymi punktami końcowymi, a ustawienia mogą być inne. Dopóki nie przestaniemy obsługiwać niektórych starszych punktów końcowych, jest to najlepszy sposób na pełną kontrolę
pqsk
Myślę, że Microsoft.AspNetCore.Mvc.JsonResult to w pełni kwalifikowana nazwa. Brak FQN lub „używanie” odpowiedzi doprowadza mnie do szału. :) Zespół 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
granadaCoder
1
To zadziałało, gdy miałem silny typ („Wynik ITem = nowy element” w tym przykładzie… Element jest znanym typem w czasie wykonywania)). Zobacz moją odpowiedź (na to pytanie), kiedy typ jest ~ nieznany. (Miałem json w db ... i typ json nie był znany w czasie wykonywania). Dzięki Gerald.
granadaCoder
15

To jest moje najłatwiejsze rozwiązanie:

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

lub

public IActionResult InfoTag()
{
    return Json(new {name = "Fabio", age = 42, gender = "M"});
}
Fabio
źródło
4

Zamiast używać kodów stanu 404/201 przy użyciu 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
źródło
Enum to świetny pomysł!
Bhimbim
2

Niesamowite odpowiedzi, które znalazłem tutaj, a także wypróbowałem to oświadczenie zwrotne, zobacz StatusCode(whatever code you wish)i zadziałało !!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });
Oge Nwike
źródło
1
Jak ten! Dobry pomysł!
łaskotanie
0

Zapoznaj się z poniższym kodem, możesz zarządzać wieloma kodami stanu za pomocą innego typu 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
źródło
9
Nie. To jest złe.
Phillip Copley,
0

To, co robię w moich aplikacjach Asp Net Core Api, polega na utworzeniu klasy, która rozszerza się z ObjectResult i udostępnieniu wielu konstruktorów w celu dostosowania zawartości i kodu stanu. Wtedy wszystkie moje działania kontrolera używają jednego z projektorów jako odpowiedniego. Możesz rzucić okiem na moją realizację pod adresem : https://github.com/melardev/AspNetCoreApiPaginatedCrud

i

https://github.com/melardev/ApiAspCoreEcommerce

oto jak wygląda klasa (przejdź do mojego repozytorium po pełny kod):

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

Zwróć uwagę na bazę (dto), którą zastępujesz dto przez swój obiekt i powinno być dobrze.

Melardev
źródło
0

Mam to do dzieła. Moim wielkim problemem było to, że mój plik json był ciągiem (w mojej bazie danych ... a nie określonym / znanym typem).

Ok, w końcu to zadziałało.

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

Tak się składa, że ​​jestem na 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

Mam tutaj wskazówkę: https://www.jianshu.com/p/7b3e92c42b61

granadaCoder
źródło