如何通过提示下载的AJAX返回一个zip文件?

时间:2017-06-13 11:56:21

标签: c# asp.net asp.net-core

我有一个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,而是响应如下:

enter image description here

这是我的工作,如果这有任何关系:

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来测试我的代码是否正常工作,并且所有内容都按预期运行,所以我认为我的操作和不是我的服务。

有人可以告诉我这里我做错了什么,我该怎么办才能换回拉链?

5 个答案:

答案 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.Namefile.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");}