You need to enable JavaScript to run this app. 使用HttpWebRequest而非HttpClient调用Web API返回Base64异常时如何正确获取文件
优惠活动
大模型
产品
解决方案
定价
更多

使用HttpWebRequest而非HttpClient调用Web API返回Base64异常时如何正确获取文件

基于HttpWebRequest的正确实现方案

原有代码核心问题定位

  • 请求构造漏参:后端接口要求接收IFormFile file和string test两个参数,现有代码仅构造了file字段的表单段,完全没有传递test参数,导致后端参数绑定失败,返回的Base64字符串实际是后端参数校验异常的默认响应内容,不是预期的文件字节
  • 响应流重复消费缺陷:代码同时对同一个响应流创建了StreamReader和BinaryReader实例,StreamReader的内部缓冲区会提前吞掉响应流的起始内容,导致后续BinaryReader读到的内容残缺、错乱
  • 响应读取硬截断:reader1.ReadBytes(16*1024) 固定只读取16KB内容,超过该大小的文件会被直接截断
  • multipart格式不规范:现有边界拼接逻辑缺少标准换行符、段分隔不完整,不符合RFC对multipart/form-data格式的要求,容易导致后端解析失败
  • 输入流位置未重置:读取HttpPostedFileBase的文件流前没有将流位置重置到起点,可能读取到空字节数组

修正后的完整代码

[HttpPost]
public ActionResult DownloadProfilePicture(HttpPostedFileBase file)
{
    // 生成合规的multipart请求边界
    string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri("https://localhost:44387/api/file/UploadFileAndOtherParamsAndGetDoc"));
    request.ContentType = $"multipart/form-data; boundary={boundary}";
    request.Method = "POST";
    request.KeepAlive = true;

    using (var memStream = new MemoryStream())
    {
        // 1. 构造普通字符串参数 test(补全缺失的必填表单段)
        string testSection = string.Format(
            "\r\n--{0}\r\nContent-Disposition: form-data; name=\"test\"\r\n\r\n{1}\r\n",
            boundary,
            "这里替换为实际的test参数值"
        );
        byte[] testSectionBytes = Encoding.UTF8.GetBytes(testSection);
        memStream.Write(testSectionBytes, 0, testSectionBytes.Length);

        // 2. 构造文件上传段
        string fileSectionHeader = string.Format(
            "--{0}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n",
            boundary,
            file.FileName
        );
        byte[] fileHeaderBytes = Encoding.UTF8.GetBytes(fileSectionHeader);
        memStream.Write(fileHeaderBytes, 0, fileHeaderBytes.Length);

        // 重置文件流位置后读取完整文件字节
        file.InputStream.Position = 0;
        byte[] fileBytes = new byte[file.InputStream.Length];
        file.InputStream.Read(fileBytes, 0, fileBytes.Length);
        memStream.Write(fileBytes, 0, fileBytes.Length);

        // 写入multipart结束边界
        byte[] endBoundaryBytes = Encoding.ASCII.GetBytes($"\r\n--{boundary}--");
        memStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);

        request.ContentLength = memStream.Length;

        // 添加基础认证头
        var username = "fdg";
        var password = "dsffsd";
        string authEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
        request.Headers.Add("Authorization", $"Basic {authEncoded}");

        // 将内存流内容写入请求流
        memStream.Position = 0;
        using (Stream requestStream = request.GetRequestStream())
        {
            memStream.CopyTo(requestStream);
        }

        // 全量读取响应流,不做硬截断,不重复实例化流读取器
        byte[] responseFileBytes;
        using (var response = (HttpWebResponse)request.GetResponse())
        {
            using (Stream responseStream = response.GetResponseStream())
            {
                using (var responseMs = new MemoryStream())
                {
                    responseStream.CopyTo(responseMs);
                    responseFileBytes = responseMs.ToArray();
                }
            }
        }

        return File(responseFileBytes, System.Net.Mime.MediaTypeNames.Application.Octet);
    }
}

实现注意事项

  • multipart边界值不能在请求体和请求头中重复出现,否则会导致解析错乱
  • 普通表单参数和文件参数的顺序可以互换,但必须按照格式补齐每段的Content-Disposition头
  • 读取响应流时全程只维护一个流实例,不要同时叠加多个读取器,避免流内容被内部缓冲吞掉
  • 用CopyTo替代手动定长读取,避免大文件内容截断

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 09:15:03