ASP.NET Core 8.0 MVC文件下载无超时问题解决方案咨询
用户取消文件下载时.NET连接超时的解决办法
问题描述
我有一个.NET路由接口用于获取文档,可根据逻辑返回JSON(供浏览器直接打开支持的文件)或直接返回完整文件流。相关代码如下:
C# 后端代码
public async Task<IActionResult> DownloadAndServeFile(List<string> num_lst, bool view) { cancellationToken.ThrowIfCancellationRequested(); HttpContext.RequestAborted.ThrowIfCancellationRequested(); if (browserSupportedExtensions.Contains(Path.GetExtension(fileName).ToLower()) && view == "true") { string fileUrlToServe = Url.Content($"~/DownloadedFiles/{fileName}"); return Json(new { success = true, url = fileUrlToServe, openInBrowser = true }); } else { byte[] fileBytes = await System.IO.File.ReadAllBytesAsync(savePath); // adding bytes to memory stream return File(fileBytes, "application/octet-stream", fileName); } }
JavaScript 前端调用代码
$.ajax({ type: "POST", url: '/Home/DownloadAndServeFile', data: { id: some_num }, success: function (response, status, xhr) { // Check if the content type is JSON var contentType = xhr.getResponseHeader('Content-Type'); if (contentType.includes('application/json')) { // The server returned a JSON response if (response.success) { window.location.href = response.url; const link = document.createElement('a'); link.href = response.url; link.download = ''; document.body.appendChild(link); link.click(); document.body.removeChild(link); } else { showToast(response.message, "error"); } } else { // The server returned a file directly (binary data) console.log(xhr.getResponseHeader('Content-Disposition')); var fileName = xhr.getResponseHeader('Content-Disposition').split(';')[1].split('filename=')[1]; var blob = new Blob([response], { type: xhr.getResponseHeader('Content-Type') }); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = fileName; link.click(); } }, error: function (xhr, status, error) { } });
目前所有流程正常,但当用户弹出下载窗口后选择“取消”时,.NET连接会超时。推测原因是服务器已开始传输文件,但未收到用户确认导致连接挂起超时。
解决方案
1. 改用流式传输替代预加载整个文件到内存
原代码使用ReadAllBytesAsync将整个文件读取到内存后再返回,会导致服务器提前加载所有文件数据,且无法及时感知客户端的取消操作。改用文件流直接返回,ASP.NET Core会自动处理流式传输,当客户端断开连接(如用户取消下载),HttpContext.RequestAborted令牌会触发,服务器立即停止传输并释放资源。
修改后端代码的文件返回逻辑:
else { // 直接打开文件流,指定异步读取和共享权限 var fileStream = new FileStream( savePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true ); // 关联请求取消令牌,确保客户端断开时及时终止流 await fileStream.CopyToAsync(HttpContext.Response.Body, 4096, HttpContext.RequestAborted); return File(fileStream, "application/octet-stream", fileName); }
2. 前端改用浏览器原生下载方式,避免AJAX处理文件流
原代码通过AJAX接收文件流再创建a标签触发下载,会导致浏览器无法直接管理下载连接。改用表单提交或直接跳转的方式,让浏览器直接处理下载流程,用户取消时会立即断开与服务器的连接,服务器能及时感知。
示例前端代码(表单提交方式):
function triggerFileDownload(some_num) { const form = document.createElement('form'); form.method = 'POST'; form.action = '/Home/DownloadAndServeFile'; form.style.display = 'none'; // 添加请求参数 const idInput = document.createElement('input'); idInput.name = 'id'; idInput.value = some_num; form.appendChild(idInput); const viewInput = document.createElement('input'); viewInput.name = 'view'; viewInput.value = 'false'; // 根据业务逻辑设置view参数 form.appendChild(viewInput); document.body.appendChild(form); form.submit(); document.body.removeChild(form); }
3. 可选:调整服务器请求超时配置
如果仍存在超时问题,可以在Program.cs中配置Kestrel的请求超时时间,避免连接长时间挂起:
builder.WebHost.ConfigureKestrel(options => { // 设置请求头超时时间为30秒 options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30); });
核心原理
问题根源在于预加载文件到内存的方式导致服务器无法及时响应客户端的取消操作。改用流式传输后,服务器边读取文件边发送数据,一旦客户端断开连接,ASP.NET Core会通过RequestAborted令牌终止传输;同时让浏览器直接处理下载,能确保取消操作立即触发连接断开,从根本上避免超时问题。
内容的提问来源于stack exchange,提问作者Aggerio6969
相关产品推荐
相关产品推荐

