.NET Razor Pages Scoped依赖注入回发时属性值丢失问题求助
解决Razor Pages页面回发时属性值丢失的问题
问题根源
用AddScoped注册的IDataService每个HTTP请求都会生成新实例,回发属于新请求,所以之前存储的属性值自然清零;AddSingleton是全局单例,所有页面、用户共用,不符合“仅当前页面回发保留,刷新/跳转清除”的需求。
可行解决方案
方案一:用TempData存储状态
TempData专为跨请求传递数据设计,默认读取后自动标记删除,刚好匹配“回发保留,跳转/刷新清除”的需求。
步骤1:添加TempData扩展方法
用来序列化/反序列化复杂类型和二进制数据:
public static class TempDataExtensions { public static void Set<T>(this ITempDataDictionary tempData, string key, T value) { tempData[key] = JsonSerializer.Serialize(value); } public static T? Get<T>(this ITempDataDictionary tempData, string key) { if (tempData.TryGetValue(key, out var obj)) { return JsonSerializer.Deserialize<T>((string)obj); } return default; } public static void SetBytes(this ITempDataDictionary tempData, string key, byte[]? value) { if (value == null) { tempData.Remove(key); return; } tempData[key] = Convert.ToBase64String(value); } public static byte[]? GetBytes(this ITempDataDictionary tempData, string key) { if (tempData.TryGetValue(key, out var obj)) { return Convert.FromBase64String((string)obj); } return default; } }
步骤2:修改页面模型
在OnGet中恢复数据,OnPost中保存数据:
public class ProfileModel : PageModel { private readonly ILogger<ProfileModel> _loggerService; private readonly IUserService _userService; // 假设用于获取初始用户数据 public Models.User? User { get; set; } public byte[]? LogoData { get; set; } public byte[]? SignatureData { get; set; } public ProfileModel(ILogger<ProfileModel> loggerService, IUserService userService) { _loggerService = loggerService; _userService = userService; } public void OnGet() { // 从TempData恢复数据 User = TempData.Get<Models.User>("User"); LogoData = TempData.GetBytes("LogoData"); SignatureData = TempData.GetBytes("SignatureData"); // 首次加载时从服务获取初始数据 if (User == null) { User = _userService.GetCurrentUser(User.Identity?.Name); } // 保留TempData,确保回发后还能读取 TempData.Keep("User"); TempData.Keep("LogoData"); TempData.Keep("SignatureData"); } public async Task<IActionResult> OnPostRecreateApiKeyAsync() { // 业务逻辑:比如重新生成API Key // ... // 更新TempData中的数据 TempData.Set("User", User); TempData.SetBytes("LogoData", LogoData); TempData.SetBytes("SignatureData", SignatureData); return Page(); } }
方案二:用Session存储状态
Session适合存储较多数据(包括二进制数据),生命周期为用户会话(直到超时或关闭浏览器),但需要手动管理清除逻辑。
步骤1:启用Session
在Program.cs中添加Session服务和中间件:
// 注册Session服务 builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); // 设置会话超时 options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 注册HttpContextAccessor(用于在服务中获取Session) builder.Services.AddHttpContextAccessor(); // 配置中间件(注意顺序:在UseRouting之后,UseAuthorization之前) var app = builder.Build(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseSession(); // 启用Session app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.Run();
步骤2:修改DataService用Session存取数据
public class DataService : IDataService { private readonly IHttpContextAccessor _httpContextAccessor; private ISession Session => _httpContextAccessor.HttpContext!.Session; public DataService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Models.User? User { get => Session.Get<Models.User>("User"); set => Session.Set("User", value); } public byte[]? LogoData { get => Session.GetBytes("LogoData"); set => Session.SetBytes("LogoData", value); } public byte[]? SignatureData { get => Session.GetBytes("SignatureData"); set => Session.SetBytes("SignatureData", value); } } // Session扩展方法 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); } public static void SetBytes(this ISession session, string key, byte[]? value) { if (value == null) { session.Remove(key); return; } session.Set(key, value); } public static byte[]? GetBytes(this ISession session, string key) { session.TryGetValue(key, out var value); return value; } }
步骤3:页面模型中使用DataService
和原有代码逻辑一致,注入IDataService后直接使用即可:
public class ProfileModel : PageModel { private readonly ILogger<ProfileModel> _loggerService; private readonly IDataService _dataService; private readonly IUserService _userService; public Models.User? User { get; set; } public byte[]? LogoData { get; set; } public byte[]? SignatureData { get; set; } public ProfileModel(ILogger<ProfileModel> loggerService, IDataService dataService, IUserService userService) { _loggerService = loggerService; _dataService = dataService; _userService = userService; } public void OnGet() { // 从DataService恢复数据 User = _dataService.User; LogoData = _dataService.LogoData; SignatureData = _dataService.SignatureData; // 首次加载时初始化 if (User == null) { User = _userService.GetCurrentUser(User.Identity?.Name); _dataService.User = User; } } public async Task<IActionResult> OnPostRecreateApiKeyAsync() { // 业务逻辑... // 更新DataService中的数据 _dataService.User = User; _dataService.LogoData = LogoData; _dataService.SignatureData = SignatureData; return Page(); } }
方案三:隐藏字段(仅适合简单小数据)
如果数据量小且无二进制数据,可以在表单中添加隐藏字段,让数据随回发请求传递:
<form method="post"> <!-- 你的表单控件 --> <input type="hidden" asp-for="User.Id" /> <input type="hidden" asp-for="User.Name" /> <!-- 其他需要保留的User属性 --> <button type="submit">提交</button> </form>
页面模型会自动绑定隐藏字段的值,无需额外存储。但二进制数据(如LogoData)不适合用这种方式,会导致请求体积过大。
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| TempData | 自动管理生命周期,使用简单,符合需求 | 存储容量有限,不适合超大数据 |
| Session | 支持大数据和二进制数据,存储灵活 | 需要配置中间件,生命周期是会话级(需手动清除) |
| 隐藏字段 | 无需依赖额外服务,实现简单 | 不支持二进制数据,数据量大时请求过大 |
内容的提问来源于stack exchange,提问作者nerdalert
相关产品推荐
相关产品推荐

