我有一个ASP.NET核心操作方法,我想返回一个zip文件以便下载到客户端,它看起来像这样:
[HttpPost, ValidateModel]
public async Task<IActionResult> DownloadCarousel([FromBody]CarouselViewModel model)
{
File zip = await MediaService.DownloadAndZipCarousel(model.Images, BaseServerPath);
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{zip.Name}.zip\"");
return File(zip.Content, "application/zip", zip.Name);
}
zip.Content包含我想要作为zip返回的字节数组,以便客户端可以下载它。但是,不是获得可下载的zip,而是响应如下:
这是我的工作,如果这有任何关系:
public async Task<Models.Models.File> DownloadAndZipCarousel(IEnumerable<Image> images, string baseServerPath)
{
HttpClient client = CreateClient();
var file = new Models.Models.File();
var random = new Random();
using (var archiveStream = new MemoryStream())
using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
{
foreach (Image image in images)
{
ZipArchiveEntry archiveEntry = archive.CreateEntry($"Instagram{random.Next()}.jpg", CompressionLevel.Optimal);
using (Stream entryStream = archiveEntry.Open())
using (Stream contentStream = await client.GetStreamAsync(image.StandartResolutionURL))
{
await contentStream.CopyToAsync(entryStream);
}
}
file.Name = "RandomZip";
file.Content = archiveStream.ToArray();
}
return file;
}
我没有使用MemoryStream
,而是尝试使用FileStream
来测试我的代码是否正常工作,并且所有内容都按预期运行,所以我认为我的操作和不是我的服务。
有人可以告诉我这里我做错了什么,我该怎么办才能换回拉链?
答案 0 :(得分:2)
OP中未知该呼叫是通过AJAX进行的。
基于OP自己提供的答案。可以执行以下操作以避免将存档保存到磁盘。
而不是在POST上下载和保存文件。存储必要的URL以便在生成存档时使用。内存缓存是一种选择。
存储抽象示例
public interface IStorage {
Task<T> GetAsync<T>(string key);
Task SetAsync<T>(string key, T item);
}
任何存储实现都应该可用,一旦您可以持久化,然后在以后访问URL。
服务实施可以保留所需的信息。
private readonly IStorage cache;
public async Task<string> CacheCarouselAsync(IEnumerable<Image> images) {
var token = Guid.NewGuid().ToString();
await cache.SetAsync(token, images.Select(image => image.StandartResolutionURL).ToList());
return token;
}
发布时,媒体服务将交换数据以获取令牌
[HttpPost, ValidateModel]
public async Task<IActionResult> SaveCarousel([FromBody]CarouselViewModel model) {
var token = await MediaService.CacheCarouselAsync(model.Images);
// Generates /Media/DownloadCarousel?name={token}
var url = Url.Action("DownloadCarousel","Media", new { name = token });
var content = new { location = url };
return Created(url, content);
}
获取存档的URL也将被返回称为客户端。
$.ajax({
method: "POST",
url: "/Media/SaveCarousel",
data: requestData,
contentType: "application/json; charset=utf-8",
success: function (data) {
//data.location should hold the download path
//setting window.location should now allow save prompt on GET
window.location = data.location;
},
error: function (error) {
console.log(error);
}
});
GET操作只需要为生成的zip文件交换令牌。
[HttpGet]
public async Task<IActionResult> DownloadCarousel(string name) {
var zip = await MediaService.DownloadAndZipCarousel(name);
var filename = $"{zip.Name}.zip";
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{filename}\"");
return File(zip.Content, "application/zip");
}
服务将使用该令牌从存储中获取URL,然后下载轮播并生成存档。
public async Task<File> DownloadAndZipCarousel(string token) {
//Get the model data from storage
var images = await cache.GetAsync<IEnumerable<string>>(token);
var client = CreateClient();
var file = new File();
var random = new Random();
using (var archiveStream = new MemoryStream()) {
using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, false)) {
foreach (var uri in images) {
var imagename = $"Instagram{random.Next()}.jpg";
var archiveEntry = archive.CreateEntry(imagename, CompressionLevel.Optimal);
using (var entryStream = archiveEntry.Open()) {
using (var contentStream = await client.GetStreamAsync(uri)) {
await contentStream.CopyToAsync(entryStream);
}
}
}
}
file.Name = "RandomZip"; //TODO: derive a better naming system
file.Content = archiveStream.ToArray();
}
return file;
}
答案 1 :(得分:2)
将您的file.Name
和file.Content
行移到using
的{{1}}块之外:
ZipArchive
public async Task<Models.Models.File> DownloadAndZipCarousel(IEnumerable<Image> images, string baseServerPath) {
...
using (var archiveStream = new MemoryStream()) {
using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, false)) {
...
}
file.Name = "RandomZip";
file.Content = archiveStream.ToArray();
}
return file;
}
特别需要在处理之前写出zip条目,以获取内容数组。
答案 2 :(得分:0)
file.Content = archiveStream.ToArray();
应该是:
archive.Dispose();
file.Content = archiveStream.ToArray();
或者,更改您的using
语句,以便在archiveStream.ToArray()
被处置后archive
之前调用archiveStream
。
ZipArchive.Dispose
<强>说明强>
此方法完成了存档...
答案 3 :(得分:0)
在得知我无法提示用户下载带有POST
请求的文件这一事实后,我采取了一种不同的方法来解决这个问题,这对我来说非常有用,我想分享一下我如何解决我的问题。
现在我有2个动作。我的第一个动作在内存中创建zip并返回字节数组,之后我将文件保存在缓存中,这样我就不必将它保存在服务器上并在以后检索它,操作如下所示:
[HttpPost, ValidateModel]
public async Task<IActionResult> SaveCarousel([FromBody]CarouselViewModel model)
{
File zip = await MediaService.ZipCarouselAsync(model.Images, ZipFolder);
if(zip == null)
{
return BadRequest();
}
CachingService.Set<File>(zip.Name, zip);
return Ok(zip);
}
第二个操作响应GET
调用并从缓存中获取文件并将其返回以供下载。
[HttpGet]
public IActionResult DownloadCarousel(string name)
{
File zip = CachingService.Get<File>(name);
if(zip == null)
{
return BadRequest();
}
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{zip.Name}.zip\"");
return File(zip.Content, "application/zip");
}
这就是我发出POST请求的AJAX调用的样子:
$.ajax({
method: "POST",
url: "/Media/SaveCarousel",
data: requestData,
contentType: "application/json; charset=utf-8",
success: function (data) {
window.location = "/Media/DownloadCarousel?name=" + data.name;
},
error: function (error) {
console.log(error);
}
});
基本上我的想法是,使用POST
请求发送我需要发送的数据并将zip文件保存在缓存中并返回文件名。获得文件名后,我使用AJAX成功方法上的文件名window.location
进行从缓存中获取文件的操作,并将其返回给用户下载。
这样,我不必在服务器上保存任何给定的文件,这意味着我不必自己维护文件,这是我从一开始就试图实现的。< / em>的
答案 4 :(得分:-1)
来自Mike Brind的博客:link
您可以简化代码,如下所示:
[HttpPost]
public FileResult Download(List files)
{
var archive = Server.MapPath("~/archive.zip");
var temp = Server.MapPath("~/temp");
// clear any existing archive
if (System.IO.File.Exists(archive))
{
System.IO.File.Delete(archive);
}
// empty the temp folder
Directory.EnumerateFiles(temp).ToList().ForEach(f => System.IO.File.Delete(f));
// copy the selected files to the temp folder
files.ForEach(f => System.IO.File.Copy(f, Path.Combine(temp, Path.GetFileName(f))));
// create a new archive
ZipFile.CreateFromDirectory(temp, archive);
return File(archive, "application/zip", "archive.zip");}