Search code examples
c#xmlxmlserializer

How to get an XML node value as string when deserializing


I am sending a XML to a aspnet core web api. The value for the namespace prefix cfdi: is defined in a containing node:

<cfdi:Comprobante>
  <cfdi:Conceptos>
  </cfdi:Conceptos>
  <cfdi:Addenda>
    <bfa2:AddendaBuzonFiscal version="2.0" xmlns:bfa2="http://www.buzonfiscal.com/ns/addenda/bf/2"><bfa2:TipoDocumento nombreCorto="FAC" descripcion="Factura"/><bfa2:CFD totalConLetra="CINCUENTA Y DOS MIL QUINIENTOS OCHENTA Y NUEVE PESOS 64/100 M.N." observaciones="OBSERVACIONES"/><bfa2:Extra atributo="ClaveTransportista" valor="00328"/><bfa2:Extra atributo="NoRelacionPemex" valor="1-2"/>
    <bfa2:Extra atributo="NoConvenio" valor="5"/>
    </bfa2:AddendaBuzonFiscal>
    <Encabezado NumOrden="" NumFacturaOriginal="" FechaDePedido=""/>
    <Envio Calle="" NoExterior="" Colonia="" Localidad="" Municipio="" Estado="" Pais="" CodigoPostal="" NombreEnviar=""/><Detalle OrdenCompraLinea="10" GRNumber="GRN"/><Detalle OrdenCompraLinea="10" GRNumber="GRN"/><Detalle OrdenCompraLinea="10" GRNumber="GRN"/>
  </cfdi:Addenda>
</cfdi:Comprobante>

To deserialize this I made the class Comprobante:

public class Comprobante : IValidatableObject
{
    [Required]
    [XmlArray("Conceptos"), XmlArrayItem(typeof(Concepto), ElementName = "Concepto")]
    public List<Concepto> Conceptos { get; set; }

    public Addenda Addenda { get; set; }
}

Everything is mapped to the class properties but the Addenda node could receive anything -- any number of valid XML nodes -- so I don´t have a class definition. I.e. the Addenda node could contain n nodes that I don’t know about, the information is validated in the recipient end. For example a customer could ask to add a node with a PO number, another could ask for buyer name. Etc.

If I need to get all the Addenda node content as string, how should I declare it in the class?


Solution

  • You can deserialize arbitrary, free-form XML data using XmlSerializer by marking target properties with [XmlAnyElement].

    E.g. you can define your Addenda type as follows:

    [XmlRoot("Comprobante", Namespace = "http://cfdi")]
    public class Comprobante : IValidatableObject
    {
        [Required]
        [XmlArray("Conceptos"), XmlArrayItem(typeof(Concepto), ElementName = "Concepto")]
        public List<Concepto> Conceptos { get; set; }
    
        public Addenda Addenda { get; set; }
    }
    
    public class Addenda
    {
        [XmlAnyElement]
        [XmlText]
        public XmlNode[] Nodes { get; set; }
    }
    

    Sample working .Net fiddle #1.

    Or, you could eliminate the Addenda type completely and replace it with an XmlElement property in the containing type:

    [XmlRoot("Comprobante", Namespace = "http://cfdi")]
    public class Comprobante : IValidatableObject
    {
        [Required]
        [XmlArray("Conceptos"), XmlArrayItem(typeof(Concepto), ElementName = "Concepto")]
        public List<Concepto> Conceptos { get; set; }
    
        [XmlAnyElement("Addenda")]
        public XmlElement Addenda { get; set; }
    }
    

    Sample working .Net fiddle #2.

    Notes:

    • When applied without an element name, [XmlAnyElement] specifies that the member is an array of XmlElement or XmlNode objects which will contain all arbitrary XML data that is not bound to some other member in the containing type.

    • When applied with an element name (and optional namespace), [XmlAnyElement("Addenda")] specifies that the member is a either a single XmlElement object or an array of such objects, and will contain all arbitrary XML elements named <Addenda>. Using this form eliminates the need for the extra Addenda type.

    • Combining [XmlText] with [XmlAnyElement] allows arbitrary mixed content to be deserialized.

    • If you are using .NET Core you may need to nuget System.Xml.XmlDocument.