会话状态是许多 Web 应用程序的关键组件,跨 HTTP 请求存储特定于用户的数据。 从 ASP.NET Framework 迁移到 ASP.NET Core 时,会话状态会带来独特的挑战,因为两个框架处理会话的方式大相径庭。
为什么会话迁移很复杂
ASP.NET Framework 和 ASP.NET Core 具有完全不同的会话管理方法:
- ASP.NET Framework 提供自动对象序列化和内置会话锁定
- ASP.NET Core 需要手动序列化,并且不提供会话锁定保证
这些差异意味着您无法在不进行更改的情况下将会话代码从 Framework 移动到 Core。
迁移策略概述
在迁移过程中,有三种处理会话状态的主要方法:
- 完全重写 - 重写所有会话代码以使用 ASP.NET Core 的本机会话实现
- 使用单独的会话进行增量 - 逐条迁移组件,每个应用都保持自己的会话状态
- 增量式共享会话 - 在 Framework 和 Core 应用程序之间共享会话数据的同时迁移组件
对于大多数应用程序,迁移到 ASP.NET Core 会话 可提供最佳性能和可维护性。 但是,较大的应用程序或具有复杂会话要求的应用程序可能受益于使用 System.Web 适配器的增量方法。
选择迁移方法
有三个主要选项用于将会话状态从 ASP.NET Framework 迁移到 ASP.NET Core。 选择取决于迁移时间线、是否需要同时运行这两个应用程序,以及你愿意重写的代码量。
快速决策指南
回答以下问题以选择你的方法:
是否执行完全重写或增量迁移?
- 完整重写→ 内置 ASP.NET 核心会话
- 增量迁移→继续问题 2
ASP.NET Framework 和 ASP.NET Core 应用是否需要访问相同的会话数据?
- 需要共享会话→远程应用会话。
- 否,单独的会话正常 包装的 ASP.NET Core 会话
了解差异
在深入了解实现详细信息之前,请务必了解 ASP.NET Framework 和 ASP.NET Core 如何处理会话状态的不同:
- 对象序列化
- ASP.NET Framework 自动序列化和反序列化对象(除非使用内存中存储)
- ASP.NET Core 需要手动序列化/反序列化,并将数据存储为
byte[]
- 会话锁定
- ASP.NET 框架在会话中锁定会话使用情况,并串行处理后续请求
- ASP.NET Core 不提供会话锁定保证
迁移方法比较
| 方法 | 代码更改 | 性能 | 会话共享 | 何时使用 |
|---|---|---|---|---|
| 内置 ASP.NET 核心 | 高 - 重写所有会话代码 | 最佳 | 没有 | 完全重写,性能关键型应用 |
| 包装的 ASP.NET Core | 低 - 保留现有会话模式 | 好 | 没有 | 增量迁移,无需共享状态 |
| 远程应用 | 低 - 保留现有会话模式 | 一般 | 完整 | 同时运行这两个应用 |
System.Web 适配器通过两个关键接口桥接 ASP.NET Framework 和 ASP.NET Core 会话实现之间的差异,实现“包装”和“远程应用”方法:
Microsoft.AspNetCore.SystemWebAdapters.ISessionManager:接受一个HttpContext对象和会话元数据,返回一个ISessionState对象Microsoft.AspNetCore.SystemWebAdapters.ISessionState:描述会话对象状态并支持 HttpSessionState 类型
内置 ASP.NET 核心会话状态
执行完整迁移时,请选择此方法,并可以重写与会话相关的代码以使用 ASP.NET Core 的本机会话实现。
ASP.NET Core 提供了一个轻量级、高性能的会话状态实现,它将数据存储为 byte[],并要求显式序列化。 此方法提供最佳性能,但在迁移过程中需要更多代码更改。
有关如何设置和使用它的详细信息,请参阅 [ASP.NET 会话文档](xref:fundamentals/app-state.md)。
优点和缺点
| 优点 | 缺点 |
|---|---|
| 最佳性能和内存占用最少 | 需要重写所有会话访问代码 |
| 本机 ASP.NET Core 实现 | 无自动对象序列化 |
| 完全控制序列化策略 | 无会话锁定(并发请求可能导致冲突) |
| 没有其他依赖项 | ASP.NET Framework 模式的重大更改 |
| 支持所有 ASP.NET 核心会话提供程序 | 会话密钥区分大小写(与 Framework 不同) |
迁移注意事项
迁移到内置 ASP.NET 核心会话时:
需要更改代码:
- 将
Session["key"]替换为HttpContext.Session.GetString("key") - 将
Session["key"] = value替换为HttpContext.Session.SetString("key", value) - 为复杂对象添加显式序列化/反序列化
- 显式处理 null 值(无自动类型转换)
数据迁移:
- 会话数据结构更改需要仔细规划
- 考虑在迁移期间并行运行这两个系统
- 根据需要实现会话数据导入/导出实用工具
测试策略:
- 单元测试会话的序列化/反序列化逻辑
- 跨请求的集成测试会话行为
- 负载测试并发会话访问模式
何时选择此方法:
- 可以负担得起重写与会话相关的代码
- 性能是首要任务
- 你未与旧版应用程序共享会话数据
- 想要完全消除 System.Web 依赖项
System.Web 适配器会话
注释
这利用 System.Web 适配器 来简化迁移。
序列化配置
该 HttpSessionState 对象需要远程应用会话状态的序列化。
在 ASP.NET Framework 中, BinaryFormatter 用于自动序列化会话值内容。 为了将这些内容序列化,以便与 System.Web 适配器一起使用,序列化必须使用 ISessionKeySerializer 实现显式配置。
开箱即用,有一个简单的 JSON 序列化程序,允许使用 JsonSessionSerializerOptions 将每个会话密钥注册到已知类型:
RegisterKey<T>(string)- 注册会话密钥为已知类型。 此注册对于正确的序列化/反序列化是必需的。 缺少注册会导致错误并阻止会话访问。
builder.Services.AddSystemWebAdapters()
.AddJsonSessionSerializer(options =>
{
// Serialization/deserialization requires each session key to be registered to a type
options.RegisterKey<int>("test-value");
});
如果需要更多自定义项,则可以实现 ISessionKeySerializer。
builder.Services.AddSystemWebAdapters()
.AddSessionSerializer();
builder.Services.AddSingleton<ISessionKeySerializer, CustomSessionKeySerializer>();
sealed class CustomSessionKeySerializer : ISessionKeySerializer
{
public bool TryDeserialize(string key, byte[] bytes, out object? obj)
{
// Custom deserialization logic
}
public bool TrySerialize(string key, object? value, out byte[] bytes)
{
// Custom serialization logic
}
}
注释
使用 AddJsonSessionSerializer 注册模式时,无需调用 AddSessionSerializer ,因为它会自动添加。 如果只想使用自定义实现,则必须手动添加它。
启用会话
会话支持需要显式激活。 使用 ASP.NET 核心元数据按路由或全局配置它:
- 批注控制器
[Session]
public class SomeController : Controller
{
}
- 在所有终结点上启用全局功能
app.MapDefaultControllerRoute()
.RequireSystemWebAdapterSession();
包装过的 ASP.NET Core 会话状态
如果迁移的组件不需要与旧应用程序共享会话数据,请选择此方法。
Microsoft.Extensions.DependencyInjection.WrappedSessionExtensions.AddWrappedAspNetCoreSession 扩展方法添加了一个包装的 ASP.NET Core 会话,与适配器一起使用。 它使用与 ISession 相同的后备存储,同时提供强类型访问。
ASP.NET Core 的配置:
builder.Services.AddSystemWebAdapters()
.AddJsonSessionSerializer(options =>
{
// Serialization/deserialization requires each session key to be registered to a type
options.RegisterKey<int>("test-value");
options.RegisterKey<SessionDemoModel>("SampleSessionItem");
})
.AddWrappedAspNetCoreSession();
框架应用程序无需进行任何更改。
有关详细信息,请参阅 封装的会话状态示例应用
远程应用会话状态
注释
这利用 System.Web 适配器 来简化迁移。
如果需要在增量迁移期间在 ASP.NET Framework 和 ASP.NET Core 应用程序之间共享会话状态,请选择此方法。
远程应用会话通过公开 ASP.NET Framework 应用上的终结点,使应用程序之间的通信能够检索和设置会话状态。
先决条件
完成 远程应用设置 说明,以连接 ASP.NET Core 和 ASP.NET Framework 应用程序。
应用程序配置
ASP.NET 核心配置:
调用 AddRemoteAppSession 和 AddJsonSessionSerializer 注册已知会话项类型:
builder.Services.AddSystemWebAdapters()
.AddJsonSessionSerializer(options =>
{
// Serialization/deserialization requires each session key to be registered to a type
options.RegisterKey<int>("test-value");
options.RegisterKey<SessionDemoModel>("SampleSessionItem");
})
.AddRemoteAppClient(options =>
{
// Provide the URL for the remote app that has enabled session querying
options.RemoteAppUrl = new(builder.Configuration["ReverseProxy:Clusters:fallbackCluster:Destinations:fallbackApp:Address"]);
// Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
options.ApiKey = builder.Configuration["RemoteAppApiKey"];
})
.AddSessionClient();
ASP.NET 框架配置:
将此更改添加到 Global.asax.cs:
public class Global : HttpApplication
{
protected void Application_Start()
{
SystemWebAdapterConfiguration.AddSystemWebAdapters(this)
.AddJsonSessionSerializer(options =>
{
// Serialization/deserialization requires each session key to be registered to a type
options.RegisterKey<int>("test-value");
options.RegisterKey<SessionDemoModel>("SampleSessionItem");
})
// Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
// ApiKey is a string representing a GUID
.AddRemoteAppServer(options => options.ApiKey = ConfigurationManager.AppSettings["RemoteAppApiKey"])
.AddSessionServer();
}
}
使用 Aspire 时,配置将通过环境变量完成,并由 AppHost 设置。 若要启用远程会话,必须启用该选项:
...
var coreApp = builder.AddProject<Projects.CoreApplication>("core")
.WithHttpHealthCheck()
.WaitFor(frameworkApp)
.WithIncrementalMigrationFallback(frameworkApp, options => options.RemoteSession = RemoteSession.Enabled);
...
通信协议
只读会话
只读会话在不锁定的情况下检索会话状态。 此过程使用返回会话状态并立即关闭的单个 GET 请求。

可写会话
可写入的会话需要附加步骤:
- 从与只读会话相同的
GET请求开始 - 使初始
GET请求保持打开状态,直到会话完成 - 使用额外的
PUT请求更新状态 - 仅在更新完成后关闭初始请求
