32. 会话和状态管理
32.1 关于会话和状态管理
HTTP
是无状态的协议。 默认情况下,HTTP
请求是不保留用户值的独立消息。但是我们可以通过以下几种方式保留请求用户数据:
Cookie
:通常存储在客户端的数据,请求时带回服务端Session
:存储在服务端的数据(可以在存储在内存、进程等介质中)Query Strings
:通过Http
请求地址参数共享HttpContext.Items
:存储在服务端,只在请求声明周期内使用,请求结束自动销毁Cache
:服务端缓存,包括内存缓存、分布式内存缓存、IO 缓存、序列化缓存以及数据库缓存AsyncLocal<T>
:通过异步控制流实现本地数据共享,跨线程
32.2 如何使用
32.2.1 Cookie
使用
使用 Cookie
非常简单,如:
// 读取 Cookiesvar value = httpContext.Request.Cookies["key"];// 设置 Cookiesvar option = new CookieOptions();option.Expires = DateTime.Now.AddMilliseconds(10);httpContext.Response.Cookies.Append(key, value, option);// 删除 CookieshttpContext.Response.Cookies.Delete(key);
特别说明
httpContext
可以通过 IHttpContextAccessor
获取,也可以通过 App.HttpContext
获取。
我们还可以通过 Cookie
实现授权功能及单点登录(SSO):网站共享 Cookie
32.2.2 Session
使用
在使用 Session
之前,必须注册 Session
服务:(如果
public class Startup{ 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(); } 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(); }); }}
中间件注册顺序
app.UseSession()
必须在 app.UseRouting()
和 app.UseEndpoints()
之间注册!
- 常见例子:
// 读取 Sessionvar byteArr = httpContext.Session.Get("key"); // 返回 byte[]var str = httpContext.Session.GetString("key"); // 返回 string[]var num = httpContext.Session.GetInt32("key"); // 返回 int// 设置 SessionhttpContext.Session.SetString("key", "value"); // 设置字符串httpContext.Session.SetInt32("key", 1); // 设置 int 类型
- 自定义设置任意类型拓展:
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); }}
- 防止
Session ID
改变或Session
失效
在 Startup.cs
的 ConfigureServices
配置即可:
services.Configure<CookiePolicyOptions>(options =>{ options.CheckConsentNeeded = context => false; // 默认为true,改为false options.MinimumSameSitePolicy = SameSiteMode.None;});
32.2.3 Query Strings
使用
该方式使用非常简单,只需 httpContext.Request.Query["key"]
即可。
32.2.4 HttpContext.Items
使用
HttpContext
对象提供了 Items
集合属性,可以让我们在单次请求间共享数据,请求结束立即销毁,可以存储任何数据。使用也非常简单,如:
// 读取var value = httpContext.Items["key"];// 添加httpContext.Items["key"] = "任何值包括对象";// 删除httpContext.Items.Remove("key");
32.2.5 Cache
方式
参见 分布式缓存 文档
32.2.6 AsyncLocal<T>
方式
AsyncLocal<T>
可以说是进程内共享数据的大利器,可以通过该类实现跨线程、异步控制流中共享数据,如:
using System;using System.Threading;using System.Threading.Tasks;class Example{ static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>(); static ThreadLocal<string> _threadLocalString = new ThreadLocal<string>(); static async Task AsyncMethodA() { // Start multiple async method calls, with different AsyncLocal values. // We also set ThreadLocal values, to demonstrate how the two mechanisms differ. _asyncLocalString.Value = "Value 1"; _threadLocalString.Value = "Value 1"; var t1 = AsyncMethodB("Value 1"); _asyncLocalString.Value = "Value 2"; _threadLocalString.Value = "Value 2"; var t2 = AsyncMethodB("Value 2"); // Await both calls await t1; await t2; } static async Task AsyncMethodB(string expectedValue) { Console.WriteLine("Entering AsyncMethodB."); Console.WriteLine(" Expected '{0}', AsyncLocal value is '{1}', ThreadLocal value is '{2}'", expectedValue, _asyncLocalString.Value, _threadLocalString.Value); await Task.Delay(100); Console.WriteLine("Exiting AsyncMethodB."); Console.WriteLine(" Expected '{0}', got '{1}', ThreadLocal value is '{2}'", expectedValue, _asyncLocalString.Value, _threadLocalString.Value); } static async Task Main(string[] args) { await AsyncMethodA(); }}// The example displays the following output:// Entering AsyncMethodB.// Expected 'Value 1', AsyncLocal value is 'Value 1', ThreadLocal value is 'Value 1'// Entering AsyncMethodB.// Expected 'Value 2', AsyncLocal value is 'Value 2', ThreadLocal value is 'Value 2'// Exiting AsyncMethodB.// Expected 'Value 2', got 'Value 2', ThreadLocal value is ''// Exiting AsyncMethodB.// Expected 'Value 1', got 'Value 1', ThreadLocal value is ''
为了简化操作,Furion v2.18+
版本实现了轻量级的 CallContext
静态类,内部使用 AsyncLocal<T>
实现,使用如下:
CallContext.SetLocalValue("name", "Furion");CallContext.GetLocalValue("name");CallContext<int>.SetLocalValue("count", 1);CallContext<int>.GetLocalValue("count");
了解更多 AsyncLocal<T>
知识:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.asynclocal-1?redirectedfrom=MSDN&view=net-5.0
32.3 反馈与建议
与我们交流
给 Furion 提 Issue。