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

前端取消文件上传后,如何在C#中终止文件复制操作?

如何在前端取消上传时终止后端的文件复制操作

你遇到的问题其实很常见——前端调用XMLHttpRequest.abort()只是切断了客户端和服务器的连接,但此时后端可能已经开始处理文件复制了,所以不会自动停止。要解决这个问题,我们需要让后端能够感知到取消信号,进而中断正在进行的文件操作。下面给你两种可行的方案:

方案一:利用请求中止的内置令牌(简单直接)

ASP.NET Core提供了HttpContext.RequestAborted这个取消令牌,它会在客户端断开连接(比如XHR调用abort)时自动触发取消。我们只需要把这个令牌结合到异步文件复制操作中即可:

修改你的C#代码,将同步的CopyTo替换为异步的CopyToAsync,并传入RequestAborted令牌:

using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
    // 使用异步复制并绑定请求中止令牌
    await Content.CopyToAsync(stream, HttpContext.RequestAborted);
}

原理说明:

  • 当你在前端调用ajax.abort()时,客户端会断开与服务器的连接,此时RequestAborted令牌会被触发取消。
  • CopyToAsync方法会监听这个令牌,一旦检测到取消信号,就会立即中断文件复制操作,并抛出OperationCanceledException。
  • 你可以捕获这个异常,做一些清理工作(比如删除已经写入的部分文件):
try
{
    using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
    {
        await Content.CopyToAsync(stream, HttpContext.RequestAborted);
    }
}
catch (OperationCanceledException)
{
    // 清理未完成的文件
    if (System.IO.File.Exists(fullPath))
    {
        System.IO.File.Delete(fullPath);
    }
    return StatusCode(StatusCodes.Status499ClientClosedRequest);
}

方案二:主动发送取消信号(更可靠)

如果你的场景需要更可靠的取消机制(比如客户端网络波动导致连接没有正常断开),可以通过生成唯一请求ID的方式,让前端主动通知后端取消上传:

1. 修改前端JS代码

在上传时生成一个唯一的请求ID,随FormData一起发送;点击取消时,额外发送一个取消请求到后端:

for(var i = 0; i < file.length; i++){ 
    // 生成唯一的上传请求ID
    const requestId = `upload-${Date.now()}-${i}`;
    var formdata = new FormData(); 
    formdata.append("Files", file[i]); 
    formdata.append("RequestId", requestId); // 携带请求ID
    
    var ajax = new XMLHttpRequest(); 
    ajax.upload.addEventListener("progress", progressHandler, false); 
    ajax.addEventListener("load", completeHandler, false); 
    ajax.addEventListener("error", errorHandler, false); 
    ajax.addEventListener("abort", () => {
        // 发送取消请求到后端
        fetch('/CancelUpload', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ RequestId: requestId })
        });
        abortHandler();
    }, false); 
    
    ajax.open("POST", "/Save"); 
    ajax.send(formdata); 
}

2. 修改后端C#代码

首先定义一个线程安全的字典来存储每个上传请求的取消令牌源,然后在上传接口中绑定令牌,取消接口中触发取消:

// 全局存储上传请求的取消令牌源(线程安全)
private static readonly ConcurrentDictionary<string, CancellationTokenSource> _uploadCancelTokens = new ConcurrentDictionary<string, CancellationTokenSource>();

// 上传接口
[HttpPost("/Save")]
public async Task<IActionResult> Save(IFormFile Files, string RequestId)
{
    var cts = new CancellationTokenSource();
    // 将令牌源存入字典
    if (!_uploadCancelTokens.TryAdd(RequestId, cts))
    {
        return BadRequest("重复的上传请求ID");
    }

    var fullPath = Path.Combine("你的上传目录", Files.FileName);
    try
    {
        using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
        {
            // 使用自定义令牌控制文件复制
            await Files.OpenReadStream().CopyToAsync(stream, cts.Token);
        }
        return Ok("上传完成");
    }
    catch (OperationCanceledException)
    {
        // 清理未完成的文件
        if (System.IO.File.Exists(fullPath))
        {
            System.IO.File.Delete(fullPath);
        }
        return StatusCode(StatusCodes.Status499ClientClosedRequest, "上传已取消");
    }
    finally
    {
        // 从字典中移除并释放资源
        _uploadCancelTokens.TryRemove(RequestId, out _);
        cts.Dispose();
    }
}

// 取消上传接口
[HttpPost("/CancelUpload")]
public IActionResult CancelUpload([FromBody] CancelUploadRequest request)
{
    if (_uploadCancelTokens.TryRemove(request.RequestId, out var cts))
    {
        cts.Cancel(); // 触发取消信号
        cts.Dispose();
        return Ok("上传已取消");
    }
    return NotFound("未找到对应的上传请求");
}

// 取消请求的模型类
public class CancelUploadRequest
{
    public string RequestId { get; set; }
}

原理说明:

  • 每个上传请求对应一个唯一ID和一个CancellationTokenSource,后端用字典维护它们的关联。
  • 前端点击取消时,发送请求告知后端对应的请求ID,后端找到对应的令牌源并触发取消。
  • 文件复制操作会监听这个令牌,一旦取消信号触发,立即中断操作。

方案选择

  • 如果你的场景比较简单,优先用方案一,不需要额外的前端后端改动,利用框架内置的机制即可。
  • 如果需要更可靠的取消(比如客户端网络不稳定),或者需要在取消时做更多自定义逻辑,选择方案二。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:44:06