Управление сеансами и состоянием в ASP.NET Core

Авторы: Рик Андерсон (Rick Anderson), Кирк Ларкин (Kirk Larkin) и Диана Лароуз (Diana LaRose)

HTTP — это протокол без отслеживания состояния. По умолчанию HTTP-запросы являются независимыми сообщениями, которые не сохраняют значения пользователя. В этой статье описывается несколько подходов для сохранения данных пользователя между запросами.

Управление данными о состоянии

Состояние можно сохранить несколькими способами. Каждый подход описан далее в этой статье.

Способ хранения данных Механизм хранения
Cookies Файлы cookie HTTP. Могут содержать данные, сохраненные с помощью кода приложения на стороне сервера.
Состояние сеанса Файлы cookie HTTP и код приложения на стороне сервера
TempData Файлы cookie HTTP или состояние сеанса
Строки запросов Строки запросов HTTP
Скрытые поля Поля формы HTTP
HttpContext.Items Код приложения на стороне сервера
Cache Код приложения на стороне сервера

SignalR/Blazor Server и управление контекстом HTTP

SignalR приложения не должны использовать состояние сеанса и другие подходы к управлению состоянием, основанные на стабильном контексте HTTP для хранения информации. SignalR приложения могут хранить состояние подключения в Context.Items концентраторе. Дополнительные сведения и альтернативные подходы к управлению состояниями для Blazor Server приложений см. в разделе ASP.NET Управление состоянием CoreBlazor.

Cookies

Файлы Cookie хранят данные между запросами. Так как файлы cookie отправляются с каждым запросом, их размер нужно свести к минимуму. В идеальном случае файл cookie должен содержать лишь идентификатор, а сами данные хранятся в приложении. Большинство браузеров позволяют использовать файлы cookie размером до 4096 байт. Для каждого домена доступно лишь ограниченное число файлов cookie.

Так как файлы cookie могут быть незаконно изменены, их нужно проверять в приложении. Файлы Cookie могут удаляться пользователем и имеют ограниченный срок хранения на клиентах. Тем не менее файлы cookie обычно являются самым надежным способом хранения данных на стороне клиента.

Файлы Cookie часто используются для персонализации, когда содержимое настраивается под известного пользователя. В большинстве случаев пользователь проходит только идентификацию, а не проверку подлинности. Файл cookie может хранить имя пользователя, имя учетной записи или уникальный идентификатор пользователя, например GUID. Можно использовать файл cookie для доступа к личным настройкам пользователя, таким как цвет фона на веб-сайте.

См. Общий регламент по защите данных в ЕС (GDPR) при создании файлов cookie и решении вопросов с конфиденциальностью. Дополнительные сведения см. в разделе Общий регламент по защите данных (GDPR), принятый в ЕС, в ASP.NET Core.

Состояние сеанса

Состояние сеанса — это сценарий ASP.NET Core для хранения пользовательских данных, когда пользователь находится в веб-приложении. Состояние сеанса использует хранилище, обслуживаемое приложением, для сохранения данных между запросами от клиента. Данные сеанса поддерживаются кэшем и считаются временными. Сайт должен продолжать работать без данных сеанса. Критически важные данные приложения должны храниться в пользовательской базе данных и кэшироваться в сеансе только в качестве оптимизации производительности.

Сеанс не поддерживается в приложениях SignalR, так как концентратор SignalR может работать независимо от контекста HTTP. Например, это может произойти, если хаб поддерживает длительный запрос на опрос открытым, когда время существования контекста HTTP для запроса уже истекло.

ASP.NET Core сохраняет состояние сеанса, предоставляя клиенту файл cookie, содержащий идентификатор сеанса. Идентификатор файла cookie сеанса.

  • Отправляется в приложение с каждым запросом.
  • Используется приложением для получения данных сеанса.

Состояние сеанса реагирует на события следующим образом:

  • Файл cookie сеанса относится к браузеру. Сеансы не используются в разных браузерах.
  • Файлы cookie сеанса удаляются при завершении сеанса браузера.
  • Если получен файл cookie для просроченного сеанса, создается сеанс, использующий этот же файл cookie.
  • Пустые сеансы не сохраняются. В сеансе должно быть хотя бы одно значение, чтобы его можно быть сохранить между запросами. Если сеанс не сохраняется, для каждого нового запроса создается новый идентификатор сеанса.
  • Приложение хранит сеанс некоторое время после последнего запроса. Приложение устанавливает время ожидания сеанса или использует значение по умолчанию, равное 20 минутам. Состояние сеанса идеально подходит для хранения пользовательских данных:
    • Если они относятся к конкретному сеансу.
    • Если данные не нуждаются в постоянном хранении между сеансами.
  • Данные сеанса удаляются при вызове реализации ISession.Clear или когда истекает срок действия сеанса.
  • Не существует механизма по умолчанию, который бы информировал код приложения о том, что браузер клиента закрыт или файл cookie сеанса удален или просрочен на клиенте.
  • Файлы cookie состояния сеанса по умолчанию не отмечены как основные. Состояние сеанса не работает, если отслеживание не разрешено посетителем сайта. Дополнительные сведения см. в разделе Общий регламент по защите данных (GDPR), принятый в ЕС, в ASP.NET Core.
  • Примечание. Не существует замены функции меньшего cookieсеанса из ASP платформа .NET Framework поскольку она считается небезопасной и может привести к атакам фиксации сеансов.

Предупреждение

Не храните конфиденциальные данные в состоянии сеанса. Пользователь может не закрывать браузер и очистить файл cookie сеанса. Некоторые браузеры сохраняют файлы cookie сеанса в нескольких окнах браузера. Сеанс может быть не ограничен одним пользователем. Следующий пользователь продолжит использовать приложение с тем же файлом cookie сеанса.

Поставщик кэша в памяти хранит данные сеанса в памяти сервера, содержащего приложение. В сценарии с фермой серверов:

Настройка состояния сеанса

ПО промежуточного слоя для управления состоянием сеанса включается в платформу. Чтобы включить сеанс ПО промежуточного слоя для сеансов, 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();

Приведенный выше код задает короткий тайм-аут для упрощения тестирования.

Порядок ПО промежуточного слоя важен. UseSession вызывается после UseRouting, но перед MapRazorPages и MapDefaultControllerRoute. См. Порядок ПО промежуточного слоя.

После настройки состояния сеанса доступно свойство HttpContext.Session.

Невозможно получить доступ к HttpContext.Session до вызова UseSession.

Невозможно создать новый сеанс с новым файлом cookie сеанса после того, как приложение начинает запись в поток ответа. Исключение записывается в журнал веб-сервера и не отображается в браузере.

Асинхронная загрузка состояния сеанса

Поставщик сеансов по умолчанию в ASP.NET Core загружает записи сеанса из базового резервного хранилища IDistributedCache в асинхронном режиме только при явном вызове метода ISession.LoadAsync перед методами TryGetValue, Set или Remove. Если LoadAsync не вызывается первым, базовая запись сеанса загружается синхронно, что может негативно повлиять на производительность.

Чтобы принудительно использовать этот режим в приложениях, включите реализации DistributedSessionStore и DistributedSession в версии, которые выдают исключение, когда метод LoadAsync не вызывается перед TryGetValue, Set или Remove. Зарегистрируйте версии оболочки в контейнере служб.

Параметры сеанса

Чтобы переопределить значения по умолчанию для сеанса, используйте 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 для отслеживания и идентификации запросов в одном браузере. По умолчанию этот файл 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. Каждый запрос, проходящий через ПО промежуточного слоя сеанса, сбрасывает это время ожидания.

Состояние сеанса является неблокирующим. Когда два запроса пытаются изменить содержимое сеанса, последний из них переопределяет первый. Session реализован в виде согласованного сеанса, что означает, что все содержимое хранится вместе. Когда два запроса пытаются изменить разные значения сеанса, последний запрос может переопределить изменения, внесенные первым.

Установка и получение значений сеанса

Доступ к состоянию сеанса осуществляется из класса Razor Pages PageModel или класса MVC Controller с HttpContext.Session. Это свойство является реализацией ISession.

Реализация ISession предоставляет несколько методов расширения для задания и извлечения значений типа integer и string. Методы расширения относятся к пространству имен Microsoft.AspNetCore.Http.

Методы расширения ISession:

В следующем примере извлекается значение сеанса для ключа IndexModel.SessionKeyName (_Name в примере приложения) на странице Razor Pages.

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

...

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

В следующем примере показано, как задать, а затем получить значения integer и string:

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 предоставляет TempDataRazor Pages или 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.*@

В предыдущей разметке в конце запроса TempData["Message"]не удаляется, так как используется Peek. На обновляемой странице отображается содержимое 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 и IndexKeepTempData["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

Поставщик TempData, основанный на файлах cookie, используется по умолчанию для хранения TempData в файлах cookie.

Данные файлов cookie шифруются с помощью IDataProtector с кодировкой с использованием Base64UrlTextEncoder и последующей фрагментацией. Максимальный размер файла cookie меньше 4096 байт из-за шифрования и фрагментирования. Данные cookie не сжимаются, так как сжатие зашифрованных данных может привести к проблемам безопасности, таким как CRIME атаки и BREACH атаки. Дополнительные сведения о поставщике TempData, основанном на файлах cookie, см. здесь: CookieTempDataProvider.

Выбор поставщика TempData

Выбор поставщика TempData включает в себя несколько аспектов:

  • Приложение уже использует состояние сеанса? Если это так, использование поставщика TempData для состояния сеанса не требует от приложения никаких дополнительных издержек, кроме объема данных.
  • Приложение использует TempData лишь ограниченно, для сравнительно небольших объемов данных до 500 байт? Если это так, поставщик TempData на основе файлов cookie незначительно увеличивает издержки для каждого запроса, переносящего TempData. В противном случае поставщик TempData для состояния сеанса удобно использовать, чтобы устранить круговой обход большого объема данных в каждом запросе до полного использования TempData.
  • Приложение выполняется на ферме серверов на нескольких серверах? Если это так, не требуется дополнительная настройка для использования поставщика TempData для файлов cookie, за исключением защиты данных. Дополнительные сведения см. в статье Защита данных в ASP.NET Core и Поставщики хранилища ключей.

Большинство веб-клиентов (например, веб-браузеры) налагают ограничение на максимальный размер каждого файла cookie и общее количество таких файлов cookie. При использовании поставщика TempData на основе файлов cookie убедитесь, что приложение не нарушает эти ограничения. Учитывайте общий объем данных. Учитывайте увеличение размеров файла cookie в результате шифрования и фрагментирования.

Настройка поставщика TempData

Поставщик TempData на основе файлов cookie включен по умолчанию.

Чтобы включить поставщик TempData на основе сеанса, используйте метод расширения AddSessionStateTempDataProvider. Требуется только один вызов AddSessionStateTempDataProvider:

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. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Скрытые поля

Данные можно сохранить в скрытых полях формы и опубликовать обратно при следующем запросе. Это типичное поведение для многостраничных форм. Так как клиент может незаконно изменить данные, приложение всегда должно перепроверять данные в скрытых полях.

HttpContext.Items

Коллекция HttpContext.Items используется для хранения данных при обработке отдельного запроса. Ее содержимое удаляется после обработки каждого запроса. Коллекцию Items часто используют для обеспечения взаимодействия компонентов или ПО промежуточного слоя, когда они выполняются в разные моменты времени во время обработки запроса и не могут передавать параметры напрямую.

В следующем примере ПО промежуточного слоя добавляет isVerified в коллекцию Items:

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();

Для ПО промежуточного слоя, которое используется только в одном приложении, фиксированный ключ 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!");
    }
}

Cache

Кэширование — это эффективный способ хранения и извлечения данных. Приложение может управлять временем существования элементов кэша. Дополнительные сведения см. в статье Кэширование ответов в ASP.NET Core.

Кэшированные данные не связаны с конкретным запросом, пользователем или сеансом. Не кэшируйте данные пользователя, которые можно извлечь по запросу другого пользователя.

Сведения о кэшировании данных для всего приложения см. в статье Кэширование в памяти в ASP.NET Core.

Проверка состояния сеанса

ISession.IsAvailable предназначен для проверка временных сбоев. Вызов IsAvailable до запуска ПО промежуточного слоя сеанса вызывает исключение InvalidOperationException.

Библиотеки, необходимые для тестирования доступности сеанса, могут использовать HttpContext.Features.Get<ISessionFeature>()?.Session != null.

Распространенные ошибки

  • "Unable to resolve service for type "Microsoft.Extensions.Caching.Distributed.IDistributedCache" while attempting to activate "Microsoft.AspNetCore.Session.DistributedSessionStore"" (Не удается разрешить службу для типа "Microsoft.Extensions.Caching.Distributed.IDistributedCache" при попытке активировать "Microsoft.AspNetCore.Session.DistributedSessionStore").

    Обычно она вызвана невозможностью настройки по меньшей мере одной реализации IDistributedCache. Дополнительные сведения см. в статье Распределенное кэширование в ASP.NET Core и Кэширование в памяти в ASP.NET Core.

Если ПО промежуточного слоя сеанса не удается сохранить сеанс:

  • ПО промежуточного слоя регистрирует исключение, и запрос продолжит работу в обычном режиме.
  • Это приводит к непредсказуемому поведению.

ПО промежуточного слоя сеанса может не сохранять сеанс, если резервное хранилище недоступно. Например, пользователь сохраняет корзину для покупок в сеансе. Он добавляет товар в корзину, но фиксация завершается сбоем. Приложению неизвестно о сбое, поэтому оно сообщает пользователю, что товар добавлен в корзину, хотя это не так.

Для проверки на наличие таких ошибок рекомендуется вызывать await feature.Session.CommitAsync по окончании записи в сеанс. CommitAsync создает исключение, если резервное хранилище недоступно. Если CommitAsync завершается ошибкой, приложение может обработать исключение. Исключение LoadAsync возникает в таких же условиях, когда хранилище данных недоступно.

Дополнительные ресурсы

Просмотреть или скачать образец кода (описание загрузки)

Узел ASP.NET Core в веб-ферме

Авторы: Рик Андерсон (Rick Anderson), Кирк Ларкин (Kirk Larkin) и Диана Лароуз (Diana LaRose)

HTTP — это протокол без отслеживания состояния. По умолчанию HTTP-запросы являются независимыми сообщениями, которые не сохраняют значения пользователя. В этой статье описывается несколько подходов для сохранения данных пользователя между запросами.

Просмотреть или скачать образец кода (описание загрузки)

Управление данными о состоянии

Состояние можно сохранить несколькими способами. Каждый подход описан далее в этой статье.

Способ хранения данных Механизм хранения
Cookies Файлы cookie HTTP. Могут содержать данные, сохраненные с помощью кода приложения на стороне сервера.
Состояние сеанса Файлы cookie HTTP и код приложения на стороне сервера
TempData Файлы cookie HTTP или состояние сеанса
Строки запросов Строки запросов HTTP
Скрытые поля Поля формы HTTP
HttpContext.Items Код приложения на стороне сервера
Cache Код приложения на стороне сервера

SignalR/Blazor Server и управление контекстом HTTP

SignalR приложения не должны использовать состояние сеанса и другие подходы к управлению состоянием, основанные на стабильном контексте HTTP для хранения информации. SignalR приложения могут хранить состояние подключения в Context.Items концентраторе. Дополнительные сведения и альтернативные подходы к управлению состояниями для Blazor Server приложений см. в разделе ASP.NET Управление состоянием CoreBlazor.

Cookies

Файлы Cookie хранят данные между запросами. Так как файлы cookie отправляются с каждым запросом, их размер нужно свести к минимуму. В идеальном случае файл cookie должен содержать лишь идентификатор, а сами данные хранятся в приложении. Большинство браузеров позволяют использовать файлы cookie размером до 4096 байт. Для каждого домена доступно лишь ограниченное число файлов cookie.

Так как файлы cookie могут быть незаконно изменены, их нужно проверять в приложении. Файлы Cookie могут удаляться пользователем и имеют ограниченный срок хранения на клиентах. Тем не менее файлы cookie обычно являются самым надежным способом хранения данных на стороне клиента.

Файлы Cookie часто используются для персонализации, когда содержимое настраивается под известного пользователя. В большинстве случаев пользователь проходит только идентификацию, а не проверку подлинности. Файл cookie может хранить имя пользователя, имя учетной записи или уникальный идентификатор пользователя, например GUID. Можно использовать файл cookie для доступа к личным настройкам пользователя, таким как цвет фона на веб-сайте.

См. Общий регламент по защите данных в ЕС (GDPR) при создании файлов cookie и решении вопросов с конфиденциальностью. Дополнительные сведения см. в разделе Общий регламент по защите данных (GDPR), принятый в ЕС, в ASP.NET Core.

Состояние сеанса

Состояние сеанса — это сценарий ASP.NET Core для хранения пользовательских данных, когда пользователь находится в веб-приложении. Состояние сеанса использует хранилище, обслуживаемое приложением, для сохранения данных между запросами от клиента. Данные сеанса поддерживаются кэшем и считаются временными. Сайт должен продолжать работать без данных сеанса. Критически важные данные приложения должны храниться в пользовательской базе данных и кэшироваться в сеансе только в качестве оптимизации производительности.

Сеанс не поддерживается в приложениях SignalR, так как концентратор SignalR может работать независимо от контекста HTTP. Например, это может произойти, если хаб поддерживает длительный запрос на опрос открытым, когда время существования контекста HTTP для запроса уже истекло.

ASP.NET Core сохраняет состояние сеанса, предоставляя клиенту файл cookie, содержащий идентификатор сеанса. Идентификатор файла cookie сеанса.

  • Отправляется в приложение с каждым запросом.
  • Используется приложением для получения данных сеанса.

Состояние сеанса реагирует на события следующим образом:

  • Файл cookie сеанса относится к браузеру. Сеансы не используются в разных браузерах.
  • Файлы cookie сеанса удаляются при завершении сеанса браузера.
  • Если получен файл cookie для просроченного сеанса, создается сеанс, использующий этот же файл cookie.
  • Пустые сеансы не сохраняются. В сеансе должно быть хотя бы одно значение, чтобы его можно быть сохранить между запросами. Если сеанс не сохраняется, для каждого нового запроса создается новый идентификатор сеанса.
  • Приложение хранит сеанс некоторое время после последнего запроса. Приложение устанавливает время ожидания сеанса или использует значение по умолчанию, равное 20 минутам. Состояние сеанса идеально подходит для хранения пользовательских данных:
    • Если они относятся к конкретному сеансу.
    • Если данные не нуждаются в постоянном хранении между сеансами.
  • Данные сеанса удаляются при вызове реализации ISession.Clear или когда истекает срок действия сеанса.
  • Не существует механизма по умолчанию, который бы информировал код приложения о том, что браузер клиента закрыт или файл cookie сеанса удален или просрочен на клиенте.
  • Файлы cookie состояния сеанса по умолчанию не отмечены как основные. Состояние сеанса не работает, если отслеживание не разрешено посетителем сайта. Дополнительные сведения см. в разделе Общий регламент по защите данных (GDPR), принятый в ЕС, в ASP.NET Core.

Предупреждение

Не храните конфиденциальные данные в состоянии сеанса. Пользователь может не закрывать браузер и очистить файл cookie сеанса. Некоторые браузеры сохраняют файлы cookie сеанса в нескольких окнах браузера. Сеанс может быть не ограничен одним пользователем. Следующий пользователь продолжит использовать приложение с тем же файлом cookie сеанса.

Поставщик кэша в памяти хранит данные сеанса в памяти сервера, содержащего приложение. В сценарии с фермой серверов:

Настройка состояния сеанса

Пакет 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();
        });
    }
}

Приведенный выше код задает короткий тайм-аут для упрощения тестирования.

Порядок ПО промежуточного слоя важен. UseSession вызывается после UseRouting, но перед UseEndpoints. См. Порядок ПО промежуточного слоя.

После настройки состояния сеанса доступно свойство HttpContext.Session.

Невозможно получить доступ к HttpContext.Session до вызова UseSession.

Невозможно создать новый сеанс с новым файлом cookie сеанса после того, как приложение начинает запись в поток ответа. Исключение записывается в журнал веб-сервера и не отображается в браузере.

Асинхронная загрузка состояния сеанса

Поставщик сеансов по умолчанию в ASP.NET Core загружает записи сеанса из базового резервного хранилища IDistributedCache в асинхронном режиме только при явном вызове метода ISession.LoadAsync перед методами TryGetValue, Set или Remove. Если LoadAsync не вызывается первым, базовая запись сеанса загружается синхронно, что может негативно повлиять на производительность.

Чтобы принудительно использовать этот режим в приложениях, включите реализации DistributedSessionStore и DistributedSession в версии, которые выдают исключение, когда метод LoadAsync не вызывается перед TryGetValue, Set или Remove. Зарегистрируйте версии оболочки в контейнере служб.

Параметры сеанса

Чтобы переопределить значения по умолчанию для сеанса, используйте 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 для отслеживания и идентификации запросов в одном браузере. По умолчанию этот файл 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. Каждый запрос, проходящий через ПО промежуточного слоя сеанса, сбрасывает это время ожидания.

Состояние сеанса является неблокирующим. Когда два запроса пытаются изменить содержимое сеанса, последний из них переопределяет первый. Session реализован в виде согласованного сеанса, что означает, что все содержимое хранится вместе. Когда два запроса пытаются изменить разные значения сеанса, последний запрос может переопределить изменения, внесенные первым.

Установка и получение значений сеанса

Доступ к состоянию сеанса осуществляется из класса Razor Pages PageModel или класса MVC Controller с HttpContext.Session. Это свойство является реализацией ISession.

Реализация ISession предоставляет несколько методов расширения для задания и извлечения значений типа integer и string. Методы расширения относятся к пространству имен Microsoft.AspNetCore.Http.

Методы расширения ISession:

В следующем примере извлекается значение сеанса для ключа IndexModel.SessionKeyName (_Name в примере приложения) на странице Razor Pages.

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

...

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

В следующем примере показано, как задать, а затем получить значения integer и string:

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 предоставляет TempDataRazor Pages или 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.*@

В предыдущей разметке в конце запроса TempData["Message"]не удаляется, так как используется Peek. На обновляемой странице отображается содержимое 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 и IndexKeepTempData["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

Поставщик TempData, основанный на файлах cookie, используется по умолчанию для хранения TempData в файлах cookie.

Данные файлов cookie шифруются с помощью IDataProtector с кодировкой с использованием Base64UrlTextEncoder и последующей фрагментацией. Максимальный размер файла cookie меньше 4096 байт из-за шифрования и фрагментирования. Данные cookie не сжимаются, так как сжатие зашифрованных данных может привести к проблемам безопасности, таким как CRIME атаки и BREACH атаки. Дополнительные сведения о поставщике TempData, основанном на файлах cookie, см. здесь: CookieTempDataProvider.

Выбор поставщика TempData

Выбор поставщика TempData включает в себя несколько аспектов:

  • Приложение уже использует состояние сеанса? Если это так, использование поставщика TempData для состояния сеанса не требует от приложения никаких дополнительных издержек, кроме объема данных.
  • Приложение использует TempData лишь ограниченно, для сравнительно небольших объемов данных до 500 байт? Если это так, поставщик TempData на основе файлов cookie незначительно увеличивает издержки для каждого запроса, переносящего TempData. В противном случае поставщик TempData для состояния сеанса удобно использовать, чтобы устранить круговой обход большого объема данных в каждом запросе до полного использования TempData.
  • Приложение выполняется на ферме серверов на нескольких серверах? Если это так, дополнительная настройка для использования поставщика TempData для файлов cookie, за исключением защиты данных, не требуется (см. статьи Защита данных в ASP.NET Core и Поставщики хранилища ключей).

Большинство веб-клиентов (например, веб-браузеры) налагают ограничение на максимальный размер каждого файла cookie и общее количество таких файлов cookie. При использовании поставщика TempData на основе файлов cookie убедитесь, что приложение не нарушает эти ограничения. Учитывайте общий объем данных. Учитывайте увеличение размеров файла cookie в результате шифрования и фрагментирования.

Настройка поставщика TempData

Поставщик TempData на основе файлов cookie включен по умолчанию.

Чтобы включить поставщик TempData на основе сеанса, используйте метод расширения AddSessionStateTempDataProvider. Требуется только один вызов AddSessionStateTempDataProvider:

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. Дополнительные сведения см. на странице Предотвращение атак с использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.

Скрытые поля

Данные можно сохранить в скрытых полях формы и опубликовать обратно при следующем запросе. Это типичное поведение для многостраничных форм. Так как клиент может незаконно изменить данные, приложение всегда должно перепроверять данные в скрытых полях.

HttpContext.Items

Коллекция HttpContext.Items используется для хранения данных при обработке отдельного запроса. Ее содержимое удаляется после обработки каждого запроса. Коллекцию Items часто используют для обеспечения взаимодействия компонентов или ПО промежуточного слоя, когда они выполняются в разные моменты времени во время обработки запроса и не могут передавать параметры напрямую.

В следующем примере ПО промежуточного слоя добавляет isVerified в коллекцию Items:

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"]}");
        });
    });
}

Для ПО промежуточного слоя, которое используется всего одним приложением, допустимы фиксированные ключи 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!";

Данный подход также позволяет не использовать строки ключей в коде.

Cache

Кэширование — это эффективный способ хранения и извлечения данных. Приложение может управлять временем существования элементов кэша. Дополнительные сведения см. в статье Кэширование ответов в ASP.NET Core.

Кэшированные данные не связаны с конкретным запросом, пользователем или сеансом. Не кэшируйте данные пользователя, которые можно извлечь по запросу другого пользователя.

Сведения о кэшировании данных для всего приложения см. в статье Кэширование в памяти в ASP.NET Core.

Распространенные ошибки

  • "Unable to resolve service for type "Microsoft.Extensions.Caching.Distributed.IDistributedCache" while attempting to activate "Microsoft.AspNetCore.Session.DistributedSessionStore"" (Не удается разрешить службу для типа "Microsoft.Extensions.Caching.Distributed.IDistributedCache" при попытке активировать "Microsoft.AspNetCore.Session.DistributedSessionStore").

    Обычно она вызвана невозможностью настройки по меньшей мере одной реализации IDistributedCache. Дополнительные сведения см. в статье Распределенное кэширование в ASP.NET Core и Кэширование в памяти в ASP.NET Core.

Если ПО промежуточного слоя сеанса не удается сохранить сеанс:

  • ПО промежуточного слоя регистрирует исключение, и запрос продолжит работу в обычном режиме.
  • Это приводит к непредсказуемому поведению.

ПО промежуточного слоя сеанса может не сохранять сеанс, если резервное хранилище недоступно. Например, пользователь сохраняет корзину для покупок в сеансе. Он добавляет товар в корзину, но фиксация завершается сбоем. Приложению неизвестно о сбое, поэтому оно сообщает пользователю, что товар добавлен в корзину, хотя это не так.

Для проверки на наличие таких ошибок рекомендуется вызывать await feature.Session.CommitAsync по окончании записи в сеанс. CommitAsync создает исключение, если резервное хранилище недоступно. Если CommitAsync завершается ошибкой, приложение может обработать исключение. Исключение LoadAsync возникает в таких же условиях, когда хранилище данных недоступно.

Дополнительные ресурсы

Узел ASP.NET Core в веб-ферме