Corrupted Zip while returning from Web API?

2019-07-17 05:42发布

问题:

On my MVC project I have an AJAX call to a Web API.

I send an array of documents' routes, the API (should) zips them and returns the zip file.

self.zipDocs = function (docs, callback) {
    $.ajax({
        url: "../SharedAPI/documents/zip",
        type: "POST",
        data: docs,
        contentType: "application/json",
        success: function (data) {
            var zip = new JSZip(data);
            var content = zip.generate({ type: "blob" });
            saveAs(content, "example.zip");
        },
        error: function (data) {
            callback(data);
        }
    });
}

And my ZipDocs function on the WebAPI (using the DotNetZip library):

[HttpPost]
    [Route("documents/zip")]
    public HttpResponseMessage ZipDocs([FromBody] string[] docs)
    {

        using (var zipFile = new ZipFile())
        {
            zipFile.AddFiles(docs, false, "");
            return ZipContentResult(zipFile);
        }
    }

    protected HttpResponseMessage ZipContentResult(ZipFile zipFile)
    {
        // inspired from http://stackoverflow.com/a/16171977/92756
        var pushStreamContent = new PushStreamContent((stream, content, context) =>
        {
           zipFile.Save(stream);
            stream.Close(); // After save we close the stream to signal that we are done writing.
        }, "application/zip");

        return new HttpResponseMessage(HttpStatusCode.OK) { Content = pushStreamContent };
    }

But when the Zip is returned I got the following error:

Uncaught Error: Corrupted zip: missing 16053 bytes.

What is really weird, when I save on the API the zip file to the disk it gets saved properly and I can open the file without any problem!

What am I doing wrong? am I missing something? Please help!

Thanks in advance.

回答1:

Two things:

1/ $.ajax handles text responses and will try to (utf-8) decode the content: your zip file isn't text, you will get a corrupted content. jQuery doesn't support binary content so you need to use the previous link and add an ajax transport on jQuery or use directly a XmlHttpRequest. With an xhr, you need to set xhr.responseType = "blob" and read from xhr.response the blob.

2/ assuming your js code snippet is the whole function, you (try to) get a binary content, parse the zip file, re-generate it, give the content to the user. You can give directly the result:

// with xhr.responseType = "arraybuffer"
var arraybuffer = xhr.response;
var blob = new Blob([arraybuffer], {type:"application/zip"});
saveAs(blob, "example.zip");

// with xhr.responseType = "blob"
var blob = xhr.response;
saveAs(blob, "example.zip");

Edit: examples:

with jquery.binarytransport.js (any library that let you download a Blob or an ArrayBuffer will do)

$.ajax({
  url: "../SharedAPI/documents/zip",
  dataType: 'binary', // to use the binary transport
  // responseType:'blob', this is the default
  // [...]
  success: function (blob) {
    // the result is a blob, we can trigger the download directly
    saveAs(blob, "example.zip");
  }
  // [...]
});

with a raw XMLHttpRequest, you can see this question, you just need to add a xhr.responseType = "blob" to get a blob.

I never used C# but from what I can see, [FromBody] is quite sensitive to the format of the data, the first solution should be easier to implement in your case.