Reputation: 1107
I'm trying to deserialise a small snippet of XML with the DataContractSerializer:
<iopbase:SoapFaultException xmlns:iopbase=""http://example.com/foo/"">
<faultcode></faultcode>
<faultstring>Hello World</faultstring>
</iopbase:SoapFaultException>
The problem is the DataContractSerializer doesn't 'see' the <faultcode>
and <faultstring>
elements and I get the following Exception:
SerializationException: 'EndElement' 'SoapFaultException' from namespace
'http://example.com/foo/' is not expected. Expecting element 'faultcode'.
Here's a stripped down bit of code to replicate the issue:
[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
And finally, stack trace of the Exception.
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()
Upvotes: 0
Views: 4891
Reputation: 116516
The reason you are seeing the exception is that the namespaces of the elements <faultcode>
and <faultstring>
are inconsistent with the data contract.
On the one hand, in your XML, the root element is in the xmlns:iopbase="http://example.com/foo/"
namespace but its child elements are not in any namespace, lacking the xmlns:
prefix. On the other hand, by adding [DataContract(Namespace = "http://example.com/foo/")]
to SoapFaultException
you are specifying that the SoapFaultException
root element as well as its child elements are to appear in the specified namespace. And since the XML elements are not in this namespace, they are not deserialized successfully.
To confirm this, if I serialize out an instance of your type, setting the namespace prefix xmlns:iopbase="http://example.com/foo/"
, I get:
<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>
Using the code
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);
To solve the problem, ideally you would like to be able to specify data member namespaces independently of the overall data contract namespace, but unfortunately the data contract serializer doesn't support that. Instead, you have the following options:
You could split your SoapFaultException
into a class hierarchy and specify a different namespace for each level in the hierarchy, placing the most derived type in the root element's namespace like so:
[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
{
}
You could leave your SoapFaultException
in the empty namespace, and construct your DataContractSerializer
with a supplied XML root element name and namespace:
[DataContract(Namespace = "")]
public class SoapFaultException
{
[DataMember(IsRequired = true)]
public string faultcode { get; set; }
[DataMember(IsRequired = true)]
public string faultstring { get; set; }
}
And then do:
var serializer = new DataContractSerializer(typeof(SoapFaultException), "SoapFaultException", "http://example.com/foo/");
var e = (SoapFaultException)serializer.ReadObject(xml.CreateReader());
Upvotes: 2