卡在从 ASP.NET Web API 后端生成 + 发送 .xlsx 文件到 Vue.js 前端

Stuck generating + sending .xlsx file from ASP.NET Web API backend to Vue.js frontend

我正在尝试在我的 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 字节(稍后相关)。

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

[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文件。但是,文件无法读取,打开时显示消息 "We found a problem with some content in asdfq.xlsx. Do you want us to try to recover as much as we can? If you trust the source of this workbook, click Yes." 当我单击是时,我得到 "Excel cannot open the file asdfq.xlsx because the file format or file extension is not valid. Verify that the file has not been corrupted and that the file extension matches the format of the file."

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

这是响应 headers 的图片:

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

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

编辑:我在粘贴的代码片段中注意到,我有

 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;

但是 none 这有什么不同。

我能够通过将 excel 文件作为 StringContent 而不是 ByteArrayContent 发送来让它工作,如下所示:

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.

也不行