ASP.NET Core 中的会话和应用状态Session and app state in ASP.NET Core

作者:Rick AndersonSteve SmithDiana LaRoseLuke LathamBy Rick Anderson, Steve Smith, Diana LaRose, and Luke Latham

HTTP 是无状态的协议。HTTP is a stateless protocol. 不采取其他步骤的情况下,HTTP 请求是不保留用户值或应用状态的独立消息。Without taking additional steps, HTTP requests are independent messages that don't retain user values or app state. 本文介绍了几种保留请求间用户数据和应用状态的方法。This article describes several approaches to preserve user data and app state between requests.

查看或下载示例代码如何下载View or download sample code (how to download)

状态管理State management

可以使用几种方法存储状态。State can be stored using several approaches. 本主题稍后将对每个方法进行介绍。Each approach is described later in this topic.

存储方法Storage approach 存储机制Storage mechanism
CookieCookies HTTP Cookie(可能包括使用服务器端应用代码存储的数据)HTTP cookies (may include data stored using server-side app code)
会话状态Session state HTTP Cookie 和服务器端应用代码HTTP cookies and server-side app code
TempDataTempData HTTP Cookie 或会话状态HTTP cookies or session state
查询字符串Query strings HTTP 查询字符串HTTP query strings
隐藏字段Hidden fields HTTP 窗体字段HTTP form fields
HttpContext.ItemsHttpContext.Items 服务器端应用代码Server-side app code
缓存Cache 服务器端应用代码Server-side app code
依赖关系注入Dependency Injection 服务器端应用代码Server-side app code

CookieCookies

Cookie 存储所有请求的数据。Cookies store data across requests. 因为 Cookie 是随每个请求发送的,所以它们的大小应该保持在最低限度。Because cookies are sent with every request, their size should be kept to a minimum. 理想情况下,仅标识符应存储在 Cookie 中,而数据则由应用存储。Ideally, only an identifier should be stored in a cookie with the data stored by the app. 大多数浏览器 Cookie 大小限制为 4096 个字节。Most browsers restrict cookie size to 4096 bytes. 每个域仅有有限数量的 Cookie 可用。Only a limited number of cookies are available for each domain.

由于 Cookie 易被篡改,因此它们必须由服务器进行验证。Because cookies are subject to tampering, they must be validated by the app. 客户端上的 Cookie 可能被用户删除或者过期。Cookies can be deleted by users and expire on clients. 但是,Cookie 通常是客户端上最持久的数据暂留形式。However, cookies are generally the most durable form of data persistence on the client.

Cookie 通常用于个性化设置,其中的内容是为已知用户定制的。Cookies are often used for personalization, where content is customized for a known user. 大多数情况下,仅标识用户,但不对其进行身份验证。The user is only identified and not authenticated in most cases. Cookie 可以存储用户名、帐户名或唯一的用户 ID(例如 GUID)。The cookie can store the user's name, account name, or unique user ID (such as a GUID). 然后,可以使用 Cookie 访问用户的个性化设置,例如首选的网站背景色。You can then use the cookie to access the user's personalized settings, such as their preferred website background color.

发布 Cookie 和处理隐私问题时,请留意欧盟一般数据保护条例 (GDPR)Be mindful of the European Union General Data Protection Regulations (GDPR) when issuing cookies and dealing with privacy concerns. 有关详细信息,请参阅 ASP.NET Core 中的一般数据保护条例 (GDPR) 支持For more information, see General Data Protection Regulation (GDPR) support in ASP.NET Core.

会话状态Session state

会话状态是在用户浏览 Web 应用时用来存储用户数据的 ASP.NET Core 方案。Session state is an ASP.NET Core scenario for storage of user data while the user browses a web app. 会话状态使用应用维护的存储来保存客户端所有请求的数据。Session state uses a store maintained by the app to persist data across requests from a client. 会话数据由缓存支持并被视为临时数据 - 站点应在没有会话数据的情况下继续运行。The session data is backed by a cache and considered ephemeral data—the site should continue to function without the session data. 关键应用程序数据应存储在用户数据库中,并仅作为性能优化缓存在会话中。Critical application data should be stored in the user database and cached in session only as a performance optimization.

备注

SignalR 应用不支持会话,因为 SignalR 中心可能独立于 HTTP 上下文执行。Session isn't supported in SignalR apps because a SignalR Hub may execute independent of an HTTP context. 例如,当中心打开的长轮询请求超出请求的 HTTP 上下文的生存期时,可能发生这种情况。For example, this can occur when a long polling request is held open by a hub beyond the lifetime of the request's HTTP context.

ASP.NET Core 通过向客户端提供包含会话 ID 的 Cookie 来维护会话状态,该会话 ID 与每个请求一起发送到应用。ASP.NET Core maintains session state by providing a cookie to the client that contains a session ID, which is sent to the app with each request. 应用使用会话 ID 来获取会话数据。The app uses the session ID to fetch the session data.

会话状态具有以下行为:Session state exhibits the following behaviors:

  • 由于会话 Cookie 是特定于浏览器的,因此不能跨浏览器共享会话。Because the session cookie is specific to the browser, sessions aren't shared across browsers.
  • 浏览器会话结束时删除会话 Cookie。Session cookies are deleted when the browser session ends.
  • 如果收到过期的会话 Cookie,则创建使用相同会话 Cookie 的新会话。If a cookie is received for an expired session, a new session is created that uses the same session cookie.
  • 不会保留空会话 - 会话中必须设置了至少一个值以保存所有请求的会话。Empty sessions aren't retained—the session must have at least one value set into it to persist the session across requests. 会话未保留时,为每个新的请求生成新会话 ID。When a session isn't retained, a new session ID is generated for each new request.
  • 应用在上次请求后保留会话的时间有限。The app retains a session for a limited time after the last request. 应用设置会话超时,或者使用 20 分钟的默认值。The app either sets the session timeout or uses the default value of 20 minutes. 会话状态适用于存储特定于特定会话的用户数据,但该数据无需永久的会话存储。Session state is ideal for storing user data that's specific to a particular session but where the data doesn't require permanent storage across sessions.
  • 调用 ISession.Clear 实现或者会话过期时,会删除会话数据。Session data is deleted either when the ISession.Clear implementation is called or when the session expires.
  • 没有默认机制告知客户端浏览器已关闭或者客户端上的会话 Cookie 被删除或过期的应用代码。There's no default mechanism to inform app code that a client browser has been closed or when the session cookie is deleted or expired on the client.
  • ASP.NET Core MVC 和 Razor Pages 模板包括对一般数据保护条例 (GDPR) 的支持。The ASP.NET Core MVC and Razor pages templates include support for General Data Protection Regulation (GDPR). 默认情况下,会话状态 cookie 不标记为“基本”,因此,除非站点访问者允许跟踪,否则会话状态不起作用。Session state cookies aren't marked essential by default, so session state isn't functional unless tracking is permitted by the site visitor. 有关更多信息,请参见在 ASP.NET Core中的常规数据保护法规 (GDPR) 支持For more information, see 在 ASP.NET Core中的常规数据保护法规 (GDPR) 支持.

警告

请勿将敏感数据存储在会话状态中。Don't store sensitive data in session state. 用户可能不会关闭浏览器并清除会话 Cookie。The user might not close the browser and clear the session cookie. 某些浏览器会保留所有浏览器窗口中的有效会话 Cookie。Some browsers maintain valid session cookies across browser windows. 会话可能不限于单个用户 - 下一个用户可能继续使用同一会话 Cookie 浏览应用。A session might not be restricted to a single user—the next user might continue to browse the app with the same session cookie.

内存中缓存提供程序在应用驻留的服务器内存中存储会话数据。The in-memory cache provider stores session data in the memory of the server where the app resides. 在服务器场方案中:In a server farm scenario:

配置会话状态Configure session state

Microsoft.AspNetCore.App metapackage 中包含的 Microsoft.AspNetCore.Session 包提供中间件来管理会话状态。The Microsoft.AspNetCore.Session package, which is included in the Microsoft.AspNetCore.App metapackage, provides middleware for managing session state. 若要启用会话中间件,Startup 必须包含:To enable the session middleware, Startup must contain:

以下代码演示如何使用 IDistributedCache 的默认内存中实现设置内存中会话提供程序:The following code shows how to set up the in-memory session provider with a default in-memory implementation of IDistributedCache:

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

        services.AddSession(options =>
        {
            // Set a short timeout for easy testing.
            options.IdleTimeout = TimeSpan.FromSeconds(10);
            options.Cookie.HttpOnly = true;
            // Make the session cookie essential
            options.Cookie.IsEssential = true;
        });

        services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseSession();
        app.UseHttpContextItemsMiddleware();
        app.UseMvc();
    }
}

中间件的顺序很重要。The order of middleware is important. 在前面的示例中,在 UseMvc 之后调用 UseSession 时会发生 InvalidOperationException 异常。In the preceding example, an InvalidOperationException exception occurs when UseSession is invoked after UseMvc. 有关详细信息,请参阅中间件排序For more information, see Middleware Ordering.

配置会话状态后,HttpContext.Session 可用。HttpContext.Session is available after session state is configured.

调用 UseSession 以前无法访问 HttpContext.SessionHttpContext.Session can't be accessed before UseSession has been called.

在应用已经开始写入到响应流之后,不能创建有新会话 Cookie 的新会话。A new session with a new session cookie can't be created after the app has begun writing to the response stream. 此异常记录在 Web 服务器日志中但不显示在浏览器中。The exception is recorded in the web server log and not displayed in the browser.

以异步方式加载会话状态Load session state asynchronously

只有在 TryGetValueSetRemove 方法之前显式调用 ISession.LoadAsync 方法,ASP.NET Core 中的默认会话提供程序才会从基础 IDistributedCache 后备存储以异步方式加载会话记录。The default session provider in ASP.NET Core loads session records from the underlying IDistributedCache backing store asynchronously only if the ISession.LoadAsync method is explicitly called before the TryGetValue, Set, or Remove methods. 如果未先调用 LoadAsync,则会同步加载基础会话记录,这可能对性能产生大规模影响。If LoadAsync isn't called first, the underlying session record is loaded synchronously, which can incur a performance penalty at scale.

若要让应用强制实施此模式,如果未在 TryGetValueSetRemove 之前调用 LoadAsync 方法,那么使用引起异常的版本包装 DistributedSessionStoreDistributedSession 实现。To have apps enforce this pattern, wrap the DistributedSessionStore and DistributedSession implementations with versions that throw an exception if the LoadAsync method isn't called before TryGetValue, Set, or Remove. 在服务容器中注册的已包装的版本。Register the wrapped versions in the services container.

会话选项Session options

若要替代会话默认值,请使用 SessionOptionsTo override session defaults, use SessionOptions.

选项Option 说明Description
CookieCookie 确定用于创建 Cookie 的设置。Determines the settings used to create the cookie. 名称默认为 SessionDefaults.CookieName (.AspNetCore.Session)。Name defaults to SessionDefaults.CookieName (.AspNetCore.Session). 路径默认为 SessionDefaults.CookiePath (/)。Path defaults to SessionDefaults.CookiePath (/). SameSite 默认为 SameSiteMode.Lax (1)。SameSite defaults to SameSiteMode.Lax (1). HttpOnly 默认为 trueHttpOnly defaults to true. IsEssential 默认为 falseIsEssential defaults to false.
IdleTimeoutIdleTimeout IdleTimeout 显示放弃其内容前,内容可以空闲多长时间。The IdleTimeout indicates how long the session can be idle before its contents are abandoned. 每个会话访问都会重置超时。Each session access resets the timeout. 此设置仅适用于会话内容,不适用于 Cookie。This setting only applies to the content of the session, not the cookie. 默认为 20 分钟。The default is 20 minutes.
IOTimeoutIOTimeout 允许从存储加载会话或者将其提交回存储的最大时长。The maximum amount of time allowed to load a session from the store or to commit it back to the store. 此设置可能仅适用于异步操作。This setting may only apply to asynchronous operations. 可以使用 InfiniteTimeSpan 禁用超时。This timeout can be disabled using InfiniteTimeSpan. 默认值为 1 分钟。The default is 1 minute.

会话使用 Cookie 跟踪和标识来自单个浏览器的请求。Session uses a cookie to track and identify requests from a single browser. 默认情况下,此 Cookie 名为 .AspNetCore.Session ,并使用路径 /By default, this cookie is named .AspNetCore.Session, and it uses a path of /. 由于 Cookie 默认值不指定域,因此它不提供页上的客户端脚本(因为 HttpOnly 默认为 true)。Because the cookie default doesn't specify a domain, it isn't made available to the client-side script on the page (because HttpOnly defaults to true).

若要替换 Cookie 会话默认值,请使用 SessionOptionsTo override cookie session defaults, use SessionOptions:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDistributedMemoryCache();

    services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

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

应用使用 IdleTimeout 属性确定放弃服务器缓存中的内容前,内容可以空闲多长时间。The app uses the IdleTimeout property to determine how long a session can be idle before its contents in the server's cache are abandoned. 此属性独立于 Cookie 到期时间。This property is independent of the cookie expiration. 通过会话中间件传递的每个请求都会重置超时。Each request that passes through the Session Middleware resets the timeout.

会话状态为“非锁定” 。Session state is non-locking. 如果两个请求同时尝试修改同一会话的内容,则后一个请求替代前一个请求。If two requests simultaneously attempt to modify the contents of a session, the last request overrides the first. Session 是作为一个连贯会话实现的,这意味着所有内容都存储在一起 。Session is implemented as a coherent session, which means that all the contents are stored together. 两个请求试图修改不同的会话值时,后一个请求可能替代前一个做出的会话更改。When two requests seek to modify different session values, the last request may override session changes made by the first.

设置和获取会话值Set and get Session values

使用 HttpContext.Session 从 Razor Pages PageModel 类或 MVC 控制器类访问会话状态。Session state is accessed from a Razor Pages PageModel class or MVC Controller class with HttpContext.Session. 此属性是 ISession 实现。This property is an ISession implementation.

ISession 实现提供用于设置和检索整数和字符串值的若干扩展方法。The ISession implementation provides several extension methods to set and retrieve integer and string values. 项目引用 Microsoft.AspNetCore.Http.Extensions 包时,扩展方法位于 Microsoft.AspNetCore.Http 命名空间中(添加 using Microsoft.AspNetCore.Http; 语句获取对扩展方法的访问权限)。The extension methods are in the Microsoft.AspNetCore.Http namespace (add a using Microsoft.AspNetCore.Http; statement to gain access to the extension methods) when the Microsoft.AspNetCore.Http.Extensions package is referenced by the project. 这两个包均包括在 Microsoft.AspNetCore.App 元包中。Both packages are included in the Microsoft.AspNetCore.App metapackage.

ISession 扩展方法:ISession extension methods:

以下示例在 Razor Pages 页中检索 IndexModel.SessionKeyName 键(示例应用中的 _Name)的会话值:The following example retrieves the session value for the IndexModel.SessionKeyName key (_Name in the sample app) in a Razor Pages page:

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

...

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

以下示例显示如何设置和获取整数和字符串:The following example shows how to set and get an integer and a 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);

必须对所有会话数据进行序列化以启用分布式缓存方案,即使是在使用内存中缓存的时候。All session data must be serialized to enable a distributed cache scenario, even when using the in-memory cache. 提供最小的字符串和数字序列化程序(请参阅 ISession 的方法和扩展方法)。Minimal string and number serializers are provided (see the methods and extension methods of ISession). 用户必须使用另一种机制(例如 JSON)序列化复杂类型。Complex types must be serialized by the user using another mechanism, such as JSON.

添加以下扩展方法以设置和获取可序列化的对象:Add the following extension methods to set and get serializable objects:

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

    public static T Get<T>(this ISession session, string key)
    {
        var value = session.GetString(key);

        return value == null ? default(T) : 
            JsonConvert.DeserializeObject<T>(value);
    }
}

以下示例演示如何使用扩展方法设置和获取可序列化的对象:The following example shows how to set and get a serializable object with the extension methods:

// Requires you add the Set and Get extension method mentioned in the topic.
if (HttpContext.Session.Get<DateTime>(SessionKeyTime) == default(DateTime))
{
    HttpContext.Session.Set<DateTime>(SessionKeyTime, currentTime);
}

TempDataTempData

ASP.NET Core 公开 Razor Pages 页模型的 TempData 属性MVC 控制器的 TempDataASP.NET Core exposes the TempData property of a Razor Pages page model or TempData of an MVC controller. 此属性存储未读取的数据。This property stores data until it's read. KeepPeek 方法可用于检查数据,而不执行删除。The Keep and Peek methods can be used to examine the data without deletion. 多个请求需要数据时,TempData 非常有助于进行重定向。TempData is particularly useful for redirection when data is required for more than a single request. 使用 Cookie 或会话状态通过 TempData 提供程序实现 TempData。TempData is implemented by TempData providers using either cookies or session state.

TempData 提供程序TempData providers

基于 cookie 的 TempData 提供程序默认用于存储 cookie 中的 TempData。The cookie-based TempData provider is used by default to store TempData in cookies.

使用由 Base64UrlTextEncoder 编码的 IDataProtector 对 Cookie 数据进行加密,然后进行分块。The cookie data is encrypted using IDataProtector, encoded with Base64UrlTextEncoder, then chunked. 因为 Cookie 进行了分块,所以 ASP.NET Core 1.x 中的单个 Cookie 大小限制不适用。Because the cookie is chunked, the single cookie size limit found in ASP.NET Core 1.x doesn't apply. 未压缩 Cookie 数据,因为压缩加密的数据会导致安全问题,如 CRIMEBREACH 攻击。The cookie data isn't compressed because compressing encrypted data can lead to security problems such as the CRIME and BREACH attacks. 有关基于 Cookie 的 TempData 提供程序的详细信息,请参阅 CookieTempDataProviderFor more information on the cookie-based TempData provider, see CookieTempDataProvider.

选择 TempData 提供程序Choose a TempData provider

选择 TempData 提供程序涉及几个注意事项,例如:Choosing a TempData provider involves several considerations, such as:

  1. 应用是否已使用会话状态?Does the app already use session state? 如果是,使用会话状态 TempData 提供程序对应用没有额外的成本(除了数据的大小)。If so, using the session state TempData provider has no additional cost to the app (aside from the size of the data).
  2. 应用是否只对相对较小的数据量(最多 500 个字节)使用 TempData?Does the app use TempData only sparingly for relatively small amounts of data (up to 500 bytes)? 如果是,Cookie TempData 提供程序将为每个携带 TempData 的请求增加较小的成本。If so, the cookie TempData provider adds a small cost to each request that carries TempData. 如果不是,会话状态 TempData 提供程序有助于在使用 TempData 前,避免在每个请求中来回切换大量数据。If not, the session state TempData provider can be beneficial to avoid round-tripping a large amount of data in each request until the TempData is consumed.
  3. 应用是否在多个服务器上的服务器场中运行?Does the app run in a server farm on multiple servers? 如果是,无需其他任何配置,即可在数据保护外使用 Cookie TempData 提供程序(请参阅 ASP.NET Core 数据保护密钥存储提供程序)。If so, there's no additional configuration required to use the cookie TempData provider outside of Data Protection (see ASP.NET Core 数据保护 and Key storage providers).

备注

大多数 Web 客户端(如 Web 浏览器)针对每个 Cookie 的最大大小和/或 Cookie 总数强制实施限制。Most web clients (such as web browsers) enforce limits on the maximum size of each cookie, the total number of cookies, or both. 使用 Cookie TempData 提供程序时,请验证应用未超过这些限制。When using the cookie TempData provider, verify the app won't exceed these limits. 考虑数据的总大小。Consider the total size of the data. 解释加密和分块导致的 Cookie 大小增加。Account for increases in cookie size due to encryption and chunking.

配置 TempData 提供程序Configure the TempData provider

默认情况下启用基于 Cookie 的 TempData 提供程序。The cookie-based TempData provider is enabled by default.

若要启用基于会话的 TempData 提供程序,请使用 AddSessionStateTempDataProvider 扩展方法:To enable the session-based TempData provider, use the AddSessionStateTempDataProvider extension method:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .AddSessionStateTempDataProvider();

    services.AddSession();
}

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

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseSession();
    app.UseMvc();
}

中间件的顺序很重要。The order of middleware is important. 在前面的示例中,在 UseMvc 之后调用 UseSession 时会发生 InvalidOperationException 异常。In the preceding example, an InvalidOperationException exception occurs when UseSession is invoked after UseMvc. 有关详细信息,请参阅中间件排序For more information, see Middleware Ordering.

重要

如果面向 .NET Framework 并使用基于会话的 TempData 提供程序,请将 Microsoft.AspNetCore.Session 包添加到项目。If targeting .NET Framework and using the session-based TempData provider, add the Microsoft.AspNetCore.Session package to the project.

查询字符串Query strings

可以将有限的数据从一个请求传递到另一个请求,方法是将其添加到新请求的查询字符串中。A limited amount of data can be passed from one request to another by adding it to the new request's query string. 这有利于以一种持久的方式捕获状态,这种方式允许通过电子邮件或社交网络共享嵌入式状态的链接。This is useful for capturing state in a persistent manner that allows links with embedded state to be shared through email or social networks. 由于 URL 查询字符串是公共的,因此请勿对敏感数据使用查询字符串。Because URL query strings are public, never use query strings for sensitive data.

除了意外的共享,在查询字符串中包含数据还会为跨站点请求伪造 (CSRF) 攻击创造机会,从而欺骗用户在通过身份验证时访问恶意网站。In addition to unintended sharing, including data in query strings can create opportunities for Cross-Site Request Forgery (CSRF) attacks, which can trick users into visiting malicious sites while authenticated. 然后,攻击者可以从应用中窃取用户数据,或者代表用户采取恶意操作。Attackers can then steal user data from the app or take malicious actions on behalf of the user. 任何保留的应用或会话状态必须防止 CSRF 攻击。Any preserved app or session state must protect against CSRF attacks. 有关详细信息,请参阅预防跨网站请求伪造 (XSRF/CSRF) 攻击For more information, see Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks.

隐藏字段Hidden fields

数据可以保存在隐藏的表单域中,并在下一个请求上回发。Data can be saved in hidden form fields and posted back on the next request. 这在多页窗体中很常见。This is common in multi-page forms. 由于客户端可能篡改数据,因此应用必须始终重新验证存储在隐藏字段中的数据。Because the client can potentially tamper with the data, the app must always revalidate the data stored in hidden fields.

HttpContext.ItemsHttpContext.Items

处理单个请求时,使用 HttpContext.Items 集合存储数据。The HttpContext.Items collection is used to store data while processing a single request. 处理请求后,放弃集合的内容。The collection's contents are discarded after a request is processed. 通常使用 Items 集合允许组件或中间件在请求期间在不同时间点操作且没有直接传递参数的方法时进行通信。The Items collection is often used to allow components or middleware to communicate when they operate at different points in time during a request and have no direct way to pass parameters.

在下面示例中,中间件isVerified 添加到 Items 集合。In the following example, middleware adds isVerified to the Items collection.

app.Use(async (context, next) =>
{
    // perform some verification
    context.Items["isVerified"] = true;
    await next.Invoke();
});

然后,在管道中,另一个中间件可以访问 isVerified 的值:Later in the pipeline, another middleware can access the value of isVerified:

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

对于只供单个应用使用的中间件,string 键是可以接受的。For middleware that's only used by a single app, string keys are acceptable. 应用实例间共享的中间件应使用唯一的对象键以避免键冲突。Middleware shared between app instances should use unique object keys to avoid key collisions. 以下示例演示如何使用中间件类中定义的唯一对象键:The following example shows how to use a unique object key defined in a middleware class:

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 builder)
    {
        return builder.UseMiddleware<HttpContextItemsMiddleware>();
    }
}

其他代码可以使用通过中间件类公开的键访问存储在 HttpContext.Items 中的值:Other code can access the value stored in HttpContext.Items using the key exposed by the middleware class:

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

此方法还有避免在代码中使用关键字符串的优势。This approach also has the advantage of eliminating the use of key strings in the code.

缓存Cache

缓存是存储和检索数据的有效方法。Caching is an efficient way to store and retrieve data. 应用可以控制缓存项的生存期。The app can control the lifetime of cached items.

缓存数据未与特定请求、用户或会话相关联。Cached data isn't associated with a specific request, user, or session. 请注意不要缓存可能由其他用户请求检索的特定于用户的数据。Be careful not to cache user-specific data that may be retrieved by other users' requests.

有关更多信息,请参见响应缓存在 ASP.NET CoreFor more information, see 响应缓存在 ASP.NET Core.

依赖关系注入Dependency Injection

使用依赖关系注入可向所有用户提供数据:Use Dependency Injection to make data available to all users:

  1. 定义一项包含数据的服务。Define a service containing the data. 例如,定义一个名为 MyAppData 的类:For example, a class named MyAppData is defined:

    public class MyAppData
    {
        // Declare properties and methods
    }
    
  2. 将服务类添加到 Startup.ConfigureServicesAdd the service class to Startup.ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<MyAppData>();
    }
    
  3. 使用数据服务类:Consume the data service class:

    public class IndexModel : PageModel
    {
        public IndexModel(MyAppData myService)
        {
            // Do something with the service
            //    Examples: Read data, store in a field or property
        }
    }
    

常见错误Common errors

  • “在尝试激活‘Microsoft.AspNetCore.Session.DistributedSessionStore’时无法为类型‘Microsoft.Extensions.Caching.Distributed.IDistributedCache’解析服务。”"Unable to resolve service for type 'Microsoft.Extensions.Caching.Distributed.IDistributedCache' while attempting to activate 'Microsoft.AspNetCore.Session.DistributedSessionStore'."

    这通常是由于不能配置至少一个 IDistributedCache 实现而造成的。This is usually caused by failing to configure at least one IDistributedCache implementation. 有关详细信息,请参阅 分布式缓存在 ASP.NET Core 中缓存在内存中 ASP.NET CoreFor more information, see 分布式缓存在 ASP.NET Core 中 and 缓存在内存中 ASP.NET Core.

  • 在会话中间件保存会话失败的事件中(例如,如果后备存储不可用),中间件记录异常而请求继续正常进行。In the event that the session middleware fails to persist a session (for example, if the backing store isn't available), the middleware logs the exception and the request continues normally. 这会导致不可预知的行为。This leads to unpredictable behavior.

    例如,用户将购物车存储在会话中。For example, a user stores a shopping cart in session. 用户将商品添加到购物车,但提交失败。The user adds an item to the cart but the commit fails. 应用不知道有此失败,因此它向用户报告商品已添加到购物车,但事实并非如此。The app doesn't know about the failure so it reports to the user that the item was added to their cart, which isn't true.

    检查此类错误的建议方法是完成将应用写入到该会话后,从应用代码调用 await feature.Session.CommitAsync();The recommended approach to check for errors is to call await feature.Session.CommitAsync(); from app code when the app is done writing to the session. 如果后备存储不可用,则 CommitAsync 引发异常。CommitAsync throws an exception if the backing store is unavailable. 如果 CommitAsync 失败,应用可以处理异常。If CommitAsync fails, the app can process the exception. 在与数据存储不可用的相同的条件下,LoadAsync 引发异常。LoadAsync throws under the same conditions where the data store is unavailable.

其他资源Additional resources

在 Web 场中托管 ASP.NET Core