在ASP.NET Core MVC应用中显示进度图/加载动画
ASP.NET Core MVC长耗时操作实现进度提示方案
针对你的长耗时操作需求,我们可以通过异步提交+进度轮询的方式实现用户友好的进度提示,以下是具体改造步骤:
一、前端改造
1. 添加进度提示模态框
在原有视图代码末尾添加Bootstrap风格的进度弹窗(确保项目已引入Bootstrap的CSS和JS):
<!-- 进度提示模态框 --> <div class="modal fade" id="progressModal" tabindex="-1" aria-labelledby="progressModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="progressModalLabel">处理中...</h5> </div> <div class="modal-body"> <div class="progress"> <div class="progress-bar" id="progressBar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div> </div> <p id="progressText" class="mt-2">正在初始化...</p> </div> </div> </div> </div>
2. 替换表单同步提交为AJAX异步提交
添加JavaScript代码,拦截表单提交事件,改为异步请求并启动进度轮询:
<script> let pollInterval; // 拦截表单提交 document.getElementById('myform').addEventListener('submit', async function(e) { e.preventDefault(); // 显示进度模态框 const progressModal = new bootstrap.Modal(document.getElementById('progressModal')); progressModal.show(); // 构造表单数据 const formData = new FormData(this); const antiForgeryToken = document.querySelector('input[name="__RequestVerificationToken"]').value; try { // 第一步:提交处理请求,获取任务ID const submitResp = await fetch('/Process/StartYearEndProcess', { method: 'POST', body: formData, headers: { 'RequestVerificationToken': antiForgeryToken } }); if (!submitResp.ok) throw new Error('提交请求失败'); const taskInfo = await submitResp.json(); // 启动进度轮询(每秒一次) pollInterval = setInterval(async () => { const progressResp = await fetch(`/Process/GetProcessProgress?taskId=${taskInfo.taskId}`); if (!progressResp.ok) { clearInterval(pollInterval); document.getElementById('progressText').textContent = '获取进度失败'; return; } const progressData = await progressResp.json(); // 更新进度条和状态文本 document.getElementById('progressBar').style.width = `${progressData.progress}%`; document.getElementById('progressBar').setAttribute('aria-valuenow', progressData.progress); document.getElementById('progressText').textContent = progressData.status; // 处理完成或失败时停止轮询 if (progressData.progress >= 100 || progressData.status.includes('失败')) { clearInterval(pollInterval); setTimeout(() => { progressModal.hide(); // 跳转到结果页面 window.location.href = `/Process/YearEndProcess?taskId=${taskInfo.taskId}`; }, 1500); } }, 1000); } catch (err) { clearInterval(pollInterval); document.getElementById('progressText').textContent = `错误:${err.message}`; } }); </script>
二、后端改造
1. 添加进度跟踪类与依赖注入
创建存储任务进度的类,并在控制器中注入内存缓存(用于存储进度状态):
// 进度跟踪类 public class ProcessProgress { public string TaskId { get; set; } public int Progress { get; set; } public string Status { get; set; } public ProcessingViewModel ResultData { get; set; } } // 修改控制器构造函数,注入IMemoryCache private readonly IConfiguration _configuration; private readonly ILogger<ProcessController> _logger; private readonly IMemoryCache _cache; public ProcessController(IConfiguration configuration, ILogger<ProcessController> logger, IMemoryCache cache) { _configuration = configuration; _logger = logger; _cache = cache; }
2. 添加启动处理的接口
将原有的同步处理逻辑改为后台异步执行,并返回任务ID:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> StartYearEndProcess(ProcessingViewModel processData) { if (!ModelState.IsValid) { return BadRequest(ModelState); } // 生成唯一任务ID var taskId = Guid.NewGuid().ToString(); // 初始化进度缓存 var progress = new ProcessProgress { TaskId = taskId, Progress = 0, Status = "正在准备处理..." }; _cache.Set(taskId, progress, TimeSpan.FromMinutes(30)); // 后台执行长耗时操作 _ = Task.Run(async () => { try { var yearEndProcessBAL = new YearEndProcessBAL(_configuration); // 更新进度:数据插入阶段 progress.Progress = 20; progress.Status = "正在执行数据插入..."; _cache.Set(taskId, progress, TimeSpan.FromMinutes(30)); var re = await yearEndProcessBAL.Process(processData); // 更新进度:获取结果阶段 progress.Progress = 60; progress.Status = "正在获取处理后的数据..."; _cache.Set(taskId, progress, TimeSpan.FromMinutes(30)); processData.processedData = await yearEndProcessBAL.getProcessedData(processData, 1); // 处理完成 progress.Progress = 100; progress.Status = "处理完成!"; progress.ResultData = processData; _cache.Set(taskId, progress, TimeSpan.FromMinutes(30)); } catch (Exception ex) { _logger.LogError(ex, "处理过程中发生错误"); progress.Progress = 0; progress.Status = $"处理失败:{ex.Message}"; _cache.Set(taskId, progress, TimeSpan.FromMinutes(30)); } }); return Json(new { taskId }); }
3. 添加进度查询接口
供前端轮询获取当前任务进度:
[HttpGet] public IActionResult GetProcessProgress(string taskId) { if (!_cache.TryGetValue(taskId, out ProcessProgress progress)) { return NotFound(new { progress = 0, status = "任务不存在或已过期" }); } return Json(new { progress = progress.Progress, status = progress.Status }); }
4. 修改原结果返回接口
通过任务ID从缓存中获取处理结果:
[HttpGet] public IActionResult YearEndProcess(string taskId) { if (!_cache.TryGetValue(taskId, out ProcessProgress progress)) { return View(new ProcessingViewModel()); } return View("YearEndProcess", progress.ResultData ?? new ProcessingViewModel()); }
三、注意事项
- 缓存选型:示例使用
IMemoryCache,适合单服务器场景;如果是多服务器集群,需替换为Redis等分布式缓存。 - 后台任务可靠性:示例用
Task.Run快速实现,生产环境建议使用IHostedService或Hangfire等专业后台任务框架,避免应用池回收导致任务中断。 - 进度精细化:可在BAL层添加进度回调方法,根据业务步骤更精准地更新进度值。
内容的提问来源于stack exchange,提问作者banupriya
相关产品推荐
相关产品推荐

