DataContractSerializer:SerializationException,'EndElement' 不是预期的

DataContractSerializer: SerializationException, 'EndElement' is not expected

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

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

问题是 DataContractSerializer 没有 'see' <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()

您看到异常的原因是元素 <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 拆分为 class 层次结构,并为层次结构中的每个级别指定不同的名称空间,将派生程度最高的类型放在根元素的名称空间中,例如所以:

    [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 保留在空命名空间中,并使用提供的 XML 根元素名称和命名空间 construct your DataContractSerializer

    [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());