【问题标题】:Download Zip Archive from WebAPI in Response to POST Request从 WebAPI 下载 Zip 存档以响应 POST 请求
【发布时间】:2018-04-11 17:32:04
【问题描述】:

我正在尝试提供一个 zip 存档以响应从 Axios 向 WebAPI 发出的 AJAX POST 请求。

在客户端我有

import AjaxDownload from "../../data/AjaxDownload";

AjaxDownload.post(id, pageRecords, {
            responseType: "blob"
        }).then((response) => {
            let blob = new Blob([response.data], { type: extractContentType(response) }),
                url = window.URL.createObjectURL(blob);    
            window.open(url, "_self");
        }).catch((error) => {
            // ...
        }).then(() => {
            // ...
        });

function extractContentType(response: AxiosResponse): string {
    return response.headers["content-type"] || "";
}

// AjaxDownload:
import * as axios from "axios";
import { apiUrl } from "./Ajax";

const ajax = axios.default.create({
    baseURL: new URL("download", apiUrl).toString(),
    timeout: 3600000    // 1 hour
});

export default ajax;

发布到以下 WebAPI 方法 - 并且该客户端逻辑的 POST 部分完全按预期工作。

[HttpPost]
[Route("download/{id:guid}")]
public async Task<HttpResponseMessage> Download(Guid id, [FromBody] IEnumerable<PageRecord> pageRecords)
{
    var stream = await _repo.GetAsArchiveStream(id,
                                                pageRecords,
                                                true).ConfigureAwait(false);

    stream.Position = 0;

    var result = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(stream)};
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {FileName = $"{...}.zip"};
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");    // "application/zip" has same result
    result.Content.Headers.ContentLength = stream.Length;

    return result;
}

但是,浏览器将 result.Content 显示为 JSON 对象,没有 zip 存档。我假设它显示为 JSON,因为请求提到了 JSON,但为什么它似乎忽略了二进制内容 - 特别是 Content-Type 标头详细说明了内容的类型?

如您所见,JavaScript 也希望将内容作为 blob 读取。

我看不出我的代码与 this answer 有何显着不同 - 请说明是否存在关键区别。

在服务器端,我也尝试过返回...

return new FileStreamResult(stream, "application/zip");

这种方法的问题是无法设置文件名。 Firefox 确实下载了 zip,尽管名称是随机的,而 Chrome 似乎根本没有下载任何东西。

一定有办法做到这一点,对吧?将请求发布到返回 zip 存档的 WebAPI 方法,然后客户端会显示 Save 对话框?我错过了什么?

【问题讨论】:

  • 为此使用 POST 很奇怪。如果文件很大怎么办?您现在正在将其加载到浏览器内存中。也许更好地使用常规 GET 请求?
  • 我必须使用 POST,因为客户端定义了不适合 GET 查询字符串的文件和结构(可能来自 very 长列表)。至于花费时间,这不是什么大问题,因为 UI 向用户解释并更新用户的方式。
  • 我仍然认为从您的 POST 请求中将 url 返回到真实文件是更好的解决方案。现在您使用的是Blob,您将response.data 提供给它,这意味着1)用户只有在从服务器完全下载后才能下载它,并且2)整个文件都在浏览器内存中。如果您对此感到满意,并且您的文件不能大于 500 MB - 那么,这应该可以。但是,如果您尝试以这种方式下载 1GB 之类的内容 - 我相信浏览器只会崩溃。

标签: c# asp.net-web-api download zip axios


【解决方案1】:

我设法通过使用...从控制器操作返回 zip 来解决这个问题

return File(stream,
            "application/zip",
            "FILENAME.zip");

在客户端代码中,我可以使用 this SO answer 中的一些 JavaScript 从标头中获取文件名。

let blob = new Blob([response.data], { type: extractContentType(response) }),
                downloadUrl = window.URL.createObjectURL(blob),
                filename = "",
                disposition = response.headers["content-disposition"];

if (disposition && disposition.indexOf("attachment") !== -1) {
    let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/,
        matches = filenameRegex.exec(disposition);

    if (matches != null && matches[1]) {
        filename = matches[1].replace(/['"]/g, '');
    }
}

var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
    window.location.href = downloadUrl;
} else {
    a.href = downloadUrl;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-17
    相关资源
    最近更新 更多