カスタムミドルウェアの作成
Use/Run/Mapでインラインミドルウェアを作成し、InvokeAsyncを使うクラスベースのミドルウェアで再利用可能なコンポーネントを構築します。
「カスタムミドルウェアの作成」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
カスタムミドルウェアが必要な理由
カスタムミドルウェアは、多くのリクエストに適用される横断的関心事を処理します。ロギング、計測、エラー処理、認証、圧縮、キャッシュなどです。一度作成すれば、条件に一致するすべてのリクエストに対して自動的にロジックが実行されます。
Use によるインラインミドルウェア
ミドルウェアを追加する最も簡単な方法は、app.Use()にデリゲートを渡すことです。プロトタイプ作成や、本当に単純なロジックに適しています。
app.Use(async (context, next) =>
{
var start = DateTimeOffset.UtcNow;
await next(context);
var elapsed = DateTimeOffset.UtcNow - start;
context.Response.Headers.Append(
"X-Elapsed", elapsed.TotalMilliseconds.ToString("F0") + "ms");
});クラスベースのミドルウェア(規約方式)
規約ベースのミドルウェアクラスには、RequestDelegateを受け取るコンストラクターと、publicなInvokeAsyncメソッドがあります。追加のコンストラクター引数はDIから注入できます。
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context)
{
var headers = context.Response.Headers;
headers.Append("X-Content-Type-Options", "nosniff");
headers.Append("X-Frame-Options", "DENY");
headers.Append("X-XSS-Protection", "1; mode=block");
headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
await _next(context);
}
}
// Extension method for clean registration:
public static class SecurityHeadersExtensions
{
public static IApplicationBuilder UseSecurityHeaders(
this IApplicationBuilder app) =>
app.UseMiddleware<SecurityHeadersMiddleware>();
}DI の Scoped 依存関係向け IMiddleware
ミドルウェアでDbContextのようなScopedサービスが必要な場合は、IMiddlewareを実装してください。Scopedとして登録すると、フレームワークがリクエストごとに新しく解決します。
public class AuditMiddleware : IMiddleware
{
private readonly AuditDbContext _db;
private readonly IHttpContextAccessor _http;
public AuditMiddleware(AuditDbContext db, IHttpContextAccessor http)
{
_db = db;
_http = http;
}
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
await next(ctx);
if (ctx.Request.Method != "GET")
{
_db.AuditLogs.Add(new AuditLog
{
User = ctx.User.Identity?.Name,
Path = ctx.Request.Path,
Status = ctx.Response.StatusCode,
At = DateTime.UtcNow
});
await _db.SaveChangesAsync();
}
}
}
builder.Services.AddScoped<AuditMiddleware>();
app.UseMiddleware<AuditMiddleware>();オプションを使用するミドルウェア
コンストラクターのオプションオブジェクトを通じて、ミドルウェアに設定を渡します。DIコンテナーにオプションを登録するか、ミドルウェアの登録時にインラインで渡してください。
public class ThrottleOptions
{
public int MaxRequestsPerSecond { get; set; } = 100;
public string[] ExcludedPaths { get; set; } = Array.Empty<string>();
}
public class ThrottleMiddleware
{
private readonly RequestDelegate _next;
private readonly ThrottleOptions _options;
public ThrottleMiddleware(RequestDelegate next, ThrottleOptions options)
{
_next = next;
_options = options;
}
// ...
}
// Registration with options:
app.UseMiddleware<ThrottleMiddleware>(new ThrottleOptions
{
MaxRequestsPerSecond = 50
});例外処理ミドルウェア
カスタム例外ハンドラーミドルウェアは、処理されていないすべての例外を捕捉し、構造化されたエラーレスポンスを返します。
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> l)
{
_next = next;
_logger = l;
}
public async Task InvokeAsync(HttpContext ctx)
{
try { await _next(ctx); }
catch (NotFoundException ex)
{
ctx.Response.StatusCode = 404;
await ctx.Response.WriteAsJsonAsync(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception");
ctx.Response.StatusCode = 500;
await ctx.Response.WriteAsJsonAsync(new { error = "Internal server error" });
}
}
}リクエストログミドルウェア
構造化されたリクエスト/レスポンスロギングミドルウェアは、すべてのリクエストについて、メソッド、パス、ステータスコード、経過時間を記録します。
public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestLoggingMiddleware> _log;
public RequestLoggingMiddleware(RequestDelegate n, ILogger<RequestLoggingMiddleware> l)
{ _next = n; _log = l; }
public async Task InvokeAsync(HttpContext ctx)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
await _next(ctx);
sw.Stop();
_log.LogInformation(
"{Method} {Path} -> {Status} in {Ms}ms",
ctx.Request.Method,
ctx.Request.Path,
ctx.Response.StatusCode,
sw.ElapsedMilliseconds);
}
}UseWhen による条件付きミドルウェア
UseWhenは、述語に基づいて条件付きでパイプラインを分岐しますが、恒久的に分割するわけではありません。分岐は処理後にメインパイプラインへ再合流します。
// Apply authentication middleware only to /api/* routes
app.UseWhen(
ctx => ctx.Request.Path.StartsWithSegments("/api"),
apiApp =>
{
apiApp.UseAuthentication();
apiApp.UseAuthorization();
});
// Public routes (/public/*) bypass authentication
app.MapGet("/public/health", () => "OK");
app.MapGet("/api/data", [Authorize] async (DataService s) => await s.GetAsync());レスポンスキャッシュミドルウェア
組み込みのレスポンスキャッシュミドルウェアは、完全なレスポンスをメモリに保存します。ヘッダーまたは属性を使用して、エンドポイントごとにキャッシュポリシーを設定できます。
builder.Services.AddResponseCaching();
app.UseResponseCaching();
app.MapGet("/products", async (AppDbContext db) =>
{
var products = await db.Products.AsNoTracking().ToListAsync();
return Results.Ok(products);
})
.CacheOutput(p => p.Expire(TimeSpan.FromMinutes(2)).Tag("products"));実践例: メンテナンスモードミドルウェア
設定からフラグを読み取り(IOptionsMonitorによるホットリロードにも対応)、メンテナンス中にヘルスチェック以外のリクエストを拒否するメンテナンスモードミドルウェアです。
public class MaintenanceMiddleware
{
private readonly RequestDelegate _next;
private readonly IOptionsMonitor<MaintenanceOptions> _options;
public MaintenanceMiddleware(RequestDelegate next,
IOptionsMonitor<MaintenanceOptions> opts)
{ _next = next; _options = opts; }
public async Task InvokeAsync(HttpContext ctx)
{
if (_options.CurrentValue.IsEnabled
&& !ctx.Request.Path.StartsWithSegments("/health"))
{
ctx.Response.StatusCode = 503;
ctx.Response.Headers.Append("Retry-After", "300");
await ctx.Response.WriteAsJsonAsync(
new { message = "Service under maintenance" });
return;
}
await _next(ctx);
}
}確認問題
DbContextのようなScopedサービスが必要な場合、規約ベースの方法ではなくIMiddlewareを実装すべきなのはなぜですか。
まとめ: カスタムミドルウェアの作成
重要なポイント:
- インライン(Useデリゲート)は手早い一行処理向け、クラスベースは再利用可能でテストしやすい
- 規約ベースのクラス: コンストラクターがRequestDelegateを受け取り、InvokeAsync(HttpContext)を実装します
- IMiddleware: リクエストごとにDIで解決されるため、Scoped依存関係に必要です
- UseWhen: メインパイプラインへ再合流する条件付き分岐です
- すべてのエラーを捕捉できるよう、例外処理ミドルウェアを最初(最外側)に記述します
- UseXxxの拡張メソッドを作成すると、登録処理を簡潔で見つけやすくできます
よくある質問
「カスタムミドルウェアの作成」レッスンは無料ですか?
はい。「カスタムミドルウェアの作成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「カスタムミドルウェアの作成」で何を学びますか?
Use/Run/Mapでインラインミドルウェアを作成し、InvokeAsyncを使うクラスベースのミドルウェアで再利用可能なコンポーネントを構築します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「カスタムミドルウェアの作成」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。