如何移除
和以生成合法XML?WCF服务问题咨询
解决WCF服务返回非法XML字符的方案
你遇到的问题是WCF响应中包含XML规范不允许的控制字符(比如这类),逐个字段处理确实低效且容易遗漏,以下是几种现成的全局处理方案:
1. 使用WCF消息检查器全局清理响应
消息检查器可以在WCF发送响应前统一拦截并修改内容,无需修改业务代码,侵入性极低。
实现步骤:
第一步:编写消息检查器类
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; using System.Xml; public class XmlCleanupMessageInspector : IDispatchMessageInspector { // 处理请求(此处不需要,返回null) public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { return null; } // 发送响应前统一清理XML内容 public void BeforeSendReply(ref Message reply, object correlationState) { if (reply == null) return; // 将响应消息读取为XML文档 XmlDocument doc = new XmlDocument(); using (MemoryStream ms = new MemoryStream()) { XmlWriter writer = XmlWriter.Create(ms); reply.WriteMessage(writer); writer.Flush(); ms.Position = 0; doc.Load(ms); } // 递归清理所有文本节点的非法字符 CleanXmlNodes(doc.DocumentElement); // 重新生成清理后的响应消息 using (MemoryStream newMs = new MemoryStream()) { doc.Save(newMs); newMs.Position = 0; XmlReader reader = XmlReader.Create(newMs); reply = Message.CreateMessage(reader, int.MaxValue, reply.Version); } } // 递归遍历并处理XML节点 private void CleanXmlNodes(XmlNode node) { if (node.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) { node.Value = RemoveInvalidXmlCharacters(node.Value); } foreach (XmlNode childNode in node.ChildNodes) { CleanXmlNodes(childNode); } } // 严格按照XML规范过滤非法字符 private string RemoveInvalidXmlCharacters(string input) { if (string.IsNullOrEmpty(input)) return input; return new string(input.Where(c => c == '\t' || c == '\n' || c == '\r' || (c >= '\x20' && c <= '\xD7FF') || (c >= '\xE000' && c <= '\xFFFD') || (c >= '\x10000' && c <= '\x10FFFF') ).ToArray()); } }
第二步:编写服务行为类注册检查器
public class XmlCleanupBehavior : IServiceBehavior { public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>()) { foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new XmlCleanupMessageInspector()); } } } // 接口其他方法空实现 public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } }
第三步:在服务宿主中启用行为
ServiceHost host = new ServiceHost(typeof(YourWcfServiceImplementation)); host.Description.Behaviors.Add(new XmlCleanupBehavior()); host.Open();
如果使用配置文件管理服务,也可以注册自定义行为扩展(需额外编写BehaviorExtensionElement子类,代码略),通过配置启用该行为。
2. 自定义序列化行为过滤字符
如果你的WCF服务使用DataContractSerializer或XmlSerializer,可以通过自定义序列化行为,在序列化过程中直接处理非法字符,更贴近数据处理流程。
示例(针对DataContractSerializer):
public class CustomDataContractSerializerBehavior : DataContractSerializerOperationBehavior { public CustomDataContractSerializerBehavior(OperationDescription operation) : base(operation) { } public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes) { XmlWriterSettings settings = new XmlWriterSettings(); // 使用自定义XmlWriter处理字符 return new DataContractSerializer(type, name, ns, knownTypes, int.MaxValue, false, true, null, new CustomXmlWriter(settings)); } } // 自定义XmlWriter重写字符写入逻辑 public class CustomXmlWriter : XmlWriter { private readonly XmlWriter _innerWriter; public CustomXmlWriter(XmlWriterSettings settings) { _innerWriter = XmlWriter.Create(new MemoryStream(), settings); } public override void WriteString(string text) { string cleanedText = RemoveInvalidXmlCharacters(text); _innerWriter.WriteString(cleanedText); } // 重写CData写入逻辑(如果业务中用到CData) public override void WriteCData(string text) { string cleanedText = RemoveInvalidXmlCharacters(text); _innerWriter.WriteCData(cleanedText); } // 其他XmlWriter方法直接委托给内部Writer public override void Close() => _innerWriter.Close(); public override void Flush() => _innerWriter.Flush(); public override string LookupPrefix(string ns) => _innerWriter.LookupPrefix(ns); public override void WriteBase64(byte[] buffer, int index, int count) => _innerWriter.WriteBase64(buffer, index, count); public override void WriteCharEntity(char ch) => _innerWriter.WriteCharEntity(ch); public override void WriteChars(char[] buffer, int index, int count) => _innerWriter.WriteChars(buffer, index, count); // 按需实现其他XmlWriter抽象方法 // 复用非法字符过滤逻辑 private string RemoveInvalidXmlCharacters(string input) { if (string.IsNullOrEmpty(input)) return input; return new string(input.Where(c => c == '\t' || c == '\n' || c == '\r' || (c >= '\x20' && c <= '\xD7FF') || (c >= '\xE000' && c <= '\xFFFD') || (c >= '\x10000' && c <= '\x10FFFF') ).ToArray()); } }
之后通过IServiceBehavior将这个自定义序列化行为注册到服务的操作中即可(注册逻辑类似消息检查器的行为注册)。
方案对比
- 消息检查器:侵入性最低,无需修改数据契约或序列化配置,全局覆盖所有响应,适合绝大多数场景。
- 自定义序列化行为:更贴近数据序列化流程,适合需要精细控制序列化逻辑的场景,但需针对不同序列化器做适配。
- 自定义文本编码器(进阶):在传输层处理,适合所有文本绑定,但实现复杂度高,一般前两种方案足够解决问题。
注意事项
XML规范允许的字符范围是:U+0009(制表符)、U+000A(换行)、U+000D(回车),以及U+0020到U+D7FF、U+E000到U+FFFD、U+10000到U+10FFFF的字符,过滤逻辑严格遵循此范围即可避免合规问题。
内容的提问来源于stack exchange,提问作者Banshee
相关产品推荐
相关产品推荐

