You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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());
}

三、注意事项

  1. 缓存选型:示例使用IMemoryCache,适合单服务器场景;如果是多服务器集群,需替换为Redis等分布式缓存。
  2. 后台任务可靠性:示例用Task.Run快速实现,生产环境建议使用IHostedService或Hangfire等专业后台任务框架,避免应用池回收导致任务中断。
  3. 进度精细化:可在BAL层添加进度回调方法,根据业务步骤更精准地更新进度值。

内容的提问来源于stack exchange,提问作者banupriya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 15:47:04