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

如何在SOAP错误响应中保留XML声明头?

我来帮你解决这个SOAP错误消息添加XML声明的问题。你遇到的核心问题是WCF的Message对象本身并不包含XML声明——它只代表SOAP信封的内容,当WCF序列化Message发送给客户端时,默认不会输出XML声明,而你直接修改字符串再重新创建Message的方式,会被XmlDictionaryReader忽略掉XML声明,因为它不属于SOAP信封的一部分。

下面给你两种可行的解决方案,优先推荐第一种,因为它更符合WCF的扩展机制,也能避免前缀丢失的问题:

方案一:自定义MessageEncoder控制XML声明输出

这种方法通过扩展WCF的消息编码器,在最终序列化消息到客户端时,判断是否是错误消息,动态添加XML声明,同时保留原有的SOAP命名空间前缀。

1. 实现自定义TextMessageEncoder

public class CustomFaultTextMessageEncoder : TextMessageEncoder
{
    private readonly TextMessageEncoder _innerEncoder;
    private readonly XmlWriterSettings _xmlSettings;

    public CustomFaultTextMessageEncoder(TextMessageEncoder innerEncoder)
        : base(innerEncoder.MediaType, innerEncoder.CharSet, innerEncoder.MessageVersion)
    {
        _innerEncoder = innerEncoder;
        // 配置XML写入设置,默认不忽略声明(但仅在错误消息时启用)
        _xmlSettings = new XmlWriterSettings
        {
            OmitXmlDeclaration = false,
            Encoding = Encoding.UTF8,
            Indent = false, // 根据你的需求调整是否缩进
            NamespaceHandling = NamespaceHandling.OmitDuplicates
        };
    }

    public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
    {
        if (message.IsFault)
        {
            // 先将原始消息写入内存流,获取完整的SOAP信封内容
            using (var originalMs = new MemoryStream())
            {
                _innerEncoder.WriteMessage(message, originalMs);
                originalMs.Position = 0;

                // 创建新流,先写入XML声明,再写入SOAP内容
                using (var newMs = new MemoryStream())
                {
                    using (var xmlWriter = XmlWriter.Create(newMs, _xmlSettings))
                    {
                        // 写入XML声明(XmlWriter会根据设置自动生成正确的编码声明)
                        xmlWriter.WriteStartDocument();
                        // 复制原始SOAP内容
                        originalMs.CopyTo(newMs);
                    }

                    newMs.Position = 0;
                    // 准备返回的缓冲区
                    byte[] buffer = bufferManager.TakeBuffer((int)newMs.Length);
                    newMs.Read(buffer, messageOffset, (int)newMs.Length - messageOffset);
                    return new ArraySegment<byte>(buffer, messageOffset, (int)newMs.Length);
                }
            }
        }
        else
        {
            // 成功响应使用默认逻辑,不输出XML声明
            return _innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
        }
    }

    // 其他方法直接委托给内部编码器,保证原有功能不变
    public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
    {
        return _innerEncoder.ReadMessage(buffer, bufferManager, contentType);
    }

    public override bool IsContentTypeSupported(string contentType)
    {
        return _innerEncoder.IsContentTypeSupported(contentType);
    }
}

2. 实现编码器工厂

public class CustomFaultTextMessageEncoderFactory : MessageEncoderFactory
{
    private readonly MessageEncoder _encoder;
    private readonly MessageVersion _messageVersion;

    public CustomFaultTextMessageEncoderFactory(string mediaType, string charSet, MessageVersion messageVersion)
    {
        var innerFactory = new TextMessageEncoderFactory(mediaType, charSet, messageVersion);
        _encoder = new CustomFaultTextMessageEncoder(innerFactory.Encoder);
        _messageVersion = messageVersion;
    }

    public override MessageEncoder Encoder => _encoder;
    public override MessageVersion MessageVersion => _messageVersion;
}

3. 配置绑定使用自定义编码器

你可以通过配置文件或者代码来设置绑定:

配置文件方式

<system.serviceModel>
  <bindings>
    <customBinding>
      <binding name="SoapWithCustomFaultEncoder">
        <textMessageEncoding messageVersion="Soap11">
          <encoder type="YourAssembly.Namespace.CustomFaultTextMessageEncoderFactory, YourAssembly" />
        </textMessageEncoding>
        <httpTransport />
      </binding>
    </customBinding>
  </bindings>
  <!-- 服务配置中使用这个customBinding -->
  <services>
    <service name="YourService">
      <endpoint address="" binding="customBinding" bindingConfiguration="SoapWithCustomFaultEncoder" contract="YourContract" />
    </service>
  </services>
</system.serviceModel>

代码绑定方式

var customBinding = new CustomBinding();
// 替换为你需要的MessageVersion(比如Soap11或Soap12)
var messageVersion = MessageVersion.Soap11;
var textEncodingElement = new TextMessageEncodingBindingElement(messageVersion, Encoding.UTF8);
// 设置自定义编码器工厂
textEncodingElement.MessageEncoderFactory = new CustomFaultTextMessageEncoderFactory("text/xml", "UTF-8", messageVersion);
customBinding.Elements.Add(textEncodingElement);
customBinding.Elements.Add(new HttpTransportBindingElement());

// 将绑定应用到服务宿主
var host = new ServiceHost(typeof(YourService));
host.AddServiceEndpoint(typeof(YourContract), customBinding, "http://your-service-url");

方案二:直接构造带XML声明的响应字节流(简易版)

如果你不想自定义编码器,也可以在ProvideFault方法中直接构造包含XML声明的完整XML字符串,然后通过自定义的Message返回。不过这种方法需要注意手动处理SOAP信封的完整性:

public void ProvideFault(Exception error, MessageVersion messageVersion, ref Message fault)
{
    var faultException = new FaultException(error.Message);
    var messageFault = faultException.CreateMessageFault();

    // 先创建标准的Fault Message,获取完整的SOAP信封
    using (var tempMs = new MemoryStream())
    {
        var tempMessage = Message.CreateMessage(messageVersion, messageFault, null);
        var encoder = new TextMessageEncoderFactory("text/xml", "UTF-8", messageVersion).Encoder;
        encoder.WriteMessage(tempMessage, tempMs);
        tempMs.Position = 0;
        string soapEnvelope = Encoding.UTF8.GetString(tempMs.ToArray());

        // 拼接XML声明和SOAP信封
        string fullXml = $"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{soapEnvelope}";
        byte[] xmlBytes = Encoding.UTF8.GetBytes(fullXml);

        // 创建包含完整XML的Message
        var reader = XmlDictionaryReader.CreateTextReader(xmlBytes, new XmlDictionaryReaderQuotas());
        var newMessage = Message.CreateMessage(reader, int.MaxValue, messageVersion);
        newMessage.Properties.CopyProperties(fault.Properties);

        fault = new ProposalMessage(newMessage);
    }
}

不过这种方法的问题在于,当WCF处理这个Message时,仍然可能在序列化时忽略XML声明,所以还是方案一更可靠。

为什么你之前的方法失效?

你之前的ChangeString方法中,修改后的字符串包含XML声明,但通过XmlDictionaryReader读取时,XML声明会被当作文档级别的元数据而被丢弃——因为Message对象只封装SOAP信封元素,不包含XML声明。只有在最终的消息编码阶段(也就是Encoder负责的部分)添加XML声明,才能确保它被发送到客户端。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 07:22:30