ASP.NET Core のミドルウェア

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

作成者: Rick AndersonSteve Smith

ミドルウェアとは、要求と応答を処理するために、アプリのパイプラインに組み込まれたソフトウェアです。 各コンポーネントで次の処理を実行します。

  • 要求をパイプライン内の次のコンポーネントに渡すかどうかを選択します。
  • パイプラインの次のコンポーネントの前または後に作業を実行できます。

要求デリゲートは、要求パイプラインの構築に使用されます。 要求デリゲートは、各 HTTP 要求を処理します。

要求デリゲートは、RunMapUse の各拡張メソッドを使って構成されます。 個々の要求デリゲートは、匿名メソッドとしてインラインで指定するか (インライン ミドルウェアと呼ばれます)、または再利用可能なクラスで定義することができます。 これらの再利用可能なクラスとインラインの匿名メソッドは、"ミドルウェア" です。"ミドルウェア コンポーネント" とも呼ばれます。 要求パイプライン内の各ミドルウェア コンポーネントは、パイプラインの次のコンポーネントを呼び出すか、パイプラインをショートさせます。 ショートサーキットしたミドルウェアは "ターミナル ミドルウェア" と呼ばれます。これによってさらなるミドルウェアによる要求の処理が回避されるためです。

HTTP ハンドラーとモジュールを ASP.NET Core ミドルウェアに移行する」では、ASP.NET Core と ASP.NET 4.x の要求パイプラインの違いについて説明し、その他のミドルウェア サンプルを提供しています。

ミドルウェアのコード分析

ASP.NET Core には、アプリケーション コードの品質を検査するコンパイラ プラットフォーム アナライザーが多数含まれています。 詳細については、「ASP.NET Core アプリのコード分析」を参照してください。

WebApplication を使用してミドルウェア パイプラインを作成する

ASP.NET Core 要求パイプラインは、順番に呼び出される一連の要求デリゲートで構成されています。 次の図は、その概念を示しています。 実行のスレッドは黒い矢印をたどります。

要求の到着、3 つのミドルウェアによる処理、アプリからの応答の送信を示す要求処理パターン。3 番目のミドルウェアが要求を処理した後、要求は前の 2 つのミドルウェアを逆の順序で通過して、それぞれの next() ステートメントの後の追加処理が行われた後、クライアントへの応答としてアプリを終了します。

各デリゲートは、次のデリゲートの前と後に操作を実行できます。 例外処理デリゲートは、パイプラインの後のステージで発生する例外をキャッチできるように、パイプラインの早い段階で呼び出される必要があります。

考えられる最も簡単な ASP.NET Core アプリは、1 つの要求デリゲートを設定してすべての要求を処理するものです。 この場合、実際の要求パイプラインは含まれません。 代わりに、すべての HTTP 要求に対応して単一の匿名関数が呼び出されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

複数の要求デリゲートを Use と一緒にチェーンします。 next パラメーターは、パイプラインの次のデリゲートを表します next パラメーターを "呼び出さない" ことで、パイプラインをショートさせることができます。 アクションは通常、次の例で示すように、next デリゲートの前後の両方で実行できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

要求パイプラインのショートサーキット

デリゲートが次のデリゲートに要求を渡さない場合、これは "要求パイプラインのショートサーキット" と呼ばれます。 不要な処理を回避するために、ショートさせることが望ましい場合がよくあります。 たとえば、静的ファイル ミドルウェアは、静的ファイルの要求を処理して残りのパイプラインをショートサーキットすることにより、"ターミナル ミドルウェア" として動作させることができます。 後続の処理を終了させるミドルウェアの前にパイプラインに追加されたミドルウェアでは、その next.Invoke ステートメントの後も引き続きコードが処理されます。 ただし、既に送信された応答に対する書き込みの試行については、次の警告を参照してください。

警告

応答がクライアントに送信されている最中、または送信された後に next.Invoke を呼び出さないでください。 HttpResponse の開始後の変更は例外を引き起こします。 たとえば、応答の開始後にヘッダーと状態コードを設定すると例外がスローされますnext を呼び出した後で応答本文に書き込むと、次のようになります。

  • 記載された Content-Length を超える書き込みなど、プロトコル違反を引き起こす可能性があります。
  • HTML フッターの CSS ファイルへの書き込みなど、本文の形式が破損される可能性があります。

HasStarted は、ヘッダーが送信されたかどうかや本文が書き込まれたかどうかを示すために役立つヒントです。

詳しくは、「ルーティング後のミドルウェアのショートサーキット」を参照してください。

Run デリゲート

Run デリゲートでは、next パラメーターは受け取られません。 最初の Run デリゲートが常に終点となり、パイプラインが終了されます。 Run は規則です。 一部のミドルウェア コンポーネントでは、パイプラインの最後に実行される Run[Middleware] メソッドが公開されることがあります。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。

前の例では、Run デリゲートによって応答に "Hello from 2nd delegate." が書き込まれ、その後、パイプラインが終了となります。 別の Use または Run デリゲートが Run デリゲートの後に追加される場合、そのデリゲートは呼び出されません。

アプリを優先します。コンテキストを次に渡す必要があるオーバーロードを使用する

非割り当て アプリ。拡張メソッドを使用する :

  • コンテキストをに渡す必要があり next ます。
  • 他のオーバーロードを使用するときに必要な2つの内部要求ごとの割り当てを保存します。

詳細については、次を参照してください。この GitHub の問題します。

ミドルウェアの順序

次の図は、ASP.NET Core MVC と Razor Pages アプリの完全な要求処理パイプラインを示しています。 一般的なアプリでどのように既存のミドルウェアが順序付けされ、どこにカスタム ミドルウェアが追加されるかを確認できます。 シナリオでの必要性に応じて、既存のミドルウェアの順序を変更したり、新しいカスタム ミドルウェアを挿入したりする方法については、完全に制御できます。

ASP.NET Core のミドルウェア パイプライン

上記の図のエンドポイント ミドルウェアでは、対応するアプリの種類 (MVC または Razor Pages) のフィルター パイプラインが実行されます。

前の図でルーティング ミドルウェアの次には、静的ファイルが示されています。 これは、app.UseRouting を明示的に呼び出し、プロジェクト テンプレートが実装する順序です。 app.UseRouting を呼び出さない場合、既定でルーティング ミドルウェアがパイプラインの先頭で実行されます。 詳細については、ルーティングに関するページを参照してください。

ASP.NET Core のフィルター パイプライン

Program.cs ファイルでミドルウェア コンポーネントを追加する順序は、要求でミドルウェア コンポーネントが呼び出される順序および応答での逆の順序を定義します。 この順序は、セキュリティ、パフォーマンス、および機能にとって重要です。

次の Program.cs で強調表示されているコードは、一般的な推奨される順序でセキュリティ関連のミドルウェア コンポーネントを追加します。

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

上のコードでは以下の操作が行われます。

  • 個人のユーザー アカウントを使用して新しい Web アプリを作成するときに追加されないミドルウェアは、コメント アウトされています。
  • すべてのミドルウェアが厳密にこの順序で配置されるわけではありませんが、多くの場合はそのように配置されます。 例:
    • UseCorsUseAuthentication、および UseAuthorization は、示されている順序で配置する必要があります。
    • 現時点では、UseCorsUseResponseCaching の前にある必要があります。 この要件については、GitHub issue dotnet/aspnetcore #23218 に関するページで説明されています。
    • UseRequestLocalization は、要求のカルチャをチェックする可能性があるすべてのミドルウェア (たとえば、app.UseStaticFiles()) の前に配置する必要があります。
    • レート制限エンドポイント固有の API を使う場合は、UseRouting の後に UseRateLimiter を呼び出す必要があります。 たとえば、[EnableRateLimiting] 属性を使う場合は、UseRouting の後に UseRateLimiter を呼び出す必要があります。 グローバル リミッターのみを呼び出す場合は、UseRouting の前に UseRateLimiter を呼び出すことができます。

シナリオによっては、ミドルウェアの順序が異なる場合があります。 たとえば、シナリオによって異なるキャッシュと圧縮の順序には、有効な順序は複数あります。 次に例を示します。

app.UseResponseCaching();
app.UseResponseCompression();

上記のコードでは、圧縮された応答をキャッシュして CPU の使用を減らすことができますが、Gzip や Brotli などの異なる圧縮アルゴリズムを使用してリソースの表現が複数キャッシュされてしまう場合があります。

次の順序では、圧縮された静的ファイルをキャッシュできるように、静的ファイルが結合されます。

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

次の Program.cs コードにより、共通アプリ シナリオのためのミドルウェア コンポーネントが追加されます。

  1. 例外/エラー処理
    • 開発環境でアプリを実行する場合:
      • 開発者例外ページ ミドルウェア (UseDeveloperExceptionPage) によりアプリの実行時エラーが報告されます。
      • データベース エラー ページ ミドルウェア (UseDatabaseErrorPage) によりデータベースの実行時エラーが報告されます。
    • 運用環境でアプリを実行する場合:
      • 例外ハンドラー ミドルウェア (UseExceptionHandler) によって、後続のミドルウェアによってスローされた例外がキャッチされます。
      • HTTP Strict Transport Security プロトコル (HSTS) ミドルウェア (UseHsts) により Strict-Transport-Security ヘッダーが追加されます。
  2. HTTPS リダイレクト ミドルウェア (UseHttpsRedirection) により、HTTP 要求が HTTPS にリダイレクトされます。
  3. 静的ファイル ミドルウェア (UseStaticFiles) によって静的ファイルが返され、さらなる要求の処理がスキップされます。
  4. Cookie ポリシー ミドルウェア (UseCookiePolicy) により、アプリを EU の一般データ保護規制 (GDPR) に準拠させます。
  5. ルーティング ミドルウェア (UseRouting) により、要求がルーティングされます。
  6. 認証ミドルウェア (UseAuthentication) により、ユーザーがセキュリティで保護されたリソースにアクセスする前に、ユーザーの認証が試行されます。
  7. 承認ミドルウェア (UseAuthorization) により、ユーザーがセキュリティで保護されたリソースにアクセスすることが承認されます。
  8. セッション ミドルウェア (UseSession) により、セッション状態が確立され保持されます。 アプリでセッション状態が使用されている場合は、Cookie ポリシー ミドルウェアの後、MVC ミドルウェアの前に、セッション ミドルウェアを呼び出します。
  9. エンドポイント ルーティング ミドルウェア (MapRazorPages を含む UseEndpoints) により、Razor Pages エンドポイントが要求パイプラインに追加されます。
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

前のコード例では、各ミドルウェアの拡張メソッドが Microsoft.AspNetCore.Builder 名前空間を通じて WebApplicationBuilder で公開されています。

パイプラインに追加された最初のミドルウェア コンポーネントは UseExceptionHandler です。 そのため、例外ハンドラー ミドルウェアでは、以降の呼び出しで発生するすべての例外がキャッチされます。

静的ファイル ミドルウェアはパイプラインの早い段階で呼び出されるので、要求を処理し、残りのコンポーネントを通過せずにショートさせることができます。 静的ファイル ミドルウェアでは、承認チェックは提供されませんwwwroot の下にあるものも含め、この静的ファイル ミドルウェアによって提供されるすべてのファイルは、一般に公開されます。 静的ファイルをセキュリティで保護する方法については、「ASP.NET Core の静的ファイル」を参照してください。

要求が静的ファイル ミドルウェアによって処理されない場合、要求は認証を実行する認証ミドルウェア (UseAuthentication) に渡されます。 認証は、認証されない要求をショートさせません。 認証ミドルウェアは要求を認証しますが、承認 (および却下) は、MVC が特定の Razor ページまたは MVC コントローラーとアクションを選んだ後でのみ行われます。

次の例は、静的ファイルの要求が応答圧縮ミドルウェアの前に静的ファイル ミドルウェアによって処理される、ミドルウェアの順序を示します。 静的ファイルは、このミドルウェアの順序では圧縮されません。 Razor Pages の応答は圧縮できます。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

シングル ページ アプリケーションの詳細については、「ASP.NET Core のシングル ページ アプリ (SPA) の概要」を参照してください。

UseCors と UseStaticFiles の順序

UseCorsUseStaticFiles を呼び出す順序はアプリによって異なります。 詳細については、「UseCors と UseStaticFiles の順序」を参照してください

Forwarded Headers Middleware の順序

Forwarded Headers Middleware は、他のミドルウェアの前に実行する必要があります。 この順序により、転送されるヘッダー情報に依存するミドルウェアが処理にヘッダー値を使用できます。 診断およびエラー処理ミドルウェアの後に Forwarded Headers Middleware を実行する方法については、「Forwarded Headers Middleware の順序」を参照してください。

ミドルウェア パイプラインを分岐する

Map 拡張メソッドは、パイプラインを分岐する規則として使われます。 Map は、指定された要求パスの一致に基づいて、要求パイプラインを分岐します。 要求パスが指定されたパスで開始する場合、分岐が実行されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 応答
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Map を使用すると、一致したパス セグメントが HttpRequest.Path から削除され、要求ごとに HttpRequest.PathBase に追加されます。

Map は入れ子をサポートします。次にその例を示します。

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map では、次のように一度に複数のセグメントを照合できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen は、指定された述語の結果に基づいて、要求パイプラインを分岐します。 Func<HttpContext, bool> という型の任意の述語を使って、要求をパイプラインの新しい分岐にマップできます。 次の例では、クエリ文字列変数 branch の存在を検出するために術後が使用されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

また UseWhen では、指定された述語の結果に基づいて、要求パイプラインが分岐されます。 MapWhen とは異なり、この分岐は、ショートしたり、ターミナル ミドルウェアが含まれたりしなければ、メイン パイプラインに再参加します。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

上記の例では、すべての要求に対して Hello from non-Map delegate. の応答が書き込まれます。 要求にクエリ文字列変数 branch が含まれる場合、メイン パイプラインの再参加前にその値がログに記録されます。

組み込みミドルウェア

ASP.NET Core には、次のミドルウェア コンポーネントが付属しています。 "順番" 列には、要求を処理するパイプライン内のミドルウェアの配置と、ミドルウェアが要求処理を終了する条件に関するメモが記載されています。 ミドルウェアが要求を処理するパイプラインをショートサーキットし、下流のさらなるミドルウェアによる要求の処理を回避する場合、これは "ターミナル ミドルウェア" と呼ばれます。 ショートサーキットについて詳しくは、「WebApplication を使用してミドルウェア パイプラインを作成する」セクションを参照してください。

ミドルウェア 説明 順番
認証 認証のサポートを提供します。 HttpContext.User が必要な場所の前。 OAuth コールバックの終端。
承認 承認のサポートを提供します。 認証ミドルウェアの直後。
Cookie ポリシー 個人情報の保存に関してユーザーからの同意を追跡し、secureSameSite など、cookie フィールドの最小要件を適用します。 cookie を発行するミドルウェアの前。 次に例を示します。 認証、セッション、MVC (TempData)
CORS クロス オリジン リソース共有を構成します。 CORS を使うコンポーネントの前。 現時点では、こちらのバグのため、UseResponseCaching の前に UseCors を追加する必要があります。
DeveloperExceptionPage 開発環境での使用のみを意図としたエラー情報を含むページが生成されます。 エラーを生成するコンポーネントの前。 プロジェクト テンプレートは、環境が開発の場合、パイプラインの最初のミドルウェアとしてこのミドルウェアを自動登録します。
診断 開発者の例外ページ、例外処理、状態コード ページ、および新しいアプリの既定の Web ページを提供する複数の独立したミドルウェア。 エラーを生成するコンポーネントの前。 例外または新しいアプリ用の既定の Web ページの提供の終端。
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 更新されたフィールドを使用するコンポーネントの前。 例: スキーム、ホスト、クライアント IP、メソッド。
正常性チェック ASP.NET Core アプリとその依存関係の正常性を、データベースの可用性などで確認します。 要求が正常性チェックのエンドポイントと一致した場合の終端。
ヘッダーの伝達 HTTP ヘッダーを受信要求から送信 HTTP クライアント要求に伝達します。
HTTP ログ HTTP 要求と応答をログに記録します。 ミドルウェア パイプラインの先頭。
HTTP メソッドのオーバーライド メソッドをオーバーライドする受信 POST 要求を許可します。 更新されたメソッドを使うコンポーネントの前。
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 URL を使うコンポーネントの前。
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 応答が送信される前と要求を変更するコンポーネントの後。 次に例を示します。 転送されるヘッダー、URL リライト。
MVC MVC または Razor Pages で要求を処理します。 要求がルートと一致した場合の終端。
OWIN OWIN ベースのアプリ、サーバー、およびミドルウェアと相互運用します。 OWIN ミドルウェアが要求を完全に処理した場合の終端。
出力キャッシュ 構成に基づく応答のキャッシュのサポートを提供します。 キャッシュが必要なコンポーネントの前。 UseOutputCaching の前に UseRouting を追加する必要があります。 UseOutputCaching の前に UseCORS を追加する必要があります。
応答キャッシュ 応答のキャッシュのサポートを提供します。 これには、クライアントの参加が機能する必要があります。 サーバーを完全に制御するには、出力キャッシュを使用します。 キャッシュが必要なコンポーネントの前。 UseResponseCaching の前に UseCORS を追加する必要があります。 通常、ブラウザーによってキャッシュを防ぐ要求ヘッダーが設定されるため、Razor Pages などの UI アプリには有益ではありません。 UI アプリには出力キャッシュが有益です。
圧縮解除を要求する 圧縮解除の要求をサポートします。 要求本文を読み取るコンポーネントの前。
応答圧縮 応答の圧縮のサポートを提供します。 圧縮が必要なコンポーネントの前。
要求のローカライズ ローカライズのサポートを提供します。 ローカリゼーションが重要なコンポーネントの前。 RouteDataRequestCultureProvider を使用する場合は、ルーティング ミドルウェアの後に配置する必要があり ます。
要求のタイムアウト グローバルおよびエンドポイントごとに、要求のタイムアウトを構成するサポートを提供します。 UseRequestTimeoutsUseExceptionHandlerUseDeveloperExceptionPageUseRouting の後に来る必要があります。
エンドポイント ルーティング 要求のルートを定義および制約します。 一致するルートの終端。
SPA シングルページ アプリケーション (SPA) の既定のページを返し、ミドルウェア チェーン内のこの時点以降のすべての要求を処理します。 チェーンの終わりで、静的ファイルや MVC アクションなどにサービスを提供する他のミドルウェアが優先されるようにするためです。
セッション ユーザー セッションの管理のサポートを提供します。 セッションが必要なコンポーネントの前。
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 要求がファイルと一致した場合の終端。
URL 書き換え URL の書き換えと要求のリダイレクトのサポートを提供します。 URL を使うコンポーネントの前。
W3CLogging W3C 拡張ログ ファイル形式でサーバー アクセス ログを生成します。 ミドルウェア パイプラインの先頭。
WebSocket WebSocket プロトコルを有効にします。 WebSocket 要求を受け入れる必要があるコンポーネントの前。

その他の技術情報

作成者: Rick AndersonSteve Smith

ミドルウェアとは、要求と応答を処理するために、アプリのパイプラインに組み込まれたソフトウェアです。 各コンポーネントで次の処理を実行します。

  • 要求をパイプライン内の次のコンポーネントに渡すかどうかを選択します。
  • パイプラインの次のコンポーネントの前または後に作業を実行できます。

要求デリゲートは、要求パイプラインの構築に使用されます。 要求デリゲートは、各 HTTP 要求を処理します。

要求デリゲートは、RunMapUse の各拡張メソッドを使って構成されます。 個々の要求デリゲートは、匿名メソッドとしてインラインで指定するか (インライン ミドルウェアと呼ばれます)、または再利用可能なクラスで定義することができます。 これらの再利用可能なクラスとインラインの匿名メソッドは、"ミドルウェア" です。"ミドルウェア コンポーネント" とも呼ばれます。 要求パイプライン内の各ミドルウェア コンポーネントは、パイプラインの次のコンポーネントを呼び出すか、パイプラインをショートさせます。 ショートサーキットしたミドルウェアは "ターミナル ミドルウェア" と呼ばれます。これによってさらなるミドルウェアによる要求の処理が回避されるためです。

HTTP ハンドラーとモジュールを ASP.NET Core ミドルウェアに移行する」では、ASP.NET Core と ASP.NET 4.x の要求パイプラインの違いについて説明し、その他のミドルウェア サンプルを提供しています。

ミドルウェアのコード分析

ASP.NET Core には、アプリケーション コードの品質を検査するコンパイラ プラットフォーム アナライザーが多数含まれています。 詳細については、「ASP.NET Core アプリのコード分析」を参照してください。

WebApplication を使用してミドルウェア パイプラインを作成する

ASP.NET Core 要求パイプラインは、順番に呼び出される一連の要求デリゲートで構成されています。 次の図は、その概念を示しています。 実行のスレッドは黒い矢印をたどります。

要求の到着、3 つのミドルウェアによる処理、アプリからの応答の送信を示す要求処理パターン。3 番目のミドルウェアが要求を処理した後、要求は前の 2 つのミドルウェアを逆の順序で通過して、それぞれの next() ステートメントの後の追加処理が行われた後、クライアントへの応答としてアプリを終了します。

各デリゲートは、次のデリゲートの前と後に操作を実行できます。 例外処理デリゲートは、パイプラインの後のステージで発生する例外をキャッチできるように、パイプラインの早い段階で呼び出される必要があります。

考えられる最も簡単な ASP.NET Core アプリは、1 つの要求デリゲートを設定してすべての要求を処理するものです。 この場合、実際の要求パイプラインは含まれません。 代わりに、すべての HTTP 要求に対応して単一の匿名関数が呼び出されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

複数の要求デリゲートを Use と一緒にチェーンします。 next パラメーターは、パイプラインの次のデリゲートを表します next パラメーターを "呼び出さない" ことで、パイプラインをショートさせることができます。 アクションは通常、次の例で示すように、next デリゲートの前後の両方で実行できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

デリゲートが次のデリゲートに要求を渡さない場合、これは "要求パイプラインのショートサーキット" と呼ばれます。 不要な処理を回避するために、ショートさせることが望ましい場合がよくあります。 たとえば、静的ファイル ミドルウェアは、静的ファイルの要求を処理して残りのパイプラインをショートサーキットすることにより、"ターミナル ミドルウェア" として動作させることができます。 後続の処理を終了させるミドルウェアの前にパイプラインに追加されたミドルウェアでは、その next.Invoke ステートメントの後も引き続きコードが処理されます。 ただし、既に送信された応答に対する書き込みの試行については、次の警告を参照してください。

警告

応答がクライアントに送信された後に、next.Invoke を呼び出さないでください。 応答が開始した後で HttpResponse を変更すると、例外がスローされます。 たとえば、ヘッダーや状態コードを設定すると、例外がスローされますnext を呼び出した後で応答本文に書き込むと、次のようになります。

  • プロトコル違反が発生する可能性があります。 たとえば、示されている Content-Length より多くを書き込んだ場合。
  • 本文の形式が破損する可能性があります。 たとえば、CSS ファイルに HTML フッターを書き込んだ場合。

HasStarted は、ヘッダーが送信されたかどうかや本文が書き込まれたかどうかを示すために役立つヒントです。

Run デリゲートでは、next パラメーターは受け取られません。 最初の Run デリゲートが常に終点となり、パイプラインが終了されます。 Run は規則です。 一部のミドルウェア コンポーネントでは、パイプラインの最後に実行される Run[Middleware] メソッドが公開されることがあります。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。

前の例では、Run デリゲートによって応答に "Hello from 2nd delegate." が書き込まれ、その後、パイプラインが終了となります。 別の Use または Run デリゲートが Run デリゲートの後に追加される場合、そのデリゲートは呼び出されません。

アプリを優先します。コンテキストを次に渡す必要があるオーバーロードを使用する

非割り当て アプリ。拡張メソッドを使用する :

  • コンテキストをに渡す必要があり next ます。
  • 他のオーバーロードを使用するときに必要な2つの内部要求ごとの割り当てを保存します。

詳細については、次を参照してください。この GitHub の問題します。

ミドルウェアの順序

次の図は、ASP.NET Core MVC と Razor Pages アプリの完全な要求処理パイプラインを示しています。 一般的なアプリでどのように既存のミドルウェアが順序付けされ、どこにカスタム ミドルウェアが追加されるかを確認できます。 シナリオでの必要性に応じて、既存のミドルウェアの順序を変更したり、新しいカスタム ミドルウェアを挿入したりする方法については、完全に制御できます。

ASP.NET Core のミドルウェア パイプライン

上記の図のエンドポイント ミドルウェアでは、対応するアプリの種類 (MVC または Razor Pages) のフィルター パイプラインが実行されます。

前の図でルーティング ミドルウェアの次には、静的ファイルが示されています。 これは、app.UseRouting を明示的に呼び出し、プロジェクト テンプレートが実装する順序です。 app.UseRouting を呼び出さない場合、既定でルーティング ミドルウェアがパイプラインの先頭で実行されます。 詳細については、ルーティングに関するページを参照してください。

ASP.NET Core のフィルター パイプライン

Program.cs ファイルでミドルウェア コンポーネントを追加する順序は、要求でミドルウェア コンポーネントが呼び出される順序および応答での逆の順序を定義します。 この順序は、セキュリティ、パフォーマンス、および機能にとって重要です。

次の Program.cs で強調表示されているコードは、一般的な推奨される順序でセキュリティ関連のミドルウェア コンポーネントを追加します。

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

上のコードでは以下の操作が行われます。

  • 個人のユーザー アカウントを使用して新しい Web アプリを作成するときに追加されないミドルウェアは、コメント アウトされています。
  • すべてのミドルウェアが厳密にこの順序で配置されるわけではありませんが、多くの場合はそのように配置されます。 例:
    • UseCorsUseAuthentication、および UseAuthorization は、示されている順序で配置する必要があります。
    • 現時点では、UseCorsUseResponseCaching の前にある必要があります。 この要件については、GitHub issue dotnet/aspnetcore #23218 に関するページで説明されています。
    • UseRequestLocalization は、要求のカルチャをチェックする可能性があるすべてのミドルウェア (たとえば、app.UseStaticFiles()) の前に配置する必要があります。
    • レート制限エンドポイント固有の API を使う場合は、UseRouting の後に UseRateLimiter を呼び出す必要があります。 たとえば、[EnableRateLimiting] 属性を使う場合は、UseRouting の後に UseRateLimiter を呼び出す必要があります。 グローバル リミッターのみを呼び出す場合は、UseRouting の前に UseRateLimiter を呼び出すことができます。

シナリオによっては、ミドルウェアの順序が異なる場合があります。 たとえば、シナリオによって異なるキャッシュと圧縮の順序には、有効な順序は複数あります。 次に例を示します。

app.UseResponseCaching();
app.UseResponseCompression();

上記のコードでは、圧縮された応答をキャッシュして CPU の使用を減らすことができますが、Gzip や Brotli などの異なる圧縮アルゴリズムを使用してリソースの表現が複数キャッシュされてしまう場合があります。

次の順序では、圧縮された静的ファイルをキャッシュできるように、静的ファイルが結合されます。

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

次の Program.cs コードにより、共通アプリ シナリオのためのミドルウェア コンポーネントが追加されます。

  1. 例外/エラー処理
    • 開発環境でアプリを実行する場合:
      • 開発者例外ページ ミドルウェア (UseDeveloperExceptionPage) によりアプリの実行時エラーが報告されます。
      • データベース エラー ページ ミドルウェア (UseDatabaseErrorPage) によりデータベースの実行時エラーが報告されます。
    • 運用環境でアプリを実行する場合:
      • 例外ハンドラー ミドルウェア (UseExceptionHandler) によって、後続のミドルウェアによってスローされた例外がキャッチされます。
      • HTTP Strict Transport Security プロトコル (HSTS) ミドルウェア (UseHsts) により Strict-Transport-Security ヘッダーが追加されます。
  2. HTTPS リダイレクト ミドルウェア (UseHttpsRedirection) により、HTTP 要求が HTTPS にリダイレクトされます。
  3. 静的ファイル ミドルウェア (UseStaticFiles) によって静的ファイルが返され、さらなる要求の処理がスキップされます。
  4. Cookie ポリシー ミドルウェア (UseCookiePolicy) により、アプリを EU の一般データ保護規制 (GDPR) に準拠させます。
  5. ルーティング ミドルウェア (UseRouting) により、要求がルーティングされます。
  6. 認証ミドルウェア (UseAuthentication) により、ユーザーがセキュリティで保護されたリソースにアクセスする前に、ユーザーの認証が試行されます。
  7. 承認ミドルウェア (UseAuthorization) により、ユーザーがセキュリティで保護されたリソースにアクセスすることが承認されます。
  8. セッション ミドルウェア (UseSession) により、セッション状態が確立され保持されます。 アプリでセッション状態が使用されている場合は、Cookie ポリシー ミドルウェアの後、MVC ミドルウェアの前に、セッション ミドルウェアを呼び出します。
  9. エンドポイント ルーティング ミドルウェア (MapRazorPages を含む UseEndpoints) により、Razor Pages エンドポイントが要求パイプラインに追加されます。
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

前のコード例では、各ミドルウェアの拡張メソッドが Microsoft.AspNetCore.Builder 名前空間を通じて WebApplicationBuilder で公開されています。

パイプラインに追加された最初のミドルウェア コンポーネントは UseExceptionHandler です。 そのため、例外ハンドラー ミドルウェアでは、以降の呼び出しで発生するすべての例外がキャッチされます。

静的ファイル ミドルウェアはパイプラインの早い段階で呼び出されるので、要求を処理し、残りのコンポーネントを通過せずにショートさせることができます。 静的ファイル ミドルウェアでは、承認チェックは提供されませんwwwroot の下にあるものも含め、この静的ファイル ミドルウェアによって提供されるすべてのファイルは、一般に公開されます。 静的ファイルをセキュリティで保護する方法については、「ASP.NET Core の静的ファイル」を参照してください。

要求が静的ファイル ミドルウェアによって処理されない場合、要求は認証を実行する認証ミドルウェア (UseAuthentication) に渡されます。 認証は、認証されない要求をショートさせません。 認証ミドルウェアは要求を認証しますが、承認 (および却下) は、MVC が特定の Razor ページまたは MVC コントローラーとアクションを選んだ後でのみ行われます。

次の例は、静的ファイルの要求が応答圧縮ミドルウェアの前に静的ファイル ミドルウェアによって処理される、ミドルウェアの順序を示します。 静的ファイルは、このミドルウェアの順序では圧縮されません。 Razor Pages の応答は圧縮できます。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

シングル ページ アプリケーションの詳細については、ReactAngular プロジェクト テンプレートのガイドを参照してください。

UseCors と UseStaticFiles の順序

UseCorsUseStaticFiles を呼び出す順序はアプリによって異なります。 詳細については、「UseCors と UseStaticFiles の順序」を参照してください

Forwarded Headers Middleware の順序

Forwarded Headers Middleware は、他のミドルウェアの前に実行する必要があります。 この順序により、転送されるヘッダー情報に依存するミドルウェアが処理にヘッダー値を使用できます。 診断およびエラー処理ミドルウェアの後に Forwarded Headers Middleware を実行する方法については、「Forwarded Headers Middleware の順序」を参照してください。

ミドルウェア パイプラインを分岐する

Map 拡張メソッドは、パイプラインを分岐する規則として使われます。 Map は、指定された要求パスの一致に基づいて、要求パイプラインを分岐します。 要求パスが指定されたパスで開始する場合、分岐が実行されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 応答
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Map を使用すると、一致したパス セグメントが HttpRequest.Path から削除され、要求ごとに HttpRequest.PathBase に追加されます。

Map は入れ子をサポートします。次にその例を示します。

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map では、次のように一度に複数のセグメントを照合できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen は、指定された述語の結果に基づいて、要求パイプラインを分岐します。 Func<HttpContext, bool> という型の任意の述語を使って、要求をパイプラインの新しい分岐にマップできます。 次の例では、クエリ文字列変数 branch の存在を検出するために術後が使用されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

また UseWhen では、指定された述語の結果に基づいて、要求パイプラインが分岐されます。 MapWhen とは異なり、この分岐は、ショートしたり、ターミナル ミドルウェアが含まれたりしなければ、メイン パイプラインに再参加します。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

上記の例では、すべての要求に対して Hello from non-Map delegate. の応答が書き込まれます。 要求にクエリ文字列変数 branch が含まれる場合、メイン パイプラインの再参加前にその値がログに記録されます。

組み込みミドルウェア

ASP.NET Core には、次のミドルウェア コンポーネントが付属しています。 "順番" 列には、要求を処理するパイプライン内のミドルウェアの配置と、ミドルウェアが要求処理を終了する条件に関するメモが記載されています。 ミドルウェアが要求を処理するパイプラインをショートサーキットし、下流のさらなるミドルウェアによる要求の処理を回避する場合、これは "ターミナル ミドルウェア" と呼ばれます。 ショートサーキットについて詳しくは、「WebApplication を使用してミドルウェア パイプラインを作成する」セクションを参照してください。

ミドルウェア 説明 順番
認証 認証のサポートを提供します。 HttpContext.User が必要な場所の前。 OAuth コールバックの終端。
承認 承認のサポートを提供します。 認証ミドルウェアの直後。
Cookie ポリシー 個人情報の保存に関してユーザーからの同意を追跡し、secureSameSite など、cookie フィールドの最小要件を適用します。 cookie を発行するミドルウェアの前。 次に例を示します。 認証、セッション、MVC (TempData)
CORS クロス オリジン リソース共有を構成します。 CORS を使うコンポーネントの前。 現時点では、こちらのバグのため、UseResponseCaching の前に UseCors を追加する必要があります。
DeveloperExceptionPage 開発環境での使用のみを意図としたエラー情報を含むページが生成されます。 エラーを生成するコンポーネントの前。 プロジェクト テンプレートは、環境が開発の場合、パイプラインの最初のミドルウェアとしてこのミドルウェアを自動登録します。
診断 開発者の例外ページ、例外処理、状態コード ページ、および新しいアプリの既定の Web ページを提供する複数の独立したミドルウェア。 エラーを生成するコンポーネントの前。 例外または新しいアプリ用の既定の Web ページの提供の終端。
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 更新されたフィールドを使用するコンポーネントの前。 例: スキーム、ホスト、クライアント IP、メソッド。
正常性チェック ASP.NET Core アプリとその依存関係の正常性を、データベースの可用性などで確認します。 要求が正常性チェックのエンドポイントと一致した場合の終端。
ヘッダーの伝達 HTTP ヘッダーを受信要求から送信 HTTP クライアント要求に伝達します。
HTTP ログ HTTP 要求と応答をログに記録します。 ミドルウェア パイプラインの先頭。
HTTP メソッドのオーバーライド メソッドをオーバーライドする受信 POST 要求を許可します。 更新されたメソッドを使うコンポーネントの前。
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 URL を使うコンポーネントの前。
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 応答が送信される前と要求を変更するコンポーネントの後。 次に例を示します。 転送されるヘッダー、URL リライト。
MVC MVC または Razor Pages で要求を処理します。 要求がルートと一致した場合の終端。
OWIN OWIN ベースのアプリ、サーバー、およびミドルウェアと相互運用します。 OWIN ミドルウェアが要求を完全に処理した場合の終端。
出力キャッシュ 構成に基づく応答のキャッシュのサポートを提供します。 キャッシュが必要なコンポーネントの前。 UseOutputCaching の前に UseRouting を追加する必要があります。 UseOutputCaching の前に UseCORS を追加する必要があります。
応答キャッシュ 応答のキャッシュのサポートを提供します。 これには、クライアントの参加が機能する必要があります。 サーバーを完全に制御するには、出力キャッシュを使用します。 キャッシュが必要なコンポーネントの前。 UseResponseCaching の前に UseCORS を追加する必要があります。 通常、ブラウザーによってキャッシュを防ぐ要求ヘッダーが設定されるため、Razor Pages などの UI アプリには有益ではありません。 UI アプリには出力キャッシュが有益です。
圧縮解除を要求する 圧縮解除の要求をサポートします。 要求本文を読み取るコンポーネントの前。
応答圧縮 応答の圧縮のサポートを提供します。 圧縮が必要なコンポーネントの前。
要求のローカライズ ローカライズのサポートを提供します。 ローカリゼーションが重要なコンポーネントの前。 RouteDataRequestCultureProvider を使用する場合は、ルーティング ミドルウェアの後に配置する必要があり ます。
エンドポイント ルーティング 要求のルートを定義および制約します。 一致するルートの終端。
SPA シングルページ アプリケーション (SPA) の既定のページを返し、ミドルウェア チェーン内のこの時点以降のすべての要求を処理します。 チェーンの終わりで、静的ファイルや MVC アクションなどにサービスを提供する他のミドルウェアが優先されるようにするためです。
セッション ユーザー セッションの管理のサポートを提供します。 セッションが必要なコンポーネントの前。
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 要求がファイルと一致した場合の終端。
URL 書き換え URL の書き換えと要求のリダイレクトのサポートを提供します。 URL を使うコンポーネントの前。
W3CLogging W3C 拡張ログ ファイル形式でサーバー アクセス ログを生成します。 ミドルウェア パイプラインの先頭。
WebSocket WebSocket プロトコルを有効にします。 WebSocket 要求を受け入れる必要があるコンポーネントの前。

その他の技術情報

作成者: Rick AndersonSteve Smith

ミドルウェアとは、要求と応答を処理するために、アプリのパイプラインに組み込まれたソフトウェアです。 各コンポーネントで次の処理を実行します。

  • 要求をパイプライン内の次のコンポーネントに渡すかどうかを選択します。
  • パイプラインの次のコンポーネントの前または後に作業を実行できます。

要求デリゲートは、要求パイプラインの構築に使用されます。 要求デリゲートは、各 HTTP 要求を処理します。

要求デリゲートは、RunMapUse の各拡張メソッドを使って構成されます。 個々の要求デリゲートは、匿名メソッドとしてインラインで指定するか (インライン ミドルウェアと呼ばれます)、または再利用可能なクラスで定義することができます。 これらの再利用可能なクラスとインラインの匿名メソッドは、"ミドルウェア" です。"ミドルウェア コンポーネント" とも呼ばれます。 要求パイプライン内の各ミドルウェア コンポーネントは、パイプラインの次のコンポーネントを呼び出すか、パイプラインをショートさせます。 ショートサーキットしたミドルウェアは "ターミナル ミドルウェア" と呼ばれます。これによってさらなるミドルウェアによる要求の処理が回避されるためです。

HTTP ハンドラーとモジュールを ASP.NET Core ミドルウェアに移行する」では、ASP.NET Core と ASP.NET 4.x の要求パイプラインの違いについて説明し、その他のミドルウェア サンプルを提供しています。

ミドルウェアのコード分析

ASP.NET Core には、アプリケーション コードの品質を検査するコンパイラ プラットフォーム アナライザーが多数含まれています。 詳細については、「ASP.NET Core アプリのコード分析」を参照してください。

WebApplication を使用してミドルウェア パイプラインを作成する

ASP.NET Core 要求パイプラインは、順番に呼び出される一連の要求デリゲートで構成されています。 次の図は、その概念を示しています。 実行のスレッドは黒い矢印をたどります。

要求の到着、3 つのミドルウェアによる処理、アプリからの応答の送信を示す要求処理パターン。3 番目のミドルウェアが要求を処理した後、要求は前の 2 つのミドルウェアを逆の順序で通過して、それぞれの next() ステートメントの後の追加処理が行われた後、クライアントへの応答としてアプリを終了します。

各デリゲートは、次のデリゲートの前と後に操作を実行できます。 例外処理デリゲートは、パイプラインの後のステージで発生する例外をキャッチできるように、パイプラインの早い段階で呼び出される必要があります。

考えられる最も簡単な ASP.NET Core アプリは、1 つの要求デリゲートを設定してすべての要求を処理するものです。 この場合、実際の要求パイプラインは含まれません。 代わりに、すべての HTTP 要求に対応して単一の匿名関数が呼び出されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello world!");
});

app.Run();

複数の要求デリゲートを Use と一緒にチェーンします。 next パラメーターは、パイプラインの次のデリゲートを表します next パラメーターを "呼び出さない" ことで、パイプラインをショートさせることができます。 アクションは通常、次の例で示すように、next デリゲートの前後の両方で実行できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

デリゲートが次のデリゲートに要求を渡さない場合、これは "要求パイプラインのショートサーキット" と呼ばれます。 不要な処理を回避するために、ショートさせることが望ましい場合がよくあります。 たとえば、静的ファイル ミドルウェアは、静的ファイルの要求を処理して残りのパイプラインをショートサーキットすることにより、"ターミナル ミドルウェア" として動作させることができます。 後続の処理を終了させるミドルウェアの前にパイプラインに追加されたミドルウェアでは、その next.Invoke ステートメントの後も引き続きコードが処理されます。 ただし、既に送信された応答に対する書き込みの試行については、次の警告を参照してください。

警告

応答がクライアントに送信された後に、next.Invoke を呼び出さないでください。 応答が開始した後で HttpResponse を変更すると、例外がスローされます。 たとえば、ヘッダーや状態コードを設定すると、例外がスローされますnext を呼び出した後で応答本文に書き込むと、次のようになります。

  • プロトコル違反が発生する可能性があります。 たとえば、示されている Content-Length より多くを書き込んだ場合。
  • 本文の形式が破損する可能性があります。 たとえば、CSS ファイルに HTML フッターを書き込んだ場合。

HasStarted は、ヘッダーが送信されたかどうかや本文が書き込まれたかどうかを示すために役立つヒントです。

Run デリゲートでは、next パラメーターは受け取られません。 最初の Run デリゲートが常に終点となり、パイプラインが終了されます。 Run は規則です。 一部のミドルウェア コンポーネントでは、パイプラインの最後に実行される Run[Middleware] メソッドが公開されることがあります。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>
{
    // Do work that can write to the Response.
    await next.Invoke();
    // Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。

前の例では、Run デリゲートによって応答に "Hello from 2nd delegate." が書き込まれ、その後、パイプラインが終了となります。 別の Use または Run デリゲートが Run デリゲートの後に追加される場合、そのデリゲートは呼び出されません。

アプリを優先します。コンテキストを次に渡す必要があるオーバーロードを使用する

非割り当て アプリ。拡張メソッドを使用する :

  • コンテキストをに渡す必要があり next ます。
  • 他のオーバーロードを使用するときに必要な2つの内部要求ごとの割り当てを保存します。

詳細については、次を参照してください。この GitHub の問題します。

ミドルウェアの順序

次の図は、ASP.NET Core MVC と Razor Pages アプリの完全な要求処理パイプラインを示しています。 一般的なアプリでどのように既存のミドルウェアが順序付けされ、どこにカスタム ミドルウェアが追加されるかを確認できます。 シナリオでの必要性に応じて、既存のミドルウェアの順序を変更したり、新しいカスタム ミドルウェアを挿入したりする方法については、完全に制御できます。

ASP.NET Core のミドルウェア パイプライン

上記の図のエンドポイント ミドルウェアでは、対応するアプリの種類 (MVC または Razor Pages) のフィルター パイプラインが実行されます。

前の図でルーティング ミドルウェアの次には、静的ファイルが示されています。 これは、app.UseRouting を明示的に呼び出し、プロジェクト テンプレートが実装する順序です。 app.UseRouting を呼び出さない場合、既定でルーティング ミドルウェアがパイプラインの先頭で実行されます。 詳細については、ルーティングに関するページを参照してください。

ASP.NET Core のフィルター パイプライン

Program.cs ファイルでミドルウェア コンポーネントを追加する順序は、要求でミドルウェア コンポーネントが呼び出される順序および応答での逆の順序を定義します。 この順序は、セキュリティ、パフォーマンス、および機能にとって重要です。

次の Program.cs で強調表示されているコードは、一般的な推奨される順序でセキュリティ関連のミドルウェア コンポーネントを追加します。

using IndividualAccountsExample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

上のコードでは以下の操作が行われます。

  • 個人のユーザー アカウントを使用して新しい Web アプリを作成するときに追加されないミドルウェアは、コメント アウトされています。
  • すべてのミドルウェアが厳密にこの順序で配置されるわけではありませんが、多くの場合はそのように配置されます。 例:
    • UseCorsUseAuthentication、および UseAuthorization は、示されている順序で配置する必要があります。
    • 現時点では、UseCorsUseResponseCaching の前にある必要があります。 この要件については、GitHub issue dotnet/aspnetcore #23218 に関するページで説明されています。
    • UseRequestLocalization は、要求のカルチャをチェックする可能性があるすべてのミドルウェア (たとえば、app.UseMvcWithDefaultRoute()) の前に配置する必要があります。

シナリオによっては、ミドルウェアの順序が異なる場合があります。 たとえば、シナリオによって異なるキャッシュと圧縮の順序には、有効な順序は複数あります。 次に例を示します。

app.UseResponseCaching();
app.UseResponseCompression();

上記のコードでは、圧縮された応答をキャッシュして CPU の使用を減らすことができますが、Gzip や Brotli などの異なる圧縮アルゴリズムを使用してリソースの表現が複数キャッシュされてしまう場合があります。

次の順序では、圧縮された静的ファイルをキャッシュできるように、静的ファイルが結合されます。

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

次の Program.cs コードにより、共通アプリ シナリオのためのミドルウェア コンポーネントが追加されます。

  1. 例外/エラー処理
    • 開発環境でアプリを実行する場合:
      • 開発者例外ページ ミドルウェア (UseDeveloperExceptionPage) によりアプリの実行時エラーが報告されます。
      • データベース エラー ページ ミドルウェア (UseDatabaseErrorPage) によりデータベースの実行時エラーが報告されます。
    • 運用環境でアプリを実行する場合:
      • 例外ハンドラー ミドルウェア (UseExceptionHandler) によって、後続のミドルウェアによってスローされた例外がキャッチされます。
      • HTTP Strict Transport Security プロトコル (HSTS) ミドルウェア (UseHsts) により Strict-Transport-Security ヘッダーが追加されます。
  2. HTTPS リダイレクト ミドルウェア (UseHttpsRedirection) により、HTTP 要求が HTTPS にリダイレクトされます。
  3. 静的ファイル ミドルウェア (UseStaticFiles) によって静的ファイルが返され、さらなる要求の処理がスキップされます。
  4. Cookie ポリシー ミドルウェア (UseCookiePolicy) により、アプリを EU の一般データ保護規制 (GDPR) に準拠させます。
  5. ルーティング ミドルウェア (UseRouting) により、要求がルーティングされます。
  6. 認証ミドルウェア (UseAuthentication) により、ユーザーがセキュリティで保護されたリソースにアクセスする前に、ユーザーの認証が試行されます。
  7. 承認ミドルウェア (UseAuthorization) により、ユーザーがセキュリティで保護されたリソースにアクセスすることが承認されます。
  8. セッション ミドルウェア (UseSession) により、セッション状態が確立され保持されます。 アプリでセッション状態が使用されている場合は、Cookie ポリシー ミドルウェアの後、MVC ミドルウェアの前に、セッション ミドルウェアを呼び出します。
  9. エンドポイント ルーティング ミドルウェア (MapRazorPages を含む UseEndpoints) により、Razor Pages エンドポイントが要求パイプラインに追加されます。
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

前のコード例では、各ミドルウェアの拡張メソッドが Microsoft.AspNetCore.Builder 名前空間を通じて WebApplicationBuilder で公開されています。

パイプラインに追加された最初のミドルウェア コンポーネントは UseExceptionHandler です。 そのため、例外ハンドラー ミドルウェアでは、以降の呼び出しで発生するすべての例外がキャッチされます。

静的ファイル ミドルウェアはパイプラインの早い段階で呼び出されるので、要求を処理し、残りのコンポーネントを通過せずにショートさせることができます。 静的ファイル ミドルウェアでは、承認チェックは提供されませんwwwroot の下にあるものも含め、この静的ファイル ミドルウェアによって提供されるすべてのファイルは、一般に公開されます。 静的ファイルをセキュリティで保護する方法については、「ASP.NET Core の静的ファイル」を参照してください。

要求が静的ファイル ミドルウェアによって処理されない場合、要求は認証を実行する認証ミドルウェア (UseAuthentication) に渡されます。 認証は、認証されない要求をショートさせません。 認証ミドルウェアは要求を認証しますが、承認 (および却下) は、MVC が特定の Razor ページまたは MVC コントローラーとアクションを選んだ後でのみ行われます。

次の例は、静的ファイルの要求が応答圧縮ミドルウェアの前に静的ファイル ミドルウェアによって処理される、ミドルウェアの順序を示します。 静的ファイルは、このミドルウェアの順序では圧縮されません。 Razor Pages の応答は圧縮できます。

// Static files aren't compressed by Static File Middleware.
app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

シングル ページ アプリケーションの詳細については、ReactAngular プロジェクト テンプレートのガイドを参照してください。

UseCors と UseStaticFiles の順序

UseCorsUseStaticFiles を呼び出す順序はアプリによって異なります。 詳細については、「UseCors と UseStaticFiles の順序」を参照してください

Forwarded Headers Middleware の順序

Forwarded Headers Middleware は、他のミドルウェアの前に実行する必要があります。 この順序により、転送されるヘッダー情報に依存するミドルウェアが処理にヘッダー値を使用できます。 診断およびエラー処理ミドルウェアの後に Forwarded Headers Middleware を実行する方法については、「Forwarded Headers Middleware の順序」を参照してください。

ミドルウェア パイプラインを分岐する

Map 拡張メソッドは、パイプラインを分岐する規則として使われます。 Map は、指定された要求パスの一致に基づいて、要求パイプラインを分岐します。 要求パスが指定されたパスで開始する場合、分岐が実行されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

static void HandleMapTest2(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 2");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 応答
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Map を使用すると、一致したパス セグメントが HttpRequest.Path から削除され、要求ごとに HttpRequest.PathBase に追加されます。

Map は入れ子をサポートします。次にその例を示します。

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map では、次のように一度に複数のセグメントを照合できます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test 1");
    });
}

MapWhen は、指定された述語の結果に基づいて、要求パイプラインを分岐します。 Func<HttpContext, bool> という型の任意の述語を使って、要求をパイプラインの新しい分岐にマップできます。 次の例では、クエリ文字列変数 branch の存在を検出するために術後が使用されます。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        var branchVer = context.Request.Query["branch"];
        await context.Response.WriteAsync($"Branch used = {branchVer}");
    });
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 Response
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

また UseWhen では、指定された述語の結果に基づいて、要求パイプラインが分岐されます。 MapWhen とは異なり、この分岐は、ショートしたり、ターミナル ミドルウェアが含まれたりしなければ、メイン パイプラインに再参加します。

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
    appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)
{
    var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>(); 

    app.Use(async (context, next) =>
    {
        var branchVer = context.Request.Query["branch"];
        logger.LogInformation("Branch used = {branchVer}", branchVer);

        // Do work that doesn't write to the Response.
        await next();
        // Do other work that doesn't write to the Response.
    });
}

上記の例では、すべての要求に対して Hello from non-Map delegate. の応答が書き込まれます。 要求にクエリ文字列変数 branch が含まれる場合、メイン パイプラインの再参加前にその値がログに記録されます。

組み込みミドルウェア

ASP.NET Core には、次のミドルウェア コンポーネントが付属しています。 "順番" 列には、要求を処理するパイプライン内のミドルウェアの配置と、ミドルウェアが要求処理を終了する条件に関するメモが記載されています。 ミドルウェアが要求を処理するパイプラインをショートサーキットし、下流のさらなるミドルウェアによる要求の処理を回避する場合、これは "ターミナル ミドルウェア" と呼ばれます。 ショートサーキットについて詳しくは、「WebApplication を使用してミドルウェア パイプラインを作成する」セクションを参照してください。

ミドルウェア 説明 順番
認証 認証のサポートを提供します。 HttpContext.User が必要な場所の前。 OAuth コールバックの終端。
承認 承認のサポートを提供します。 認証ミドルウェアの直後。
Cookie ポリシー 個人情報の保存に関してユーザーからの同意を追跡し、secureSameSite など、cookie フィールドの最小要件を適用します。 cookie を発行するミドルウェアの前。 次に例を示します。 認証、セッション、MVC (TempData)
CORS クロス オリジン リソース共有を構成します。 CORS を使うコンポーネントの前。 現時点では、こちらのバグのため、UseResponseCaching の前に UseCors を追加する必要があります。
DeveloperExceptionPage 開発環境での使用のみを意図としたエラー情報を含むページが生成されます。 エラーを生成するコンポーネントの前。 プロジェクト テンプレートは、環境が開発の場合、パイプラインの最初のミドルウェアとしてこのミドルウェアを自動登録します。
診断 開発者の例外ページ、例外処理、状態コード ページ、および新しいアプリの既定の Web ページを提供する複数の独立したミドルウェア。 エラーを生成するコンポーネントの前。 例外または新しいアプリ用の既定の Web ページの提供の終端。
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 更新されたフィールドを使用するコンポーネントの前。 例: スキーム、ホスト、クライアント IP、メソッド。
正常性チェック ASP.NET Core アプリとその依存関係の正常性を、データベースの可用性などで確認します。 要求が正常性チェックのエンドポイントと一致した場合の終端。
ヘッダーの伝達 HTTP ヘッダーを受信要求から送信 HTTP クライアント要求に伝達します。
HTTP ログ HTTP 要求と応答をログに記録します。 ミドルウェア パイプラインの先頭。
HTTP メソッドのオーバーライド メソッドをオーバーライドする受信 POST 要求を許可します。 更新されたメソッドを使うコンポーネントの前。
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 URL を使うコンポーネントの前。
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 応答が送信される前と要求を変更するコンポーネントの後。 次に例を示します。 転送されるヘッダー、URL リライト。
MVC MVC または Razor Pages で要求を処理します。 要求がルートと一致した場合の終端。
OWIN OWIN ベースのアプリ、サーバー、およびミドルウェアと相互運用します。 OWIN ミドルウェアが要求を完全に処理した場合の終端。
圧縮解除を要求する 圧縮解除の要求をサポートします。 要求本文を読み取るコンポーネントの前。
応答キャッシュ 応答のキャッシュのサポートを提供します。 キャッシュが必要なコンポーネントの前。 UseResponseCaching の前に UseCORS を追加する必要があります。
応答圧縮 応答の圧縮のサポートを提供します。 圧縮が必要なコンポーネントの前。
要求のローカライズ ローカライズのサポートを提供します。 ローカリゼーションが重要なコンポーネントの前。 RouteDataRequestCultureProvider を使用する場合は、ルーティング ミドルウェアの後に配置する必要があり ます。
エンドポイント ルーティング 要求のルートを定義および制約します。 一致するルートの終端。
SPA シングルページ アプリケーション (SPA) の既定のページを返し、ミドルウェア チェーン内のこの時点以降のすべての要求を処理します。 チェーンの終わりで、静的ファイルや MVC アクションなどにサービスを提供する他のミドルウェアが優先されるようにするためです。
セッション ユーザー セッションの管理のサポートを提供します。 セッションが必要なコンポーネントの前。
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 要求がファイルと一致した場合の終端。
URL 書き換え URL の書き換えと要求のリダイレクトのサポートを提供します。 URL を使うコンポーネントの前。
W3CLogging W3C 拡張ログ ファイル形式でサーバー アクセス ログを生成します。 ミドルウェア パイプラインの先頭。
WebSocket WebSocket プロトコルを有効にします。 WebSocket 要求を受け入れる必要があるコンポーネントの前。

その他の技術情報

作成者: Rick AndersonSteve Smith

ミドルウェアとは、要求と応答を処理するために、アプリのパイプラインに組み込まれたソフトウェアです。 各コンポーネントで次の処理を実行します。

  • 要求をパイプライン内の次のコンポーネントに渡すかどうかを選択します。
  • パイプラインの次のコンポーネントの前または後に作業を実行できます。

要求デリゲートは、要求パイプラインの構築に使用されます。 要求デリゲートは、各 HTTP 要求を処理します。

要求デリゲートは、RunMapUse の各拡張メソッドを使って構成されます。 個々の要求デリゲートは、匿名メソッドとしてインラインで指定するか (インライン ミドルウェアと呼ばれます)、または再利用可能なクラスで定義することができます。 これらの再利用可能なクラスとインラインの匿名メソッドは、"ミドルウェア" です。"ミドルウェア コンポーネント" とも呼ばれます。 要求パイプライン内の各ミドルウェア コンポーネントは、パイプラインの次のコンポーネントを呼び出すか、パイプラインをショートさせます。 ショートサーキットしたミドルウェアは "ターミナル ミドルウェア" と呼ばれます。これによってさらなるミドルウェアによる要求の処理が回避されるためです。

HTTP ハンドラーとモジュールを ASP.NET Core ミドルウェアに移行する」では、ASP.NET Core と ASP.NET 4.x の要求パイプラインの違いについて説明し、その他のミドルウェア サンプルを提供しています。

IApplicationBuilder を使用したミドルウェア パイプラインの作成

ASP.NET Core 要求パイプラインは、順番に呼び出される一連の要求デリゲートで構成されています。 次の図は、その概念を示しています。 実行のスレッドは黒い矢印をたどります。

要求の到着、3 つのミドルウェアによる処理、アプリからの応答の送信を示す要求処理パターン。3 番目のミドルウェアが要求を処理した後、要求は前の 2 つのミドルウェアを逆の順序で通過して、それぞれの next() ステートメントの後の追加処理が行われた後、クライアントへの応答としてアプリを終了します。

各デリゲートは、次のデリゲートの前と後に操作を実行できます。 例外処理デリゲートは、パイプラインの後のステージで発生する例外をキャッチできるように、パイプラインの早い段階で呼び出される必要があります。

考えられる最も簡単な ASP.NET Core アプリは、1 つの要求デリゲートを設定してすべての要求を処理するものです。 この場合、実際の要求パイプラインは含まれません。 代わりに、すべての HTTP 要求に対応して単一の匿名関数が呼び出されます。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello, World!");
        });
    }
}

複数の要求デリゲートを Use と一緒にチェーンします。 next パラメーターは、パイプラインの次のデリゲートを表します next パラメーターを "呼び出さない" ことで、パイプラインをショートさせることができます。 次の例で示すように、通常は、次のデリゲートの前と後の両方でアクションを実行できます。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

デリゲートが次のデリゲートに要求を渡さない場合、これは "要求パイプラインのショートサーキット" と呼ばれます。 不要な処理を回避するために、ショートさせることが望ましい場合がよくあります。 たとえば、静的ファイル ミドルウェアは、静的ファイルの要求を処理して残りのパイプラインをショートサーキットすることにより、"ターミナル ミドルウェア" として動作させることができます。 後続の処理を終了させるミドルウェアの前にパイプラインに追加されたミドルウェアでは、その next.Invoke ステートメントの後も引き続きコードが処理されます。 ただし、既に送信された応答に対する書き込みの試行については、次の警告を参照してください。

警告

応答がクライアントに送信された後に、next.Invoke を呼び出さないでください。 応答が開始した後で HttpResponse を変更すると、例外がスローされます。 たとえば、ヘッダーや状態コードを設定すると、例外がスローされますnext を呼び出した後で応答本文に書き込むと、次のようになります。

  • プロトコル違反が発生する可能性があります。 たとえば、示されている Content-Length より多くを書き込んだ場合。
  • 本文の形式が破損する可能性があります。 たとえば、CSS ファイルに HTML フッターを書き込んだ場合。

HasStarted は、ヘッダーが送信されたかどうかや本文が書き込まれたかどうかを示すために役立つヒントです。

Run デリゲートでは、next パラメーターは受け取られません。 最初の Run デリゲートが常に終点となり、パイプラインが終了されます。 Run は規則です。 一部のミドルウェア コンポーネントでは、パイプラインの最後に実行される Run[Middleware] メソッドが公開されることがあります。

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

コードのコメントを英語以外の言語に翻訳し表示したい場合、こちらの GitHub ディスカッション イシューにてお知らせください。

前の例では、Run デリゲートによって応答に "Hello from 2nd delegate." が書き込まれ、その後、パイプラインが終了となります。 別の Use または Run デリゲートが Run デリゲートの後に追加される場合、そのデリゲートは呼び出されません。

ミドルウェアの順序

次の図は、ASP.NET Core MVC と Razor Pages アプリの完全な要求処理パイプラインを示しています。 一般的なアプリでどのように既存のミドルウェアが順序付けされ、どこにカスタム ミドルウェアが追加されるかを確認できます。 シナリオでの必要性に応じて、既存のミドルウェアの順序を変更したり、新しいカスタム ミドルウェアを挿入したりする方法については、完全に制御できます。

ASP.NET Core のミドルウェア パイプライン

上記の図のエンドポイント ミドルウェアでは、対応するアプリの種類 (MVC または Razor Pages) のフィルター パイプラインが実行されます。

ASP.NET Core のフィルター パイプライン

Startup.Configure メソッドでミドルウェア コンポーネントを追加する順序は、要求でミドルウェア コンポーネントが呼び出される順序および応答での逆の順序を定義します。 この順序は、セキュリティ、パフォーマンス、および機能にとって重要です。

次の Startup.Configure メソッドでは、セキュリティ関連のミドルウェア コンポーネントが、一般的な推奨される順序で追加されています。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    // app.UseCookiePolicy();

    app.UseRouting();
    // app.UseRequestLocalization();
    // app.UseCors();

    app.UseAuthentication();
    app.UseAuthorization();
    // app.UseSession();
    // app.UseResponseCompression();
    // app.UseResponseCaching();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

上のコードでは以下の操作が行われます。

  • 個人のユーザー アカウントを使用して新しい Web アプリを作成するときに追加されないミドルウェアは、コメント アウトされています。
  • すべてのミドルウェアが厳密にこの順序で配置されるわけではありませんが、多くの場合はそのように配置されます。 例:
    • UseCorsUseAuthentication、および UseAuthorization は、示されている順序で配置する必要があります。
    • 現時点では、このバグのため、UseCorsUseResponseCaching の前に配置する必要があります。
    • UseRequestLocalization は、要求のカルチャをチェックする可能性があるすべてのミドルウェア (たとえば、app.UseMvcWithDefaultRoute()) の前に配置する必要があります。

シナリオによっては、ミドルウェアの順序が異なる場合があります。 たとえば、キャッシュと圧縮の順序はシナリオ固有のものであり、複数の有効な順序があります。 次に例を示します。

app.UseResponseCaching();
app.UseResponseCompression();

上記のコードでは、圧縮された応答をキャッシュすることによって CPU を節約できますが、Gzip や Brotli などの異なる圧縮アルゴリズムを使用してリソースの複数の表現をキャッシュすることができます。

次の順序では、圧縮された静的ファイルをキャッシュできるように、静的ファイルが結合されます。

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

次の Startup.Configure メソッドにより、共通アプリ シナリオのためのミドルウェア コンポーネントが追加されます。

  1. 例外/エラー処理
    • 開発環境でアプリを実行する場合:
      • 開発者例外ページ ミドルウェア (UseDeveloperExceptionPage) によりアプリの実行時エラーが報告されます。
      • データベース エラー ページ ミドルウェアによりデータベースの実行時エラーが報告されます。
    • 運用環境でアプリを実行する場合:
      • 例外ハンドラー ミドルウェア (UseExceptionHandler) によって、後続のミドルウェアによってスローされた例外がキャッチされます。
      • HTTP Strict Transport Security プロトコル (HSTS) ミドルウェア (UseHsts) により Strict-Transport-Security ヘッダーが追加されます。
  2. HTTPS リダイレクト ミドルウェア (UseHttpsRedirection) により、HTTP 要求が HTTPS にリダイレクトされます。
  3. 静的ファイル ミドルウェア (UseStaticFiles) によって静的ファイルが返され、さらなる要求の処理がスキップされます。
  4. Cookie ポリシー ミドルウェア (UseCookiePolicy) により、アプリを EU の一般データ保護規制 (GDPR) に準拠させます。
  5. ルーティング ミドルウェア (UseRouting) により、要求がルーティングされます。
  6. 認証ミドルウェア (UseAuthentication) により、ユーザーがセキュリティで保護されたリソースにアクセスする前に、ユーザーの認証が試行されます。
  7. 承認ミドルウェア (UseAuthorization) により、ユーザーがセキュリティで保護されたリソースにアクセスすることが承認されます。
  8. セッション ミドルウェア (UseSession) により、セッション状態が確立され保持されます。 アプリでセッション状態が使用されている場合は、Cookie ポリシー ミドルウェアの後、MVC ミドルウェアの前に、セッション ミドルウェアを呼び出します。
  9. エンドポイント ルーティング ミドルウェア (MapRazorPages を含む UseEndpoints) により、Razor Pages エンドポイントが要求パイプラインに追加されます。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseSession();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

前のコード例では、各ミドルウェアの拡張メソッドが Microsoft.AspNetCore.Builder 名前空間を通じて IApplicationBuilder で公開されています。

パイプラインに追加された最初のミドルウェア コンポーネントは UseExceptionHandler です。 そのため、例外ハンドラー ミドルウェアでは、以降の呼び出しで発生するすべての例外がキャッチされます。

静的ファイル ミドルウェアはパイプラインの早い段階で呼び出されるので、要求を処理し、残りのコンポーネントを通過せずにショートさせることができます。 静的ファイル ミドルウェアでは、承認チェックは提供されませんwwwroot の下にあるものも含め、この静的ファイル ミドルウェアによって提供されるすべてのファイルは、一般に公開されます。 静的ファイルをセキュリティで保護する方法については、「ASP.NET Core の静的ファイル」を参照してください。

要求が静的ファイル ミドルウェアによって処理されない場合、要求は認証を実行する認証ミドルウェア (UseAuthentication) に渡されます。 認証は、認証されない要求をショートさせません。 認証ミドルウェアは要求を認証しますが、承認 (および却下) は、MVC が特定の Razor ページまたは MVC コントローラーとアクションを選んだ後でのみ行われます。

次の例は、静的ファイルの要求が応答圧縮ミドルウェアの前に静的ファイル ミドルウェアによって処理される、ミドルウェアの順序を示します。 静的ファイルは、このミドルウェアの順序では圧縮されません。 Razor Pages の応答は圧縮できます。

public void Configure(IApplicationBuilder app)
{
    // Static files aren't compressed by Static File Middleware.
    app.UseStaticFiles();

    app.UseRouting();

    app.UseResponseCompression();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

シングルページ アプリケーション (SPA) の場合、通常はミドルウェア パイプラインの最後に SPA ミドルウェア UseSpaStaticFiles を配置します。 SPA ミドルウェアは、次のために最後になります。

  • 対応する要求に最初にその他のミドルウェアが応答するため。
  • クライアント側ルーティングを使用する SPA がサーバー アプリに認識されないすべてのルートを実行するため。

SPA の詳細については、ReactAngular プロジェクト テンプレートのガイドを参照してください。

Forwarded Headers Middleware の順序

Forwarded Headers Middleware は、他のミドルウェアの前に実行する必要があります。 この順序により、転送されるヘッダー情報に依存するミドルウェアが処理にヘッダー値を使用できます。 診断およびエラー処理ミドルウェアの後に Forwarded Headers Middleware を実行する方法については、「Forwarded Headers Middleware の順序」を参照してください。

ミドルウェア パイプラインを分岐する

Map 拡張メソッドは、パイプラインを分岐する規則として使われます。 Map は、指定された要求パスの一致に基づいて、要求パイプラインを分岐します。 要求パスが指定されたパスで開始する場合、分岐が実行されます。

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 応答
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

Map を使用すると、一致したパス セグメントが HttpRequest.Path から削除され、要求ごとに HttpRequest.PathBase に追加されます。

Map は入れ子をサポートします。次にその例を示します。

app.Map("/level1", level1App => {
    level1App.Map("/level2a", level2AApp => {
        // "/level1/level2a" processing
    });
    level1App.Map("/level2b", level2BApp => {
        // "/level1/level2b" processing
    });
});

Map では、次のように一度に複数のセグメントを照合できます。

public class Startup
{
    private static void HandleMultiSeg(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map multiple segments.");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1/seg1", HandleMultiSeg);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

MapWhen は、指定された述語の結果に基づいて、要求パイプラインを分岐します。 Func<HttpContext, bool> という型の任意の述語を使って、要求をパイプラインの新しい分岐にマップできます。 次の例では、クエリ文字列変数 branch の存在を検出するために術後が使用されます。

public class Startup
{
    private static void HandleBranch(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            var branchVer = context.Request.Query["branch"];
            await context.Response.WriteAsync($"Branch used = {branchVer}");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
                               HandleBranch);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate.");
        });
    }
}

次の表は、前のコードを使用した http://localhost:1234 からの要求および応答を示しています。

要求 応答
localhost:1234 Hello from non-Map delegate.
localhost:1234/?branch=main Branch used = main

また UseWhen では、指定された述語の結果に基づいて、要求パイプラインが分岐されます。 MapWhen とは異なり、この分岐は、ショートしたり、ターミナル ミドルウェアが含まれたりしなければ、メイン パイプラインに再参加します。

public class Startup
{
    private void HandleBranchAndRejoin(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.Use(async (context, next) =>
        {
            var branchVer = context.Request.Query["branch"];
            logger.LogInformation("Branch used = {branchVer}", branchVer);

            // Do work that doesn't write to the Response.
            await next();
            // Do other work that doesn't write to the Response.
        });
    }

    public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
    {
        app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                               appBuilder => HandleBranchAndRejoin(appBuilder, logger));

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from main pipeline.");
        });
    }
}

上記の例では、すべての要求に対して "Hello from main pipeline." の応答が書き込まれます。 要求にクエリ文字列変数 branch が含まれる場合、メイン パイプラインの再参加前にその値がログに記録されます。

組み込みミドルウェア

ASP.NET Core には、次のミドルウェア コンポーネントが付属しています。 "順番" 列には、要求を処理するパイプライン内のミドルウェアの配置と、ミドルウェアが要求処理を終了する条件に関するメモが記載されています。 ミドルウェアが要求を処理するパイプラインをショートサーキットし、下流のさらなるミドルウェアによる要求の処理を回避する場合、これは "ターミナル ミドルウェア" と呼ばれます。 ショートサーキットについて詳しくは、「IApplicationBuilder を使用したミドルウェア パイプラインの作成」セクションをご覧ください。

ミドルウェア 説明 順番
認証 認証のサポートを提供します。 HttpContext.User が必要な場所の前。 OAuth コールバックの終端。
承認 承認のサポートを提供します。 認証ミドルウェアの直後。
Cookie ポリシー 個人情報の保存に関してユーザーからの同意を追跡し、secureSameSite など、cookie フィールドの最小要件を適用します。 cookie を発行するミドルウェアの前。 次に例を示します。 認証、セッション、MVC (TempData)
CORS クロス オリジン リソース共有を構成します。 CORS を使うコンポーネントの前。 現時点では、こちらのバグのため、UseResponseCaching の前に UseCors を追加する必要があります。
診断 開発者の例外ページ、例外処理、状態コード ページ、および新しいアプリの既定の Web ページを提供する複数の独立したミドルウェア。 エラーを生成するコンポーネントの前。 例外または新しいアプリ用の既定の Web ページの提供の終端。
転送されるヘッダー プロキシされたヘッダーを現在の要求に転送します。 更新されたフィールドを使用するコンポーネントの前。 例: スキーム、ホスト、クライアント IP、メソッド。
正常性チェック ASP.NET Core アプリとその依存関係の正常性を、データベースの可用性などで確認します。 要求が正常性チェックのエンドポイントと一致した場合の終端。
ヘッダーの伝達 HTTP ヘッダーを受信要求から送信 HTTP クライアント要求に伝達します。
HTTP メソッドのオーバーライド メソッドをオーバーライドする受信 POST 要求を許可します。 更新されたメソッドを使うコンポーネントの前。
HTTPS リダイレクト すべての HTTP 要求を HTTPS にリダイレクトします。 URL を使うコンポーネントの前。
HTTP Strict Transport Security (HSTS) 特殊な応答ヘッダーを追加するセキュリティ拡張機能のミドルウェア。 応答が送信される前と要求を変更するコンポーネントの後。 次に例を示します。 転送されるヘッダー、URL リライト。
MVC MVC または Razor Pages で要求を処理します。 要求がルートと一致した場合の終端。
OWIN OWIN ベースのアプリ、サーバー、およびミドルウェアと相互運用します。 OWIN ミドルウェアが要求を完全に処理した場合の終端。
応答キャッシュ 応答のキャッシュのサポートを提供します。 キャッシュが必要なコンポーネントの前。 UseResponseCaching の前に UseCORS を追加する必要があります。
応答圧縮 応答の圧縮のサポートを提供します。 圧縮が必要なコンポーネントの前。
要求のローカライズ ローカライズのサポートを提供します。 ローカリゼーションが重要なコンポーネントの前。 RouteDataRequestCultureProvider を使用する場合は、ルーティング ミドルウェアの後に配置する必要があり ます。
エンドポイント ルーティング 要求のルートを定義および制約します。 一致するルートの終端。
SPA シングルページ アプリケーション (SPA) の既定のページを返し、ミドルウェア チェーン内のこの時点以降のすべての要求を処理します。 チェーンの終わりで、静的ファイルや MVC アクションなどにサービスを提供する他のミドルウェアが優先されるようにするためです。
セッション ユーザー セッションの管理のサポートを提供します。 セッションが必要なコンポーネントの前。
静的ファイル 静的ファイルとディレクトリ参照に対応するサポートを提供します。 要求がファイルと一致した場合の終端。
URL 書き換え URL の書き換えと要求のリダイレクトのサポートを提供します。 URL を使うコンポーネントの前。
WebSocket WebSocket プロトコルを有効にします。 WebSocket 要求を受け入れる必要があるコンポーネントの前。

その他の技術情報