前端取消文件上传后,如何在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
相关产品推荐
相关产品推荐

