これは少し古いスレッドですが、私がここに到着してから、他の人を助けるために私の発見を投稿すると思いました。
まず、同じ問題がありました。Request.Bodyを取得し、それを使って何かをしたいと考えていました(ロギング/監査)。しかし、それ以外の場合は、エンドポイントが同じに見えるようにしたかった。
したがって、EnableBuffering()呼び出しがうまくいくように思われました。次に、本文でSeek(0、xxx)を実行して、内容を再度読み取ることができます。
しかし、これが私の次の問題につながりました。エンドポイントにアクセスすると、「Synchornousオペレーションは許可されていません」という例外が発生します。したがって、回避策は、オプションでプロパティAllowSynchronousIO = trueを設定することです。これを行うにはいくつかの方法があります(ただし、ここで詳しく説明することは重要ではありません)。
次に、次の問題は、Request.Bodyを読みに行ったときにすでに破棄されていることです。ああ。だから、何を与えるのですか?
Endtonion呼び出しの[FromBody]パーサーとしてNewtonsoft.JSONを使用しています。それが同期読み取りの原因であり、完了時にストリームを閉じます。解決?JSON解析に入る前にストリームを読み取りますか?確かに、それはうまくいき、私はこれで終わりました:
/// <summary>
/// quick and dirty middleware that enables buffering the request body
/// </summary>
/// <remarks>
/// this allows us to re-read the request body's inputstream so that we can capture the original request as is
/// </remarks>
public class ReadRequestBodyIntoItemsAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context == null) return;
// NEW! enable sync IO beacuse the JSON reader apparently doesn't use async and it throws an exception otherwise
var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
var req = context.HttpContext.Request;
req.EnableBuffering();
// read the body here as a workarond for the JSON parser disposing the stream
if (req.Body.CanSeek)
{
req.Body.Seek(0, SeekOrigin.Begin);
// if body (stream) can seek, we can read the body to a string for logging purposes
using (var reader = new StreamReader(
req.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 8192,
leaveOpen: true))
{
var jsonString = reader.ReadToEnd();
// store into the HTTP context Items["request_body"]
context.HttpContext.Items.Add("request_body", jsonString);
}
// go back to beginning so json reader get's the whole thing
req.Body.Seek(0, SeekOrigin.Begin);
}
}
}
}
これで、[ReadRequestBodyIntoItems]属性を持つエンドポイントでHttpContext.Items ["request_body"]を使用して本文にアクセスできます。
しかし、男、これはあまりにも多くのフープが飛び越えられないようです。だからここで私は終わりました、そして私はそれで本当に満足しています。
私のエンドポイントは次のようなものとして始まりました:
[HttpPost("")]
[ReadRequestBodyIntoItems]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value)
{
val bodyString = HttpContext.Items["request_body"];
// use the body, process the stuff...
}
ただし、次のように署名を変更する方がはるかに簡単です。
[HttpPost("")]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData()
{
using (var reader = new StreamReader(
Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false
))
{
var bodyString = await reader.ReadToEndAsync();
var value = JsonConvert.DeserializeObject<MyJsonObjectType>(bodyString);
// use the body, process the stuff...
}
}
ボディストリームを1回だけ読み取るため、これが本当に好きで、逆シリアル化を制御できます。確かに、ASP.NETコアがこの魔法を使ってくれればいいのですが、ここではストリームを2回(おそらく毎回バッファリング)読み取るのに時間を無駄にしていません。コードは非常に明確でクリーンです。
多くのエンドポイントでこの機能が必要な場合は、ミドルウェアアプローチの方がクリーンである可能性があります。または、少なくとも本体の抽出を拡張関数にカプセル化して、コードをより簡潔にすることができます。
とにかく、私はこの問題の3つの側面すべてに触れたソースを見つけられなかったので、この投稿。うまくいけば、これは誰かを助けるでしょう!
ところで:これはASP .NET Core 3.1を使用していました。