C# Blazor从流下载文件 C# Blazor Server/WASM如何生成并下载文件

4次阅读

blazor server 中直接使用 FileStreamresult 或 filecontentresult 会失败,因 signalr 接管 http 响应;正确做法是通过独立 controller 接口返回文件,并用 js interop 触发下载。

C# Blazor从流下载文件 C# Blazor Server/WASM如何生成并下载文件

Blazor Server 中用 FileStreamResultFileContentResult 会失败

Blazor Server 是服务端渲染,但 HTTP 响应已由 SignalR 连接接管,直接返回 FileStreamResult 或调用 Response.Body.WriteAsync 不生效——浏览器收不到文件头,下载不会触发。你看到的可能是空白页、控制台报错 Failed to launch download: no file,或完全静默。

正确做法是:在后端提供一个标准 mvc/Controller 接口(非组件内方法),生成文件并返回 FileContentResultPhysicalFileResult,再从 Blazor 组件中用 JS 启动下载。

  • 新建一个 Controllers/DownloadController.cs,添加 [HttpGet("api/download/{id}")] 方法
  • 文件内容建议先写入 MemoryStream,再转为 byte[],避免流生命周期问题
  • 设置 ContentType(如 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")和 FileDownloadName
  • 前端用 NavigationManager.NavigateTo("/api/download/123", forceLoad: true) 会刷新页面;更推荐用 JS interop 触发 window.location.hreffetch + URL.createObjectURL

Blazor wasm 中无法直接访问服务器文件系统

WASM 运行在浏览器沙箱里,System.IO.File 只能读取嵌入资源或通过 IJSRuntime 拿到的用户选择文件。想“生成并下载”,所有逻辑必须在客户端完成。

典型场景:导出 CSV、生成 PDF(用 jsPDF)、导出 excel(用 SheetJS)。关键点是别试图在 C# 里写文件到磁盘——没意义,也做不到。

  • MemoryStream 构造数据(如 CSV 字符串Encoding.UTF8.GetBytes()
  • 调用 JS interop:await JS.InvokeVoidAsync("downloadFile", fileName, base64String)
  • JS 端用 Uint8ArrayBlobURL.createObjectURLa.download 触发保存
  • 注意:WASM 下 Encoding.UTF8.GetBytes() 中文不乱码,但若用 Encoding.default 可能出错

JS.InvokeVoidAsync("downloadFile", ...) 的 JS 实现要处理 Blob 兼容性

常见错误是只用 data:text/csv;base64,... 链接,safari 不支持长 URL,edge 旧版可能截断。稳妥方式是走 Blob + createObjectURL

wwwroot/js/site.js 里定义:

function downloadFile(fileName, base64String) {     const byteString = atob(base64String);     const len = byteString.length;     const bytes = new Uint8Array(len);     for (let i = 0; i < len; i++) {         bytes[i] = byteString.charCodeAt(i);     }     const blob = new Blob([bytes], { type: 'application/octet-stream' });     const url = URL.createObjectURL(blob);     const a = document.createElement('a');     a.href = url;     a.download = fileName;     document.body.appendChild(a);     a.click();     document.body.removeChild(a);     URL.revokeObjectURL(url); }
  • 不要省略 URL.revokeObjectURL,否则内存泄漏
  • 如果文件类型明确(如 PDF),把 type 改成 "application/pdf",有助于浏览器识别
  • IE11 不支持 Blob 构造函数Uint8Array,需降级用 new Blob([bytes.buffer])

大文件下载时内存和超时要单独处理

生成 100MB Excel 或 ZIP 时,MemoryStream 会吃光 WASM 内存(默认 128MB),Server 端则可能触发 Kestrel 请求超时或 iis 上传限制。

  • WASM:改用分块生成 + 流式下载不可行;实际应避免前端生成大文件,改由后端异步生成、返回下载链接(带 Token 防盗链)
  • Server:Controller 接口里别用 FileContentResult 加载整个文件到内存;改用 FileStreamResult 包裹 FileStream(注意 FileShare.Read 和异步 CopyToAsync
  • 无论哪种模式,都应在响应头加 Content-Disposition: attachment; filename="xxx",且确保 Controller 方法不被 [ValidateAntiForgeryToken] 拦截

最易被忽略的是:Blazor Server 的 SignalR 连接默认 5 分钟超时,而大文件生成可能耗时更久——得同时调大 HubOptions.KeepAliveInterval 和客户端重连逻辑。

text=ZqhQzanResources