az blog

UnityやASP.NET Core、それらの周辺技術についてまとめていきます。

ASP.NET Core MVCでRequestとResponseのBodyをログ出力する

結論

以下の3つ実装すれば取得できる。

① Streamの読み取りを何回でも可能にするためにカスタムMiddlewareを実装する

RequestEnableBuffering.cs

public class RequestEnableBufferingMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
context.Request.EnableBuffering();
await next(context);
}
}

② ログを出力するためにActionFilterを実装する

LoggerFIlter.cs

public class LoggerFilter : IAsyncActionFilter
{
async Task IAsyncActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.HttpContext.Request.Body.Position = 0;
using var requestReader = new StreamReader(context.HttpContext.Request.Body);
var requestText = await requestReader.ReadToEndAsync();
Console.WriteLine(JsonSerializer.Serialize(requestText));
context.HttpContext.Request.Body.Position = 0;

var executedContext = await next();
if (executedContext.Result is ObjectResult objectResult)
{
Console.WriteLine(JsonSerializer.Serialize(objectResult.Value));
}
}
}

③ Global FilterとMiddlewareを使用するように設定する

builder.Services.AddControllers(
option =>
{
// Global Filters
option.Filters.Add<LoggerFilter>();
});
builder.Services.AddSingleton<RequestEnableBufferingMiddleware>();

// --- この間にその他のServiceのDI ---

// --- この間にその他のServiceのDI ---

var app = builder.Build();

// --- この間にその他のMiddlewareのUse ---

// --- この間にその他のMiddlewareのUse ---


// MapControllersの前にMiddlewareを追加
app.UseMiddleware<RequestEnableBufferingMiddleware>();

app.MapControllers();

app.Run(url);

解説

Responseに関してはResultをキャストするだけで取得できるので、Requestの取得方法について解説する。

ネットで検索すると以下の関数をLoggerFilterの中で呼び出すコードが出てくる。

context.HttpContext.Request.EnableBuffering();

HttpRequestのBodyはStreamになっておりデフォルトでは一回しかReadできない。一方で、その一回はControllerがBodyの情報を取得するために使用する。なので、ログ用にBodyの中身を取得するには、Streamを複数回Readできるようにする必要がある。この複数回Readできるようにする関数がEnableBuffering()である。EnableBuffering()を実行することでReadした後にStreamが指すポインタを以下のコードで戻せるようになる。

context.HttpContext.Request.Body.Position = 0;

では上の二行をLoggerFilter内に実装すれば良いかというと実はそれでは上手くいかない。公式ドキュメントにもあるようにActionFilterに到達するタイミングでは既にModelBindingが完了しているため、ActionFilter内で上記設定を行なっても手遅れなのだ。したがって、ModelBindingより前にEnableBuffering()を実行する必要がある。そこで、Middlewareを追加しEnableBuffering()を行うというのがこの記事の本題だ。

Middlewareの処理順は自分がProgram.csやStartup.csでUseMiddleware<T>()を呼び出した順序できまる(厳密にいうとUse()関数です)。この辺は、公式のドキュメントを読むとわかる。今回はActionFilterの前に呼ばれさえすれば良いので、MapControllersの前に呼ぶのが適切だろう。なので、RequestEnableBufferingMiddleware.csを実装しMapControllersの前でUseMiddleware()を行う。

以上それらを合わせると最終的に結論で書いたようなコードとなる。最後に、今回はBodyをReadする方法をメインで解説しているが本番のコードでログを実装する場合は以下も検討した方が良い。

  • 構造化ロギング
  • ILoggerの使用
  • ログ処理のエラーの握りつぶし

おまけ

構造化ロギングを行うときに呼び出されたRequestに紐づくControllerの引数の型情報が必要になることがある。その場合以下のコードで取得することができる。

async Task IAsyncActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var controller = context.Controller as ControllerBase;
var parameters = controller.ControllerContext.ActionDescriptor.Parameters;
}

【Unity】URPで複数カメラを使用している場合に、全てのカメラを合成したスクリーンショットを撮る

概要

趣味でUnityを触っている分には必要になるケースはあまり多くありませんが、実務でUnityを触っていると

  • InGameを描画するためのカメラ
  • UIを描画するカメラ

の2つを利用したいというケースがしばしばあります。

こういった複数のカメラをURPで使用している場合に、BuiltInと同じ感覚でスクリーンショット機能を実装しようとしたら結構ハマったので書き記すことにしました。

前提

描画パイプラインはURPを使っており、InGame用カメラのカメラスタックにUI用のカメラをOverlayに設定して追加している状態です。

実装

気をつける点としては、Camera.Render()を呼び出す順序がBuiltInの時と比較すると逆になることです。3つ以上のカメラを使用するときも同様で一番最後に描画されるCameraから順番にCamera.Render()を呼び出す必要があります。

【MagicOnion】SwaggerでRequestするときのHeaderに認証情報を追加する

概要

最近、Grpcによる通信が普及し始めています。

Untiy内でGrpcを使用したい!というときにネットで色々検索すると、MagicOnionというライブラリ目にすることがあると思います。

今回は、そのMagicOnionで作成したServiceをテストするときに、MagicOnionが提供しているSwaggerから認証情報付きで関数を実行する方法を備忘録として残していきたいと思います。

方針

まず、簡単にMagicOnion側のSwaggerからServiceへのルーティング部分を説明します。

MagicOnionでは以下の2つのミドルウェアを組み合わせることで、SwaggerからServiceのメソッドを実行しています。

  1. MagicOnionSwaggerMiddleware
  2. MagicOnionHttpGatewayMiddleware

1がSwaggerを立ち上げるためのミドルウェア

2がHttp1からGrpcに変換するためのミドルウェア

になっています。

つまり、2のタイミングでHeaderに認証情報を追加することができれば、目的を達成することができます。

そして、追加する方法としてさっきからちらほら名前が出てきているASP.NET Coreのミドルウェアという仕組みを使います。ミドルウェアについて詳細に知りたい方は公式ドキュメントを読んでください。

実装

MagicOnionHttpGatewayMiddlewareをUseMiddlewareする前にUseを呼び出すことで、Headerに認証情報を追加しています。

例として、key = "x-auth-key", value = "id-token"として追加しています。