.NET 5.0 ASP.NET Core Web API返回ZIP文件异常排查
.NET 5.0 ASP.NET Core Web API ZIP下载问题修复
问题背景
- 项目场景:练习开发基于.NET 5.0的ASP.NET Core Web API项目,需要实现多图片传输功能,临时采用ZIP打包下载方案
- 初始异常:执行
return File(fullName, "text/plain");代码时抛出错误:
System.InvalidOperationException: No file provider has been configured to process the supplied file.
- 中间迭代问题:尝试内存流传ZIP方案时,浏览器直接展示字节数据未触发下载;调整为读取本地ZIP转字节数组返回
application/zip类型后可触发下载,但存在两个遗留问题:- 下载的文件默认不带
.zip后缀,需要手动追加才能被Windows识别为压缩包 - 实现过程会在系统图片文件夹生成本地临时ZIP文件,存在冗余磁盘IO
- 下载的文件默认不带
关联实体定义
Posting实体
using System; using System.Collections.Generic; namespace ImageRepository { public sealed class Posting { public DateTime CreationTime { get; set; } public List<ImageProperties> Imageproperties { get; } public Posting(DateTime creationTime, List<ImageProperties> imPr) { CreationTime = creationTime; Imageproperties = imPr; } } }
ImageProperties实体
namespace ImageRepository { public sealed class ImageProperties { public string FullName { get; set; } public string _Name { get; set; } public byte[] DataBytes { get; set; } public ImageProperties(string FullName, string Name, byte[] dataBytes) { this.FullName = FullName; this._Name = Name; this.DataBytes = dataBytes; } } }
原有问题代码
using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using ImageRepository; using System.IO.Compression; namespace WebApp2.Controllers { [Route("api/[controller]")] [ApiController] public class MyController : ControllerBase { private readonly IImageTransferRepository imageRepository; private readonly System.Globalization.CultureInfo Deu = new System.Globalization.CultureInfo("de-DE"); public MyController(IImageTransferRepository imageTransferRepository) { this.imageRepository = imageTransferRepository; } [HttpGet("WhatAreTheNamesOfTheLatestPictures")] public ActionResult GetNamesOfNewestPosting() { List<string> imageNames = this.imageRepository.GetImageNames(); if (imageNames.Count == 0) { return NoContent(); } return Ok(imageNames); } [HttpGet("ImagesOfLatestPost")] public ActionResult DownloadZip() { List<Posting> Postings = this.imageRepository.GetImages(); if (Postings is null || Postings.Count == 0) { return NoContent(); } System.DateTime now = System.DateTime.Now; string now_as_string = now.ToString("G", Deu).Replace(':', '-'); string folderPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures); string fullName = $"{folderPath}\\{now_as_string}.zip"; using (ZipArchive newFile = ZipFile.Open(fullName, ZipArchiveMode.Create)) { for (int i = 0; i < Postings[0].Imageproperties.Count; i++) { newFile.CreateEntryFromFile(Postings[0].Imageproperties[i].FullName, Postings[0].Imageproperties[i]._Name); } } byte[] zip_as_ByteArray = System.IO.File.ReadAllBytes(fullName); return File(zip_as_ByteArray, "application/zip"); } } }
修复实现
核心修复点
- 解决文件无后缀问题:使用
File方法的三参数重载,第三个参数传入带.zip后缀的下载文件名,浏览器会自动按该名称保存文件,无需手动修改后缀 - 解决本地临时文件问题:替换本地文件写入逻辑为内存流
MemoryStream承载ZIP压缩内容,压缩全程在内存完成,不会在磁盘生成任何临时文件 - 额外优化:优先使用
ImageProperties中已存储的DataBytes字节数组写入压缩包,避免重复读取磁盘文件,降低IO开销
修复后DownloadZip方法代码
[HttpGet("ImagesOfLatestPost")] public ActionResult DownloadZip() { List<Posting> Postings = this.imageRepository.GetImages(); if (Postings is null || Postings.Count == 0) { return NoContent(); } DateTime now = DateTime.Now; string now_as_string = now.ToString("G", Deu).Replace(':', '-'); string downloadFileName = $"{now_as_string}.zip"; using (MemoryStream zipStream = new MemoryStream()) { // leaveOpen设为true,避免ZipArchive释放时关闭内存流 using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true)) { foreach (var img in Postings[0].Imageproperties) { ZipArchiveEntry entry = archive.CreateEntry(img._Name, CompressionLevel.Optimal); using (Stream entryStream = entry.Open()) { // 优先使用已加载的图片字节,不存在时再读取本地文件 byte[] imgContent = img.DataBytes ?? System.IO.File.ReadAllBytes(img.FullName); entryStream.Write(imgContent, 0, imgContent.Length); } } } // 返回压缩包字节数组,指定正确MIME类型和下载文件名 return File(zipStream.ToArray(), "application/zip", downloadFileName); } }
注意事项
- 所有流对象都用
using包裹,方法执行完成后会自动释放资源,不会造成内存泄漏 - MIME类型使用标准
application/zip,主流浏览器均可正确识别并触发文件下载,不会直接在页面展示字节内容
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

