ASP.NET Forms与ASP.NET Core Redis Session跨站点共享方案咨询
针对你的问题,核心矛盾在于传统ASP.NET(Forms)和ASP.NET Core的Session加密/序列化机制不兼容:前者用MachineKey加密Cookie、BinaryFormatter序列化Session数据;后者默认用DataProtection系统、JSON序列化。下面分两种场景给出解决方案:
一、不修改ASP.NET Forms主站点(当前3个月冻结期)
这种场景需要让ASP.NET Core主动兼容传统ASP.NET的机制,步骤如下:
1. 安装必要NuGet包
Install-Package Microsoft.AspNetCore.DataProtection.SystemWeb Install-Package StackExchange.Redis
2. 配置DataProtection兼容MachineKey
传统ASP.NET的ASP.NET_SessionId Cookie是用WebConfig中的machineKey加密的,Core需要用相同的密钥来解密。在Startup.cs的ConfigureServices中添加:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // 从Forms站点的WebConfig复制machineKey的decryptionKey和validationKey var machineKeySettings = new MachineKeySettings { DecryptionKey = "你的Forms站点decryptionKey", ValidationKey = "你的Forms站点validationKey", Decryption = "AES", Validation = "HMACSHA256" }; // 配置DataProtection使用传统MachineKey解密Cookie var dataProtectionProvider = DataProtectionProvider.Create(config => { config.SetApplicationName("myApp"); // 与Forms站点的applicationName一致 config.UseMachineKey(machineKeySettings); }); services.AddDataProtection().UseProvider(dataProtectionProvider);
3. 自定义Redis Session存储,兼容BinaryFormatter序列化
传统ASP.NET的RedisSessionProvider用BinaryFormatter序列化Session数据,Core默认用JSON,所以需要自定义SessionStore来匹配:
// 配置Redis连接 string redisConn = Configuration.GetValue<string>("Redis:ConnectionString"); var redis = ConnectionMultiplexer.Connect(redisConn); services.AddSingleton<IConnectionMultiplexer>(redis); // 添加自定义Session存储 services.AddSingleton<ISessionStore, BinaryFormatterRedisSessionStore>(); // 配置Session参数与Forms站点一致 services.AddSession(options => { options.Cookie.Name = "ASP.NET_SessionId"; options.Cookie.Path = "/"; // 与Forms站点Cookie路径一致 options.IdleTimeout = TimeSpan.FromMinutes(20); // 与Forms站点Session超时一致 options.Cookie.HttpOnly = true; }); }
自定义SessionStore实现代码
public class BinaryFormatterRedisSessionStore : ISessionStore { private readonly IConnectionMultiplexer _redis; private readonly string _appName = "myApp"; public BinaryFormatterRedisSessionStore(IConnectionMultiplexer redis) { _redis = redis; } public ISession Create(string sessionId, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSession) { return new BinaryFormatterRedisSession(sessionId, idleTimeout, _redis.GetDatabase(), _appName); } } public class BinaryFormatterRedisSession : ISession { private readonly string _sessionId; private readonly TimeSpan _idleTimeout; private readonly IDatabase _redisDb; private readonly string _appName; private Dictionary<string, byte[]> _sessionData; private bool _isModified; public BinaryFormatterRedisSession(string sessionId, TimeSpan idleTimeout, IDatabase redisDb, string appName) { _sessionId = sessionId; _idleTimeout = idleTimeout; _redisDb = redisDb; _appName = appName; _sessionData = new Dictionary<string, byte[]>(); } public string Id => _sessionId; public bool IsAvailable => true; public IEnumerable<string> Keys => _sessionData.Keys; public Task LoadAsync(CancellationToken cancellationToken = default) { var redisKey = $"{_appName}:{_sessionId}"; var data = _redisDb.StringGet(redisKey); if (!data.IsNull) { using var ms = new MemoryStream(data); var formatter = new BinaryFormatter(); _sessionData = (Dictionary<string, byte[]>)formatter.Deserialize(ms); _redisDb.KeyExpire(redisKey, _idleTimeout); } return Task.CompletedTask; } public Task CommitAsync(CancellationToken cancellationToken = default) { if (_isModified) { var redisKey = $"{_appName}:{_sessionId}"; using var ms = new MemoryStream(); var formatter = new BinaryFormatter(); formatter.Serialize(ms, _sessionData); _redisDb.StringSet(redisKey, ms.ToArray(), _idleTimeout); _isModified = false; } return Task.CompletedTask; } public bool TryGetValue(string key, out byte[] value) => _sessionData.TryGetValue(key, out value); public void Set(string key, byte[] value) { _sessionData[key] = value; _isModified = true; } public void Remove(string key) { _sessionData.Remove(key); _isModified = true; } public void Clear() { _sessionData.Clear(); _isModified = true; } }
4. 确保中间件顺序正确
在Configure方法中,UseSession必须在UseMvc之前:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseSession(); // 必须在UseMvc之前 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
二、未来可修改ASP.NET Forms主站点时的优雅方案
这种场景下,我们统一两边的加密和序列化机制,让Forms站点使用ASP.NET Core的DataProtection系统,步骤如下:
1. 在Forms站点中配置DataProtection
- 安装NuGet包:
Install-Package Microsoft.AspNetCore.DataProtection.SystemWeb - 在
Global.asax的Application_Start中添加:
protected void Application_Start() { // 配置DataProtection与Core站点共享密钥存储(Redis) var redisConn = ConfigurationManager.ConnectionStrings["RedisConnection"].ConnectionString; var redis = ConnectionMultiplexer.Connect(redisConn); var dataProtectionProvider = DataProtectionProvider.Create(config => { config.SetApplicationName("myApp"); config.PersistKeysToRedis(redis, "DataProtection-Keys"); // 与Core站点的密钥存储路径一致 }); // 替换传统MachineKey为DataProtection DataProtectionSetup.SetupMachineKey(dataProtectionProvider); }
2. 统一Session序列化方式
如果需要更灵活的序列化(比如JSON),可以自定义Forms站点的RedisSessionProvider序列化器,或者保持BinaryFormatter(Core站点也用对应的自定义SessionStore)。
3. 同步两边配置
确保Forms和Core站点的:
- Session超时时间一致
- Cookie名称均为
ASP.NET_SessionId - Redis连接字符串一致
- ApplicationName一致
这样修改后,两边的Session加密和序列化机制完全统一,Session共享会更稳定可靠。
内容的提问来源于stack exchange,提问作者Hendel

