ASP.NET Core MVC中PRG模式高性能实现:保留输入与验证信息
ASP.NET Core MVC多页表单PRG模式性能疑问解答
背景与代码实现
我基于ASP.NET Core MVC构建了一个用于提交和更新活动申请的多页表单,采用POST-Redirect-GET(PRG)模式,需保留输入内容与验证错误信息。表单每页包含主办方管理的展示信息(如场地列表)和用户输入信息(如用户选择的场地)。现有可运行实现,但对性能存疑,代码如下:
using System.Text.Json; using Microsoft.AspNetCore.Mvc; public class ApplicationController : Controller { [HttpGet] public async Task<IActionResult> Step1(int id) { this.TempData.TryGetValue("viewModel", out Object? viewModelJSON); Step1ViewModel viewModel; if (viewModelJSON == null) { // create empty view model viewModel = new Step1ViewModel(); viewModel.ID = id; // when simple GET: show data from db var userInputModel = await this.LoadVenueChoicesAsync(id); // fill view model if data exists if (userInputModel != null) { viewModel.UserInput = BuildUserInputViewModel(userInputModel); } } else { // when GET after POST: show input viewModel = JsonSerializer.Deserialize<Step1ViewModel>(viewModelJSON); await this.TryValidateModelAsync(viewModel); } var venueList = await this.LoadVenuesAsync(); viewModel.Information = BuildInformationViewModel(venueList); return this.View(viewModel); } [HttpPost] public async Task<IActionResult> Step1(Step1ViewModel viewModel) { if (this.ModelState.IsValid == false) { var viewModelJSON = JsonSerializer.Serialize<Step1ViewModel>(viewModel); this.TempData["viewModel"] = viewModelJSON; return this.RedirectToAction("Step1", new { id = viewModel.ID }); } else { await this.StoreVenueChoicesAsync(viewModel.UserInput); return this.RedirectToAction("Step2", new { id = viewModel.ID }); } } }
疑问解答
1. JSON序列化是否会引发性能问题?是否有其他替代方案?
- 常规场景下,
System.Text.Json的序列化/反序列化性能足以应对高并发,只有当Step1ViewModel包含超大对象(如大文本、二进制数据)时,才会出现明显性能损耗。 - 替代方案:
- 仅序列化用户输入部分:当前序列化了整个ViewModel,但展示信息(
Information)可在GET请求时重新从数据库加载,只需保留UserInput和验证错误相关数据,减少序列化体积。 - 序列化ModelState而非整个ViewModel:直接将验证状态
ModelState序列化存入TempData,避免冗余数据。示例代码:// POST时保存ModelState TempData["ModelState"] = JsonSerializer.Serialize(ModelState); // GET时恢复ModelState if (TempData.ContainsKey("ModelState")) { var serializedModelState = TempData["ModelState"].ToString(); var modelState = JsonSerializer.Deserialize<ModelStateDictionary>(serializedModelState); ModelState.Merge(modelState); } - 二进制序列化库:若需序列化大对象,可使用MessagePack这类二进制序列化库,性能优于JSON,但需额外引入依赖。
- 仅序列化用户输入部分:当前序列化了整个ViewModel,但展示信息(
2. TempData依赖Session,是否需要调用LoadAsync()?
- 需要。TempData默认依赖Session存储,在读取TempData前调用
await HttpContext.Session.LoadAsync()可避免重复读取Session存储,提升高并发场景下的性能,尤其是使用分布式Session时。 - 修改后的GET方法示例:
[HttpGet] public async Task<IActionResult> Step1(int id) { await HttpContext.Session.LoadAsync(); // 先加载Session this.TempData.TryGetValue("viewModel", out Object? viewModelJSON); // 后续逻辑不变 } - 另外,高并发场景建议使用分布式Session(如Redis)替代内存Session,避免多服务器部署时的数据一致性问题。
3. 此方案还存在其他问题吗?
- TempData过期问题:TempData默认单次读取后即被删除,用户刷新页面会丢失输入和错误信息。可通过
TempData.Keep("viewModel")读取后保留数据,但需注意避免数据冗余。 - 内存与传输开销:序列化整个ViewModel会增加服务器内存消耗(分布式场景下还会增加网络传输量),建议仅序列化必要的用户输入数据。
- 重复验证与查询:POST时已完成Model验证,GET时无需再次调用
TryValidateModelAsync;展示信息若不常变化,可通过IDistributedCache缓存减少数据库查询次数。 - 分布式部署一致性:若应用部署在多台服务器上,必须使用分布式Session,否则TempData无法跨服务器读取,导致用户数据丢失。
内容的提问来源于stack exchange,提问作者pikachu
相关产品推荐
相关产品推荐

