エラーメッセージまたは例外でNotFound()IHttpActionResultを返すにはどうすればよいですか?


98

IHttpActionResultWebApi GETアクションで何かが見つからない場合、NotFoundを返します。この応答とともに、カスタムメッセージや例外メッセージ(ある場合)を送信します。current ApiControllerNotFound()メソッドは、メッセージを渡すためのオーバーロードを提供しません。

これを行う方法はありますか?または私は自分のカスタムを作成する必要がありIHttpActionResultますか?


見つかりませんでしたすべての結果に対して同じメッセージを返しますか?
Nikolai Samteladze 2013年

@NikolaiSamteladzeいいえ、状況によってメッセージが異なる場合があります。
Ajay Jadhav 2013年

回答:


84

応答メッセージの形状をカスタマイズする場合は、独自のアクション結果を作成する必要があります。

単純な空の404などの最も一般的な応答メッセージの形状をそのまま提供したかったのですが、これらの結果をできるだけ単純にしたかったのです。アクション結果を使用する主な利点の1つは、アクションメソッドの単体テストがはるかに簡単になることです。アクションの結果に設定するプロパティが多いほど、アクションメソッドが期待どおりの動作をしていることを確認するために、単体テストで考慮する必要のある事項が多くなります。

私はしばしばカスタムメッセージも提供できるようにしたいので、バグをログに記録して、将来のリリースでそのアクションの結果をサポートすることを検討してくださいhttps : //aspnetwebstack.codeplex.com/workitem/list/advanced

ただし、アクションの結果の良い点の1つは、少し異なることをしたい場合は、常にかなり簡単に独自のアクションを作成できることです。あなたのケースでこれを行う方法は次のとおりです(text / plainにエラーメッセージが必要だと仮定します。JSONが必要な場合は、コンテンツで少し異なることを行います)。

public class NotFoundTextPlainActionResult : IHttpActionResult
{
    public NotFoundTextPlainActionResult(string message, HttpRequestMessage request)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }

        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        Message = message;
        Request = request;
    }

    public string Message { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    public HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.NotFound);
        response.Content = new StringContent(Message); // Put the message in the response body (text/plain content).
        response.RequestMessage = Request;
        return response;
    }
}

public static class ApiControllerExtensions
{
    public static NotFoundTextPlainActionResult NotFound(this ApiController controller, string message)
    {
        return new NotFoundTextPlainActionResult(message, controller.Request);
    }
}

次に、アクションメソッドで、次のようにすることができます。

public class TestController : ApiController
{
    public IHttpActionResult Get()
    {
        return this.NotFound("These are not the droids you're looking for.");
    }
}

(ApiControllerから直接継承する代わりに)カスタムコントローラーの基本クラスを使用した場合は、「this」を削除することもできます。パート(これは、残念ながら拡張メソッドを呼び出すときに必要です):

public class CustomApiController : ApiController
{
    protected NotFoundTextPlainActionResult NotFound(string message)
    {
        return new NotFoundTextPlainActionResult(message, Request);
    }
}

public class TestController : CustomApiController
{
    public IHttpActionResult Get()
    {
        return NotFound("These are not the droids you're looking for.");
    }
}

1
私は 'IHttpActionResult'のまったく同じような実装を書きましたが、 'NotFound'結果に固有ではありません。これはおそらくすべての「HttpStatusCodes」で機能します。私のCustomActionResultコードは次のようになります。 また、私のコントローラーの 'Get()'アクションは次のようになります。 'public IHttpActionResult Get(){return CustomNotFoundResult( "Meessage to Return。"); また、将来のリリースでこれを検討するために、CodePlexにバグを記録しました。
Ajay Jadhav 2013年

私はODataControllersを使用しており、this.NotFound( "blah");を使用する必要がありました。
Jerther、2014

1
とても良い投稿ですが、継承のヒントに対してはお勧めしません。私のチームはずっと前にそれを正確に行うことを決めました、そしてそれはそれをすることによって多くのクラスを膨らませました。最近、すべてを拡張メソッドにリファクタリングし、継承チェーンから離れました。このような継承をいつ使用すべきかを慎重に検討することを強くお勧めします。通常、合成ははるかに分離されているので、はるかに優れています。
julealgon 2015

6
この機能はすぐに使えるはずです。オプションの「ResponseBody」パラメーターを含めても、単体テストには影響しません。
Theodore Zographos

230

簡単なメッセージでIHttpActionResult NotFoundを返すためのワンライナーは次のとおりです。

return Content(HttpStatusCode.NotFound, "Foo does not exist.");

24
人々はこの答えに投票すべきです。それは素晴らしくて簡単です!
Jess

2
このソリューションでは、HTTPヘッダーのステータスを「404 Not Found」に設定しないことに注意してください。
Kasper Halvas Jensen

4
@KasperHalvasJensenサーバーからのhttpステータスコードは404ですが、さらに何か必要ですか?
アンソニーF

4
@AnthonyFあなたは正しいです。Controller.Content(...)を使用していました。ShoudはApiController.Content(...)を使用しています。
Kasper Halvas Jensen

おかげで、これはまさに私が探していたものでした
Kaptein Babbalas

28

あなたがResponseMessageResult好きなら使うことができます:

var myCustomMessage = "your custom message which would be sent as a content-negotiated response"; 
return ResponseMessage(
    Request.CreateResponse(
        HttpStatusCode.NotFound, 
        myCustomMessage
    )
);

そうです、もっと短いバージョンが必要な場合は、カスタムアクションの結果を実装する必要があると思います。


すっきりしているので、この方法を使いました。別の場所でカスタムメッセージを定義し、インデントしたリターンコードを定義しました。
ozzy432836

これは、標準のBadRequestメソッドと同様に、Messageプロパティで解析できるオブジェクトを実際に返すため、Contentよりも優れています。
user1568891 2017

7

HttpResponseMessageクラスのReasonPhraseプロパティを使用できます

catch (Exception exception)
{
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
  {
    ReasonPhrase = exception.Message
  });
}

ありがとう。これはうまくいくはずですが、すべてのアクションで自分でHttpResponseExceptionを構築する必要があります。コードを少なくするために、WebApi 2の機能(既製のNotFount()Ok()メソッドのように)を使用して、ReasonPhraseメッセージをそれに渡すことができるかどうか考えていました。
Ajay Jadhav 2013年

独自の拡張メソッドNotFound(Exception exception)を作成できます。これにより、正しいHttpResponseExceptionがスローされます
Dmytro Rudenko

@DmytroRudenko:テスト結果を改善するためにアクション結果が導入されました。ここでHttpResponseExceptionをスローすると、妥協することになります。ここでも例外はありませんが、OPはメッセージの返信を探しています。
キランチャラ2013年

テストにNUintを使用したくない場合は、NotFoundResultの独自の実装を作成し、メッセージデータを返すようにExecuteAsyncを書き換えることができます。そして、アクション呼び出しの結果としてこのクラスのインスタンスを返します。
Dmytro Rudenko 2013年

1
ステータスコードを直接渡すことができるようになりました。例:HttpResponseException(HttpStatusCode.NotFound)
Mark Sowul

3

d3m3t3erが提案するように、カスタムのネゴシエートされたコンテンツ結果を作成できます。しかし、私はから継承します。また、NotFoundを返すためだけに必要な場合は、コンストラクターからhttpステータスを初期化する必要はありません。

public class NotFoundNegotiatedContentResult<T> : NegotiatedContentResult<T>
{
    public NotFoundNegotiatedContentResult(T content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller)
    {
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => task.Result, cancellationToken);
    }
}

2

OkNegotiatedContentResult結果の応答メッセージのHTTPコードから単純に派生させてオーバーライドすることで解決しました。このクラスを使用すると、任意のHTTP応答コードでコンテンツ本文を返すことができます。

public class CustomNegotiatedContentResult<T> : OkNegotiatedContentResult<T>
{
    public HttpStatusCode HttpStatusCode;

    public CustomNegotiatedContentResult(
        HttpStatusCode httpStatusCode, T content, ApiController controller)
        : base(content, controller)
    {
        HttpStatusCode = httpStatusCode;
    }

    public override Task<HttpResponseMessage> ExecuteAsync(
        CancellationToken cancellationToken)
    {
        return base.ExecuteAsync(cancellationToken).ContinueWith(
            task => { 
                // override OK HTTP status code with our own
                task.Result.StatusCode = HttpStatusCode;
                return task.Result;
            },
            cancellationToken);
    }
}

1

NegotitatedContentResult<T>前述のようにbaseから継承し、変換する必要がない場合content(たとえば、文字列を返したいだけの場合)、ExecuteAsyncメソッドをオーバーライドする必要はありません。

必要なのは、適切な型定義と、どのHTTPステータスコードを返すかをベースに伝えるコンストラクタを提供することだけです。それ以外はすべて機能します。

ここでは、両方のための例ですNotFoundInternalServerError

public class NotFoundNegotiatedContentResult : NegotiatedContentResult<string>
{
    public NotFoundNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.NotFound, content, controller) { }
}

public class InternalServerErrorNegotiatedContentResult : NegotiatedContentResult<string>
{
    public InternalServerErrorNegotiatedContentResult(string content, ApiController controller)
        : base(HttpStatusCode.InternalServerError, content, controller) { }
}

そして、対応する拡張メソッドを作成できますApiController(または、基本クラスがある場合はそれを行います)。

public static NotFoundNegotiatedContentResult NotFound(this ApiController controller, string message)
{
    return new NotFoundNegotiatedContentResult(message, controller);
}

public static InternalServerErrorNegotiatedContentResult InternalServerError(this ApiController controller, string message)
{
    return new InternalServerErrorNegotiatedContentResult(message, controller);
}

そして、それらは組み込みメソッドと同じように機能します。既存のものを呼び出すかNotFound()、新しいカスタムを呼び出すことができますNotFound(myErrorMessage)

そしてもちろん、カスタムタイプ定義で「ハードコードされた」文字列タイプを削除して、必要に応じてそれをジェネリックのままにしておくこともできますが、その場合ExecuteAsync<T>実際のがあります。

ソースコードを調べて、NegotiatedContentResult<T>すべての機能を確認できます。それにはあまりありません。


1

プロパティを設定するためにIHttpActionResultIExceptionHandlerクラスの本体にインスタンスを作成する必要がありましたExceptionHandlerContext.Result。しかし、私もカスタムを設定したかったReasonPhrase

ResponseMessageResultラップできることがわかりましたHttpResponseMessage(これにより、ReasonPhraseを簡単に設定できます)。

例えば:

public class MyExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        var ex = context.Exception as IRecordNotFoundException;
        if (ex != null)
        {
            context.Result = new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = $"{ex.EntityName} not found" });
        }
    }
}

0

Iknow POがメッセージテキストで尋ねたが、404を返すだけの別のオプションは、メソッドがIHttpActionResultを返し、StatusCode関数を使用することです

    public async Task<IHttpActionResult> Get([FromUri]string id)
    {
       var item = await _service.GetItem(id);
       if(item == null)
       {
           StatusCode(HttpStatusCode.NotFound);
       }
       return Ok(item);
    }

0

ここでの回答には、開発者ストーリーの小さな問題が欠けています。ApiControllerクラスはまだAを公開していますNotFound()開発者が使用してよい方法を。これにより、一部の404応答に、制御されていない結果本体が含まれることになります。

ここでは、開発者が「404を送信するためのより良い方法」を知る必要がない、エラーが発生しにくいメソッドを提供するコードのいくつかの部分「より良いApiController NotFoundメソッド」を示します。

  • 呼び出されたクラスを継承ApiControllerするクラスを作成するApiController
    • この手法を使用して、開発者が元のクラスを使用できないようにします
  • そのNotFoundメソッドオーバーライドして、開発者が最初に利用可能なAPIを使用できるようにします
  • これを思いとどまらせたい場合は、これを [Obsolete("Use overload instead")]
  • 追加する protected NotFoundResult NotFound(string message)奨励したいをします
  • 問題:結果はボディでの応答をサポートしていません。ソリューション:継承して使用しますNegotiatedContentResult。付属のNotFoundResultクラスを参照してください。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.