ASP.NET Coreがステータスコードを含むJSONを返す


153

.NET Core Web APIコントローラーでHTTPステータスコードを使用してJSONを返す正しい方法を探しています。私はこれを次のように使用しています:

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

これは4.6 MVCアプリケーションにありましたが、現在.NET Coreを使用しているので、これIHttpActionResultActionResult使用していないようで、次のように使用します。

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

しかし、以下の画像のように、サーバーからの応答は奇妙です。

ここに画像の説明を入力してください

Web APIコントローラで、Web API 2で行ったように、HTTPステータスコードを含むJSONを返すだけです。


1
「ok」メソッドはステータスコードとして200を返します。事前定義されたメソッドは、すべての一般的なケースをカバーしています。復路201(新しいリソースの場所に+ヘッダ)には、使用するCreatedAtRoute等の方法を
ツェン

回答:


191

a JsonResultで応答する最も基本的なバージョンは次のとおりです。

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

ただし、独自の応答コードを明示的に処理できないため、これは問題の解決にはなりません。

ステータスの結果を制御する方法ActionResultは、StatusCodeResult型を利用できる場所であるを返す必要があることです。

例えば:

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

上記の例は両方とも、Microsoftのドキュメントから入手できる優れたガイドに基づいています:応答データのフォーマット


余分なもの

私が頻繁に遭遇する問題は、VSの「新規プロジェクト」テンプレートからのデフォルト構成をそのまま使用するのではなく、WebAPIをより詳細に制御したいということです。

いくつかの基本事項を確認してみましょう...

ステップ1:サービスを構成する

ASP.NET Core WebAPIがステータスコードのフルコントロールに沿ってJSONシリアル化オブジェクトで応答できるようにするには、通常にあるメソッドにAddMvc()サービスが含まれていることを確認することから始めます。ConfigureServicesStartup.cs

AddMvc()は、他のリクエストタイプへの応答とともに、JSONの入出力フォーマッタが自動的に含まれることに注意することが重要です。

プロジェクトがフルコントロールを必要とし、WebAPIがapplication/json他のリクエストタイプ(標準ブラウザリクエストなど)を含むさまざまなリクエストタイプに対してどのように動作するかなど、サービスを厳密に定義したい場合は、次のコード:

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

別のシリアル化フォーマット(protobuf、thriftなど)に応答する必要がある場合に備えて、独自のカスタム入出力フォーマッターを追加する方法も含まれていることに気づくでしょう。

上記のコードのチャンクは、ほとんどがAddMvc()メソッドの複製です。ただし、事前に出荷されたテンプレートを使用するのではなく、すべてのサービスを定義することにより、各「デフォルト」サービスを独自に実装しています。コードブロックにリポジトリリンクを追加しました。またはAddMvc() 、GitHubリポジトリからチェックアウトできます。

最初に単に実装するのではなく、デフォルトを「元に戻す」ことによってこれを解決しようとするいくつかのガイドがあることに注意してください...オープンソースで作業していることを考慮に入れると、これは冗長な作業です、悪いコード、率直に言って、すぐに消える古い習慣。


ステップ2:コントローラーを作成する

あなたの質問を分類するために、本当に簡単なものを紹介します。

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

ステップ3:あなたContent-TypeAccept

リクエストのContent-TypeおよびAcceptヘッダーが正しく設定されていることを確認する必要があります。あなたのケース(JSON)では、に設定する必要があります。application/json

WebAPIがデフォルトでJSONとして応答するようにしたい場合は、要求ヘッダーが何を指定しているかに関係なく、いくつかの方法で行うことができます

方法1 先に推奨した記事(応答データのフォーマット)で示したように、コントローラー/アクションレベルで特定のフォーマットを強制できます。私は個人的にはこのアプローチが好きではありません...しかし、ここでは完全性のためです:

特定の形式を強制する特定のアクションの応答形式を制限したい場合は、[Produces]フィルターを適用できます。[Produces]フィルターは、特定のアクション(またはコントローラー)の応答形式を指定します。ほとんどのフィルターと同様に、これはアクション、コントローラー、またはグローバルスコープで適用できます。

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

[Produces]フィルタ内のすべてのアクションを強制的に AuthorsController他のフォーマッタは、アプリケーションのために構成され、クライアントは、提供された場合であっても、JSON形式の応答を返すようにAccept異なる、利用可能なフォーマットを要求ヘッダー。

方法2 私が推奨する方法は、WebAPIが要求されたフォーマットですべての要求に応答することです。ただし、要求された形式を受け入れない場合は、デフォルト(JSONなど)にフォールバックします。

最初に、それをオプションに登録する必要があります(前述のように、デフォルトの動作をやり直す必要があります)。

options.RespectBrowserAcceptHeader = true; // false by default

最後に、サービスビルダーで定義されたフォーマッターのリストを並べ替えるだけで、Webホストはデフォルトでリストの一番上(つまり、位置0)に配置するフォーマッターになります。

詳細については、この.NET Web開発とツールのブログエントリをご覧ください。


あなたが入れた努力に感謝します。あなたの答えは私IActionResultreturn Ok(new {response = "123"});乾杯で実行するように促しました!
ロスコ2017

1
@Rossco問題ありません。うまくいけば、残りのコードがプロジェクトの開発に役立つでしょう。
Svek 2017

1
このトピックを拡張するために、ここにWebAPIを実装するための追加のより完全なガイドを作成しました:stackoverflow.com/q/42365275/3645638
Svek

設定時:RespectBrowserAcceptHeader = true; あなたはなぜそれをしているのかを説明しているわけではなく、通常そうする必要はなく、間違っています。ブラウザーはhtmlを要求するため、いずれにしてもフォーマッターの選択に影響を与えるべきではありません(Chromeは残念ながらXMLを要求することで影響を与えます)。要するに、それは私が
控える

@YishaiGalatzer私の回答のその部分の主なテーマは、クライアントとAPIロジックの間のデフォルトのミドルウェアの負荷を軽減する方法を強調することでした。私の意見でRespectBrowserAcceptHeaderは、代替シリアライザの使用を実装するとき、またはより一般的には、クライアントが不正なリクエストを送信していないことを確認したいときに重要です。したがって、「プロジェクトで完全な制御が必要で、サービスを厳密に定義したい場合」を強調し、そのステートメントの上の強調表示されたブロック引用にも注意してください。
Svek 2017年

57

最も一般的なステータスコードの定義済みメソッドがあります。

  • Ok(result)200応答を返します
  • CreatedAtRoute201+新しいリソースURLを返します
  • NotFound 戻り値 404
  • BadRequest返品400

すべてのメソッドのリストについてはBaseController.cs、とController.csを参照してください。

ただし、実際にStatusCodeカスタムコードの設定に使用できると主張する場合は、コードの可読性が低下し、ヘッダーを設定するためにコードを繰り返す必要があるため(のようにCreatedAtRoute)、実際には使用しないでください。

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

1
これにより、以下の私の応答に対する洞察が得られました。ありがとう
Oge Nwike

このコードはASP.NET Core 2.2では正しくありません。私はそれを試したところ、メソッドによって作成されJSONたものにシリアル化ActionResultされましたJson()。「123」文字列は直接含まれません。
amedina

1
@amedina:私の悪い、削除しJson(...)て文字列を渡す StatusCode
Tseng

あなたが「OK(結果)」と言うとき-結果は何ですか?それはJSON形式の文字列ですか、それともC#オブジェクトですか(自動的にJSON文字列に変換されますか?)?
変数

@変数:常にPOCO /クラス/オブジェクト。文字列を返す場合は、代わりに「コンテンツ」を使用する必要があります
Tseng

42

ASP.NETコア2.0からオブジェクトを返すために理想的な方法Web API(MVCと一体と同じ基本クラスを使用しているController)であります

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

そのことに注意してください

  1. 200 OKステータスコードを返します(これはのOkタイプですObjectResult
  2. コンテンツのネゴシエーションを行います。つまりAccept、リクエストのヘッダーに基づいて戻ります。Accept: application/xmlリクエストで送信された場合、として返されXMLます。何も送信されない場合は、JSONデフォルトです。

特定のステータスコードで送信する必要がある場合は、ObjectResultまたはをStatusCode使用してください。どちらも同じことを行い、コンテンツネゴシエーションをサポートします。

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

または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 };

特にJSONとして返したい場合は、いくつかの方法があります

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

そのことに注意してください

  1. どちらもJSON2つの異なる方法で適用されます。
  2. どちらもコンテンツのネゴシエーションを無視します。
  3. 最初の方法は、特定のシリアライザーでJSONを適用しますJson(object)
  4. 第二の方法は、使用して同じことをProduces()(ある属性をResultFilter持ちます)contentType = application/json

それらの詳細については、公式ドキュメントをご覧ください。フィルターについては、こちらをご覧ください。

サンプルで使用される単純なモデルクラス

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

10
これは質問に焦点を当て、いくつかの実用性を簡潔に説明しているので、これは良い答えです。
18

33

私が思いついた最も簡単な方法は:

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

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

2
私は彼のソリューションは、ステータスコードなどのフィールド重複含まれているため、これは@tsengからの回答よりも優れていると思う
クリスチャン・ザウアーを

2
次のように、Microsoft.AspNetCore.Httpで定義されたStatusCodesを使用することで、改善を加えることができます。return new JsonResult(new {}){StatusCode = StatusCodes.Status404NotFound};
ブライアンベダール

2
これは受け入れられる答えになるはずです。jsonを普遍的にセットアップする方法はありますが、レガシーエンドポイントを使用する必要があり、設定が異なる場合があります。一部のレガシーエンドポイントのサポートを停止できるまでは、これが完全な制御を行うための究極の方法です
pqsk

Microsoft.AspNetCore.Mvc.JsonResultは、私が思う完全修飾名です。FQNや「使用中」の答えはありません。:)アセンブリ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
これは、私が強い型(この例では「ITem結果=新しい項目」...項目は実行時の既知の型)であるときに機能しました。タイプがわからない場合は、この質問に対する私の回答を参照してください。(私はdb ..にjsonがあり、実行時にjsonタイプが不明でした)。ジェラルドに感謝します。
granadaCoder

15

これは私の最も簡単な解決策です:

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

または

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

4

列挙型を使用して404/201ステータスコードを使用する代わり

     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は素晴らしいアイデアです。
Bhimbim、

2

私がここで見つけた素晴らしい答えと私はまた、このリターンステートメントを試してみましたが、StatusCode(whatever code you wish)うまくいきました!!!

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

1
このように!良い提案です!
くすぐったい

0

以下のコードを参照してください。異なるタイプの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
いいえ、これは悪いです。
Phillip Copley 2017年

0

私のAsp Net Core Apiアプリケーションで行うことは、ObjectResultから拡張するクラスを作成し、コンテンツとステータスコードをカスタマイズするための多くのコンストラクターを提供することです。次に、すべてのコントローラーアクションは、適切なものとしてcostructorの1つを使用します。あなたは私の実装をhttps://github.com/melardev/AspNetCoreApiPaginatedCrudで見ることができ ます

そして

https://github.com/melardev/ApiAspCoreEcommerce

これはクラスがどのように見えるかです(完全なコードについては私のリポジトリに移動してください):

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

base(dto)をdtoをオブジェクトに置き換えると、問題なく終了するはずです。


0

これでうまくいきました。私の大きな問題は、jsonが文字列であることでした(データベース内では、特定/既知のタイプではありませんでした)。

はい、ようやくこれを機能させました。

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

私はたまたまasp.netコア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

ここからヒントを得た:: https://www.jianshu.com/p/7b3e92c42b61

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.