ミドルウェアの基礎概念
ASP.NET Coreにおけるミドルウェアは、HTTPリクエストとレスポンスを処理するC#クラスです。ミドルウェアはリクエストパイプラインを構成し、各コンポーネントが特定の機能を担当します。主な役割として、ロギング、エラーハンドリング、認証、静的ファイルの配信など、アプリケーション全体の横断的関心事を処理することがあります。
ミドルウェアパイプラインは要求処理の流れを制御し、リクエストはパイプラインを通じて各ミドルウェアを順番に通過します。あるミドルウェアがレスポンスを生成すると、そのレスポンスは逆の順序でパイプラインを通過します。
ミドルウェアパイプラインの構築
基本的なミドルウェアの設定
StartupクラスのConfigureメソッドでミドルウェアパイプラインを定義します。IApplicationBuilderに対してミドルウェアを追加する順序が、実行順序となります。
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.Use(async (context, next) =>
{
// リクエスト処理の前処理
await next.Invoke();
// レスポンス処理の後処理
});
application.Run(async context =>
{
await context.Response.WriteAsync("終端ミドルウェア");
});
}
}
静的ファイルの配信
StaticFileMiddlewareを使用して、wwwrootフォルダ内の静的ファイルを配信する設定例です。
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseStaticFiles();
application.Run(async context =>
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("ファイルが見つかりません");
});
}
}
Razor Pagesアプリケーションの設定
Razor Pagesを使用する場合の典型的なミドルウェア構成です。
public class Startup
{
public void ConfigureServices(IServiceCollection collection)
{
collection.AddRazorPages();
}
public void Configure(IApplicationBuilder application, IHostEnvironment environment)
{
if (environment.IsDevelopment())
{
application.UseDeveloperExceptionPage();
}
else
{
application.UseExceptionHandler("/Error");
application.UseHsts();
}
application.UseHttpsRedirection();
application.UseStaticFiles();
application.UseRouting();
application.UseEndpoints(routes =>
{
routes.MapRazorPages();
});
}
}
エラーハンドリングの実装
開発環境での例外処理
DeveloperExceptionPageMiddlewareを使用して、開発中に詳細なエラー情報を表示します。
public class Startup
{
public void Configure(IApplicationBuilder application, IHostEnvironment environment)
{
if (environment.IsDevelopment())
{
application.UseDeveloperExceptionPage(options =>
{
options.SourceCodeLineCount = 10;
});
}
else
{
application.UseExceptionHandler("/error");
}
application.UseStaticFiles();
application.UseRouting();
application.UseEndpoints(routes =>
{
routes.MapRazorPages();
});
}
}
本番環境でのエラーハンドリング
ExceptionHandlerMiddlewareを使用して、本番環境でユーザーフレンドリーなエラーページを表示します。
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseExceptionHandler(options =>
{
options.ExceptionHandlingPath = "/custom-error";
});
application.UseStaticFiles();
application.UseRouting();
application.UseEndpoints(routes =>
{
routes.MapRazorPages();
});
}
}
ステータスコードの処理
StatusCodePagesMiddlewareを使用して、404などのエラーステータスコードをカスタムページに置き換えます。
public class Startup
{
public void Configure(IApplicationBuilder application)
{
application.UseStatusCodePages(options =>
{
options.HandleCodes(404, 500);
options.RedirectToStatusCodePages("/error/{0}");
});
application.UseStaticFiles();
application.UseRouting();
application.UseEndpoints(routes =>
{
routes.MapRazorPages();
});
}
}
Web APIでのエラーハンドリング
Web APIの場合、HTMLではなくJSON形式でエラーを返すことが一般的です。
public class CustomExceptionMiddleware
{
private readonly RequestDelegate _next;
public CustomExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception exception)
{
await HandleExceptionAsync(context, exception);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = 500;
var response = new
{
error = new
{
message = "内部サーバーエラーが発生しました",
details = exception.Message
}
};
return context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
}
カスタムミドルウェアの作成
独自のミドルウェアを作成する例として、リクエストログを記録するミドルウェアを実装します。
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
_logger.LogInformation($"リクエスト開始: {context.Request.Method} {context.Request.Path}");
var stopwatch = Stopwatch.StartNew();
await _next(context);
stopwatch.Stop();
_logger.LogInformation($"リクエスト完了: {context.Response.StatusCode} - 処理時間: {stopwatch.ElapsedMilliseconds}ms");
}
}
ミドルウェアの条件付き適用
環境や条件に応じてミドルウェアを適用する方法です。
public class Startup
{
public void Configure(IApplicationBuilder application, IHostEnvironment environment)
{
if (environment.IsDevelopment())
{
application.UseMiddleware<DevelopmentMiddleware>();
}
application.Map("/api", apiBranch =>
{
apiBranch.UseMiddleware<ApiAuthenticationMiddleware>();
apiBranch.UseMiddleware<ApiRateLimitMiddleware>();
});
application.UseWhen(context => context.Request.Headers.ContainsKey("X-Custom-Header"),
customBranch =>
{
customBranch.UseMiddleware<CustomHeaderMiddleware>();
});
application.UseStaticFiles();
application.UseRouting();
application.UseEndpoints(routes =>
{
routes.MapRazorPages();
});
}
}
ミドルウェアのベストプラクティス
- ミドルウェアは単一の責務に集中させる
- 依存性の注入を活用してテスト可能なコードを記述する
- パイプラインの順序を適切に設定する
- エラーハンドリングミドルウェアはパイプラインの先頭に配置する
- パフォーマンスに影響を与える処理は非同期で実装する
- ミドルウェアの再利用性を考慮して設計する