How to add namespaces to XML before serialization - c#

I do have a problem with serialization of a namespaces. As my code show below my logic fills the structure of my dictionary, serializes it and puts into a string variable. I use this variable to load into XMLDocument and after that I do add the namespaces. But since they are added after the serialization process the namespaces are set in UTF8? Should I add namespaces before serialization? If yes, how can I do it properly?
///Dictionary
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace TestXML
{
[Serializable]
[XmlRoot(ElementName = "Root")]
public class XMLSchema: Serialization
{
[XmlElement(ElementName = "Element1")]
public XMLElement1 Element1 { get; set; }
[XmlElement(ElementName = "Element2")]
public XMLElement2 Element2 { get; set; }
}
}
///Serialization class
public class Utf8StringWriter : StringWriter
{
// Use UTF8 encoding
public override Encoding Encoding
{
get { return new UTF8Encoding(false); }
}
}
public string Serialize()
{
var xmlserializer = new XmlSerializer(this.GetType());
var Utf8StringWriter = new Utf8StringWriter();
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
using (var writer = XmlWriter.Create(Utf8StringWriter))
{
xmlserializer.Serialize(writer, this, xns);
return Utf8StringWriter.ToString();
}
}
///create xml
str xml;
[...] my logic to add data into elements
XmlDocument doc = new XmlDocument();
xml = XMLSchema.Serialize();
doc.LoadXml(xml);
XmlElement root = doc.getNamedElement("Root");
root.SetAttribute("xmlns:etd", "http://google.com");
root.SetAttribute("xmlns:xsi", "http://google.com");
root.SetAttribute("xmlns", "http://google.com");
doc.AppendChild(root);
doc.Save(path);
Edit. Adding provided sample.
<?xml version="1.0" encoding="UTF-8"?> <XMLSample xmlns="http://crd.gov.pl/wzor/" xmlns:xsi="http://www.w3.org/2001/" xmlns:etd="http://crd.gov.pl/xml/schematy/">

So to give you an example (based on your sample):
[XmlRoot("XMLSample", Namespace = "http://crd.gov.pl/wzor/")]
public class XmlSample
{
[XmlElement]
public string Element1 { get; set; }
[XmlElement(Namespace = "http://crd.gov.pl/xml/schematy/")]
public string Element2 { get; set; }
}
Here, the root has the namespace http://crd.gov.pl/wzor/. Element1 inherits that namespace (as none is specified). Element2 has the namespace http://crd.gov.pl/xml/schematy/.
When this is serialised, the serialiser will use the root namespace as the default, so there's no need to explicitly set this. You can set the others to use the prefixes as defined in your sample:
var xsn = new XmlSerializerNamespaces();
xsn.Add("xsi", "http://www.w3.org/2001/");
xsn.Add("etd", "http://crd.gov.pl/xml/schematy/");
You can see this fiddle for a demo, the output is:
<?xml version="1.0" encoding="utf-8"?>
<XMLSample xmlns:xsi="http://www.w3.org/2001/" xmlns:etd="http://crd.gov.pl/xml/schematy/" xmlns="http://crd.gov.pl/wzor/">
<Element1>foo</Element1>
<etd:Element2>bar</etd:Element2>
</XMLSample>
Note that Element2 uses the prefix configured for its namespace.

Related

Can't Deserialize xml using DataContractSerializer

I can't deserialize this XML to an object, I don't know what is wrong with this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<ProcessOneWayEvent xmlns="http://schemas.microsoft.com/sharepoint/remoteapp/">
<properties xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<CultureLCID>1033</CultureLCID>
</properties>
</ProcessOneWayEvent>
</s:Body>
</s:Envelope>
Here is a ready sample for this, is there any workaround, because I can't modify in the request, so is there any thing wrong with my models? And is there any solution to deserialize the XML without using XmlSerializer
https://dotnetfiddle.net/RfQMSD
Body and SPRemoteEventProperties need to be in the "http://schemas.microsoft.com/sharepoint/remoteapp/" namespace:
[DataContract(Name = "Body", Namespace = "http://schemas.microsoft.com/sharepoint/remoteapp/")]
public class Body
{
[DataMember(Name = "ProcessOneWayEvent")]
public ProcessOneWayEvent ProcessOneWayEvent;
}
[DataContract(Name = "properties", Namespace = "http://schemas.microsoft.com/sharepoint/remoteapp/")]
public class SPRemoteEventProperties
{
[DataMember(Name = "CultureLCID") ]
public int CultureLCID { get; set; }
}
The DataContractAttribute.Namespace controls the namespace that the data contract object's data member elements are serialized to, as well as the namespace of the root element when the data contract object is the root element. Since the element <ProcessOneWayEvent xmlns="http://schemas.microsoft.com/sharepoint/remoteapp/"> declares a new default XML namespace, the element itself, as well as its children, are in this namespace. Thus, the containing data contract object Body must set its data member namespace accordingly. As for <properties xmlns:i="http://www.w3.org/2001/XMLSchema-instance">, the i: namespace is not a default namespace, so no elements actually get assigned to this namespace and the SPRemoteEventProperties type should not not assign itself to it.
Fixed fiddle here.
I don't immediately see why it doesn't work, but one alternative is to use System.Xml.Serialization.XmlSerializer.
Start by creating the appropriate Envelope classes by copying the XML into a new class (Edit → Paste Special → Paste XML as Classes)
Then deserialize using this as an example:
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(request);
using (var stream = new MemoryStream(byteArray))
{
var serializer = new XmlSerializer(typeof(Envelope));
Envelope response = (Envelope)serializer.Deserialize(stream);
Console.WriteLine(JsonConvert.SerializeObject(response));
}
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
StringReader sReader = new StringReader(xml);
XmlReader reader = XmlReader.Create(sReader);
XmlSerializer serializer = new XmlSerializer(typeof(Envelope));
Envelope envelope = (Envelope)serializer.Deserialize(reader);
}
}
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public class Envelope
{
[XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")]
public Body Body { get; set; }
}
public class Body
{
[XmlElement(ElementName = "ProcessOneWayEvent", Namespace = "http://schemas.microsoft.com/sharepoint/remoteapp/")]
public ProcessOneWayEvent ProcessOneWayEvent { get; set; }
}
public class ProcessOneWayEvent
{
public Properties properties { get; set; }
}
public class Properties
{
public string CultureLCID { get; set; }
}
}
Using Xml Linq
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
XDocument doc = XDocument.Parse(xml);
string CultureLCID = (string)doc.Descendants().Where(x => x.Name.LocalName == "CultureLCID").FirstOrDefault();
}
}
}

How to create a class which is to be de-serialized from XML

How to create a C# class which must be used to deserialize the XML as given below
<?xml version="1.0" encoding="utf-8"?>
<XML>
<StatusCode>-2</StatusCode>
<Warnings />
<Errors>
<Error> Debtor #2 Invalid Postal Code</Error>
<Error>Invalid lien term</Error>
</Errors>
</XML>
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication110
{
class Program
{
const string INPUT_FILENAME = #"c:\temp\test.xml";
const string OUTPUT_FILENAME = #"c:\temp\test1.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(INPUT_FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(XML));
XML xml = (XML)serializer.Deserialize(reader);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(OUTPUT_FILENAME, settings);
serializer.Serialize(writer, xml);
}
}
public class XML
{
public int StatusCode { get; set; }
public string Warnings { get; set; }
[XmlArray("Errors")]
[XmlArrayItem("Error")]
public List<string> errors { get; set; }
}
}
To create classes based on XML, copy the xml to the clipboard, then in Visual Studio 2017, choose the menu option: Edit/Paste Special/Paste XML as classes.
Your Class should look like:
public class ErrorClass
{
struct Error
{
public String message;
}
struct Warning
{
public String message;
}
int StatusCode;
List<Error> Errors;
List<Warning> Warnings;
}
Error and Warning struct could contain more items that are not used in the example you posted.

How set xmlns prefix in deserialization class for the root element

I used xmltocsharp site to create the class helper to deserialize an specific XML, but is not working, and the problem is in the root element. This is the root element (RESP_HDR and RESP_BODY were collapsed):
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
+ <SII:RESP_HDR>
+ <SII:RESP_BODY>
</SII:RESPUESTA>
And this is the root element class generated by xmltocsharp site:
[XmlRoot(ElementName = "RESPUESTA", Namespace = "http://www.sii.cl/XMLSchema")]
public class RESPUESTA
{
[XmlElement(ElementName = "RESP_HDR", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_HDR RESP_HDR { get; set; }
[XmlElement(ElementName = "RESP_BODY", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_BODY RESP_BODY { get; set; }
[XmlAttribute(AttributeName = "SII", Namespace = "http://www.w3.org/2000/xmlns/")]
public string SII { get; set; }
}
The issue is that the class fails to deserialize a XML like the showed before, but success with this:
<?xml version="1.0" encoding="UTF-8"?>
<RESPUESTA xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SII="http://www.sii.cl/XMLSchema" xmlns="http://www.sii.cl/XMLSchema">
+ <SII:RESP_HDR>
+ <SII:RESP_BODY>
</RESPUESTA>
The difference is in the namespaces, even if a create the object and serialize it, this will be the result. So, what should be changed in the class to make it work with the original XML?
UPDATE:
Looking closer I found the really issue, is in the root element still, but I notice the missing xmlns prefix in the root tag, how can I set it in the helper class?
EDIT:
This is an XML sample from the service response:
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
<SII:RESP_HDR>
<SII:ESTADO>0</SII:ESTADO>
<SII:GLOSA/>
</SII:RESP_HDR>
<SII:RESP_BODY>
<DATOS_CONSULTA>
<RUT>80182144-3</RUT>
<TIPO_CONSULTA>DEUDOR</TIPO_CONSULTA>
<DESDE_DDMMAAAA>01042017</DESDE_DDMMAAAA>
<HASTA_DDMMAAAA>01052017</HASTA_DDMMAAAA>
</DATOS_CONSULTA>
<CESION>
<VENDEDOR>11455447-9</VENDEDOR>
<ESTADO_CESION>Cesion Vigente</ESTADO_CESION>
<DEUDOR>80182144-3</DEUDOR>
<MAIL_DEUDOR/>
<TIPO_DOC>33</TIPO_DOC>
<NOMBRE_DOC>Factura Electronica</NOMBRE_DOC>
<FOLIO_DOC>107</FOLIO_DOC>
<FCH_EMIS_DTE>2017-04-04</FCH_EMIS_DTE>
<MNT_TOTAL>3324860</MNT_TOTAL>
<CEDENTE>11455447-9</CEDENTE>
<RZ_CEDENTE>JHON DOE</RZ_CEDENTE>
<MAIL_CEDENTE>jjdoe#gmail.com</MAIL_CEDENTE>
<CESIONARIO>762327129-7</CESIONARIO>
<RZ_CESIONARIO>capital sa</RZ_CESIONARIO>
<MAIL_CESIONARIO>xcap#capital.com</MAIL_CESIONARIO>
<FCH_CESION>2017-04-05 13:15</FCH_CESION>
<MNT_CESION>3324860</MNT_CESION>
<FCH_VENCIMIENTO>2017-06-04</FCH_VENCIMIENTO>
</CESION>
<CESION>
<VENDEDOR>11455447-9</VENDEDOR>
<ESTADO_CESION>Cesion Vigente</ESTADO_CESION>
<DEUDOR>80182144-3</DEUDOR>
<MAIL_DEUDOR/>
<TIPO_DOC>33</TIPO_DOC>
<NOMBRE_DOC>Factura Electronica</NOMBRE_DOC>
<FOLIO_DOC>34</FOLIO_DOC>
<FCH_EMIS_DTE>2017-03-01</FCH_EMIS_DTE>
<MNT_TOTAL>1725500</MNT_TOTAL>
<CEDENTE>11455447-9</CEDENTE>
<RZ_CEDENTE>JOE DOE</RZ_CEDENTE>
<MAIL_CEDENTE>jd#gmail.com</MAIL_CEDENTE>
<CESIONARIO>762327129-7</CESIONARIO>
<RZ_CESIONARIO>Capital S.A.</RZ_CESIONARIO>
<MAIL_CESIONARIO>jcap#capital.com</MAIL_CESIONARIO>
<FCH_CESION>2017-04-05 17:27</FCH_CESION>
<MNT_CESION>1725500</MNT_CESION>
<FCH_VENCIMIENTO>2017-03-01</FCH_VENCIMIENTO>
</CESION>
</SII:RESP_BODY>
</SII:RESPUESTA>
So far the only way that I can make it work is with a really ugly solution, this should not be considered as an answer to the problem!!.
//Query service to obtain XML response
string xmlResponse = siiClient.QueryDocuments(documentsRequest);
//Replace RESPUESTA tags in the XML response, remove the prefix SII
var replacedXML = xmlResponse .Replace("SII:RESPUESTA", "RESPUESTA" );
//Load XML string into XmlDocument
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(replacedXML);
//Add missing namespaces
xDoc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
xDoc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
xDoc.DocumentElement.SetAttribute("xmlns", "http://www.sii.cl/XMLSchema");
//Now deserialization will work
var documentResponse = xDoc.ParseXML<RESPUESTA>();
The ideal solution will take the XML Response and deserialize it directly without any preprocessing like:
//Query service to obtain XML response
string xmlResponse = siiClient.QueryDocuments(documentsRequest);
//ParseXML is an extension method, it can handle an string or an XmlDocument
var documentResponse = xmlResponse.ParseXML<RESPUESTA>();
Note: ParseXML is based on #Damian's answer
I took your xml:
<?xml version="1.0" encoding="UTF-8"?>
<SII:RESPUESTA xmlns:SII="http://www.sii.cl/XMLSchema">
<SII:RESP_HDR/>
<SII:RESP_BODY/>
</SII:RESPUESTA>
I took you class:
[XmlRoot(ElementName = "RESPUESTA", Namespace = "http://www.sii.cl/XMLSchema")]
public class RESPUESTA
{
[XmlElement(ElementName = "RESP_HDR", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_HDR RESP_HDR { get; set; }
[XmlElement(ElementName = "RESP_BODY", Namespace = "http://www.sii.cl/XMLSchema")]
public RESP_BODY RESP_BODY { get; set; }
[XmlAttribute(AttributeName = "SII", Namespace = "http://www.w3.org/2000/xmlns/")]
public string SII { get; set; }
}
public class RESP_HDR { }
public class RESP_BODY { }
Just added two empty class stub.
Try System.Xml.Serialization.XmlSerializer:
RESPUESTA respuesta;
var xs = new XmlSerializer(typeof(RESPUESTA));
using (var fs = new FileStream("test.xml", FileMode.Open))
respuesta = (RESPUESTA)xs.Deserialize(fs);
It works! I don't understand how and why it does not work for you.

Float XML Namespaces to Root?

I have XML within a C# program that looks something like this:
<el1 xmlns="http://URI1">
<el2 xmlns:namespace2="http://URI2"></el2>
<el3>
<el4 xmlns:namespace3="http://URI3"></el4>
</el3>
</el1>
For cleanliness, I would like to move all the namespace declarations to the root element. I cannot change the export that produces this XML, so a solution needs to work on the sample as shown above. What is a good way to accomplish this?
This example is stripped down for brevity, but assume that there are further child elements that actually use these prefixes. These are irrelevant for this question as all the namespace prefix declarations are unique and my goal is only to move them higher in the tree.
I've reviewed the MSDN documentation for XML but there doesn't seem to be a simple way to manipulate namespaces like this. One of the solutions I've tried is interacting with the XML as an XElement and collecting the namespaces based on XAttribute.IsNamespaceDeclaration, replacing each element with its local name and finally creating a new root element with the collected list of namespace XAttributes. That line of experimentation caused a bunch of errors about redefining prefixes, though, and I'm not sure if I'm moving in the right direction or not.
You need to add a prefix
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
El1 el1 = new El1()
{
el2 = new El2()
{
el3 = new El3() {
el4 = new El4() {
}
}
}
};
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("u4", "http://URI4");
ns.Add("u3", "http://URI3");
ns.Add("u2", "http://URI2");
ns.Add("", "http://URI1");
XmlSerializer serializer = new XmlSerializer(typeof(El1));
StreamWriter writer = new StreamWriter(FILENAME);
serializer.Serialize(writer, el1, ns);
writer.Flush();
writer.Close();
writer.Dispose();
}
}
[XmlRoot("el1", Namespace = "http://URI1")]
public class El1
{
[XmlElement("el2", Namespace = "http://URI2")]
public El2 el2 { get; set; }
}
[XmlRoot("el2")]
public class El2
{
[XmlElement("el3", Namespace = "http://URI3")]
public El3 el3 { get; set; }
}
[XmlRoot("el3", Namespace = "http://URI3")]
public class El3
{
[XmlElement("el4", Namespace = "http://URI4")]
public El4 el4 { get; set; }
}
[XmlRoot("el4", Namespace = "http://URI1")]
public class El4
{
}
}
​
You can locate all the xmls attributes using the namespace xpath axis. Add those attributes to the root element. Finally write out the xml using NamespaceHandling.OmitDuplicates, which will leave the namespace declarations on the root and remove them from all the other elements.
var xml = new XmlDocument();
xml.Load("XMLFile1.xml");
// Find all xmlns: attributes
var attributes = xml.DocumentElement.SelectNodes("//namespace::*");
// Add xmlns: attributes to the root
foreach (XmlAttribute attribute in attributes)
xml.DocumentElement.SetAttribute(attribute.Name, attribute.Value);
// Write out results, ignoring duplicate xmlns: attributes
var settings = new XmlWriterSettings();
settings.NamespaceHandling = NamespaceHandling.OmitDuplicates;
settings.Indent = true;
using (var writer = XmlWriter.Create("XMLFile2.xml", settings))
{
xml.Save(writer);
}

xmlserializernamespaces add namespace to a specific element

I have an object and I would like to serialize it. I would like to add the namespaces to a specific element of the xml document. I have created several .xsd files from 1 default xml. I use XmlSerializer.
The namespace should be described in the <sos:element. That is what I want:
<env:root
xmls:env ="httpenv"
xmlns:sos="httpsos">
<env:body>
<sos:element
xmlns:abc="" <--------------my namespaces are located in <sos:element
...
if I use something like
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("abc", "httpabc");
ns.add....
StringWriter stringWriter = new StringWriter();
serializer.Serialize(stringWriter, ObjectToSerialize, ns);
I will get the following
<env:root
xmls:env ="httpenv"
xmlns:sos="httpsos"
xmlns:abc="" <-------------I do not want it here; I want it in <sos:element
<env:body>
<sos:element>
...
Is there a way to specify where (in which element) I would like to have my namespaces declared or are they all declared in the root element?
From XML perspective, it doesn't matter where the XML namespace is defined. If you need the XML namespace declaration at a particular place, there's probably something wrong with the component that parses the XML.
Well, anyway, this is what I came up with:
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace XMLNamespaceChangeSerialization
{
internal class Program
{
private static void Main(string[] args)
{
var serialize = Serialize();
Console.WriteLine(serialize);
Console.ReadLine();
}
private static string Serialize()
{
var ns = new XmlSerializerNamespaces();
ns.Add("env", "httpenv");
// Don't add it here, otherwise it will be defined at the root element
// ns.Add("sos", "httpsos");
var stringWriter = new StringWriter();
var serializer = new XmlSerializer(typeof (RootClass), "httpenv");
serializer.Serialize(stringWriter, new RootClass(), ns);
return stringWriter.ToString();
}
}
[Serializable]
[XmlRoot(ElementName = "root")]
public class RootClass
{
[XmlElement(ElementName = "body", Namespace = "httpenv")]
public BodyClass body = new BodyClass();
}
[Serializable]
public class BodyClass
{
[XmlElement( ElementName = "element", Namespace = "httpsos")]
public SOSClass element = new SOSClass();
}
[Serializable]
public class SOSClass
{
// This will be used by XML serializer to determine the namespaces
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces(
new[] { new XmlQualifiedName("sos", "httpsos"), });
}
}

Categories