Gerenciamento de sessão e estado no ASP.NET Core

Por Rick Anderson, Kirk Larkin e Diana LaRose

O HTTP é um protocolo sem estado. Por padrão, as solicitações HTTP são mensagens independentes que não retêm valores de usuário. Este artigo descreve várias abordagens para preservar os dados do usuário entre solicitações.

Gerenciamento de estado

O estado pode ser armazenado usando várias abordagens. Cada abordagem é descrita posteriormente neste tópico.

Abordagem de armazenamento Mecanismo de armazenamento
Cookies HTTP cookies. Pode incluir dados armazenados usando o código do aplicativo do lado do servidor.
Estado da sessão Http cookies e código de aplicativo do lado do servidor
TempData HTTP cookies ou estado de sessão
Cadeias de caracteres de consulta Cadeias de caracteres de consulta HTTP
Campos ocultos Campos de formulário HTTP
HttpContext.Items Código do aplicativo do lado do servidor
Cache Código do aplicativo do lado do servidor

Cookies

Cookies armazenar dados entre solicitações. Como cookies são enviados com cada solicitação, seu tamanho deve ser mantido ao mínimo. Idealmente, apenas um identificador deve ser armazenado em um cookie com os dados armazenados pelo aplicativo. A maioria dos navegadores restringe o cookie tamanho a 4096 bytes. Apenas um número limitado de cookies está disponível para cada domínio.

Como cookieestão sujeitos a violação, eles devem ser validados pelo aplicativo. Cookies podem ser excluídos pelos usuários e expirar em clientes. No entanto, cookies são geralmente a forma mais durável de persistência de dados no cliente.

Cookiegeralmente são usados para personalização, em que o conteúdo é personalizado para um usuário conhecido. O usuário é apenas identificado, e não autenticado, na maioria dos casos. É cookie possível armazenar o nome do usuário, o nome da conta ou a ID de usuário exclusiva, como um GUID. Ele cookie pode ser usado para acessar as configurações personalizadas do usuário, como a cor da tela de fundo do site preferencial.

Consulte o GDPR (Regulamento Geral de Proteção de Dados) da União Europeia ao emitir cookies e lidar com preocupações de privacidade. Para obter mais informações, veja Suporte ao RGPD (Regulamento Geral sobre a Proteção de Dados) no ASP.NET Core.

Estado de sessão

Estado de sessão é um cenário do ASP.NET Core para o armazenamento de dados de usuário enquanto o usuário procura um aplicativo Web. Estado de sessão usa um armazenamento mantido pelo aplicativo para que os dados persistam entre solicitações de um cliente. Os dados da sessão são apoiados por um cache e considerados dados efêmeros. O site deve continuar funcionando sem os dados da sessão. Os dados críticos do aplicativo devem ser armazenados no banco de dados do usuário e armazenados em cache na sessão apenas como uma otimização de desempenho.

Não há suporte para a sessão em SignalR aplicativos porque um SignalR Hub pode ser executado independentemente de um contexto HTTP. Por exemplo, isso pode ocorrer quando uma solicitação de sondagem longa é mantida aberta por um hub além do tempo de vida do contexto HTTP da solicitação.

ASP.NET Core mantém o estado da sessão fornecendo um cookie para o cliente que contém uma ID de sessão. A cookie ID da sessão:

  • É enviado para o aplicativo com cada solicitação.
  • É usado pelo aplicativo para buscar os dados da sessão.

Estado de sessão exibe os seguintes comportamentos:

  • A sessão cookie é específica do navegador. As sessões não são compartilhadas entre navegadores.
  • As sessões cookiesão excluídas quando a sessão do navegador termina.
  • Se um cookie for recebido para uma sessão expirada, será criada uma nova sessão que usa a mesma sessão cookie.
  • As sessões vazias não são mantidas. A sessão deve ter pelo menos um valor definido para persistir a sessão entre as solicitações. Quando uma sessão não é mantida, uma nova ID de sessão é gerada para cada nova solicitação.
  • O aplicativo mantém uma sessão por um tempo limitado após a última solicitação. O aplicativo define o tempo limite da sessão ou usa o valor padrão de 20 minutos. O estado da sessão é ideal para armazenar dados do usuário:
    • Isso é específico para uma sessão específica.
    • Em que os dados não exigem armazenamento permanente entre sessões.
  • Os dados da sessão são excluídos quando a ISession.Clear implementação é chamada ou quando a sessão expira.
  • Não há nenhum mecanismo padrão para informar o código do aplicativo de que um navegador cliente foi fechado ou quando a sessão cookie é excluída ou expirada no cliente.
  • Os estados cookieda sessão não são marcados como essenciais por padrão. O estado da sessão não é funcional, a menos que o acompanhamento seja permitido pelo visitante do site. Para obter mais informações, veja Suporte ao RGPD (Regulamento Geral sobre a Proteção de Dados) no ASP.NET Core.
  • Observação: não há substituição para o cookierecurso de sessão menor do ASP.NET Framework porque ele é considerado inseguro e pode levar a ataques de correção de sessão.

Aviso

Não armazene dados confidenciais no estado de sessão. O usuário pode não fechar o navegador e limpar a sessão cookie. Alguns navegadores mantêm sessões cookieválidas entre janelas do navegador. Uma sessão pode não ser restrita a um único usuário. O próximo usuário pode continuar navegando pelo aplicativo com a mesma sessão cookie.

O provedor de cache na memória armazena dados de sessão na memória do servidor em que o aplicativo reside. Em um cenário de farm de servidores:

Configurar o estado de sessão

O pacote Microsoft.AspNetCore.Session :

  • É incluído implicitamente pela estrutura.
  • Fornece middleware para gerenciar o estado da sessão.

Para habilitar o middleware da sessão, Program.cs deve conter:

O código a seguir mostra como configurar o provedor de sessão na memória com uma implementação padrão na memória de 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();

O código anterior define um curto tempo limite para simplificar o teste.

A ordem do middleware é importante. Ligue UseSession depois UseRouting e antes MapRazorPages e MapDefaultControllerRoute ... Consulte o Middleware Ordering.

HttpContext.Session estará disponível depois que o estado de sessão for configurado.

HttpContext.Session não pode ser acessado antes que UseSession tenha sido chamado.

Uma nova sessão com uma nova sessão cookie não pode ser criada depois que o aplicativo começar a gravar no fluxo de resposta. A exceção é registrada no log do servidor Web e não é exibida no navegador.

Carregue o estado de sessão de maneira assíncrona

O provedor de sessão padrão em ASP.NET Core carrega registros de sessão do repositório IDistributedCache de backup subjacente de forma assíncrona somente se o ISession.LoadAsync método for chamado explicitamente antes do TryGetValue, Setou Remove métodos. Se LoadAsync não for chamado primeiro, o registro de sessão subjacente será carregado de maneira síncrona, o que pode incorrer em uma penalidade de desempenho em escala.

Para que os aplicativos imponham esse padrão, encapsule e DistributedSessionStoreDistributedSession implemente com versões que geram uma exceção se o LoadAsync método não for chamado antes TryGetValue, Setou Remove. Registre as versões encapsuladas no contêiner de serviços.

Opções da sessão

Para substituir os padrões de sessão, use SessionOptions.

Opção Descrição
Cookie Determina as configurações usadas para criar o cookie. NameSessionDefaults.CookieName o padrão é (.AspNetCore.Session). PathSessionDefaults.CookiePath o padrão é (/). SameSiteSameSiteMode.Lax o padrão é (1). HttpOnly usa como padrão true. IsEssential usa como padrão false.
IdleTimeout O IdleTimeout indica por quanto tempo a sessão pode ficar ociosa antes de seu conteúdo ser abandonado. Cada acesso à sessão redefine o tempo limite. Essa configuração só se aplica ao conteúdo da sessão, não ao cookie. O padrão é de 20 minutos.
IOTimeout O tempo máximo permitido para carregar uma sessão do repositório ou para confirmá-la de volta para o repositório. Essa configuração pode se aplicar somente a operações assíncronas. Esse tempo limite pode ser desabilitado usando InfiniteTimeSpan. O padrão é 1 minuto.

A sessão usa um cookie para rastrear e identificar solicitações de um único navegador. Por padrão, isso cookie é nomeado .AspNetCore.Sessione usa um caminho de /. Como o cookie padrão não especifica um domínio, ele não é disponibilizado para o script do lado do cliente na página (porque HttpOnly o padrão trueé ).

Para substituir cookie os padrões de sessão, use 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();

O aplicativo usa a IdleTimeout propriedade para determinar por quanto tempo uma sessão pode ficar ociosa antes que seu conteúdo no cache do servidor seja abandonado. Essa propriedade é independente da cookie expiração. Cada solicitação que passa pelo Middleware da Sessão redefine o tempo limite.

Estado de sessão é sem bloqueio. Se duas solicitações tentarem simultaneamente modificar o conteúdo de uma sessão, a última solicitação substituirá a primeira. Session é implementado como uma sessão coerente, o que significa que todo o conteúdo é armazenado junto. Quando duas solicitações buscam modificar valores de sessão diferentes, a última solicitação pode substituir as alterações de sessão feitas pelo primeira.

Definir e obter valores de Session

O estado da sessão é acessado de uma Razor classe Pages PageModel ou classe MVC Controller com HttpContext.Session. Essa propriedade é uma implementação ISession .

A implementação de ISession fornece vários métodos de extensão para definir e recuperar valores de inteiro e cadeia de caracteres. Os métodos de extensão estão no Microsoft.AspNetCore.Http namespace.

Métodos de extensão ISession:

O exemplo a seguir recupera o valor da sessão para a IndexModel.SessionKeyName chave (_Name no aplicativo de exemplo) em uma Razor página Páginas:

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

...

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

O exemplo a seguir mostra como definir e obter um número inteiro e uma cadeia de caracteres:

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

A marcação a seguir exibe os valores de sessão em uma Razor página:

@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>


Todos os dados de sessão devem ser serializados para habilitar um cenário de cache distribuído, mesmo ao usar o cache na memória. Serializadores de cadeia de caracteres e inteiros são fornecidos pelos métodos de extensão de ISession. Tipos complexos devem ser serializados pelo usuário usando outro mecanismo, como JSON.

Use o seguinte código de exemplo para serializar objetos:

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

O exemplo a seguir mostra como definir e obter um objeto serializável com a SessionExtensions classe:

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

        }
    }
}

TempData

ASP.NET Core expõe o Razor Páginas TempData ou ControladorTempData. Essa propriedade armazena dados até que sejam lidos em outra solicitação. Os métodos Keep(String) e Peek(string) podem ser usados para examinar os dados sem exclusão no final da solicitação. Mantenha marcas em todos os itens no dicionário de retenção. TempData é:

  • Útil para redirecionamento quando os dados são necessários para mais de uma única solicitação.
  • Implementado por TempData provedores usando o cookieestado de sessão ou s.

Exemplos de TempData

Considere a página a seguir que cria um cliente:

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

A página a seguir exibe TempData["Message"]:

@page
@model IndexModel

<h1>Peek Contacts</h1>

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

@*Content removed for brevity.*@

Na marcação anterior, no final da solicitação, TempData["Message"]não é excluído porque Peek é usado. Atualizar a página exibe o conteúdo de TempData["Message"].

A marcação a seguir é semelhante ao código anterior, mas usa Keep para preservar os dados no final da solicitação:

@page
@model IndexModel

<h1>Contacts Keep</h1>

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

@*Content removed for brevity.*@

Navegar entre as páginas IndexPeek e IndexKeep não excluirá TempData["Message"].

O código a seguir é TempData["Message"]exibido, mas no final da solicitação, TempData["Message"] é excluído:

@page
@model IndexModel

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

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

@*Content removed for brevity.*@

Provedores de TempData

O cookieprovedor TempData baseado é usado por padrão para armazenar TempData em cookies.

Os cookie dados são criptografados usando IDataProtector, codificados com Base64UrlTextEncoder, em seguida, em partes. O tamanho máximo cookie é menor que 4.096 bytes devido à criptografia e ao agrupamento. Os cookie dados não são compactados porque a compactação de dados criptografados pode levar a problemas de segurança, como ataques CRIME e BREACH ataques. Para obter mais informações sobre o cookieprovedor TempData baseado, consulte CookieTempDataProvider.

Escolha um provedor de TempData

Escolher um provedor de TempData envolve várias considerações, como:

  • O aplicativo já usa estado de sessão? Nesse caso, usar o provedor TempData do estado de sessão não terá nenhum custo adicional para o aplicativo além do tamanho dos dados.
  • O aplicativo usa TempData apenas com moderação para quantidades relativamente pequenas de dados, até 500 bytes? Nesse caso, o cookie provedor TempData adiciona um pequeno custo a cada solicitação que carrega TempData. Caso contrário, o provedor de TempData do estado de sessão pode ser útil para evitar fazer viagens de ida e volta para uma grande quantidade de dados a cada solicitação até que TempData seja consumido.
  • O aplicativo é executado em um farm de servidores em vários servidores? Nesse caso, não há nenhuma configuração adicional necessária para usar o cookie provedor TempData fora da Proteção de Dados. Para obter mais informações, consulte ASP.NET Core Visão geral da Proteção de Dados e provedores de armazenamento de chaves.

A maioria dos clientes Web, como navegadores da Web, impõe limites ao tamanho máximo de cada cookie um e ao número total de cookies. Ao usar o cookie provedor TempData, verifique se o aplicativo não excederá esses limites. Considere o tamanho total dos dados. Contabilize aumentos de cookie tamanho devido à criptografia e ao agrupamento.

Configurar o provedor de TempData

O cookieprovedor TempData baseado está habilitado por padrão.

Para habilitar o provedor TempData baseado em sessão, use o AddSessionStateTempDataProvider método de extensão. Somente uma chamada é AddSessionStateTempDataProvider necessária:

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

Cadeias de consulta

É possível passar uma quantidade limitada de dados de uma solicitação para outra adicionando-a à cadeia de caracteres de consulta da nova solicitação. Isso é útil para capturar o estado de uma maneira persistente que permita que links com estado inserido sejam compartilhados por email ou por redes sociais. Uma vez que cadeias de consulta de URL são públicas, nunca use cadeias de consulta para dados confidenciais.

Além do compartilhamento não intencional, a inclusão de dados em cadeias de caracteres de consulta pode expor o aplicativo a ataques CSRF (Solicitação forjada entre sites). Qualquer estado de sessão preservado deve proteger contra ataques CSRF. Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação intersite forjada) no ASP.NET Core.

Campos ocultos

Dados podem ser salvos em campos de formulário ocultos e postados novamente na solicitação seguinte. Isso é comum em formulários com várias páginas. Uma vez que o cliente potencialmente pode adulterar os dados, o aplicativo deve sempre revalidar os dados armazenados nos campos ocultos.

HttpContext.Items

A HttpContext.Items coleção é usada para armazenar dados durante o processamento de uma única solicitação. O conteúdo da coleção é descartado após uma solicitação ser processada. A coleção Items costuma ser usada para permitir que componentes ou middleware se comuniquem quando operam em diferentes momentos durante uma solicitação e não têm nenhuma maneira direta de passar parâmetros.

No exemplo a seguir, o middleware adiciona isVerified à Items coleção:

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

Para o middleware usado apenas em um único aplicativo, é improvável que o uso de uma chave fixa string cause uma colisão de chave. No entanto, para evitar a possibilidade de uma colisão de chave completamente, um object pode ser usado como uma chave de item. Essa abordagem é particularmente útil para o middleware compartilhado entre aplicativos e também tem a vantagem de eliminar o uso de cadeias de caracteres de chave no código. O exemplo a seguir mostra como usar uma object chave definida em uma classe de middleware:

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

Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a chave exposta pela classe do middleware:

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

O cache é uma maneira eficiente de armazenar e recuperar dados. O aplicativo pode controlar o tempo de vida de itens em cache. Para obter mais informações, consulte o cache de resposta em ASP.NET Core.

Dados armazenados em cache não são associados uma solicitação, usuário ou sessão específico. Não armazene em cache dados específicos do usuário que possam ser recuperados por outras solicitações de usuário.

Para armazenar dados em todo o aplicativo em cache, consulte Cache na memória em ASP.NET Core.

Erros comuns

  • "Não é possível resolver o serviço para o tipo 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' ao tentar ativar 'Microsoft.AspNetCore.Session.DistributedSessionStore'."

    Normalmente, isso é causado por não configurar pelo menos uma IDistributedCache implementação. Para obter mais informações, consulte Cache distribuído em ASP.NET Core e cache na memória em ASP.NET Core.

Se o middleware de sessão não persistir uma sessão:

  • O middleware registra a exceção e a solicitação continua normalmente.
  • Isso leva a um comportamento imprevisível.

O middleware de sessão poderá falhar ao persistir uma sessão se o repositório de backup não estiver disponível. Por exemplo, um usuário armazena um carrinho de compras na sessão. O usuário adiciona um item ao carrinho, mas a confirmação falha. O aplicativo não sabe sobre a falha, assim, relata ao usuário que o item foi adicionado ao seu carrinho, o que não é verdade.

A abordagem recomendada para verificar se há erros é chamar await feature.Session.CommitAsync quando o aplicativo terminar de gravar na sessão. CommitAsync gerará uma exceção se o repositório de backup não estiver disponível. Se CommitAsync falhar, o aplicativo poderá processar a exceção. LoadAsync é gerado sob as mesmas condições quando o armazenamento de dados não está disponível.

SignalR e estado da sessão

SignalR os aplicativos não devem usar o estado da sessão para armazenar informações. SignalR os aplicativos podem armazenar por estado de conexão no Context.Items hub.

Recursos adicionais

Exibir ou baixar código de exemplo (como baixar)

Hospedar o ASP.NET Core em um web farm

Por Rick Anderson, Kirk Larkin e Diana LaRose

O HTTP é um protocolo sem estado. Por padrão, as solicitações HTTP são mensagens independentes que não retêm valores de usuário. Este artigo descreve várias abordagens para preservar os dados do usuário entre solicitações.

Exibir ou baixar código de exemplo (como baixar)

Gerenciamento de estado

O estado pode ser armazenado usando várias abordagens. Cada abordagem é descrita posteriormente neste tópico.

Abordagem de armazenamento Mecanismo de armazenamento
Cookies HTTPs cookie. Pode incluir dados armazenados usando o código do aplicativo do lado do servidor.
Estado da sessão Código de aplicativo do lado do servidor e HTTP cookie
TempData HTTP cookies ou estado de sessão
Cadeias de caracteres de consulta Cadeias de caracteres de consulta HTTP
Campos ocultos Campos de formulário HTTP
HttpContext.Items Código do aplicativo do lado do servidor
Cache Código do aplicativo do lado do servidor

Cookies

Cookiearmazenar dados entre solicitações. Como cookieos s são enviados a cada solicitação, seu tamanho deve ser mantido no mínimo. Idealmente, apenas um identificador deve ser armazenado em um cookie com os dados armazenados pelo aplicativo. A maioria dos navegadores restringe o cookie tamanho a 4.096 bytes. Apenas um número limitado de cookies está disponível para cada domínio.

Como cookies estão sujeitos a violação, eles devem ser validados pelo aplicativo. Cookies podem ser excluídos pelos usuários e expirar em clientes. No entanto, cookies geralmente são a forma mais durável de persistência de dados no cliente.

Cookiegeralmente são usados para personalização, em que o conteúdo é personalizado para um usuário conhecido. O usuário é apenas identificado, e não autenticado, na maioria dos casos. Ele cookie pode armazenar o nome do usuário, o nome da conta ou a ID de usuário exclusiva, como um GUID. Ele cookie pode ser usado para acessar as configurações personalizadas do usuário, como a cor da tela de fundo do site preferida.

Consulte o GDPR (Regulamento Geral de Proteção de Dados) da União Europeia ao emitir cookies e lidar com preocupações de privacidade. Para obter mais informações, veja Suporte ao RGPD (Regulamento Geral sobre a Proteção de Dados) no ASP.NET Core.

Estado de sessão

Estado de sessão é um cenário do ASP.NET Core para o armazenamento de dados de usuário enquanto o usuário procura um aplicativo Web. Estado de sessão usa um armazenamento mantido pelo aplicativo para que os dados persistam entre solicitações de um cliente. Os dados da sessão são apoiados por um cache e considerados dados efêmeros. O site deve continuar funcionando sem os dados da sessão. Os dados críticos do aplicativo devem ser armazenados no banco de dados do usuário e armazenados em cache na sessão apenas como uma otimização de desempenho.

Não há suporte para sessão em SignalR aplicativos porque um SignalR Hub pode ser executado independentemente de um contexto HTTP. Por exemplo, isso pode ocorrer quando uma solicitação de sondagem longa é mantida aberta por um hub além do tempo de vida do contexto HTTP da solicitação.

ASP.NET Core mantém o estado da sessão fornecendo um cookie cliente que contém uma ID de sessão. A cookie ID da sessão:

  • É enviado para o aplicativo com cada solicitação.
  • É usado pelo aplicativo para buscar os dados da sessão.

Estado de sessão exibe os seguintes comportamentos:

  • A sessão cookie é específica do navegador. As sessões não são compartilhadas entre navegadores.
  • As sessões cookiesão excluídas quando a sessão do navegador termina.
  • Se um cookie for recebido para uma sessão expirada, será criada uma nova sessão que usa a mesma sessão cookie.
  • As sessões vazias não são mantidas. A sessão deve ter pelo menos um valor definido para persistir a sessão entre as solicitações. Quando uma sessão não é mantida, uma nova ID de sessão é gerada para cada nova solicitação.
  • O aplicativo mantém uma sessão por um tempo limitado após a última solicitação. O aplicativo define o tempo limite da sessão ou usa o valor padrão de 20 minutos. O estado da sessão é ideal para armazenar dados do usuário:
    • Isso é específico para uma sessão específica.
    • Em que os dados não exigem armazenamento permanente entre sessões.
  • Os dados da sessão são excluídos quando a ISession.Clear implementação é chamada ou quando a sessão expira.
  • Não há nenhum mecanismo padrão para informar o código do aplicativo de que um navegador cliente foi fechado ou quando a sessão cookie é excluída ou expirada no cliente.
  • Os estados cookiede sessão não são marcados como essenciais por padrão. O estado da sessão não é funcional, a menos que o acompanhamento seja permitido pelo visitante do site. Para obter mais informações, veja Suporte ao RGPD (Regulamento Geral sobre a Proteção de Dados) no ASP.NET Core.

Aviso

Não armazene dados confidenciais no estado de sessão. O usuário pode não fechar o navegador e limpar a sessão cookie. Alguns navegadores mantêm sessões cookieválidas entre janelas do navegador. Uma sessão pode não ser restrita a um único usuário. O próximo usuário pode continuar navegando pelo aplicativo com a mesma sessão cookie.

O provedor de cache na memória armazena dados de sessão na memória do servidor em que o aplicativo reside. Em um cenário de farm de servidores:

Configurar o estado de sessão

O pacote Microsoft.AspNetCore.Session :

  • É incluído implicitamente pela estrutura.
  • Fornece middleware para gerenciar o estado da sessão.

Para habilitar o middleware da sessão, Startup deve conter:

O código a seguir mostra como configurar o provedor de sessão na memória com uma implementação padrão na memória de 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();
        });
    }
}

O código anterior define um curto tempo limite para simplificar o teste.

A ordem do middleware é importante. Chamar UseSession depois UseRouting e antes UseEndpoints. Consulte o Middleware Ordering.

HttpContext.Session estará disponível depois que o estado de sessão for configurado.

HttpContext.Session não pode ser acessado antes que UseSession tenha sido chamado.

Uma nova sessão com uma nova sessão cookie não pode ser criada depois que o aplicativo começar a gravar no fluxo de resposta. A exceção é registrada no log do servidor Web e não é exibida no navegador.

Carregue o estado de sessão de maneira assíncrona

O provedor de sessão padrão em ASP.NET Core carrega registros de sessão do repositório IDistributedCache de backup subjacente de forma assíncrona somente se o ISession.LoadAsync método for chamado explicitamente antes do TryGetValue, Setou Remove métodos. Se LoadAsync não for chamado primeiro, o registro de sessão subjacente será carregado de maneira síncrona, o que pode incorrer em uma penalidade de desempenho em escala.

Para que os aplicativos imponham esse padrão, encapsule e DistributedSessionStoreDistributedSession implemente com versões que geram uma exceção se o LoadAsync método não for chamado antes TryGetValue, Setou Remove. Registre as versões encapsuladas no contêiner de serviços.

Opções da sessão

Para substituir os padrões de sessão, use SessionOptions.

Opção Descrição
Cookie Determina as configurações usadas para criar o cookie. NameSessionDefaults.CookieName o padrão é (.AspNetCore.Session). PathSessionDefaults.CookiePath o padrão é (/). SameSiteSameSiteMode.Lax o padrão é (1). HttpOnly usa como padrão true. IsEssential usa como padrão false.
IdleTimeout O IdleTimeout indica por quanto tempo a sessão pode ficar ociosa antes de seu conteúdo ser abandonado. Cada acesso à sessão redefine o tempo limite. Essa configuração só se aplica ao conteúdo da sessão, não ao cookie. O padrão é de 20 minutos.
IOTimeout O tempo máximo permitido para carregar uma sessão do repositório ou para confirmá-la de volta para o repositório. Essa configuração pode se aplicar somente a operações assíncronas. Esse tempo limite pode ser desabilitado usando InfiniteTimeSpan. O padrão é 1 minuto.

A sessão usa um cookie para rastrear e identificar solicitações de um único navegador. Por padrão, isso cookie é nomeado .AspNetCore.Sessione usa um caminho de /. Como o cookie padrão não especifica um domínio, ele não é disponibilizado para o script do lado do cliente na página (porque HttpOnly o padrão trueé ).

Para substituir cookie os padrões de sessão, use 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();
}

O aplicativo usa a IdleTimeout propriedade para determinar por quanto tempo uma sessão pode ficar ociosa antes que seu conteúdo no cache do servidor seja abandonado. Essa propriedade é independente da cookie expiração. Cada solicitação que passa pelo Middleware da Sessão redefine o tempo limite.

Estado de sessão é sem bloqueio. Se duas solicitações tentarem simultaneamente modificar o conteúdo de uma sessão, a última solicitação substituirá a primeira. Session é implementado como uma sessão coerente, o que significa que todo o conteúdo é armazenado junto. Quando duas solicitações buscam modificar valores de sessão diferentes, a última solicitação pode substituir as alterações de sessão feitas pelo primeira.

Definir e obter valores de Session

O estado da sessão é acessado de uma Razor classe Pages PageModel ou classe MVC Controller com HttpContext.Session. Essa propriedade é uma implementação ISession .

A implementação de ISession fornece vários métodos de extensão para definir e recuperar valores de inteiro e cadeia de caracteres. Os métodos de extensão estão no Microsoft.AspNetCore.Http namespace.

Métodos de extensão ISession:

O exemplo a seguir recupera o valor da sessão para a IndexModel.SessionKeyName chave (_Name no aplicativo de exemplo) em uma Razor página Páginas:

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

...

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

O exemplo a seguir mostra como definir e obter um número inteiro e uma cadeia de caracteres:

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

Todos os dados de sessão devem ser serializados para habilitar um cenário de cache distribuído, mesmo ao usar o cache na memória. Serializadores de cadeia de caracteres e inteiros são fornecidos pelos métodos de extensão de ISession. Tipos complexos devem ser serializados pelo usuário usando outro mecanismo, como JSON.

Use o seguinte código de exemplo para serializar objetos:

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

O exemplo a seguir mostra como definir e obter um objeto serializável com a SessionExtensions classe:

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

TempData

ASP.NET Core expõe o Razor Páginas TempData ou ControladorTempData. Essa propriedade armazena dados até que sejam lidos em outra solicitação. Os métodos Keep(String) e Peek(string) podem ser usados para examinar os dados sem exclusão no final da solicitação. Mantenha marcas em todos os itens no dicionário de retenção. TempData é:

  • Útil para redirecionamento quando os dados são necessários para mais de uma única solicitação.
  • Implementado por TempData provedores usando o cookieestado de sessão ou s.

Exemplos de TempData

Considere a página a seguir que cria um cliente:

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

A página a seguir exibe TempData["Message"]:

@page
@model IndexModel

<h1>Peek Contacts</h1>

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

@*Content removed for brevity.*@

Na marcação anterior, no final da solicitação, TempData["Message"]não é excluído porque Peek é usado. Atualizar a página exibe o conteúdo de TempData["Message"].

A marcação a seguir é semelhante ao código anterior, mas usa Keep para preservar os dados no final da solicitação:

@page
@model IndexModel

<h1>Contacts Keep</h1>

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

@*Content removed for brevity.*@

Navegar entre as páginas IndexPeek e IndexKeep não excluirá TempData["Message"].

O código a seguir é TempData["Message"]exibido, mas no final da solicitação, TempData["Message"] é excluído:

@page
@model IndexModel

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

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

@*Content removed for brevity.*@

Provedores de TempData

O cookieprovedor TempData baseado é usado por padrão para armazenar TempData em cookies.

Os cookie dados são criptografados usando IDataProtector, codificados com Base64UrlTextEncoder, em seguida, em partes. O tamanho máximo cookie é menor que 4.096 bytes devido à criptografia e ao agrupamento. Os cookie dados não são compactados porque a compactação de dados criptografados pode levar a problemas de segurança, como ataques CRIME e BREACH ataques. Para obter mais informações sobre o cookieprovedor TempData baseado, consulte CookieTempDataProvider.

Escolha um provedor de TempData

Escolher um provedor de TempData envolve várias considerações, como:

  • O aplicativo já usa estado de sessão? Nesse caso, usar o provedor TempData do estado de sessão não terá nenhum custo adicional para o aplicativo além do tamanho dos dados.
  • O aplicativo usa TempData apenas com moderação para quantidades relativamente pequenas de dados, até 500 bytes? Nesse caso, o cookie provedor TempData adiciona um pequeno custo a cada solicitação que carrega TempData. Caso contrário, o provedor de TempData do estado de sessão pode ser útil para evitar fazer viagens de ida e volta para uma grande quantidade de dados a cada solicitação até que TempData seja consumido.
  • O aplicativo é executado em um farm de servidores em vários servidores? Nesse caso, não há nenhuma configuração adicional necessária para usar o cookie provedor TempData fora da Proteção de Dados (consulte ASP.NET Core Visão geral da Proteção de Dados e provedores de armazenamento de chaves).

A maioria dos clientes Web, como navegadores da Web, impõe limites ao tamanho máximo de cada cookie um e ao número total de cookies. Ao usar o cookie provedor TempData, verifique se o aplicativo não excederá esses limites. Considere o tamanho total dos dados. Contabilize aumentos de cookie tamanho devido à criptografia e ao agrupamento.

Configurar o provedor de TempData

O cookieprovedor TempData baseado está habilitado por padrão.

Para habilitar o provedor TempData baseado em sessão, use o AddSessionStateTempDataProvider método de extensão. Somente uma chamada é AddSessionStateTempDataProvider necessária:

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

Cadeias de consulta

É possível passar uma quantidade limitada de dados de uma solicitação para outra adicionando-a à cadeia de caracteres de consulta da nova solicitação. Isso é útil para capturar o estado de uma maneira persistente que permita que links com estado inserido sejam compartilhados por email ou por redes sociais. Uma vez que cadeias de consulta de URL são públicas, nunca use cadeias de consulta para dados confidenciais.

Além do compartilhamento não intencional, a inclusão de dados em cadeias de caracteres de consulta pode expor o aplicativo a ataques CSRF (Solicitação forjada entre sites). Qualquer estado de sessão preservado deve proteger contra ataques CSRF. Para obter mais informações, consulte Impedir ataques de XSRF/CSRF (solicitação intersite forjada) no ASP.NET Core.

Campos ocultos

Dados podem ser salvos em campos de formulário ocultos e postados novamente na solicitação seguinte. Isso é comum em formulários com várias páginas. Uma vez que o cliente potencialmente pode adulterar os dados, o aplicativo deve sempre revalidar os dados armazenados nos campos ocultos.

HttpContext.Items

A HttpContext.Items coleção é usada para armazenar dados durante o processamento de uma única solicitação. O conteúdo da coleção é descartado após uma solicitação ser processada. A coleção Items costuma ser usada para permitir que componentes ou middleware se comuniquem quando operam em diferentes momentos durante uma solicitação e não têm nenhuma maneira direta de passar parâmetros.

No exemplo a seguir, o middleware adiciona isVerified à Items coleção:

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

Para middleware usado apenas em um único aplicativo, as chaves fixas string são aceitáveis. O middleware compartilhado entre aplicativos deve usar chaves de objeto exclusivas para evitar colisões de chave. O exemplo a seguir mostra como usar uma chave de objeto exclusiva definida em uma classe de middleware:

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

Outros códigos podem acessar o valor armazenado em HttpContext.Items usando a chave exposta pela classe do middleware:

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

Essa abordagem também tem a vantagem de eliminar o uso de cadeias de caracteres de chave no código.

Cache

O cache é uma maneira eficiente de armazenar e recuperar dados. O aplicativo pode controlar o tempo de vida de itens em cache. Para obter mais informações, consulte o cache de resposta em ASP.NET Core.

Dados armazenados em cache não são associados uma solicitação, usuário ou sessão específico. Não armazene em cache dados específicos do usuário que possam ser recuperados por outras solicitações de usuário.

Para armazenar dados em todo o aplicativo em cache, consulte Cache na memória em ASP.NET Core.

Erros comuns

  • "Não é possível resolver o serviço para o tipo 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' ao tentar ativar 'Microsoft.AspNetCore.Session.DistributedSessionStore'."

    Normalmente, isso é causado por não configurar pelo menos uma IDistributedCache implementação. Para obter mais informações, consulte Cache distribuído em ASP.NET Core e cache na memória em ASP.NET Core.

Se o middleware de sessão não persistir uma sessão:

  • O middleware registra a exceção e a solicitação continua normalmente.
  • Isso leva a um comportamento imprevisível.

O middleware de sessão poderá falhar ao persistir uma sessão se o repositório de backup não estiver disponível. Por exemplo, um usuário armazena um carrinho de compras na sessão. O usuário adiciona um item ao carrinho, mas a confirmação falha. O aplicativo não sabe sobre a falha, assim, relata ao usuário que o item foi adicionado ao seu carrinho, o que não é verdade.

A abordagem recomendada para verificar se há erros é chamar await feature.Session.CommitAsync quando o aplicativo terminar de gravar na sessão. CommitAsync gerará uma exceção se o repositório de backup não estiver disponível. Se CommitAsync falhar, o aplicativo poderá processar a exceção. LoadAsync é gerado sob as mesmas condições quando o armazenamento de dados não está disponível.

SignalR e estado da sessão

SignalR os aplicativos não devem usar o estado da sessão para armazenar informações. SignalR os aplicativos podem armazenar por estado de conexão no Context.Items hub.

Recursos adicionais

Hospedar o ASP.NET Core em um web farm