简体   繁体   中英

XmlSerializer and xsi: deserialization

I'm having a hard time trying to deserialize this chunk of XML code corresponding to a WCF SOAP service FAULT detail section because of the xsi:type="p:OUTPUT-HEADER" attribute:

<p:OUTPUT-HEADER xsi:type="p:OUTPUT-HEADER" xmlns:p="http://aaa.bbb.ccc/v2" xmlns:ns0="http://aaa.bbb.ccc/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <FAULT>
    <p:COD-ERROR>2951</p:COD-ERROR>
    <p:COD-SEV>8</p:COD-SEV>
    <p:MSG-ERROR>Error message</p:MSG-ERROR>
  </FAULT>
  <CNL-OUT>xxx</CNL-OUT>
</p:OUTPUT-HEADER>

These are the classes I'm using:

[XmlInclude(typeof(OutputHeader))]
public abstract class FaultDetail
{
    [XmlElement(ElementName = "FAULT", Namespace = "")]
    public Fault FaultSection{ get; set; }

    [XmlElement(ElementName = "CNL-OUT", Namespace = "")]
    public string ClnOut{ get; set; }
}

[XmlRoot(ElementName = "OUTPUT-HEADER", Namespace = "http://aaa.bbb.ccc/v2")]
public class OutputHeader : FaultDetail
{
}

public class Fault
{
    [XmlElement(ElementName = "COD-ERROR")]
    public int CodigoError { get; set; }

    [XmlElement(ElementName = "COD-SEV")]
    public int Severidad { get; set; }

    [XmlElement(ElementName = "MSG-ERROR")]
    public string Mensaje { get; set; }

}

The XmlSerializer:

XmlSerializer x = new XmlSerializer(typeof(OutputHeader));

And the error I'm getting when calling the deserialize method:

"The specified type was not recognized: name ='OUTPUT-HEADER', namespace=' http://aaa.bbb.ccc/v2 ', at <OUTPUT-HEADER xmlns=' http://aaa.bbb.ccc/v2 '>."

Is it possible to decorate the classes to deserialize this XML correctly? Any thoughts are greatly appreciated, thanks!

Rather than XmlSerializer , it seems you must use DataContractSerializer to deserialize this XML. This serializer is the default serializer for WCF so you would simply need to remove the code where you specify use of XmlSerializer .

Design your types as follows:

[DataContract(Namespace = "")]
public abstract class OutputHeaderBase
{
    [DataMember(Name = "FAULT", Order = 1)]
    public Fault FaultSection { get; set; }

    [DataMember(Name = "CNL-OUT", Order = 2)]
    public string ClnOut { get; set; }
}

[DataContract(Name = "OUTPUT-HEADER", Namespace = "http://aaa.bbb.ccc/v2")]
public class OutputHeader : OutputHeaderBase
{
}

[DataContract(Name = "FAULT", Namespace = "http://aaa.bbb.ccc/v2")]
public class Fault
{
    [DataMember(Name = "COD-ERROR", Order = 1)]
    public int CodigoError { get; set; }

    [DataMember(Name = "COD-SEV", Order = 2)]
    public int Severidad { get; set; }

    [DataMember(Name = "MSG-ERROR", Order = 3)]
    public string Mensaje { get; set; }
}

Then declare your operation contract as returning (or accepting) an object of type OutputHeader ( not OutputHeaderBase ).

Finally switch back to data contract serialization by removing [XmlSerializerFormat] from your service and/or operation contract, and you should be all set. For details of switching see Using the XmlSerializer Class

(Note also the properties of Fault need to be placed into the correct namespace.)

Why does this work ?

The "xsi:type" attribute is a w3c standard attribute that allows an element to explicitly assert its type. Both XmlSerializer and DataContractSerializer use this attribute to convey actual type information when serializing a polymorphic type. However, the following element:

<p:OUTPUT-HEADER 
    xsi:type="p:OUTPUT-HEADER" 
    xmlns:p="http://aaa.bbb.ccc/v2" 
    xmlns:ns0="http://aaa.bbb.ccc/v2" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
</p:OUTPUT-HEADER>

Has base type OUTPUT-HEADER in namespace http://aaa.bbb.ccc/v2 and subtype OUTPUT-HEADER in namespace http://aaa.bbb.ccc/v2 -- ie the type and subtype information are identical and so the xsi:type attribute is redundant.

But, if it's redundant, it should be harmless, right? You could design a type hierarchy for XmlSerializer as follows:

[XmlRoot(ElementName = "OUTPUT-HEADER", Namespace = "http://aaa.bbb.ccc/v2")]
[XmlInclude(typeof(OutputHeaderSubclass))] // Artificial subtype to trigger handling of the `xsi:type` attribute.
[XmlInclude(typeof(OutputHeader))]
public class OutputHeader
{
    [XmlElement(ElementName = "FAULT", Namespace = "")]
    public Fault FaultSection { get; set; }

    [XmlElement(ElementName = "CNL-OUT", Namespace = "")]
    public string ClnOut { get; set; }
}

[XmlRoot(ElementName = "OUTPUT-HEADER-SUBCLASS", Namespace = "http://aaa.bbb.ccc/v2")]
public class OutputHeaderSubclass : OutputHeader
{
}

Then deserializing to OutputHeader might well work. Unfortunately, it doesn't. XmlSerializer throws an exception for the redundant attribute rather than handling it. Conversely DataContractSerializer does not, so that's the one to use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM