【问题标题】:Stuck generating + sending .xlsx file from ASP.NET Web API backend to Vue.js frontend卡住生成 + 将 .xlsx 文件从 ASP.NET Web API 后端发送到 Vue.js 前端
【发布时间】:2020-03-04 19:54:20
【问题描述】:

我正在尝试在我的 ASP.NET Web API 后端生成一个 excel 文件,并将其发送到我的 Vue.js 前端以供下载。

正在通过调用 getQueryResults 发出 API 请求:

const apiClient = axios.create({
  baseURL: process.env.NODE_ENV == "production" ? PROD_URL : TEST_URL,
  withCredentials: true, // This is the default
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json"
  }
});

getQueryResults(table, filters, selected, clientId, fundId, periodId, pageNumber, pageSize, exportExcel){
    return apiClient.post(`queryResults`, {
      responseType: 'blob',
      table,
      filters,
      selected,
      clientId,
      fundId,
      periodId,
      pageNumber,
      pageSize,
      exportExcel
    });
  }

在后端,在QueryResultsExport.cs内部生成excel文件(EPPlus是4.1.0版):

public static MemoryStream exportToExcel(IQueryable<object> itemsToExport)
{
    if (itemsToExport.Count() == 0)
        return null;
    ExcelPackage excel = new ExcelPackage();
    ExcelWorksheet workSheet = excel.Workbook.Worksheets.Add("Query Results");

    DataTable dataTable = toDataTable(itemsToExport.ToList());
    workSheet.Cells["A1"].LoadFromDataTable(dataTable, true);

    /*
    string path = @"C:\Users\asdfq321\Downloads\test11.xlsx";
    Stream stream = File.Create(path);
    excel.SaveAs(stream);
    stream.Close();
    return null;
    */

    MemoryStream ms = new MemoryStream(excel.GetAsByteArray());
    excel.Dispose();
    return ms;            
}

您可以看到我注释掉了一些将 excel 文件在本地保存为 .xlsx 文件的代码,而不是返回内存流。我这样做是为了确保问题不在 excel 生成中。本地保存在服务器上的文件看起来是正确的,大小为 3442 Bytes(这在后面会相关)。

接下来,内存流被发送回客户端:

[HttpPost]
[Route("queryResults")]
public HttpResponseMessage GetQueryResults([FromBody]Dictionary<string, object> queryParams)
{
        // continued from above...
        MemoryStream ms = QueryResultsExport.exportToExcel(queryItems);
        HttpResponseMessage httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK);
        httpResponseMessage.Content = new StreamContent(ms);
        httpResponseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
        httpResponseMessage.Content.Headers.ContentDisposition.FileName = "asdfq.xlsx";
        httpResponseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
        return httpResponseMessage;
}

最后在 Vue 前端处理响应:

.then(result => {
    var myBlob = new Blob([result.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})

    const url = window.URL.createObjectURL(myBlob);
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', 'asdfq.xlsx'); //or any other extension
    document.body.appendChild(link);
    link.click();
})

结果是出现一个弹出框提示我下载一个excel文件。但是,无法读取该文件,并且在打开文件时会显示消息“我们发现 asdfq.xlsx 中的某些内容存在问题。您希望我们尽可能多地恢复吗?如果您相信此消息的来源工作簿,单击是。”当我单击是时,我得到“Excel 无法打开文件 asdfq.xlsx,因为文件格式或文件扩展名无效。验证文件没有损坏并且文件扩展名与文件格式匹配。”

当我在浏览器开发工具网络选项卡中查看响应时,我看到响应有 Content-Length: 3442,这似乎是正确的:这与我之前成功保存在服务器上的 excel 文件匹配。但是,最终下载的 excel 文件大小为 5635 字节。

这是响应标头的图片:

这是 Console.log(结果)的图片:

我尝试了各种方法,例如更改 responseType、传递给 blob 构造函数的类型、标头 ContentType、请求中的 Accept 标头、如何将内存流添加到响应中等,但我没有任何运气好让这个工作:结果总是一样的;损坏的 excel 文件,大小为 5635 字节,而不是预期的 3442 字节。任何帮助将不胜感激;谢谢!

编辑:我注意到在我粘贴的代码 sn-ps 中,我有

 httpResponseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");

在后端,但在前端我有

var myBlob = new Blob([result.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})

我确实尝试将它们都设置为“application/octet-stream”,或者都设置为“application/vnd.openxmlformats-officedocument.spreadsheetml.sheet”,但这没有区别。

我还尝试了不同的方式来生成响应:

//attempt 1
MemoryStream ms = QueryResultsExport.exportToExcel(queryItems);
HttpResponseMessage httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK);
httpResponseMessage.Content = new StreamContent(ms);
httpResponseMessage.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = "asdfq.xlsx";
httpResponseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");
// ms.Close();
return httpResponseMessage;

//attempt 2
MemoryStream ms = QueryResultsExport.exportToExcel(queryItems);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
    Content = new ByteArrayContent(ms.ToArray())
};
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "asdfq.xlsx";
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
return result;



// atempt 3
MemoryStream ms = QueryResultsExport.exportToExcel(queryItems);
ms.Position = 0;
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
    Content = new ByteArrayContent(ms.GetBuffer())
};
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment"){FileName = "export.xlsx"};
string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
result.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
result.Content.Headers.Add("content-length", ms.Length.ToString());
return result;

最后在QueryResultsExport.exportToExcel中,我也试过了

var ms = new MemoryStream();
excel.SaveAs(ms);
return ms;

但这些都没有任何区别。

【问题讨论】:

    标签: c# vue.js asp.net-web-api axios epplus-4


    【解决方案1】:

    我能够通过将 excel 文件作为 StringContent 而不是 ByteArrayContent 发送来使其工作,如下所示: How to download ByteArrayContent of HttpResponseMessage as zip

    MemoryStream ms = QueryResultsExport.exportToExcel(queryItems);
    string base64String = System.Convert.ToBase64String(ms.ToArray(), 0, ms.ToArray().Length);
    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(base64String)
    };
    return result;
    
    .then(result => {
        const link = document.createElement('a');
        link.href = 'data:application/octet-stream;charset=utf-8;base64,' + result.data;
        link.target = '_blank';
        link.download = 'asdfq.xlsx';
        link.click();
    })
    

    似乎没有必要在 api 请求中分配 responseType。也不设置 Content.Headers.ContentDisposition 或 Content.Headers.ContentType。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-06
      • 2018-07-23
      • 1970-01-01
      • 1970-01-01
      • 2017-04-09
      相关资源
      最近更新 更多