はじめに
ASP.NET Core MVC には、IActionFilterというインターフェースがあり、これを実装することで、要求処理パイプラインのアクション メソッドが呼び出される直前と直後に独自のコードを実行させることができます。
Razor Pagesでも似たようなIAsyncPageFilterインターフェース、 IPageFilterインターフェースが用意されています。
ここでは、非同期版のIAsyncPageFilter
インターフェースの簡単な使い方を説明します。
なおこの記事で作成したソースコードはGitHuibで公開しています。
https://github.com/gushwell/IAsyncPageFilterSample
IAsyncPageFilterインターフェース
IAsyncPageFilter
インターフェースには、以下の2つのメソッドがあります。
public interface IAsyncPageFilter : IFilterMetadata
{
Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context);
Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next);
}
OnPageHandlerSelectionAsync
メソッドは、Pageハンドラー メソッドが選択された後、モデル バインドが発生する前に非同期的に呼び出されます。
OnPageHandlerExecutionAsync
は、モデル バインドが完了した後、ハンドラー メソッドが呼び出される前に非同期的に呼び出されます。
IAsyncPageFilterを実装する
ここでは、簡単なアクセスログを出力するコードを書いてみます。
モデルバインディングでエラーになった場合もログを出力するようにOnPageHandlerSelectionAsync
メソッドでログを出力しています。
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebAppSample;
public class MyAsyncPageFilter : IAsyncPageFilter
{
private readonly ILogger _logger;
public MyAsyncPageFilter(ILogger logger)
{
_logger = logger;
}
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
await next.Invoke();
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
var httpContext = context.HttpContext;
var message = $"HttpType: {httpContext.Request?.Method}|Path :{httpContext.Request?.Path}\tUserAgent: {httpContext.Request?.Headers["User-Agent"].ToString()}\tPage: {context.HandlerInstance?.GetType().Name}\tHandler: {context.HandlerMethod?.MethodInfo?.Name}";
_logger.LogTrace(message);
return Task.CompletedTask;
}
}
MyAsyncPageFilterをグローバルに組み込む
Program.csで、先ほど作成したMyAsyncPageFilter
を組み込みます。
using WebAppSample;
var builder = WebApplication.CreateBuilder(args);
var logger = LoggerFactory.Create(config =>
{
config.AddConsole();
config.SetMinimumLevel(LogLevel.Trace);
}).CreateLogger("WebAppSample");
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new MyAsyncPageFilter(logger));
});
var app = builder.Build();
... 以下省略 ...
MyAsyncPageFilter
は、ILogger
のインスタンスをコンストラクタで受け取りたいのですが、残念ながらDIで自動で渡すことができません。
そのため、独自にLoggerFactory.Create
を使って、Loggerインスタンスを生成しています。
MyAsyncPageFilter
内では、
_logger.LogTrace(...);
とTraceログを出力していますので、LoggerFactory.Create
時に、ログレベルを指定しています。
config.SetMinimumLevel(LogLevel.Trace);
実行してみる
これで、MyAsyncPageFilter
がグローバルで組み込まれたので、ページにアクセスがあるたびにログが出力されることになります。
テストをわかりやすくするために、appsettings.Development.json
を書き換えて、フレームワーク側が出すログレベルを変更します。
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}
それでは実行してみます。
「ASP.NET Core Web アプリ」プロジェクトを新規作成した時にできるサンプルプロジェクトに前述のコードを組み込んだ状態で実行しています。プロジェクトの名前を WebAppSample としています。
コンソールにログが出力されていることを確認できるように、デバッグでは、「IIS Express」ではなく、プロジェクト名の「WebAppSample」を選んでデバッグします。
以下、Homeページを開いた後に、Privacyページにアクセスした結果です。IndexページのOnGetメソッド、PrivacyページのOnGetメソッドが呼び出されているのが確認できます。