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

在C#中从SOAP WSA响应的二进制附件提取文件的最优方法是什么?

问题原因
  • 你当前的实现存在两个核心错误:
    1. 方法声明返回HttpWebResponse但实际返回字符串,属于逻辑笔误
    2. 用StreamReader读取包含二进制附件的响应流:StreamReader是文本处理工具,会强制按字符编码转换字节数据,导致二进制ZIP附件被转码损坏,完全无法用该方式处理带二进制内容的响应
  • 你拿到的是*SOAP MTOM(消息传输优化机制)*规范的multipart/related类型响应,属于多段结构,分别包含SOAP XML段和二进制附件段,需要先解析多段结构再提取附件。
最优实现方案

推荐优先使用HttpClient + 内置MultipartContent解析的方案,无需手动处理边界逻辑,稳定性最高:

// 建议将HttpClient声明为静态单例,不要每次请求都新建
private static readonly HttpClient _httpClient = new HttpClient();

public async Task<byte[]> ExtractZipAttachmentFromSoapResponse(string xmlRequest, string soapAction)
{
    // 构造请求内容
    var content = new StringContent(xmlRequest, Encoding.UTF8, "text/xml");
    content.Headers.Add("SOAPAction", soapAction);
    content.Headers.Add("Use", "literal");
    content.Headers.Add("Parameters-style", "Wrapped");
    
    // 发送请求
    using var response = await _httpClient.PostAsync(_endPoint, content);
    response.EnsureSuccessStatusCode();
    
    // 解析多段内容
    var multipart = await response.Content.ReadAsMultipartAsync();
    
    // 遍历所有段,找到ZIP附件段
    foreach (var part in multipart.Contents)
    {
        if (part.Headers.ContentType?.MediaType == "application/zip")
        {
            // 直接读取附件二进制内容
            return await part.ReadAsByteArrayAsync();
        }
    }
    
    // 未找到附件时返回空或抛出异常
    return Array.Empty<byte>();
}

拿到ZIP二进制数组后,直接写入文件即可:

var zipBytes = await ExtractZipAttachmentFromSoapResponse(yourXml, yourSoapAction);
if (zipBytes.Length > 0)
{
    File.WriteAllBytes(@"C:\保存路径\附件.zip", zipBytes);
}

兼容旧HttpWebRequest的方案

如果必须保留现有HttpWebRequest的实现,可按以下步骤手动解析:

  1. 从响应头Content-Type中提取边界字符串
  2. 完整读取响应流为字节数组,全程不做编码转换
  3. 按边界切割字节数组,找到Content-Type: application/zip对应的段
  4. 跳过段头的空行,剩余内容就是ZIP的二进制数据

代码示例:

public byte[] ExtractZipWithHttpWebRequest(string xmlRequest, string soapAction)
{
    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(_endPoint);
    webRequest.ServicePoint.Expect100Continue = true;
    webRequest.Headers.Add("Use", "literal");
    webRequest.Headers.Add("Parameters-style", "Wrapped");
    webRequest.ContentType = "text/xml; charset=utf-8";
    webRequest.Method = "POST";
    webRequest.KeepAlive = true;
    webRequest.Accept = "*/*";
    webRequest.Headers.Add("SOAPAction", soapAction);

    var content = Encoding.UTF8.GetBytes(xmlRequest);
    webRequest.ContentLength = content.Length;
    using (var stream = webRequest.GetRequestStream())
    {
        stream.Write(content, 0, content.Length);
    }
    
    using (var response = (HttpWebResponse)webRequest.GetResponse())
    using (var responseStream = response.GetResponseStream())
    using (var ms = new MemoryStream())
    {
        // 完整读取响应流为字节数组
        responseStream.CopyTo(ms);
        var allBytes = ms.ToArray();
        
        // 提取边界
        var contentType = response.Headers["Content-Type"];
        var boundary = contentType.Split(';')
            .First(s => s.Trim().StartsWith("boundary="))
            .Split('=')[1]
            .Trim('"');
        var boundaryBytes = Encoding.ASCII.GetBytes($"--{boundary}");
        
        // 查找ZIP段位置
        var zipHeaderBytes = Encoding.ASCII.GetBytes("Content-Type: application/zip");
        int zipHeaderPos = IndexOf(allBytes, zipHeaderBytes);
        if (zipHeaderPos < 0) return Array.Empty<byte>();
        
        int emptyLinePos = IndexOf(allBytes, new byte[] { 13, 10, 13, 10 }, zipHeaderPos);
        if (emptyLinePos < 0) return Array.Empty<byte>();
        
        int zipStartPos = emptyLinePos + 4;
        // 找到下一个边界的位置作为ZIP结束位置
        int nextBoundaryPos = IndexOf(allBytes, boundaryBytes, zipStartPos);
        int zipLength = nextBoundaryPos - zipStartPos;
        
        var zipBytes = new byte[zipLength];
        Array.Copy(allBytes, zipStartPos, zipBytes, 0, zipLength);
        return zipBytes;
    }
}

// 辅助方法:在字节数组中查找指定序列的位置
private int IndexOf(byte[] source, byte[] pattern, int startIndex = 0)
{
    for (int i = startIndex; i <= source.Length - pattern.Length; i++)
    {
        bool match = true;
        for (int j = 0; j < pattern.Length; j++)
        {
            if (source[i + j] != pattern[j])
            {
                match = false;
                break;
            }
        }
        if (match) return i;
    }
    return -1;
}
注意事项
  • 所有包含二进制附件的响应处理,都必须在字节流/字节数组层面完成,绝对不要使用字符串类、文本读取类做转换
  • 解析多段结构时注意区分普通边界前缀--边界值和结束边界后缀--边界值--
  • 如果SOAP响应的附件做了Base64编码,可直接从SOAP Body中提取Base64字符串再转字节数组即可,不需要解析多段结构

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 01:36:02