WebApi GETアクションで何かが見つからない場合、NotFound IHttpActionResult
を返します。この応答とともに、カスタムメッセージや例外メッセージ(ある場合)を送信します。現在のApiController
のNotFound()
メソッドは、メッセージを渡すためのオーバーロードを提供しません。
これを行う方法はありますか?または、独自のカスタムIHttpActionResult
を記述する必要がありますか?
応答メッセージの形状をカスタマイズする場合は、独自のアクション結果を作成する必要があります。
単純な空の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.");
}
}
簡単なメッセージでIHttpActionResult NotFoundを返すためのワンライナーは次のとおりです。
return Content(HttpStatusCode.NotFound, "Foo does not exist.");
必要に応じてResponseMessageResult
を使用できます。
var myCustomMessage = "your custom message which would be sent as a content-negotiated response";
return ResponseMessage(
Request.CreateResponse(
HttpStatusCode.NotFound,
myCustomMessage
)
);
ええ、もっと短いバージョンが必要な場合は、カスタムアクションの結果を実装する必要があると思います。
HttpResponseMessageクラスのReasonPhraseプロパティを使用できます
catch (Exception exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)
{
ReasonPhrase = exception.Message
});
}
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);
}
}
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);
}
}
前述のようにベースNegotitatedContentResult<T>
から継承し、content
を変換する必要がない場合(たとえば、文字列を返すだけの場合)、ExecuteAsync
メソッドをオーバーライドする必要はありません。
必要なのは、適切な型定義と、どのHTTPステータスコードを返すかをベースに伝えるコンストラクタを提供することだけです。それ以外はすべて機能します。
NotFound
とInternalServerError
の両方の例を次に示します。
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)
を呼び出すことができます。
そしてもちろん、カスタムタイプ定義の「ハードコードされた」文字列タイプを取り除き、必要に応じて汎用のままにすることができますが、may心配する必要がありますあなたの<T>
が実際に何であるかに応じて、ExecuteAsync
のもの。
NegotiatedContentResult<T>
の- ソースコード を調べると、すべての機能を確認できます。それに多くはありません。
ExceptionHandlerContext.Result
プロパティを設定するには、 IHttpActionResult
クラスの本体にIExceptionHandler
インスタンスを作成する必要がありました。ただし、カスタム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" });
}
}
}