Поврежден Zip при возврате из Web API?

В моем проекте MVC у меня есть вызов AJAX к веб-API.

Я отправляю массив маршрутов документов, API (должен) сжимает их и возвращает ZIP-файл.

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);
        }
    });
}

И моя функция ZipDocs в WebAPI (с использованием библиотеки DotNetZip):

[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 };
    }

Но когда Zip возвращается, я получаю следующую ошибку:

Uncaught Error: Corrupted zip: отсутствует 16053 байта.

Что действительно странно, когда я сохраняю в API zip-файл на диск, он сохраняется правильно, и я могу без проблем открыть файл!

Что я делаю неправильно? я что-то пропустил? Пожалуйста помоги!

Заранее спасибо.


person user3378165    schedule 12.05.2016    source источник
comment
Мне интересно, заставляет ли ваш оператор using для zipFile ваш объект быть удаленным до того, как данные будут полностью сохранены в потоке. Попробуйте удалить оператор using и посмотрите, работает ли он. Ваш код сохранения не будет запущен до тех пор, пока ваш объект не вернется.   -  person TyCobb    schedule 12.05.2016
comment
Нужно ли закрывать поток в этот момент? Вы случайно не закрыли его до того, как все было отправлено по сети?   -  person Mattias Åslund    schedule 12.05.2016


Ответы (1)


Две вещи:

1/ $.ajax обрабатывает текстовые ответы и попытается (utf-8) декодировать содержимое: ваш zip-файл не является текстом, вы получите поврежденное содержимое. jQuery не поддерживает двоичное содержимое, поэтому вам нужно использовать предыдущую ссылку и добавьте транспорт ajax в jQuery или используйте напрямую XmlHttpRequest. С xhr вам нужно установить xhr.responseType = "blob" и прочитать из xhr.response большой двоичный объект.

2/предполагая, что ваш фрагмент кода js представляет собой всю функцию, вы (пытаетесь) получить двоичный контент, разобрать zip-файл, повторно сгенерировать его, передать контент пользователю. Вы можете дать непосредственно результат:

// 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");

Изменить: примеры:

с jquery.binarytransport.js (любой библиотекой, которая позволяет загружать Blob или ArrayBuffer подойдет)

$.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");
  }
  // [...]
});

с необработанным XMLHttpRequest вы можете увидеть этот вопрос, вам просто нужно добавить xhr.responseType = "blob", чтобы получить каплю.

Я никогда не использовал С#, но из того, что я вижу, [FromBody] довольно чувствителен к формату данных, первое решение должно быть проще реализовать в вашем случае.

person David Duponchel    schedule 12.05.2016
comment
Спасибо за ваш ответ, я понимаю, что вы говорите, но не совсем уверен, как это сделать, должен ли я уже использовать xhr в моем веб-API? или только когда данные возвращаются? не могли бы вы направить меня? Извините за глупый вопрос, но я не так знаком с этим... Спасибо! - person user3378165; 13.05.2016
comment
xhr заменит этот вызов $.ajax. Самое простое решение — использовать дополнительный транспорт ajax, так как нужно просто добавить библиотеку и использовать dataType: 'binary'. Я обновил свой ответ, чтобы включить пример. - person David Duponchel; 13.05.2016
comment
УДИВИТЕЛЬНО!! Работает отлично, с небольшими изменениями в функции AJAX! Я хотел бы голосовать за вас гораздо больше, чем один раз! Большое спасибо!!! - person user3378165; 15.05.2016