You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Ajax和Web Service从SQL Server下载文件无报错但无法下载

解决Ajax调用WebService无法触发文件下载的问题

首先得说清楚核心问题:Ajax请求本身没法直接触发浏览器的文件下载弹窗。浏览器不会自动把Ajax返回的二进制数据解析成可下载的文件——Ajax的设计初衷是异步获取数据来更新页面,而非处理这类需要浏览器原生交互的下载动作。结合你的代码,还有几个细节问题需要修正,下面一步步来解决:

一、先修正WebService的几个关键问题

你的WebService代码里有几个明显的错误,先把这些改了:

1. 参数类型错误

你前端传的是单个id值,但WebService方法参数是table id,这会导致参数绑定失败,根本拿不到正确的id去查数据库。把参数改成对应的数据类型(比如你的id是整数就用int):

[ScriptMethod]
[WebMethod]
public void DownloadFile(int id ) // 把table改成int(根据你的实际数据类型调整)
{
    // ... 后续代码
}

2. ContentType和FileName取值错误

你现在用sdr["id"].ToString()来设置ContentType和FileName,这完全不对:

  • ContentType是文件的MIME类型(比如图片是image/jpeg,PDF是application/pdf),如果数据库没存这个字段,就用通用的application/octet-stream
  • FileName应该是用户下载后看到的文件名,要么从数据库取存储的文件名,要么自定义一个有意义的名字(比如"attachment_" + id + ".pdf");
  • 还要判断SqlDataReader是否读到数据,避免空引用异常;
  • 文件名要URL编码,防止中文乱码。

修正后的WebService核心代码:

byte[] bytes = null;
string fileName = "";
string contentType = "application/octet-stream"; // 默认通用类型

using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["TesterConnectionString1"].ConnectionString))
{
    using (SqlCommand cmd = new SqlCommand("select image, file_name, content_type from attach where id=@Id", con))
    {
        cmd.Parameters.AddWithValue("@Id", id);
        con.Open();
        using (SqlDataReader sdr = cmd.ExecuteReader())
        {
            if (sdr.Read()) // 先判断是否找到数据
            {
                bytes = (byte[])sdr["image"];
                fileName = sdr["file_name"]?.ToString() ?? $"file_{id}"; // 优先取数据库里的文件名
                contentType = sdr["content_type"]?.ToString() ?? "application/octet-stream"; // 优先取数据库里的MIME类型
            }
            else
            {
                // 没找到文件返回404
                HttpContext.Current.Response.StatusCode = 404;
                HttpContext.Current.Response.End();
                return;
            }
        }
    }
}

// 处理下载响应
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Buffer = true;
HttpContext.Current.Response.Charset = "";
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
HttpContext.Current.Response.ContentType = contentType;

// 文件名编码,避免中文乱码
string encodedFileName = HttpUtility.UrlEncode(fileName, Encoding.UTF8);
HttpContext.Current.Response.AppendHeader("Content-Disposition", $"attachment; filename=\"{encodedFileName}\"");

HttpContext.Current.Response.BinaryWrite(bytes);
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();

二、替换Ajax为能触发下载的方式

因为Ajax没法处理文件下载,我们有两种简单的替代方案:

方案1:用window.open直接发起请求(适合GET请求)

修改前端点击事件,直接打开WebService的下载链接:

$('#btn_download').click(function () {
    var id = $('#Tid').val();
    // 用encodeURIComponent编码id,防止特殊字符问题
    window.open(`WebService1.asmx/DownloadFile?id=${encodeURIComponent(id)}`);
});

如果你的WebService需要POST请求,就用下面的表单提交方案。

方案2:创建隐藏表单提交(适合POST请求)

动态创建一个隐藏表单,提交到WebService,浏览器会自动处理下载响应:

$('#btn_download').click(function () {
    var id = $('#Tid').val();
    
    // 创建隐藏表单
    var downloadForm = $('<form>', {
        method: 'POST',
        action: 'WebService1.asmx/DownloadFile'
    });
    
    // 添加id参数
    downloadForm.append($('<input>', {
        type: 'hidden',
        name: 'id',
        value: id
    }));
    
    // 提交表单后移除
    downloadForm.appendTo('body').submit();
    downloadForm.remove();
});

三、如果一定要用Ajax(比如需要先做验证)

如果你的业务需要先通过Ajax做一些前置验证,再触发下载,可以让WebService返回Base64编码的文件数据,然后前端手动创建下载链接:

修改WebService返回Base64数据

[ScriptMethod]
[WebMethod]
public string DownloadFile(int id )
{
    // 前面的数据库查询逻辑和之前一样,获取bytes、fileName、contentType
    // ...
    
    if (bytes == null)
    {
        return null;
    }
    
    // 把字节数组转成Base64
    string base64Data = Convert.ToBase64String(bytes);
    
    // 返回包含文件信息的JSON(用Newtonsoft.Json序列化)
    return JsonConvert.SerializeObject(new {
        FileName = fileName,
        ContentType = contentType,
        Base64Data = base64Data
    });
}

前端Ajax处理并触发下载

$('#btn_download').click(function () {
    var id = $('#Tid').val();
    $.ajax({
        url: 'WebService1.asmx/DownloadFile',
        method: 'POST',
        contentType: 'application/json;charset=utf-8',
        data: JSON.stringify({id: id}), // 用JSON.stringify规范传参
        success: function (response) {
            // WebService返回的结果会被包裹在d属性里
            var fileData = JSON.parse(response.d);
            
            if (!fileData) {
                alert("文件不存在或下载失败");
                return;
            }
            
            // 把Base64转成Blob对象
            var byteCharacters = atob(fileData.Base64Data);
            var byteNumbers = new Array(byteCharacters.length);
            for (var i = 0; i < byteCharacters.length; i++) {
                byteNumbers[i] = byteCharacters.charCodeAt(i);
            }
            var byteArray = new Uint8Array(byteNumbers);
            var blob = new Blob([byteArray], {type: fileData.ContentType});
            
            // 创建a标签触发下载
            var downloadLink = document.createElement('a');
            downloadLink.href = window.URL.createObjectURL(blob);
            downloadLink.download = fileData.FileName;
            document.body.appendChild(downloadLink);
            downloadLink.click();
            
            // 清理资源
            document.body.removeChild(downloadLink);
            window.URL.revokeObjectURL(downloadLink.href);
        },
        error: function (err) {
            alert(`下载出错:${err.responseText}`);
        }
    });
});

关键总结

  1. Ajax不适合直接处理文件下载:这是浏览器的机制限制,必须用浏览器原生的请求方式(跳转、表单提交)或者前端手动处理Blob数据;
  2. 参数和数据取值要准确:别把id当成ContentType和FileName,参数类型要和前端传值匹配;
  3. 文件名要编码:避免中文或特殊字符导致的下载文件名乱码问题。

内容的提问来源于stack exchange,提问作者Hello Its Me

火山引擎 最新活动