DataContractSerializer:SerializationException,不期望'EndElement'

时间:2016-11-12 15:49:01

标签: c# datacontractserializer xml-deserialization

我正在尝试使用DataContractSerializer对一小段XML进行反序列化:

<iopbase:SoapFaultException xmlns:iopbase=""http://example.com/foo/"">
   <faultcode></faultcode>
   <faultstring>Hello World</faultstring>
</iopbase:SoapFaultException>

问题是DataContractSerializer没有“看到”<faultcode><faultstring>元素,我得到以下例外:

SerializationException: 'EndElement' 'SoapFaultException' from namespace 
'http://example.com/foo/' is not expected. Expecting element 'faultcode'.

这是一个用于复制问题的精简代码:

[DataContract(Namespace = "http://example.com/foo/")]
public class SoapFaultException
{
    [DataMember(IsRequired = true)]
    public string faultcode { get; set; }

    [DataMember(IsRequired = true)]
    public string faultstring { get; set; }
}

var xml = System.Xml.Linq.XElement.Parse(@"
<iopbase:SoapFaultException xmlns:iopbase=""http://example.com/foo/"">
  <faultcode></faultcode>
  <faultstring>Hello World</faultstring>
</iopbase:SoapFaultException>");

var serializer = new DataContractSerializer(typeof(SoapFaultException));
var e = (SoapFaultException) serializer.ReadObject(xml.CreateReader()); <- Exception Here

最后,堆叠异常的痕迹。

   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ThrowRequiredMemberMissingException(XmlReaderDelegator xmlReader, Int32 memberIndex, Int32 requiredIndex, XmlDictionaryString[] memberNames)
   at ReadSoapFaultExceptionFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadDataContractValue(DataContract dataContract, XmlReaderDelegator reader)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract& dataContract)
   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns)
   at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
   at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlReader reader)
   at XXX.Program.Main() in C:\Git\XXX\Program.cs:line 161
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

1 个答案:

答案 0 :(得分:1)

您看到异常的原因是元素<faultcode><faultstring>的名称空间与数据协定不一致。

一方面,在您的XML中,根元素位于xmlns:iopbase="http://example.com/foo/"命名空间中,但其子元素不在任何命名空间中,缺少xmlns:前缀。另一方面,通过将[DataContract(Namespace = "http://example.com/foo/")]添加到SoapFaultException,您指定SoapFaultException根元素及其子元素将出现在指定的命名空间中。由于XML元素不在此命名空间中,因此它们不会成功反序列化。

要确认这一点,如果我序列化您的类型实例,设置名称空间前缀xmlns:iopbase="http://example.com/foo/",我会得到:

<iopbase:SoapFaultException xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://example.com/foo/" xmlns:iopbase="http://example.com/foo/">
  <iopbase:faultcode></iopbase:faultcode>
  <iopbase:faultstring>Hello World</iopbase:faultstring>
</iopbase:SoapFaultException>

使用代码

var test = new SoapFaultException { faultcode = "", faultstring = "Hello World" };

var serializer = new DataContractSerializer(typeof(SoapFaultException));
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
    serializer.WriteObject(writer, test);
}
// Add the iopbase prefix for clarity
doc.Root.Add(new XAttribute(XNamespace.Xmlns + "iopbase", "http://example.com/foo/"));
Debug.WriteLine(doc);

要解决此问题,理想情况下,您希望能够独立于整个数据协定命名空间指定数据成员命名空间,但遗憾的是数据协定序列化程序doesn't support that。相反,您有以下选择:

  • 您可以将SoapFaultException拆分为类层次结构,并为层次结构中的每个级别指定不同的命名空间,将最派生的类型放在根元素的命名空间中,如下所示:

    [DataContract(Namespace = "")]
    public abstract class SoapFaultExceptionBase
    {
        [DataMember(IsRequired = true)]
        public string faultcode { get; set; }
    
        [DataMember(IsRequired = true)]
        public string faultstring { get; set; }
    }
    
    [DataContract(Namespace = "http://example.com/foo/")]
    public class SoapFaultException : SoapFaultExceptionBase
    {
    }
    
  • 您可以将SoapFaultException保留在空名称空间中,将construct your DataContractSerializer保留为提供的XML根元素名称和名称空间:

    [DataContract(Namespace = "")]
    public class SoapFaultException
    {
        [DataMember(IsRequired = true)]
        public string faultcode { get; set; }
    
        [DataMember(IsRequired = true)]
        public string faultstring { get; set; }
    }
    

    然后做:

    var serializer = new DataContractSerializer(typeof(SoapFaultException), "SoapFaultException", "http://example.com/foo/");
    var e = (SoapFaultException)serializer.ReadObject(xml.CreateReader());