ASP.NET Core でのセッションと状態の管理

作成者: Rick AndersonKirk Larkin、および Diana LaRose

HTTP はステートレス プロトコルです。 既定で、HTTP 要求は独立したメッセージであり、ユーザーの値は保持されません。 この記事では、要求間でユーザー データを保持するためのいくつかの方法について説明します。

状態管理

状態は、いくつかの方法で格納することができます。 この記事ではそれぞれの方法について説明します。

格納の方法 格納のメカニズム
Cookies HTTP cookie。 サーバー側アプリ コードを使って格納されたデータを含めることができます。
セッション状態 HTTP cookie とサーバー側アプリ コード
TempData HTTP cookie またはセッション状態
クエリ文字列 HTTP クエリ文字列
非表示フィールド HTTP フォーム フィールド
HttpContext.Items サーバー側アプリ コード
キャッシュ サーバー側アプリ コード

SignalR/Blazor Server および HTTP コンテキストベースの状態管理

SignalR アプリでは、安定した HTTP コンテキストに依存して情報を格納する、セッション状態やその他の状態管理アプローチを使用しないでください。 SignalR アプリは、ハブ内の Context.Items に接続ごとの状態を格納できます。 Blazor Server アプリに関する詳しい情報と代替状態管理アプローチについては、「ASP.NET Core Blazor 状態管理」を参照してください。

Cookies

Cookie には、要求と要求の間でデータが格納されます。 cookie は要求ごとに送信されるために、そのサイズは最小に抑える必要があります。 理想的には、識別子だけを cookie に格納し、データはアプリで格納します。 ほとんどのブラウザーで cookie のサイズは 4096 バイトに制限されています。 ドメインごとに使用できる cookie の数も制限されています。

cookie は改ざんされる可能性があるため、アプリで検証する必要があります。 Cookie は、ユーザーが削除でき、クライアント上で期限切れになります。 ただし、cookie は一般に、クライアントでデータを永続化するときに最も持続性のある形式です。

Cookie は、多くの場合、パーソナル化に利用されます。既知のユーザーのためにコンテンツをカスタマイズします。 ユーザーは識別されるだけであり、ほとんどの場合は認証されません。 cookie には、ユーザーの名前、アカウント名、または GUID などの一意のユーザー ID を格納できます。 cookie は、好みの Web サイトの背景色など、ユーザーの個人用設定にアクセスするために使用できます。

cookie を発行し、プライバシーの問題を扱うときは、欧州連合の一般データ保護規則 (GDPR) を参照してください。 詳細については、「General Data Protection Regulation (GDPR) support in ASP.NET Core」(ASP.NET Core での一般データ保護規則 (GDPR) のサポート) をご覧ください。

セッション状態

セッション状態は、ユーザーが Web アプリを参照している期間中ユーザー データを格納するための ASP.NET Core のシナリオです。 セッション状態では、アプリで管理されているストアを使用して、クライアントからの要求間でデータを保持します。 セッション データはキャッシュによってバックアップされ、一時的なデータと見なされます。 セッション データがなくても、サイトは機能し続けられる必要があります。 重要なアプリケーションのデータはユーザー データベースに格納し、パフォーマンスの最適化としてのみセッションでキャッシュする必要があります。

SignalR ハブは HTTP コンテキストとは独立して実行する可能性があるため、SignalR アプリではセッションはサポートされていません。 このようなことは、たとえば、長いポーリング要求が HTTP コンテキストの有効期間を超えてハブによって開かれている場合に発生する可能性があります。

ASP.NET Core により、セッション ID を含む cookie がクライアントに提供されて、セッションの状態が維持されます。 cookie セッション ID:

  • 各要求でアプリに送信されます。
  • アプリによってセッション データをフェッチするために使用されます。

セッション状態は次の動作を示します。

  • セッション cookie は、ブラウザーに固有です。 セッションはブラウザー間で共有されません。
  • セッション cookie は、ブラウザー セッションが終了するときに削除されます。
  • cookie を受け取り、セッションが期限切れになった場合、同じセッション cookie を使用する新しいセッションが作成されます。
  • 空のセッションは保持されません。 要求間でセッションを維持するには、少なくとも 1 つの値をセッションが持っている必要があります。 セッションが保持されないと、新しい要求ごとに新しいセッション ID が生成されます。
  • アプリは、最後の要求から限られた時間だけセッションを維持します。 アプリでは、セッション タイムアウトを設定するか、既定値の 20 分を使用します。 セッション状態は、次のユーザー データの格納に最適です。
    • 特定のセッションに固有である。
    • データがセッション間で永続的に保存される必要がない。
  • セッション データは、ISession.Clear の実装が呼び出されるか、セッションが期限切れになると、削除されます。
  • クライアント ブラウザーが閉じられたこと、またはクライアントでセッション cookie が削除されるか期限切れになったことを、アプリ コードに通知する既定のメカニズムはありません。
  • セッション状態の cookie は既定では必須としてマークされていません。 サイトの訪問者が追跡を許可しない限り、セッション状態は機能しません。 詳細については、「General Data Protection Regulation (GDPR) support in ASP.NET Core」(ASP.NET Core での一般データ保護規則 (GDPR) のサポート) をご覧ください。
  • : ASP.NET Framework からの cookie を使用しないセッション機能に代わる機能はありません。これが安全ではないと見なされ、セッション固定攻撃につながる恐れがあるためです。

警告

セッション状態には機密データを保存しないでください。 ユーザーがブラウザーを閉じず、セッション cookie がクリアされない可能性があります。 一部のブラウザーでは、ブラウザーのウィンドウ間で有効なセッションの cookie が維持されます。 セッションが 1 人のユーザーに制限されないことがあります。 次のユーザーが、同じセッション cookie でアプリを閲覧し続けることがあります。

メモリ内キャッシュ プロバイダーは、アプリが存在するサーバーのメモリにセッション データを格納します。 サーバー ファームのシナリオでは次のようになります。

  • "固定セッション" を使用して、個々のサーバー上の特定のアプリのインスタンスに、各セッションを結び付けます。 Azure App Serviceアプリケーション要求ルーティング処理 (ARR) を使って、既定で固定セッションを強制的に使用します。 ただし、固定セッションは拡張性に影響を与え、Web アプリの更新を複雑にすることがあります。 もっとよい方法は、Redis または SQL Server の分散キャッシュを使用することで、固定セッションを必要としません。 詳細については、「ASP.NET Core の分散キャッシュ」を参照してください。
  • セッション cookie は IDataProtector によって暗号化されます。 各コンピューターでセッション cookie が読み取られるようにするには、データ保護を適切に構成する必要があります。 詳細については、「ASP.NET Core のデータ保護の概要」とキー ストレージ プロバイダーに関する記事を参照してください。

セッション状態を構成する

セッション状態を管理するためのミドルウェアは、フレームワークに含まれています。 セッション ミドルウェアを有効にするには、Program.cs に次が含まれている必要があります。

次のコードでは、IDistributedCache の既定のメモリ内実装でメモリ内セッション プロバイダーを設定する方法を示します。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromSeconds(10);
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

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

app.Run();

前のコードでは、テストを簡単にするために短いタイムアウトを設定しています。

ミドルウェアの順序が重要です。 UseSessionUseRouting の後、かつ MapRazorPagesMapDefaultControllerRoute の前に呼び出します。 ミドルウェアの順序付けに関するページを参照してください。

HttpContext.Session は、セッション状態を構成した後で使用できます。

UseSession を呼び出前に HttpContext.Session にアクセスすることはできません。

アプリが応答ストリームへの書き込みを開始した後では、新しいセッション cookie を含む新しいセッションを作成できません。 例外は Web サーバー ログに記録され、ブラウザーには表示されません。

セッション状態を非同期的に読み込む

ASP.NET Core の既定のセッション プロバイダーでは、TryGetValueSet、または Remove メソッドの前に ISession.LoadAsync メソッドが明示的に呼び出された場合にのみ、基になる IDistributedCache バッキング ストアから非同期的にセッション レコードを読み込みます。 LoadAsync を最初に呼び出さないと、基になっているセッション レコードは同期的に読み込まれ、パフォーマンスが大幅に低下する可能性があります。

アプリにこのパターンを強制させるには、TryGetValueSet、または Remove の前に LoadAsync メソッドが呼び出されない場合に例外をスローするバージョンで、DistributedSessionStore および DistributedSession の実装をラップします。 ラップしたバージョンをサービス コンテナーに登録します。

セッション オプション

セッションの既定値をオーバーライドするには、SessionOptions を使用します。

オプション 説明
Cookie cookie の作成に使用される設定を決定します。 Name の既定値は SessionDefaults.CookieName (.AspNetCore.Session) です。 Path の既定値は SessionDefaults.CookiePath (/) です。 SameSite の既定値は SameSiteMode.Lax (1) です。 HttpOnly では、既定値が true に設定されます。 IsEssential では、既定値が false に設定されます。
IdleTimeout IdleTimeout は、内容を破棄されることなくセッションがアイドル状態になっていることのできる最大時間を示します。 セッションへのアクセスがあるたびに、タイムアウトはリセットされます。 この設定はセッションの内容にのみ適用され、cookie には適用されません。 既定値は 20 分です。
IOTimeout ストアからのセッションの読み込み、またはストアに戻すコミットに対して許容される最大時間です。 この設定は非同期操作にのみ適用できます。 このタイムアウトは、InfiniteTimeSpan を使用して無効にすることができます。 既定値は 1 分です。

セッションは cookie を利用し、1 つのブラウザーからの要求を追跡し、識別します。 既定では、この cookie は .AspNetCore.Session という名前であり、パス / が使用されます。 cookie の既定値ではドメインが指定されないため、ページのクライアント側スクリプトには使用できません (HttpOnly の既定値が true になるため)。

cookie セッションの既定値をオーバーライドするには、SessionOptions を使用します。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDistributedMemoryCache();

builder.Services.AddSession(options =>
{
    options.Cookie.Name = ".AdventureWorks.Session";
    options.IdleTimeout = TimeSpan.FromSeconds(10);
    options.Cookie.IsEssential = true;
});

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

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

app.Run();

アプリでは、IdleTimeout プロパティを使用して、サーバーのキャッシュ内の内容が破棄されるまでのセッションのアイドル時間が決定されます。 このプロパティは cookie の有効期限に依存しません。 要求がセッション ミドルウェアを通過するたびにタイムアウトがリセットされます。

セッション状態は "ロックなし" です。 2 つの要求がセッションの内容を同時に変更しようとした場合、最後の要求が最初の要求をオーバーライドします。 Session一貫性のあるセッションとして実装されます。つまり、コンテンツは全部まとめて保管されます。 2 つの要求が異なるセッション値を変更しようとしたとき、最後の要求が最初の要求によって行われたセッションの変更をオーバーライドすることがあります。

セッション値の設定および取得

セッション状態にアクセスするには、Razor Pages の PageModel クラスか、MVC の Controller クラスを HttpContext.Session と共に使用します。 このプロパティは ISession の実装です。

ISession の実装では、整数値や文字列値を設定および取得するための複数の拡張メソッドが提供されています。 拡張メソッドは Microsoft.AspNetCore.Http 名前空間にあります。

ISession 拡張メソッド:

次の例では、IndexModel.SessionKeyName キー (サンプル アプリ内の _Name) のセッション値を Razor Pages ページで取得します。

@page
@using Microsoft.AspNetCore.Http
@model IndexModel

...

Name: @HttpContext.Session.GetString(IndexModel.SessionKeyName)

次の例では、整数および文字列を設定および取得する方法を示します。

public class IndexModel : PageModel
{
    public const string SessionKeyName = "_Name";
    public const string SessionKeyAge = "_Age";

    private readonly ILogger<IndexModel> _logger;

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        if (string.IsNullOrEmpty(HttpContext.Session.GetString(SessionKeyName)))
        {
            HttpContext.Session.SetString(SessionKeyName, "The Doctor");
            HttpContext.Session.SetInt32(SessionKeyAge, 73);
        }
        var name = HttpContext.Session.GetString(SessionKeyName);
        var age = HttpContext.Session.GetInt32(SessionKeyAge).ToString();

        _logger.LogInformation("Session Name: {Name}", name);
        _logger.LogInformation("Session Age: {Age}", age);
    }
}

次のマークアップは、Razor ページ上にセッション値を表示します。

@page
@model PrivacyModel
@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<div class="text-center">
<p><b>Name:</b> @HttpContext.Session.GetString("_Name");<b>Age:

</b> @HttpContext.Session.GetInt32("_Age").ToString()</p>
</div>


分散キャッシュのシナリオを有効にするには、メモリ内キャッシュを使用する場合であっても、すべてのセッション データをシリアル化する必要があります。 文字列と整数のシリアライザーは、ISession の拡張メソッドによって提供されます。 複合型は、JSON などの別のメカニズムを使ってユーザーがシリアル化する必要があります。

次のサンプル コードを使用して、オブジェクトをシリアル化します。

public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T? Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }
}

次の例では、シリアル化可能オブジェクトを SessionExtensions クラスで設定および取得する方法を示します。

using Microsoft.AspNetCore.Mvc.RazorPages;
using Web.Extensions;    // SessionExtensions

namespace SessionSample.Pages
{
    public class Index6Model : PageModel
    {
        const string SessionKeyTime = "_Time";
        public string? SessionInfo_SessionTime { get; private set; }
        private readonly ILogger<Index6Model> _logger;

        public Index6Model(ILogger<Index6Model> logger)
        {
            _logger = logger;
        }

        public void OnGet()
        {
            var currentTime = DateTime.Now;

            // Requires SessionExtensions from sample.
            if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default)
            {
                HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
            }
            _logger.LogInformation("Current Time: {Time}", currentTime);
            _logger.LogInformation("Session Time: {Time}", 
                           HttpContext.Session.Get<DateTime>(SessionKeyTime));

        }
    }
}

警告

セッションでのライブ オブジェクトの保存は、シリアル化されたオブジェクトで発生するよりも多くの問題があるため、注意して使用する必要があります。 詳細については、「セッションでオブジェクトを格納できるようにする必要がある (dotnet/aspnetcore #18159)」を参照してください。

TempData

ASP.NET Core によって、Razor Pages TempData または Controller TempData が発行されます。 このプロパティには、別の要求で読み取られるまでデータが格納されます。 Keep (String) メソッドと Peek (String) メソッドを使用すると、要求の最後に削除せずにデータを調べることができます。 Keep によって、リテンション期間についてディクショナリ内のすべての項目がマークされます。 TempData は次のようになります。

  • 複数の要求に対してデータが必要な場合のリダイレクトに役立ちます。
  • TempData プロバイダーによって cookie またはセッション状態を使用して実装されます。

TempData のサンプル

顧客を作成する次のページについて考えてみましょう。

public class CreateModel : PageModel
{
    private readonly RazorPagesContactsContext _context;

    public CreateModel(RazorPagesContactsContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";

        return RedirectToPage("./IndexPeek");
    }
}

次のページに TempData["Message"] が表示されます。

@page
@model IndexModel

<h1>Peek Contacts</h1>

@{
    if (TempData.Peek("Message") != null)
    {
        <h3>Message: @TempData.Peek("Message")</h3>
    }
}

@*Content removed for brevity.*@

前のマークアップでは、Peek が使用されているため、要求の最後に TempData["Message"] が削除されません。 ページを更新すると TempData["Message"] のコンテンツが表示されます。

次のマークアップは前のコードと似ていますが、Keep を使用して要求の最後にデータを保存しています。

@page
@model IndexModel

<h1>Contacts Keep</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
    TempData.Keep("Message");
}

@*Content removed for brevity.*@

Indexpeek ページと IndexKeep ページ間を移動しても TempData["Message"] は削除されません。

次のコードでは TempData["Message"] が表示されますが、要求の最後に TempData["Message"] が削除されます。

@page
@model IndexModel

<h1>Index no Keep or Peek</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
}

@*Content removed for brevity.*@

TempData プロバイダー

cookie に TempData を格納するには、既定では、cookie ベースの TempData プロバイダーが使われます。

cookie データは、IDataProtector を使用して暗号化され、Base64UrlTextEncoder でエンコードされた後、チャンクされます。 cookie の最大サイズは、暗号化とチャンクのため、4096 バイト未満です。 暗号化されているデータを圧縮すると、CRIME 攻撃や BREACH 攻撃など、セキュリティ上の問題を起す可能性があるため、cookie データは圧縮されません。 cookie ベース TempData プロバイダーの詳細については、CookieTempDataProvider に関する記事を参照してください。

TempData プロバイダーを選択する

TempData プロバイダーを選択するときの考慮事項:

  • アプリは既にセッション状態を使っているかどうか。 その場合、データのサイズを超えて、セッション状態 TempData プロバイダーがそのアプリにコストを追加することはありません。
  • アプリでは、比較的少量のデータに対して (最大 500 バイト) TempData がわずかばかり使用されているか。 該当する場合、cookie TempData プロバイダーによって TempData を送信する要求ごとに少額のコストが追加されます。 該当しない場合、セッション状態 TempData プロバイダーは便利かもしれません。TempData が尽きるまで、要求のたびに大量のデータをラウンドトリップすることが回避されます。
  • アプリは複数サーバーのサーバー ファームで実行しているか。 そうである場合は、データ保護の外部で cookie TempData プロバイダーを使用するために、追加の構成は必要ありません。 詳細については、「ASP.NET Core のデータ保護の概要」とキー ストレージ プロバイダーに関する記事を参照してください。

Web ブラウザーなどのほとんどの Web クライアントによって、各 cookie の最大サイズと cookie の合計数に上限が設けられます。 cookie TempData プロバイダーを使用するとき、アプリでそれらの上限が超えないことを確認してください。 データの合計サイズを考慮してください。 暗号化とチャンクによる cookie のサイズの増加を考慮してください。

TempData プロバイダーを構成する

cookie ベース TempData プロバイダーは既定で有効になります。

セッション ベースの TempData プロバイダーを有効にするには、AddSessionStateTempDataProvider 拡張メソッドを使います。 AddSessionStateTempDataProvider の呼び出しは 1 つだけ必要です。

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
                    .AddSessionStateTempDataProvider();
builder.Services.AddControllersWithViews()
                    .AddSessionStateTempDataProvider();

builder.Services.AddSession();

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.UseSession();

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

app.Run();

クエリ文字列

限られた量のデータを要求間で渡すことができます。新しい要求のクエリ文字列にそれを追加します。 これは、リンクと埋め込まれた状態がメールまたはソーシャル ネットワークを通して共有されるよう、永久的に状態をキャプチャするのに役立ちます。 URL クエリ文字列はパブリックであるため、機密データにはクエリ文字列を使わないでください。

意図しない共有に加えて、クエリ文字列にデータを含めると、アプリがクロスサイト リクエスト フォージェリ (CSRF) 攻撃に晒される可能性があります。 保存されたセッション状態を CSRF 攻撃から保護する必要があります。 詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

非表示フィールド

データは非表示フォーム フィールドに保存したり、次の要求で転記したりできます。 複数ページ フォームでは、これは一般的です。 クライアントがデータを改ざんする可能性があるため、アプリで非表示フィールドに格納されているデータを常に再検証する必要があります。

HttpContext.Items

1 つの要求を処理している間にデータを格納するには、HttpContext.Items コレクションを使います。 要求が処理された後、コレクションの内容は破棄されます。 Items コレクションは、コンポーネントまたはミドルウェアが要求の間の異なる時点で動作し、パラメーターを受け渡す直接的な方法がない場合に、コンポーネントとミドルウェアが通信できるようにするためによく使われます。

次の例では、ミドルウェアにより isVerifiedItems コレクションに追加されます。

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

ILogger logger = app.Logger;

app.Use(async (context, next) =>
{
    // context.Items["isVerified"] is null
    logger.LogInformation($"Before setting: Verified: {context.Items["isVerified"]}");
    context.Items["isVerified"] = true;
    await next.Invoke();
});

app.Use(async (context, next) =>
{
    // context.Items["isVerified"] is true
    logger.LogInformation($"Next: Verified: {context.Items["isVerified"]}");
    await next.Invoke();
});

app.MapGet("/", async context =>
{
    await context.Response.WriteAsync($"Verified: {context.Items["isVerified"]}");
});

app.Run();

1 つのアプリでのみ使用されるミドルウェアの場合、固定の string キーを使用するとキーの競合が発生する可能性は低くなります。 ただし、キーの競合の可能性を完全に回避するために、object を項目キーとして使用できます。 このアプローチは、アプリ間で共有されるミドルウェアに特に役立ちます。また、コード内でキー文字列の使用が排除される利点もあります。 次の例では、ミドルウェア クラスで定義されている object キーを使用する方法を示します。

public class HttpContextItemsMiddleware
{
    private readonly RequestDelegate _next;
    public static readonly object HttpContextItemsMiddlewareKey = new();

    public HttpContextItemsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        httpContext.Items[HttpContextItemsMiddlewareKey] = "K-9";

        await _next(httpContext);
    }
}

public static class HttpContextItemsMiddlewareExtensions
{
    public static IApplicationBuilder 
        UseHttpContextItemsMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<HttpContextItemsMiddleware>();
    }
}

その他のコードは、ミドルウェア クラスで公開されるキーを利用し、HttpContext.Items に格納されている値にアクセスできます。

public class Index2Model : PageModel
{
    private readonly ILogger<Index2Model> _logger;

    public Index2Model(ILogger<Index2Model> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        HttpContext.Items
            .TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey,
                out var middlewareSetValue);

        _logger.LogInformation("Middleware value {MV}",
            middlewareSetValue?.ToString() ?? "Middleware value not set!");
    }
}

キャッシュ

キャッシュは、データを保存し、取得する効率的な方法です。 アプリでは、キャッシュされた項目の有効期間を制御できます。 詳細については、「ASP.NET Core の応答キャッシュ」を参照してください。

キャッシュされたデータは、特定の要求、ユーザー、またはセッションに関連付けられていません。 他のユーザーの要求によって取得される可能性があるので、ユーザー固有データをキャッシュしないでください。

アプリケーション全体のデータをキャッシュするには、「ASP.NET Core のメモリ内キャッシュ」を参照してください。

セッション状態の確認

ISession.IsAvailable は、一時的なエラーを確認するためのものです。 セッション ミドルウェアの実行前に IsAvailable を呼び出すと、InvalidOperationException がスローされます。

セッションの可用性をテストする必要があるライブラリには HttpContext.Features.Get<ISessionFeature>()?.Session != null を使用できます。

一般的なエラー

  • "'Microsoft.AspNetCore.Session.DistributedSessionStore' を起動しようとしましたが、型 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' のサービスを解決できません。"

    これは通常、少なくとも 1 つの IDistributedCache 実装で構成に失敗したことで発生します。 詳細については、「ASP.NET Core の分散キャッシュ」および「ASP.NET Core のメモリ内キャッシュ」を参照してください。

セッション ミドルウェアがセッションを永続化できない場合:

  • ミドルウェアは例外をログに記録し、要求は普通に続行されます。
  • これにより、予期しない動作が発生します。

バッキング ストアを利用できない場合、セッション ミドルウェアがセッションを永続化できないことがあります。 たとえば、ユーザーがセッションでショッピング カートを格納します。 ユーザーはアイテムをカートに追加しますが、コミットが失敗します。 アプリはこの失敗を認識しないので、アイテムがカートに追加されたことをユーザーに伝えますが、これは正しくありません。

エラーを確認するための推奨される方法は、アプリがセッションへの書き込みを終了したら、await feature.Session.CommitAsync を呼び出すことです。 バッキング ストアが利用できない場合、CommitAsync は例外をスローします。 CommitAsync が失敗した場合、アプリは例外を処理できます。 LoadAsync は、データ ストアが利用できない場合に同じ条件でスローします。

その他のリソース

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

Web ファームでの ASP.NET Core のホスト

作成者: Rick AndersonKirk Larkin、および Diana LaRose

HTTP はステートレス プロトコルです。 既定で、HTTP 要求は独立したメッセージであり、ユーザーの値は保持されません。 この記事では、要求間でユーザー データを保持するためのいくつかの方法について説明します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

状態管理

状態は、いくつかの方法で格納することができます。 この記事ではそれぞれの方法について説明します。

格納の方法 格納のメカニズム
Cookies HTTP cookie。 サーバー側アプリ コードを使って格納されたデータを含めることができます。
セッション状態 HTTP cookie とサーバー側アプリ コード
TempData HTTP cookie またはセッション状態
クエリ文字列 HTTP クエリ文字列
非表示フィールド HTTP フォーム フィールド
HttpContext.Items サーバー側アプリ コード
キャッシュ サーバー側アプリ コード

SignalR/Blazor Server および HTTP コンテキストベースの状態管理

SignalR アプリでは、安定した HTTP コンテキストに依存して情報を格納する、セッション状態やその他の状態管理アプローチを使用しないでください。 SignalR アプリは、ハブ内の Context.Items に接続ごとの状態を格納できます。 Blazor Server アプリに関する詳しい情報と代替状態管理アプローチについては、「ASP.NET Core Blazor 状態管理」を参照してください。

Cookies

Cookie には、要求と要求の間でデータが格納されます。 cookie は要求ごとに送信されるために、そのサイズは最小に抑える必要があります。 理想的には、識別子だけを cookie に格納し、データはアプリで格納します。 ほとんどのブラウザーで cookie のサイズは 4096 バイトに制限されています。 ドメインごとに使用できる cookie の数も制限されています。

cookie は改ざんされる可能性があるため、アプリで検証する必要があります。 Cookie は、ユーザーが削除でき、クライアント上で期限切れになります。 ただし、cookie は一般に、クライアントでデータを永続化するときに最も持続性のある形式です。

Cookie は、多くの場合、パーソナル化に利用されます。既知のユーザーのためにコンテンツをカスタマイズします。 ユーザーは識別されるだけであり、ほとんどの場合は認証されません。 cookie には、ユーザーの名前、アカウント名、または GUID などの一意のユーザー ID を格納できます。 cookie は、好みの Web サイトの背景色など、ユーザーの個人用設定にアクセスするために使用できます。

cookie を発行し、プライバシーの問題を扱うときは、欧州連合の一般データ保護規則 (GDPR) を参照してください。 詳細については、「General Data Protection Regulation (GDPR) support in ASP.NET Core」(ASP.NET Core での一般データ保護規則 (GDPR) のサポート) をご覧ください。

セッション状態

セッション状態は、ユーザーが Web アプリを参照している期間中ユーザー データを格納するための ASP.NET Core のシナリオです。 セッション状態では、アプリで管理されているストアを使用して、クライアントからの要求間でデータを保持します。 セッション データはキャッシュによってバックアップされ、一時的なデータと見なされます。 セッション データがなくても、サイトは機能し続けられる必要があります。 重要なアプリケーションのデータはユーザー データベースに格納し、パフォーマンスの最適化としてのみセッションでキャッシュする必要があります。

SignalR ハブは HTTP コンテキストとは独立して実行する可能性があるため、SignalR アプリではセッションはサポートされていません。 このようなことは、たとえば、長いポーリング要求が HTTP コンテキストの有効期間を超えてハブによって開かれている場合に発生する可能性があります。

ASP.NET Core により、セッション ID を含む cookie がクライアントに提供されて、セッションの状態が維持されます。 cookie セッション ID:

  • 各要求でアプリに送信されます。
  • アプリによってセッション データをフェッチするために使用されます。

セッション状態は次の動作を示します。

  • セッション cookie は、ブラウザーに固有です。 セッションはブラウザー間で共有されません。
  • セッション cookie は、ブラウザー セッションが終了するときに削除されます。
  • cookie を受け取り、セッションが期限切れになった場合、同じセッション cookie を使用する新しいセッションが作成されます。
  • 空のセッションは保持されません。 要求間でセッションを維持するには、少なくとも 1 つの値をセッションが持っている必要があります。 セッションが保持されないと、新しい要求ごとに新しいセッション ID が生成されます。
  • アプリは、最後の要求から限られた時間だけセッションを維持します。 アプリでは、セッション タイムアウトを設定するか、既定値の 20 分を使用します。 セッション状態は、次のユーザー データの格納に最適です。
    • 特定のセッションに固有である。
    • データがセッション間で永続的に保存される必要がない。
  • セッション データは、ISession.Clear の実装が呼び出されるか、セッションが期限切れになると、削除されます。
  • クライアント ブラウザーが閉じられたこと、またはクライアントでセッション cookie が削除されるか期限切れになったことを、アプリ コードに通知する既定のメカニズムはありません。
  • セッション状態の cookie は既定では必須としてマークされていません。 サイトの訪問者が追跡を許可しない限り、セッション状態は機能しません。 詳細については、「General Data Protection Regulation (GDPR) support in ASP.NET Core」(ASP.NET Core での一般データ保護規則 (GDPR) のサポート) をご覧ください。

警告

セッション状態には機密データを保存しないでください。 ユーザーがブラウザーを閉じず、セッション cookie がクリアされない可能性があります。 一部のブラウザーでは、ブラウザーのウィンドウ間で有効なセッションの cookie が維持されます。 セッションが 1 人のユーザーに制限されないことがあります。 次のユーザーが、同じセッション cookie でアプリを閲覧し続けることがあります。

メモリ内キャッシュ プロバイダーは、アプリが存在するサーバーのメモリにセッション データを格納します。 サーバー ファームのシナリオでは次のようになります。

  • "固定セッション" を使用して、個々のサーバー上の特定のアプリのインスタンスに、各セッションを結び付けます。 Azure App Serviceアプリケーション要求ルーティング処理 (ARR) を使って、既定で固定セッションを強制的に使用します。 ただし、固定セッションは拡張性に影響を与え、Web アプリの更新を複雑にすることがあります。 もっとよい方法は、Redis または SQL Server の分散キャッシュを使用することで、固定セッションを必要としません。 詳細については、「ASP.NET Core の分散キャッシュ」を参照してください。
  • セッション cookie は IDataProtector によって暗号化されます。 各コンピューターでセッション cookie が読み取られるようにするには、データ保護を適切に構成する必要があります。 詳細については、「ASP.NET Core のデータ保護の概要」とキー ストレージ プロバイダーに関する記事を参照してください。

セッション状態を構成する

Microsoft.AspNetCore.Session パッケージは:

  • フレームワークによって暗黙的に含まれます。
  • セッション状態を管理するためのミドルウェアを提供します。

セッション ミドルウェアを有効にするには、Startup に次が含まれている必要があります。

次のコードでは、IDistributedCache の既定のメモリ内実装でメモリ内セッション プロバイダーを設定する方法を示します。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDistributedMemoryCache();

        services.AddSession(options =>
        {
            options.IdleTimeout = TimeSpan.FromSeconds(10);
            options.Cookie.HttpOnly = true;
            options.Cookie.IsEssential = true;
        });

        services.AddControllersWithViews();
        services.AddRazorPages();
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseSession();

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

前のコードでは、テストを簡単にするために短いタイムアウトを設定しています。

ミドルウェアの順序が重要です。 UseSessionUseRouting の後、かつ UseEndpoints の前に呼び出します。 ミドルウェアの順序付けに関するページを参照してください。

HttpContext.Session は、セッション状態を構成した後で使用できます。

UseSession を呼び出前に HttpContext.Session にアクセスすることはできません。

アプリが応答ストリームへの書き込みを開始した後では、新しいセッション cookie を含む新しいセッションを作成できません。 例外は Web サーバー ログに記録され、ブラウザーには表示されません。

セッション状態を非同期的に読み込む

ASP.NET Core の既定のセッション プロバイダーでは、TryGetValueSet、または Remove メソッドの前に ISession.LoadAsync メソッドが明示的に呼び出された場合にのみ、基になる IDistributedCache バッキング ストアから非同期的にセッション レコードを読み込みます。 LoadAsync を最初に呼び出さないと、基になっているセッション レコードは同期的に読み込まれ、パフォーマンスが大幅に低下する可能性があります。

アプリにこのパターンを強制させるには、TryGetValueSet、または Remove の前に LoadAsync メソッドが呼び出されない場合に例外をスローするバージョンで、DistributedSessionStore および DistributedSession の実装をラップします。 ラップしたバージョンをサービス コンテナーに登録します。

セッション オプション

セッションの既定値をオーバーライドするには、SessionOptions を使用します。

オプション 説明
Cookie cookie の作成に使用される設定を決定します。 Name の既定値は SessionDefaults.CookieName (.AspNetCore.Session) です。 Path の既定値は SessionDefaults.CookiePath (/) です。 SameSite の既定値は SameSiteMode.Lax (1) です。 HttpOnly では、既定値が true に設定されます。 IsEssential では、既定値が false に設定されます。
IdleTimeout IdleTimeout は、内容を破棄されることなくセッションがアイドル状態になっていることのできる最大時間を示します。 セッションへのアクセスがあるたびに、タイムアウトはリセットされます。 この設定はセッションの内容にのみ適用され、cookie には適用されません。 既定値は 20 分です。
IOTimeout ストアからのセッションの読み込み、またはストアに戻すコミットに対して許容される最大時間です。 この設定は非同期操作にのみ適用できます。 このタイムアウトは、InfiniteTimeSpan を使用して無効にすることができます。 既定値は 1 分です。

セッションは cookie を利用し、1 つのブラウザーからの要求を追跡し、識別します。 既定では、この cookie は .AspNetCore.Session という名前であり、パス / が使用されます。 cookie の既定値ではドメインが指定されないため、ページのクライアント側スクリプトには使用できません (HttpOnly の既定値が true になるため)。

cookie セッションの既定値をオーバーライドするには、SessionOptions を使用します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedMemoryCache();

    services.AddSession(options =>
    {
        options.Cookie.Name = ".AdventureWorks.Session";
        options.IdleTimeout = TimeSpan.FromSeconds(10);
        options.Cookie.IsEssential = true;
    });

    services.AddControllersWithViews();
    services.AddRazorPages();
}

アプリでは、IdleTimeout プロパティを使用して、サーバーのキャッシュ内の内容が破棄されるまでのセッションのアイドル時間が決定されます。 このプロパティは cookie の有効期限に依存しません。 要求がセッション ミドルウェアを通過するたびにタイムアウトがリセットされます。

セッション状態は "ロックなし" です。 2 つの要求がセッションの内容を同時に変更しようとした場合、最後の要求が最初の要求をオーバーライドします。 Session一貫性のあるセッションとして実装されます。つまり、コンテンツは全部まとめて保管されます。 2 つの要求が異なるセッション値を変更しようとしたとき、最後の要求が最初の要求によって行われたセッションの変更をオーバーライドすることがあります。

セッション値の設定および取得

セッション状態にアクセスするには、Razor Pages の PageModel クラスか、MVC の Controller クラスを HttpContext.Session と共に使用します。 このプロパティは ISession の実装です。

ISession の実装では、整数値や文字列値を設定および取得するための複数の拡張メソッドが提供されています。 拡張メソッドは Microsoft.AspNetCore.Http 名前空間にあります。

ISession 拡張メソッド:

次の例では、IndexModel.SessionKeyName キー (サンプル アプリ内の _Name) のセッション値を Razor Pages ページで取得します。

@page
@using Microsoft.AspNetCore.Http
@model IndexModel

...

Name: @HttpContext.Session.GetString(IndexModel.SessionKeyName)

次の例では、整数および文字列を設定および取得する方法を示します。

public class IndexModel : PageModel
{
    public const string SessionKeyName = "_Name";
    public const string SessionKeyAge = "_Age";
    const string SessionKeyTime = "_Time";

    public string SessionInfo_Name { get; private set; }
    public string SessionInfo_Age { get; private set; }
    public string SessionInfo_CurrentTime { get; private set; }
    public string SessionInfo_SessionTime { get; private set; }
    public string SessionInfo_MiddlewareValue { get; private set; }

    public void OnGet()
    {
        // Requires: using Microsoft.AspNetCore.Http;
        if (string.IsNullOrEmpty(HttpContext.Session.GetString(SessionKeyName)))
        {
            HttpContext.Session.SetString(SessionKeyName, "The Doctor");
            HttpContext.Session.SetInt32(SessionKeyAge, 773);
        }

        var name = HttpContext.Session.GetString(SessionKeyName);
        var age = HttpContext.Session.GetInt32(SessionKeyAge);

分散キャッシュのシナリオを有効にするには、メモリ内キャッシュを使用する場合であっても、すべてのセッション データをシリアル化する必要があります。 文字列と整数のシリアライザーは、ISession の拡張メソッドによって提供されます。 複合型は、JSON などの別のメカニズムを使ってユーザーがシリアル化する必要があります。

次のサンプル コードを使用して、オブジェクトをシリアル化します。

public static class SessionExtensions
{
    public static void Set<T>(this ISession session, string key, T value)
    {
        session.SetString(key, JsonSerializer.Serialize(value));
    }

    public static T Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);
        return value == null ? default : JsonSerializer.Deserialize<T>(value);
    }
}

次の例では、シリアル化可能オブジェクトを SessionExtensions クラスで設定および取得する方法を示します。

// Requires SessionExtensions from sample download.
if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default)
{
    HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
}

TempData

ASP.NET Core によって、Razor Pages TempData または Controller TempData が発行されます。 このプロパティには、別の要求で読み取られるまでデータが格納されます。 Keep (String) メソッドと Peek (String) メソッドを使用すると、要求の最後に削除せずにデータを調べることができます。 Keep によって、リテンション期間についてディクショナリ内のすべての項目がマークされます。 TempData は次のようになります。

  • 複数の要求に対してデータが必要な場合のリダイレクトに役立ちます。
  • TempData プロバイダーによって cookie またはセッション状態を使用して実装されます。

TempData のサンプル

顧客を作成する次のページについて考えてみましょう。

public class CreateModel : PageModel
{
    private readonly RazorPagesContactsContext _context;

    public CreateModel(RazorPagesContactsContext context)
    {
        _context = context;
    }

    public IActionResult OnGet()
    {
        return Page();
    }

    [TempData]
    public string Message { get; set; }

    [BindProperty]
    public Customer Customer { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";

        return RedirectToPage("./IndexPeek");
    }
}

次のページに TempData["Message"] が表示されます。

@page
@model IndexModel

<h1>Peek Contacts</h1>

@{
    if (TempData.Peek("Message") != null)
    {
        <h3>Message: @TempData.Peek("Message")</h3>
    }
}

@*Content removed for brevity.*@

前のマークアップでは、Peek が使用されているため、要求の最後に TempData["Message"] が削除されません。 ページを更新すると TempData["Message"] のコンテンツが表示されます。

次のマークアップは前のコードと似ていますが、Keep を使用して要求の最後にデータを保存しています。

@page
@model IndexModel

<h1>Contacts Keep</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
    TempData.Keep("Message");
}

@*Content removed for brevity.*@

Indexpeek ページと IndexKeep ページ間を移動しても TempData["Message"] は削除されません。

次のコードでは TempData["Message"] が表示されますが、要求の最後に TempData["Message"] が削除されます。

@page
@model IndexModel

<h1>Index no Keep or Peek</h1>

@{
    if (TempData["Message"] != null)
    {
        <h3>Message: @TempData["Message"]</h3>
    }
}

@*Content removed for brevity.*@

TempData プロバイダー

cookie に TempData を格納するには、既定では、cookie ベースの TempData プロバイダーが使われます。

cookie データは、IDataProtector を使用して暗号化され、Base64UrlTextEncoder でエンコードされた後、チャンクされます。 cookie の最大サイズは、暗号化とチャンクのため、4096 バイト未満です。 暗号化されているデータを圧縮すると、CRIME 攻撃や BREACH 攻撃など、セキュリティ上の問題を起す可能性があるため、cookie データは圧縮されません。 cookie ベース TempData プロバイダーの詳細については、CookieTempDataProvider に関する記事を参照してください。

TempData プロバイダーを選択する

TempData プロバイダーを選択するときの考慮事項:

  • アプリは既にセッション状態を使っているかどうか。 その場合、データのサイズを超えて、セッション状態 TempData プロバイダーがそのアプリにコストを追加することはありません。
  • アプリでは、比較的少量のデータに対して (最大 500 バイト) TempData がわずかばかり使用されているか。 該当する場合、cookie TempData プロバイダーによって TempData を送信する要求ごとに少額のコストが追加されます。 該当しない場合、セッション状態 TempData プロバイダーは便利かもしれません。TempData が尽きるまで、要求のたびに大量のデータをラウンドトリップすることが回避されます。
  • アプリは複数サーバーのサーバー ファームで実行しているか。 そうである場合は、データ保護の外部で cookie TempData プロバイダーを使用するために、追加の構成は必要ありません (「ASP.NET Core のデータ保護の概要」とキー ストレージ プロバイダーに関する記事を参照)。

Web ブラウザーなどのほとんどの Web クライアントによって、各 cookie の最大サイズと cookie の合計数に上限が設けられます。 cookie TempData プロバイダーを使用するとき、アプリでそれらの上限が超えないことを確認してください。 データの合計サイズを考慮してください。 暗号化とチャンクによる cookie のサイズの増加を考慮してください。

TempData プロバイダーを構成する

cookie ベース TempData プロバイダーは既定で有効になります。

セッション ベースの TempData プロバイダーを有効にするには、AddSessionStateTempDataProvider 拡張メソッドを使います。 AddSessionStateTempDataProvider の呼び出しは 1 つだけ必要です。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews()
        .AddSessionStateTempDataProvider();
    services.AddRazorPages()
        .AddSessionStateTempDataProvider();

    services.AddSession();
}

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

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseSession();

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

クエリ文字列

限られた量のデータを要求間で渡すことができます。新しい要求のクエリ文字列にそれを追加します。 これは、リンクと埋め込まれた状態がメールまたはソーシャル ネットワークを通して共有されるよう、永久的に状態をキャプチャするのに役立ちます。 URL クエリ文字列はパブリックであるため、機密データにはクエリ文字列を使わないでください。

意図しない共有に加えて、クエリ文字列にデータを含めると、アプリがクロスサイト リクエスト フォージェリ (CSRF) 攻撃に晒される可能性があります。 保存されたセッション状態を CSRF 攻撃から保護する必要があります。 詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

非表示フィールド

データは非表示フォーム フィールドに保存したり、次の要求で転記したりできます。 複数ページ フォームでは、これは一般的です。 クライアントがデータを改ざんする可能性があるため、アプリで非表示フィールドに格納されているデータを常に再検証する必要があります。

HttpContext.Items

1 つの要求を処理している間にデータを格納するには、HttpContext.Items コレクションを使います。 要求が処理された後、コレクションの内容は破棄されます。 Items コレクションは、コンポーネントまたはミドルウェアが要求の間の異なる時点で動作し、パラメーターを受け渡す直接的な方法がない場合に、コンポーネントとミドルウェアが通信できるようにするためによく使われます。

次の例では、ミドルウェアにより isVerifiedItems コレクションに追加されます。

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.UseRouting();

    app.Use(async (context, next) =>
    {
        logger.LogInformation($"Before setting: Verified: {context.Items["isVerified"]}");
        context.Items["isVerified"] = true;
        await next.Invoke();
    });

    app.Use(async (context, next) =>
    {
        logger.LogInformation($"Next: Verified: {context.Items["isVerified"]}");
        await next.Invoke();
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync($"Verified: {context.Items["isVerified"]}");
        });
    });
}

1 つのアプリでのみ使用されるミドルウェアの場合、固定の string キーを使用できます。 アプリ間で共有されるミドルウェアでは、キーの競合を避けるため、一意のオブジェクト キーを使う必要があります。 次の例では、ミドルウェア クラスで定義されている一意のオブジェクト キーを使用する方法を示します。

public class HttpContextItemsMiddleware
{
    private readonly RequestDelegate _next;
    public static readonly object HttpContextItemsMiddlewareKey = new Object();

    public HttpContextItemsMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        httpContext.Items[HttpContextItemsMiddlewareKey] = "K-9";

        await _next(httpContext);
    }
}

public static class HttpContextItemsMiddlewareExtensions
{
    public static IApplicationBuilder 
        UseHttpContextItemsMiddleware(this IApplicationBuilder app)
    {
        return app.UseMiddleware<HttpContextItemsMiddleware>();
    }
}

その他のコードは、ミドルウェア クラスで公開されるキーを利用し、HttpContext.Items に格納されている値にアクセスできます。

HttpContext.Items
    .TryGetValue(HttpContextItemsMiddleware.HttpContextItemsMiddlewareKey, 
        out var middlewareSetValue);
SessionInfo_MiddlewareValue = 
    middlewareSetValue?.ToString() ?? "Middleware value not set!";

この方法には、コード内でキーの文字列を使用する必要がないという利点もあります。

キャッシュ

キャッシュは、データを保存し、取得する効率的な方法です。 アプリでは、キャッシュされた項目の有効期間を制御できます。 詳細については、「ASP.NET Core の応答キャッシュ」を参照してください。

キャッシュされたデータは、特定の要求、ユーザー、またはセッションに関連付けられていません。 他のユーザーの要求によって取得される可能性があるので、ユーザー固有データをキャッシュしないでください。

アプリケーション全体のデータをキャッシュするには、「ASP.NET Core のメモリ内キャッシュ」を参照してください。

一般的なエラー

  • "'Microsoft.AspNetCore.Session.DistributedSessionStore' を起動しようとしましたが、型 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' のサービスを解決できません。"

    これは通常、少なくとも 1 つの IDistributedCache 実装で構成に失敗したことで発生します。 詳細については、「ASP.NET Core の分散キャッシュ」および「ASP.NET Core のメモリ内キャッシュ」を参照してください。

セッション ミドルウェアがセッションを永続化できない場合:

  • ミドルウェアは例外をログに記録し、要求は普通に続行されます。
  • これにより、予期しない動作が発生します。

バッキング ストアを利用できない場合、セッション ミドルウェアがセッションを永続化できないことがあります。 たとえば、ユーザーがセッションでショッピング カートを格納します。 ユーザーはアイテムをカートに追加しますが、コミットが失敗します。 アプリはこの失敗を認識しないので、アイテムがカートに追加されたことをユーザーに伝えますが、これは正しくありません。

エラーを確認するための推奨される方法は、アプリがセッションへの書き込みを終了したら、await feature.Session.CommitAsync を呼び出すことです。 バッキング ストアが利用できない場合、CommitAsync は例外をスローします。 CommitAsync が失敗した場合、アプリは例外を処理できます。 LoadAsync は、データ ストアが利用できない場合に同じ条件でスローします。

その他の技術情報

Web ファームでの ASP.NET Core のホスト