Blazor WebAssembly .Net Core Hosted模式下如何在下载完成后删除服务端临时文件
问题根因
你当前的逻辑存在底层时序错误:JsRunTime.InvokeVoidAsync只会等待前端的downloadFromUrl函数执行完成,而这个函数里的a.click()只是触发浏览器的下载请求,不会等待请求发送、服务端处理、文件下载完成的整个流程。
小文件场景下,JS函数执行速度远快于浏览器发送下载请求到服务端的速度,导致下载请求到达GetExportFile接口前,临时文件已经被删除,自然下载失败。大文件场景只是因为后端处理下载请求的耗时更长,刚好赶在删文件前读完了文件,属于巧合正常,不是逻辑正确。
方案1:在下载接口内完成文件删除(改造成本最低,优先推荐)
直接把删除逻辑移到GetExportFile接口的最后,等文件流完全写入响应后再执行删除,不需要修改任何前端代码,也不需要额外的回调逻辑:
[HttpGet("GetExportFile")] public async Task<IActionResult> GetExportFile(string exportId, string fileDownloadName, int outputFormatType) { var exportRunner = // 根据exportId获取对应的导出任务实例 var filePath = exportRunner.GetTempFilePath(); try { // 读取文件流返回 var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); return File(stream, GetMimeType(outputFormatType), fileDownloadName); } finally { // .NET Core 3.0+ 中ASP.NET Core会先把响应流完全发送给客户端,再释放FileStream,此时删除文件不会影响下载 await exportRunner.DeleteTempFilesOnServer(); } }
注意:该方案仅适用于每个临时文件仅需下载一次的场景,如果需要支持同一个文件多次下载,不要使用该方案。
方案2:前端下载完成后通知后端删除(最准确,支持多场景)
修改前端的下载逻辑,先用fetch把文件全量拉到本地,生成Blob触发下载后,主动调用后端接口通知删除临时文件:
前端JS代码修改:
var downloadFromUrl = async function(url, exportId) { // 先全量下载文件到内存 const response = await fetch(url); const blob = await response.blob(); // 触发本地下载 const anchorElement = document.createElement('a'); anchorElement.href = URL.createObjectURL(blob); anchorElement.download = new URLSearchParams(url.split('?')[1]).get('fileDownloadName'); anchorElement.click(); URL.revokeObjectURL(anchorElement.href); anchorElement.remove(); // 下载完成后通知后端删除文件 await fetch(`api/Export/DeleteTempFile?exportId=${exportId}`, {method: 'POST'}); }
后端代码修改:
把原来的finally块里的删除逻辑删掉,单独加一个删除接口即可:
// 原触发下载的代码去掉finally块,同时传入exportId给JS函数 await JsRunTime.InvokeVoidAsync("downloadFromUrl", $"api/Export/GetExportFile?exportId={exportRunner.ExportId}&fileDownloadName={exportRunner.FinalExportFileName}&outputFormatType={exportRunner.OutputFormatType}", exportRunner.ExportId ); // 新增删除接口 [HttpPost("DeleteTempFile")] public async Task<IActionResult> DeleteTempFile(string exportId) { var exportRunner = // 根据exportId获取导出任务 await exportRunner.DeleteTempFilesOnServer(); return Ok(); }
注意:如果需要处理GB级超大文件,fetch全量下载到内存会导致浏览器内存溢出,这种场景更推荐用方案1。
方案3:增加临时文件兜底定时清理(可选补充,兼容所有场景)
不管用上面哪个方案,都可以加个后台定时任务(比如用Hangfire或者.NET自带的BackgroundService),定期删除创建时间超过1小时的临时文件,避免极端情况(比如用户断网、直接关闭页面)导致的临时文件残留占用磁盘空间。
内容的提问来源于stack exchange,提问作者Adrian Wright
相关产品推荐
相关产品推荐

