如何将SOAP消息转换为C#对象?WCF服务测试方法咨询
首先得说,你之前尝试的TypeConverter和SoapFormatter都不是WCF用来处理SOAP消息的正确工具——这也是为啥它们搞不定复杂命名空间和嵌套对象的原因。先给你讲清楚服务内部的转换逻辑,再给你实际可用的实现方法。
服务内部的转换原理
WCF处理SOAP消息和.NET对象互转,靠的是序列化器,具体用哪一种取决于你生成reference.cs时的配置:
- 如果你的服务接口或数据类上有
[XmlSerializerFormat]特性,那用的是XmlSerializer(对应传统ASMX风格的SOAP); - 否则默认用
DataContractSerializer(WCF推荐的序列化方式,对应DataContract特性的类)。
生成的reference.cs里的类都会带有对应特性(比如[DataContract(Namespace="xxx")]、[XmlElement(Namespace="xxx")]),WCF就是靠这些特性来映射SOAP XML里的命名空间、节点和.NET对象的属性/字段,自动完成转换的。
实现SOAP消息转.NET对象的具体方法
最靠谱的方式就是复用WCF自带的序列化器,这样能完美匹配服务端的转换逻辑,不会出现命名空间或嵌套对象的问题。下面分两种情况给出代码:
情况1:服务用DataContractSerializer(默认)
先写一个通用的反序列化方法,步骤是先剥掉SOAP的Envelope外层,取出Body里的目标参数节点,再用DataContractSerializer反序列化:
using System.IO; using System.Xml; using System.Runtime.Serialization; public static T ConvertSoapToDotNetObject<T>(string soapMessage) { // 加载SOAP XML文档 XmlDocument soapDoc = new XmlDocument(); soapDoc.LoadXml(soapMessage); // 配置SOAP命名空间(如果你的SOAP用了自定义Envelope命名空间,替换成对应的URI) XmlNamespaceManager nsManager = new XmlNamespaceManager(soapDoc.NameTable); nsManager.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); // 获取SOAP Body节点下的第一个子节点(也就是你的服务操作参数对象对应的XML) XmlNode bodyContent = soapDoc.SelectSingleNode("//soap:Body/*", nsManager); if (bodyContent == null) throw new InvalidOperationException("无法在SOAP消息中找到Body内容"); // 反序列化为.NET对象 using (StringReader stringReader = new StringReader(bodyContent.OuterXml)) using (XmlReader xmlReader = XmlReader.Create(stringReader)) { DataContractSerializer serializer = new DataContractSerializer(typeof(T)); return (T)serializer.ReadObject(xmlReader); } }
使用时,直接传入SOAP字符串和你要转换的参数类型(比如YourServiceReference.YourRequestType):
string soapMsg = "你的SOAP消息字符串"; var requestObj = ConvertSoapToDotNetObject<YourRequestType>(soapMsg); // 之后就可以用这个对象调用服务方法了 var service = new YourServiceClient(); var response = service.YourServiceOperation(requestObj);
情况2:服务用XmlSerializer
如果你的服务用的是XmlSerializer(看reference.cs里的服务接口是否有[XmlSerializerFormat]),只需要把上面的序列化器换成XmlSerializer即可:
using System.IO; using System.Xml; using System.Xml.Serialization; public static T ConvertSoapToDotNetObject<T>(string soapMessage) { XmlDocument soapDoc = new XmlDocument(); soapDoc.LoadXml(soapMessage); XmlNamespaceManager nsManager = new XmlNamespaceManager(soapDoc.NameTable); nsManager.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/"); XmlNode bodyContent = soapDoc.SelectSingleNode("//soap:Body/*", nsManager); if (bodyContent == null) throw new InvalidOperationException("无法在SOAP消息中找到Body内容"); using (StringReader stringReader = new StringReader(bodyContent.OuterXml)) { XmlSerializer serializer = new XmlSerializer(typeof(T)); return (T)serializer.Deserialize(stringReader); } }
关键注意事项
- 命名空间匹配:生成的
reference.cs里的类已经带有正确的命名空间特性,所以WCF的序列化器会自动处理SOAP里的命名空间,不用手动调整; - SOAP Envelope命名空间:如果你的SOAP消息用的不是标准的
http://schemas.xmlsoap.org/soap/envelope/,一定要替换成消息里实际的Envelope命名空间URI; - 嵌套对象:序列化器会自动递归处理嵌套对象,只要生成的类结构和SOAP消息的节点结构一致(这一点
svcutil或添加服务引用生成的类已经保证了),就不会有问题。
单元测试小技巧
如果是做单元测试,其实可以直接构造.NET对象调用服务方法,但如果必须用现有SOAP消息做回归测试,上面的方法就能帮你把SOAP转成对象,直接传入服务方法测试逻辑。
内容的提问来源于stack exchange,提问作者user1872862

