You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

浏览器点击按钮无法下载CSV文件问题求助

问题:点击按钮无法下载CSV文件

点击浏览器按钮后无任何反应,但已确认本地临时文件夹中CSV文件已生成。以下是相关代码:


API 代码(C#)

[HttpGet]
[Route("[controller]/downlaodCSV/{pID}/{customerID}")]
public System.Net.Http.HttpResponseMessage downlaodCSV(int pID, int customerID)
{
    //获取文件
    var rateFilename = _Client.RateRepository.downlaodCSV(pID, customerID);
    
    var stream = new System.IO.MemoryStream();
    //处理流(原代码此处省略)

    var result = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
    {
        Content = new System.Net.Http.ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
        {
            FileName = rateFilename
        };
    result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream");

    return result;
}

业务逻辑层(Rates Repository)

public string downlaodCSV(int pID, int customerID)
{
    Random _random = new Random();
    int number = _random.Next(1, 9999);

    string fileName = "";
    try
    {   
        #region 测试数据            
        //临时测试数据,后续将替换为API获取的真实数据
        DataTable rates = new DataTable();
        rates.Clear();
        rates.Columns.Add("Name");
        rates.Columns.Add("Surname");

        DataRow dr = rates.NewRow();
        dr["Name"] = "Test1";
        dr["Surname"] = "Test2";
        rates.Rows.Add(dr);

        #endregion

        if (rates.Rows.Count > 0)
        {
            StringBuilder stringBuilder = new StringBuilder();
            string[] columnNames = rates.Columns.Cast<DataColumn>().Select(column => column.ColumnName).ToArray();
            stringBuilder.AppendLine(string.Join(",", columnNames));

            foreach (DataRow row in rates.Rows)
            {
                string[] fields = row.ItemArray.Select(field => field.ToString()).ToArray();
                stringBuilder.AppendLine(string.Join(",", fields));
            }

            fileName = CsvFilename(number);

            //保存文件用于验证(后续会删除)
            string html = String.Empty;
            var newName = fileName;
            var path = @"C:\test\ " + newName + "";
            File.WriteAllText(fileName, stringBuilder.ToString());
            File.SetAttributes(fileName, FileAttributes.Normal);

            File.WriteAllText(path, html);
        }
        else
        {
            throw new Exception("未找到可下载的费率数据!");
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }
   return fileName;
}

Angular 组件代码(Component.ts)

downloadFile()
{
    try
    {
        var _pID = this.reseller.pID;
        var _customerID = this.reseller.customerID;
        
        //调用下载服务
        this.rateSheetService.downlaodCSV(_pID, _customerID).then((obj) => {
            console.log("API调用成功!");
            let blob: Blob = obj as Blob;
            console.log("blob", blob);

            var binaryData = [];
            binaryData.push(blob);
            console.log("binaryData", binaryData);
            var url = window.URL.createObjectURL(new Blob(binaryData, {type: "text/csv"}))
            console.log("Url", url);

        });
    }
    catch(e)
    {
        console.log("错误信息 ", e)
    }
}

Angular 服务代码(Service.ts)

public downlaodCSV(pID, customerID){
    return this.apiService.get<any>(`RateSheetReport/downlaodCSV/${pID}/${customerID}`);
}

HTML 按钮代码

<button 
    class="context-download-button"
    (click)="downloadFile()"
    tooltipPosition="top">
    <span class="icon icon-download mr-5"></span>Download
</button>

浏览器控制台情况

控制台已打印出Blob对象,但无下载动作触发。


解决方案

问题核心在于前端仅生成Blob URL但未触发下载行为,同时API层存在流处理空数据的错误,以下是分步修复:

1. 修复API层的流处理错误

当前API中的stream未写入任何数据,直接返回空流,需读取生成的CSV文件内容到流中,并修正MIME类型:

[HttpGet]
[Route("[controller]/downlaodCSV/{pID}/{customerID}")]
public System.Net.Http.HttpResponseMessage downlaodCSV(int pID, int customerID)
{
    var rateFilename = _Client.RateRepository.downlaodCSV(pID, customerID);
    // 读取本地生成的CSV文件内容到字节数组
    byte[] fileBytes = System.IO.File.ReadAllBytes(rateFilename);
    var stream = new System.IO.MemoryStream(fileBytes);

    var result = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK)
    {
        Content = new System.Net.Http.ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
        {
            FileName = rateFilename
        };
    // 改为CSV对应的MIME类型
    result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/csv");

    return result;
}

2. 前端添加下载触发逻辑

生成Blob URL后,通过创建临时<a>标签模拟点击,触发浏览器下载:

downloadFile()
{
    try
    {
        var _pID = this.reseller.pID;
        var _customerID = this.reseller.customerID;
        
        this.rateSheetService.downlaodCSV(_pID, _customerID).then((obj) => {
            console.log("API调用成功!");
            let blob: Blob = obj as Blob;
            const url = window.URL.createObjectURL(blob);
            // 创建临时a标签
            const downloadLink = document.createElement('a');
            downloadLink.href = url;
            // 指定下载文件名
            downloadLink.download = `rates_${_pID}_${_customerID}.csv`;
            document.body.appendChild(downloadLink);
            // 模拟点击触发下载
            downloadLink.click();
            // 清理资源
            window.URL.revokeObjectURL(url);
            document.body.removeChild(downloadLink);
        });
    }
    catch(e)
    {
        console.log("错误信息 ", e)
    }
}

3. 确保API请求返回Blob类型

修改Angular服务,指定响应类型为blob,避免二进制数据被错误解析为JSON:

public downlaodCSV(pID, customerID){
    return this.apiService.get<any>(`RateSheetReport/downlaodCSV/${pID}/${customerID}`, { responseType: 'blob' });
}

4. 清理业务逻辑冗余代码

业务逻辑中File.WriteAllText(path, html);是写入空内容的冗余操作,建议删除;同时确保CsvFilename(number)方法返回带.csv后缀的正确文件名。


内容的提问来源于stack exchange,提问作者Ribon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 02:52:33