[解決済み] 明示的に型付けされた ASP.NET Core API コントローラから 404 を返す (IActionResult ではない)
質問
ASP.NET Core APIコントローラは、通常、次のような明示的な型を返します(新しいプロジェクトを作成するとデフォルトでそのようになります)。
[Route("api/[controller]")]
public class ThingsController : Controller
{
// GET api/things
[HttpGet]
public async Task<IEnumerable<Thing>> GetAsync()
{
//...
}
// GET api/things/5
[HttpGet("{id}")]
public async Task<Thing> GetAsync(int id)
{
Thing thingFromDB = await GetThingFromDBAsync();
if(thingFromDB == null)
return null; // This returns HTTP 204
// Process thingFromDB, blah blah blah
return thing;
}
// POST api/things
[HttpPost]
public void Post([FromBody]Thing thing)
{
//..
}
//... and so on...
}
問題なのは
return null;
- を返しますが、これは HTTP
204
: 成功、コンテンツなし。
これは、多くのクライアントサイドのJavascriptコンポーネントによって成功とみなされるため、次のようなコードがあります。
const response = await fetch('.../api/things/5', {method: 'GET' ...});
if(response.ok)
return await response.json(); // Error, no content!
ネットでの検索(例えば
この質問
と
この回答
) は、役に立つ
return NotFound();
の拡張メソッドがありますが、これらはすべて
IActionResult
を返すので、私の
Task<Thing>
の戻り値とは互換性がありません。そのデザインパターンは次のようなものです。
// GET api/things/5
[HttpGet("{id}")]
public async Task<IActionResult> GetAsync(int id)
{
var thingFromDB = await GetThingFromDBAsync();
if (thingFromDB == null)
return NotFound();
// Process thingFromDB, blah blah blah
return Ok(thing);
}
これは動作しますが、これを使用するためには、返り値として
GetAsync
に変更する必要があります。
Task<IActionResult>
- に変更しなければならず、明示的な型付けが失われ、コントローラのすべての戻り値の型を変更しなければならないか(つまり、明示的な型付けを全く使用しない)、あるアクションと他のアクションが明示的な型を扱うようになるかのどちらかになります。さらに、ユニットテストはシリアライゼーションについて仮定し、明示的に
IActionResult
の内容を明示的にデシリアライズする必要があります。
これを回避する方法はたくさんありますが、簡単に設計できるような混乱した寄せ集めであるように見えますので、本当の疑問は ASP.NET Core の設計者が意図する正しい方法は何でしょうか?
可能な選択肢は以下のようなものだと思われます。
-
奇妙な (テストが面倒な) 明示的な型と
IActionResult
が混在しています。 -
明示的な型については、Core MVCではサポートされていませんので、忘れてください。
常に
を使用します。
IActionResult
(を使用します(この場合、なぜそれらが全く存在しないのでしょうか?) -
の実装を書きます。
HttpResponseException
のような実装を書き、それをArgumentOutOfRangeException
(のように使うことができます。 この答え を参照)。しかし、その場合、プログラムの流れに例外を使う必要があり、これは一般的に悪い考えであり、また MVC Coreチームによって非推奨とされた . -
の実装を書きます。
HttpNoContentOutputFormatter
を返す実装を書く。404
を返します。 - Core MVCがどのように動作することになっているのか、私が見逃している他の何か?
-
または
204
が正しくて404
は失敗した GET リクエストのために間違っていますか?
これらはすべて、何かを失ったり、MVC Core の設計とは相反する不必要な複雑さを追加したりする妥協とリファクタリングを伴います。どの妥協点が正しいのか、そしてそれはなぜなのでしょうか。
どのように解決するのですか?
これは
が ASP.NET Core 2.1 で扱われています。
と共に
ActionResult<T>
:
public ActionResult<Thing> Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
return NotFound();
return thing;
}
あるいは、さらに
public ActionResult<Thing> Get(int id) =>
GetThingFromDB() ?? NotFound();
実装したらもっと詳しくこの回答を更新します。
オリジナルの回答
ASP.NET Web API 5には
HttpResponseException
(で指摘されたように)。
ハッカーマン
によって指摘されました) が、Core から削除され、それを処理するミドルウェアも存在しません。
この変更は.NET Coreによるものだと思います。ASP.NETは箱から出したら何でもやろうとしますが、ASP.NET Coreは特に指示したことだけを行います(これが、より迅速でポータブルである理由の大きな部分です)。
これを行う既存のライブラリを見つけることができないので、自分で書きました。まず、チェックするためのカスタム例外が必要です。
public class StatusCodeException : Exception
{
public StatusCodeException(HttpStatusCode statusCode)
{
StatusCode = statusCode;
}
public HttpStatusCode StatusCode { get; set; }
}
次に
RequestDelegate
ハンドラが必要です。このハンドラは新しい例外をチェックし、それを HTTP レスポンスステータスコードに変換します。
public class StatusCodeExceptionHandler
{
private readonly RequestDelegate request;
public StatusCodeExceptionHandler(RequestDelegate pipeline)
{
this.request = pipeline;
}
public Task Invoke(HttpContext context) => this.InvokeAsync(context); // Stops VS from nagging about async method without ...Async suffix.
async Task InvokeAsync(HttpContext context)
{
try
{
await this.request(context);
}
catch (StatusCodeException exception)
{
context.Response.StatusCode = (int)exception.StatusCode;
context.Response.Headers.Clear();
}
}
}
次に、このミドルウェアを
Startup.Configure
:
public class Startup
{
...
public void Configure(IApplicationBuilder app)
{
...
app.UseMiddleware<StatusCodeExceptionHandler>();
最後に、アクションは HTTP ステータスコード例外を投げることができます。
IActionResult
:
public Thing Get(int id) {
Thing thing = GetThingFromDB();
if (thing == null)
throw new StatusCodeException(HttpStatusCode.NotFound);
return thing;
}
これにより、戻り値の明示的な型が維持され、成功した空の結果 (
return null;
を投げるようなものだと考えています)。
ArgumentOutOfRangeException
).
これは問題に対する解決策ではありますが、私の疑問に対する本当の答えにはなっていません。Web APIの設計者は、明示的な型が使われることを想定してサポートを構築し、特定の処理を追加して
return null;
に対して特定の処理を追加し、200ではなく204を生成するようにし、そして404に対処する方法を追加しなかったのでしょうか?それほど基本的なことを追加するのは大変な作業のように思えます。
関連
-
[解決済み】SmtpException: トランスポート接続からデータを読み取れません:net_io_connectionclosed
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】Nullableオブジェクトは値を持たなければならない?
-
[解決済み] ConfigureServices内からASP.NET Core DIでインスタンスを解決する
-
[解決済み] ASP.NET Core Web APIの例外処理
-
[解決済み] await/asyncを使用しているときにHttpClient.GetAsync(...)が返らない
-
[解決済み】ASP.NET Core RC2 Web ApiからHTTP 500を返すには?
-
[解決済み] [Solved] ASP.NET MVCで404を適切に処理するには?
-
[解決済み】ASP.NET Coreがステータスコード付きのJSONを返す
-
[解決済み] インターフェース実装の非同期化
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み】WebForms UnobtrusiveValidationModeは、jqueryのScriptResourceMappingを必要とする
-
[解決済み】C# ASP.NET使用時に「WebClientのリクエスト中に例外が発生しました。
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み] 2つのリストを結合する
-
[解決済み】 C# 条件演算子エラー 代入、call、increment、decrement、await、new object 式のみ文として使用可能です。
-
[解決済み] 関数を終了するには?
-
[解決済み】ASP.NET Core RC2 Web ApiからHTTP 500を返すには?
-
[解決済み】ASP.NET Coreがステータスコード付きのJSONを返す