使用DotNetZip通过ASP.NET MVC下载zip文件

12

我在文件夹中创建了一个文本文件,然后将该文件夹压缩并保存在相同的位置进行测试。我希望在创建后直接将这个zip文件下载到用户的机器上。我正在使用dotnetzip库,并已完成以下操作:

Response.Clear();
Response.ContentType = "application/zip";
Response.AddHeader("content-disposition", "filename=" + "sample.zip");
using (ZipFile zip = new ZipFile())
{
    zip.AddDirectory(Server.MapPath("~/Directories/hello"));
    zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
}

请问有人能建议如何让用户在其端下载压缩文件吗?


可能是[ASP MVC下载Zip文件]的重复问题(http://stackoverflow.com/questions/15385958/asp-mvc-download-zip-files)。 - Madan Sapkota
5个回答

27

你可以使用控制器的 File 方法来返回一个文件,例如:

public ActionResult Download()
{
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));
        zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
        return File(Server.MapPath("~/Directories/hello/sample.zip"), 
                                   "application/zip", "sample.zip");
    }
}

如果不需要将zip文件保存到服务器上,则无需将其写入文件中:

public ActionResult Download()
{
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));

        MemoryStream output = new MemoryStream();
        zip.Save(output);
        return File(output.ToArray(), "application/zip", "sample.zip");
    }  
}

收到服务器发来的重复报头错误。我的文件名中没有逗号,但仍然无法工作。Response.AddHeader()可能存在问题。请回复@CMate - user2801336
3
有人能够确定这个 ZipFile 类是从哪里来的吗?我所拥有的唯一一个是静态的,只存在于 System.IO.Compression.FileSystem 中。 - Chris
1
@Chris,你看过标签了吗?它来自于“DotNETzip”。 - cramopy
9
如果你的zip文件返回为空,请尝试将output.Position = 0以重置流的位置,否则它将从流的末尾读取,导致你得到空的zip。鸣谢:René Wolferink - Alexander Troshchenko
1
我在输出文件时不得不添加.ToArray(),否则会得到一个空的zip文件。 - Richard Vanbergen
显示剩余3条评论

6

首先,考虑一种不在服务器磁盘上创建任何文件的方法。这是不好的做法。我建议在内存中创建一个文件并压缩它。希望你会发现下面的示例有用。

/// <summary>
///     Zip a file stream
/// </summary>
/// <param name="originalFileStream"> MemoryStream with original file </param>
/// <param name="fileName"> Name of the file in the ZIP container </param>
/// <returns> Return byte array of zipped file </returns>
private byte[] GetZippedFiles(MemoryStream originalFileStream, string fileName)
{
    using (MemoryStream zipStream = new MemoryStream())
    {
        using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
        {
            var zipEntry = zip.CreateEntry(fileName);
            using (var writer = new StreamWriter(zipEntry.Open()))
            {
                originalFileStream.WriteTo(writer.BaseStream);
            }
            return zipStream.ToArray();
        }
    }
}

/// <summary>
///     Download zipped file
/// </summary>
[HttpGet]
public FileContentResult Download()
{
    var zippedFile = GetZippedFiles(/* your stream of original file */, "hello");
    return File(zippedFile, // We could use just Stream, but the compiler gets a warning: "ObjectDisposedException: Cannot access a closed Stream" then.
                "application/zip",
                "sample.zip");
}

上述代码注意事项:

  1. 传递MemoryStream实例需要检查它是否打开、有效等。我省略了这些步骤。为了使代码更加健壮,我宁愿传递文件内容的字节数组而不是MemoryStream实例,但这对于本示例来说过于繁琐。
  2. 它没有展示如何在内存中创建所需的上下文(您的文件)。我建议参考MemoryStream类的说明进行操作。

在内存中有文件时,我可以在浏览器中暂停下载吗? - Black Frog
您可以这样做,但是恢复下载将触发Download方法中的所有操作(创建流、压缩文件等)。在此处查看更多信息- http://superuser.com/a/641957。当然,为下载保留缓存文件将减少服务器的开销,但您必须处理多个用户同时访问相同资源/页面并且很可能会遇到问题。 - Alex Klaus
当然,这并不是不好的做法 - 这取决于您的情况。如果您有大量数据,则可能需要重新考虑对服务器内存的依赖性。同样,如果创建zip文件需要大量处理,并且这不是一次性文件,则有时将其存储到磁盘上是最佳选择,有时将其存储到内存中是最佳选择。以上“不良实践”只是@AlexKlaus的个人偏好。 - JDandChips

2

对 Klaus 的解决方案进行修正:(由于我无法添加评论,只能添加另一个答案!)

这个解决方案很好,但是我得到的是损坏的 zip 文件,我意识到这是因为在完成 zip 对象之前返回了,所以它没有关闭 zip 并导致 zip 文件损坏。

所以我们只需要将 return 行移动到使用 zip 块之后即可使其工作。

最终结果如下:

/// <summary>
///     Zip a file stream
/// </summary>
/// <param name="originalFileStream"> MemoryStream with original file </param>
/// <param name="fileName"> Name of the file in the ZIP container </param>
/// <returns> Return byte array of zipped file </returns>
private byte[] GetZippedFiles(MemoryStream originalFileStream, string fileName)
{
    using (MemoryStream zipStream = new MemoryStream())
    {
        using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
        {
            var zipEntry = zip.CreateEntry(fileName);
            using (var writer = new StreamWriter(zipEntry.Open()))
            {
                originalFileStream.WriteTo(writer.BaseStream);
            }
        }
        return zipStream.ToArray();
    }
}

/// <summary>
///     Download zipped file
/// </summary>
[HttpGet]
public FileContentResult Download()
{
    var zippedFile = GetZippedFiles(/* your stream of original file */, "hello");
    return File(zippedFile, // We could use just Stream, but the compiler gets a warning: "ObjectDisposedException: Cannot access a closed Stream" then.
                "application/zip",
                "sample.zip");
}

0

对于那些只想从App_Data文件夹返回现有Zip文件的人(只需将zip文件放在其中),在Home控制器中创建此操作方法:

    public FileResult DownLoad(string filename)
    {
        var content = XFile.GetFile(filename);
        return File(content, System.Net.Mime.MediaTypeNames.Application.Zip, filename);

    }

Get File 是一个扩展方法:

   public static byte[] GetFile(string name)
    {
        string path = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
        string filenanme = path + "/" + name;
        byte[] bytes = File.ReadAllBytes(filenanme);
        return bytes;
    }

主页控制器的索引视图如下:

@model  List<FileInfo>

<table class="table">
    <tr>
        <th>
            @Html.DisplayName("File Name")
        </th>
        <th>
            @Html.DisplayName("Last Write Time")
        </th>
        <th>
            @Html.DisplayName("Length (mb)")
        </th>
        <th></th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.ActionLink("DownLoad","DownLoad",new {filename=item.Name})
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.LastWriteTime)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Length)
            </td>
        </tr>
    }
</table>

主索引文件操作方法:

    public ActionResult Index()
    {
        var names = XFile.GetFileInformation();
        return View(names);
    }

GetFileInformation是一个扩展方法:

    public static List<FileInfo> GetFileInformation()
    {
        string path = AppDomain.CurrentDomain.GetData("DataDirectory").ToString();
        var dirInfo = new DirectoryInfo(path);
        return dirInfo.EnumerateFiles().ToList();
    }

0
创建一个仅支持GET请求的控制器操作,返回一个FileResult,如下所示:
[HttpGet]
public FileResult Download()
{   
    // Create file on disk
    using (ZipFile zip = new ZipFile())
    {
        zip.AddDirectory(Server.MapPath("~/Directories/hello"));
        //zip.Save(Response.OutputStream);
        zip.Save(Server.MapPath("~/Directories/hello/sample.zip"));
    }

    // Read bytes from disk
    byte[] fileBytes = System.IO.File.ReadAllBytes(
        Server.MapPath("~/Directories/hello/sample.zip"));
    string fileName = "sample.zip";

    // Return bytes as stream for download
    return File(fileBytes, "application/zip", fileName);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接