ASP.NET Core retourne JSON avec le code d'état

153

Je recherche la bonne façon de renvoyer JSON avec un code d'état HTTP dans mon contrôleur API Web .NET Core. J'utilise pour l'utiliser comme ceci:

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

C'était dans une application MVC 4.6 mais maintenant avec .NET Core, je ne semble pas avoir ce que IHttpActionResultj'ai ActionResultet utiliser comme ceci:

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

Mais la réponse du serveur est étrange, comme dans l'image ci-dessous:

entrez la description de l'image ici

Je veux juste que le contrôleur de l'API Web renvoie JSON avec un code d'état HTTP comme je l'ai fait dans l'API Web 2.

Rossco
la source
1
Les méthodes "ok" renvoient 200 comme code d'état. Les méthodes prédéfinies couvrent tous les cas courants. Pour retourner 201 (+ en-tête avec le nouvel emplacement de la ressource), vous utilisez la CreatedAtRouteméthode etc.
Tseng

Réponses:

191

La version la plus basique qui répond par un JsonResultest:

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

Cependant, cela n'aidera pas votre problème car vous ne pouvez pas traiter explicitement votre propre code de réponse.

La façon d'obtenir le contrôle sur les résultats de statut, est que vous devez renvoyer un ActionResultqui est l'endroit où vous pouvez ensuite profiter du StatusCodeResulttype.

par exemple:

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

Notez que les deux exemples ci-dessus proviennent d'un excellent guide disponible dans la documentation Microsoft: Formatage des données de réponse


Trucs supplémentaires

Le problème que je rencontre assez souvent est que je voulais un contrôle plus granulaire sur mon WebAPI plutôt que de simplement utiliser la configuration par défaut du modèle "Nouveau projet" dans VS.

Assurons-nous que vous avez quelques notions de base ...

Étape 1: Configurez votre service

Pour que votre ASP.NET Core WebAPI réponde avec un objet sérialisé JSON avec un contrôle total du code d'état, vous devez commencer par vous assurer que vous avez inclus le AddMvc()service dans votre ConfigureServicesméthode qui se trouve généralement dans Startup.cs.

Il est important de noter que cela AddMvc()inclura automatiquement le formateur d'entrée / sortie pour JSON ainsi que la réponse à d'autres types de demandes.

Si votre projet nécessite un contrôle total et que vous souhaitez définir strictement vos services, comme par exemple comment votre WebAPI se comportera face à différents types de requêtes, y compris application/jsonet ne répondra pas à d'autres types de requêtes (comme une requête de navigateur standard), vous pouvez le définir manuellement avec le code suivant:

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

Vous remarquerez que j'ai également inclus un moyen pour vous d'ajouter vos propres formateurs d'entrée / sortie personnalisés, dans le cas où vous voudriez peut-être répondre à un autre format de sérialisation (protobuf, thrift, etc.).

Le morceau de code ci-dessus est principalement un double de la AddMvc()méthode. Cependant, nous implémentons chaque service "par défaut" par nous-mêmes en définissant chaque service au lieu de choisir celui pré-livré avec le modèle. J'ai ajouté le lien du référentiel dans le bloc de code, ou vous pouvez extraire AddMvc() le référentiel GitHub. .

Notez qu'il existe des guides qui essaieront de résoudre ce problème en "annulant" les valeurs par défaut, plutôt que de simplement ne pas l'implémenter en premier lieu ... Si vous tenez compte du fait que nous travaillons maintenant avec Open Source, c'est un travail redondant , mauvais code et franchement une vieille habitude qui va bientôt disparaître.


Étape 2: créer un contrôleur

Je vais vous en montrer une très simple juste pour que votre question soit triée.

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

Étape 3: Vérifiez votre Content-TypeetAccept

Vous devez vous assurer que vos en Content-Type- Accepttêtes et dans votre demande sont correctement définis. Dans votre cas (JSON), vous voudrez le configurer pour être application/json.

Si vous souhaitez que votre WebAPI réponde en tant que JSON par défaut, indépendamment de ce que l'en-tête de la demande spécifie, vous pouvez le faire de plusieurs manières .

Méthode 1 Comme indiqué dans l'article que j'ai recommandé plus tôt ( Formatage des données de réponse ), vous pouvez forcer un format particulier au niveau Contrôleur / Action. Personnellement, je n'aime pas cette approche ... mais ici, c'est par souci d'exhaustivité:

Forcer un format particulier Si vous souhaitez restreindre les formats de réponse pour une action spécifique, vous pouvez appliquer le filtre [Produit]. Le filtre [Produit] spécifie les formats de réponse pour une action spécifique (ou un contrôleur). Comme la plupart des filtres, cela peut être appliqué au niveau de l'action, du contrôleur ou de la portée globale.

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

Le [Produces]filtre forcera toutes les actions dans le AuthorsControllerà renvoyer des réponses au format JSON, même si d'autres formateurs ont été configurés pour l'application et que le client a fourni un en- Accepttête demandant un format disponible différent.

Voie 2 Ma méthode préférée est que WebAPI réponde à toutes les demandes avec le format demandé. Cependant, dans le cas où il n'accepte pas le format demandé, revenez à une valeur par défaut (c'est-à-dire JSON)

Tout d'abord, vous devrez l'enregistrer dans vos options (nous devons retravailler le comportement par défaut, comme indiqué précédemment)

options.RespectBrowserAcceptHeader = true; // false by default

Enfin, en réorganisant simplement la liste des formateurs qui ont été définis dans le générateur de services, l'hébergeur Web adoptera par défaut le formateur que vous positionnez en haut de la liste (c'est-à-dire la position 0).

Plus d'informations peuvent être trouvées dans cette entrée du blog sur le développement Web et les outils .NET

Svek
la source
Merci beaucoup pour les efforts que vous avez déployés. Votre réponse m'a inspiré à mettre en œuvre IActionResultavec les return Ok(new {response = "123"});Cheers!
Rossco
1
@Rossco Pas de problème. Espérons que le reste du code vous guidera au fur et à mesure que votre projet se développera.
Svek
1
Pour étendre ce sujet, j'ai créé un guide supplémentaire et plus complet pour implémenter la WebAPI ici: stackoverflow.com/q/42365275/3645638
Svek
Lors du réglage: RespectBrowserAcceptHeader = true; Vous n'expliquez pas pourquoi vous le faites, et il est généralement inutile et mal de le faire. Les navigateurs demandent du HTML, et par conséquent ils ne devraient pas affecter la sélection du formateur de toute façon (ce que Chrome fait malheureusement en demandant XML). En bref, c'est quelque chose que je garderais, et le repli que vous spécifiez est déjà le comportement par défaut
Yishai Galatzer
@YishaiGalatzer Le thème principal de cette partie de ma réponse était de mettre en évidence comment décharger le middleware par défaut entre le client et la logique de l'API. À mon avis, RespectBrowserAcceptHeaderest essentiel lors de la mise en œuvre de l'utilisation d'un sérialiseur alternatif ou plus généralement, lorsque vous voulez vous assurer que vos clients n'envoient pas de demandes mal formées. Par conséquent, j'ai mis l'accent sur "Si votre projet nécessite un contrôle total et que vous souhaitez définir strictement votre service" et notez également la citation en bloc en surbrillance au-dessus de cette déclaration.
Svek
57

Vous disposez de méthodes prédéfinies pour les codes d'état les plus courants.

  • Ok(result)retourne 200avec réponse
  • CreatedAtRouterenvoie 201+ nouvelle URL de ressource
  • NotFound Retour 404
  • BadRequestretours 400etc.

Voir BaseController.cset Controller.cspour une liste de toutes les méthodes.

Mais si vous insistez vraiment, vous pouvez utiliser StatusCodepour définir un code personnalisé, mais vous ne devriez vraiment pas car cela rend le code moins lisible et vous devrez répéter le code pour définir les en-têtes (comme pour CreatedAtRoute).

public ActionResult IsAuthenticated()
{
    return StatusCode(200, "123");
}
Tseng
la source
1
cela m'a donné un aperçu de ma réponse ci-dessous. Merci
Oge Nwike
Ce code n'est pas correct pour ASP.NET Core 2.2. Je viens de l'essayer et il se sérialise dans JSONle ActionResultcréé par la Json()méthode. Il n'inclut pas directement la chaîne "123".
amedina le
1
@amedina: Mon mauvais, il suffit de retirer le Json(...)et de passer la chaîne à StatusCode
Tseng
Quand vous dites "Ok (résultat)" - quel est le résultat? Est-ce une chaîne de format JSON ou est-ce un objet C # (qui est automatiquement converti en chaîne JSON?)?
variable le
@variable: toujours un POCO / classe / objet. Si vous voulez retourner une chaîne, vous devez utiliser "Contenu" à la place
Tseng
43

Avec ASP.NET Core 2.0 , le moyen idéal de renvoyer un objet à partir de Web API(qui est unifié avec MVC et utilise la même classe de base Controller) est

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

Remarquerez que

  1. Il revient avec le 200 OKcode de statut (c'est un Oktype de ObjectResult)
  2. Il négocie le contenu, c'est-à-dire qu'il retournera en fonction de l'en- Accepttête de la demande. Si Accept: application/xmlest envoyé dans la demande, il retournera comme XML. Si rien n'est envoyé, JSONc'est la valeur par défaut.

S'il doit être envoyé avec un code d'état spécifique , utilisez ObjectResultou à la StatusCodeplace. Les deux font la même chose et prennent en charge la négociation de contenu.

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

ou encore plus fin avec 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 vous souhaitez spécifiquement revenir en tant que JSON , il existe deux façons

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

Remarquerez que

  1. Les deux s'appliquent JSONde deux manières différentes.
  2. Les deux ignorent la négociation de contenu.
  3. La première méthode applique JSON avec un sérialiseur spécifique Json(object).
  4. La deuxième méthode fait de même en utilisant l' Produces()attribut (qui est a ResultFilter) aveccontentType = application/json

En savoir plus sur eux dans la documentation officielle . En savoir plus sur les filtres ici .

La classe de modèle simple utilisée dans les exemples

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Arghya C
la source
10
C'est une bonne réponse car elle se concentre sur la question et explique quelques aspects pratiques en bref.
netfed le
33

Le moyen le plus simple que j'ai trouvé est:

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

return new JsonResult(result)
{
    StatusCode = StatusCodes.Status201Created // Status code here 
};
Gerald Hughes
la source
2
Je pense que c'est mieux que la réponse de @tseng car sa solution comprend des champs dupliqués pour les codes de statut, etc.
Christian Sauer
2
Une amélioration que vous pouvez apporter est d'utiliser les StatusCodes définis dans Microsoft.AspNetCore.Http comme ceci: return new JsonResult (new {}) {StatusCode = StatusCodes.Status404NotFound};
Bryan Bedard
2
Cela devrait être la réponse acceptée. Bien qu'il existe des moyens de configurer universellement le json, nous devons parfois travailler avec des points de terminaison hérités et les paramètres peuvent être différents. Jusqu'à ce que nous puissions arrêter de prendre en charge certains points de terminaison hérités, c'est le moyen ultime d'avoir un contrôle total
pqsk
Microsoft.AspNetCore.Mvc.JsonResult est le nom pleinement qualifié, je pense. Aucune réponse FQN ou "utilisation" me rend fou. :) 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
granadaCoder
1
Cela a fonctionné pour moi quand j'avais un type fort ("ITem result = new Item" dans cet exemple ... Item est un type connu au moment de l'exécution)). Voir ma réponse (à cette question) pour quand le type est ~ pas ~ connu. (J'avais json dans une base de données et le type json n'était pas connu à l'exécution). Merci Gerald.
granadaCoder
15

C'est ma solution la plus simple:

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
la source
4

Au lieu d'utiliser les codes d'état 404/201 en utilisant 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 !!!"));
    }
bélier dev
la source
Enum est une excellente idée!.
Bhimbim le
2

Des réponses géniales que j'ai trouvées ici et j'ai également essayé cette déclaration de retour voir StatusCode(whatever code you wish)et cela a fonctionné !!!

return Ok(new {
                    Token = new JwtSecurityTokenHandler().WriteToken(token),
                    Expiration = token.ValidTo,
                    username = user.FullName,
                    StatusCode = StatusCode(200)
                });
Oge Nwike
la source
1
Comme celui-ci! Bonne suggestion!
ticky
0

Veuillez vous référer au code ci-dessous, vous pouvez gérer plusieurs codes d'état avec différents types 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
la source
9
Non, c'est mauvais.
Phillip Copley
0

Ce que je fais dans mes applications Asp Net Core Api, c'est de créer une classe qui s'étend d'ObjectResult et de fournir de nombreux constructeurs pour personnaliser le contenu et le code d'état. Ensuite, toutes mes actions de contrôleur utilisent l'un des costructeurs comme approprié. Vous pouvez jeter un oeil à mon implémentation à: https://github.com/melardev/AspNetCoreApiPaginatedCrud

et

https://github.com/melardev/ApiAspCoreEcommerce

voici à quoi ressemble la classe (allez dans mon dépôt pour le code complet):

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

Notez la base (dto) que vous remplacez dto par votre objet et vous devriez être prêt à partir.

Melardev
la source
0

J'ai fait fonctionner ça. Mon gros problème était que mon json était une chaîne (dans ma base de données ... et non un type spécifique / connu).

Ok, j'ai enfin réussi à faire fonctionner ça.

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

Il se trouve que je suis sur 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

J'ai eu l'indice d'ici :: https://www.jianshu.com/p/7b3e92c42b61

grenadeCoder
la source