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

如何将ASP.NET文件上传API改为异步,支持校验期间用户自由操作应用?

实现方案说明

首先纠正一个认知误区:普通的异步接口(加async/await关键字)只是优化服务端的线程利用率,客户端仍然需要等待接口返回结果,无法实现你要的「上传后立刻跳转、后续主动收通知」的需求。你需要的是「请求响应+后台异步任务+主动推送」的架构,前后端改造如下:


一、后端(.NET Web API)改造

1. 基础数据结构调整

给你的文件表新增2个字段:

  • CheckStatus:int类型,0=校验中,1=校验成功,2=校验失败
  • CheckRemark:nvarchar类型,存储校验失败的原因、病毒提示等信息

2. 上传接口改造

修改原接口逻辑,只做「接收文件、落盘、写初始数据库记录、提交校验任务到后台队列」,立刻返回响应,不再等待校验完成:

// 接口加异步逻辑仅优化服务端性能,核心是不等待校验完成就返回
public async Task<HttpResponseMessage> Create()
{
    try
    {
        HttpRequest request = HttpContext.Current.Request;
        HttpPostedFile file = request.Files[0];
        // 省略原有参数拼接逻辑
        sqlDataAccess.ExecuteQuery("spCreateDokument", ar); 
        int id = sqlDataAccess.GetParamValue<int>(ar, "@Id");
        string filePath = Path.Combine(GetFolder(), id.ToString() + Path.GetExtension(file.FileName));
        file.SaveAs(filePath);

        // 核心:把校验任务提交到后台队列,不阻塞当前请求
        // 可以用Hangfire、ASP.NET Core HostedService+队列实现,这里以Hangfire为例
        BackgroundJob.Enqueue(() => ExecuteFileCheckAsync(id, filePath));

        // 返回202接受状态,同时返回文件id给前端后续关联通知
        return Request.CreateResponse(HttpStatusCode.Accepted, new { FileId = id });
    }
    catch (Exception ex)
    {
        throw ProcessException(ex, System.Reflection.MethodBase.GetCurrentMethod());
    }
}

// 单独抽离的校验逻辑,后台独立运行
public async Task ExecuteFileCheckAsync(int fileId, string filePath)
{
    try
    {
        // 调用第三方校验接口,这里是耗时1-2分钟的逻辑
        var checkResult = await ThirdPartCheckService.DoCheckAsync(filePath);
        if(checkResult.IsSuccess)
        {
            // 更新数据库状态为校验成功
            UpdateFileStatus(fileId, 1, "校验通过");
            // 推送成功通知给前端
            await NotificationHub.PushCheckResult(fileId, true, "文件校验通过,上传成功");
        }
        else
        {
            // 更新数据库状态为校验失败,可同时删除无效文件
            UpdateFileStatus(fileId, 2, checkResult.ErrorMsg);
            File.Delete(filePath);
            // 推送失败通知给前端
            await NotificationHub.PushCheckResult(fileId, false, $"文件校验失败:{checkResult.ErrorMsg}");
        }
    }
    catch(Exception ex)
    {
        // 异常处理,更新状态为失败,推送通知
        UpdateFileStatus(fileId, 2, "校验服务异常");
        await NotificationHub.PushCheckResult(fileId, false, "校验服务异常,请重新上传");
    }
}

3. 主动推送模块搭建

用ASP.NET SignalR实现WebSocket推送,只要用户页面没关,不管跳转到哪个路由都能收到通知:

// 定义通知Hub
public class NotificationHub : Hub
{
    // 推送校验结果给指定用户,这里可以根据你的用户体系关联ConnectionId
    public static async Task PushCheckResult(int fileId, bool isSuccess, string msg)
    {
        var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
        // 拿到当前上传用户的ConnectionId推送,也可以广播给指定用户组
        await context.Clients.User(CurrentUserId).ReceiveCheckResult(new { FileId = fileId, IsSuccess = isSuccess, Message = msg });
    }
}

二、前端(Angular)改造

1. 全局SignalR连接服务

创建一个单例的通知服务,应用启动时就建立WebSocket连接,路由跳转不会销毁连接:

// notification.service.ts
import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { MatSnackBar } from '@angular/material/snack-bar'; // 全局通知组件,可替换为你自己的实现

@Injectable({ providedIn: 'root' })
export class NotificationService {
  private hubConnection: signalR.HubConnection;

  constructor(private snackBar: MatSnackBar) {
    this.initConnection();
    this.listenCheckResult();
  }

  private initConnection() {
    this.hubConnection = new signalR.HubConnectionBuilder()
      .withUrl('你的API地址/notificationHub')
      .withAutomaticReconnect() // 自动重连
      .build();
    this.hubConnection.start().catch(err => console.error('连接通知服务失败:', err));
  }

  private listenCheckResult() {
    this.hubConnection.on('ReceiveCheckResult', (result) => {
      // 全局弹出通知,不管用户在哪个页面都能看到
      this.snackBar.open(result.message, '关闭', {
        duration: 5000,
        panelClass: result.isSuccess ? 'success-snack' : 'error-snack'
      });
      // 可选:把结果存到状态管理里,方便后续文件列表页展示
    });
  }
}

2. 上传逻辑调整

上传接口调用成功拿到fileId后,不需要等待后续结果,直接释放当前页面资源,用户可以正常跳转:

// 上传方法示例
uploadFile(file: File) {
  this.http.post('你的API地址/api/file/create', formData).subscribe(res => {
    this.snackBar.open('文件已提交,正在后台校验,请稍候等待结果通知', '关闭', { duration: 3000 });
    // 这里可以直接做路由跳转,不需要等待校验完成
  });
}

三、可选优化点

  • 如果用户临时关闭页面再打开,可以加个接口拉取当前用户所有校验中的文件状态,页面加载时主动查询一次
  • 后台校验任务添加重试机制,避免第三方服务临时不可用导致校验失败
  • 大文件上传可以加分片逻辑,和当前校验逻辑不冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 05:06:02