I am currently trying to understand the concept of serializing objects to XML with C#. I already got to a point where everything is working fine, but now I got the task to format the XML Output file in such a way, where the elements are side-by-side instead of underneath each other. For example:
This is how it needs to look like
<House>
<Address>
<Street>Some Street</Street><HouseNo>123</HouseNo><State>Texas</State>
</Address>
<House>
But I only manage to get it to look like this:
<House>
<Address>
<Street>Some Street</Street>
<HouseNo>123</HouseNo>
<State>Texas</State>
</Address>
</House>
Is there any way to format the output the way I described in the first example? Any help would be much appreciated.
I already tried to change some XmlWriterSettings, like Indent or NewLineHandling but none have worked so far. I also tried to read the XmlSerializer Code to try and understand what is happening or where to make changes to get to the result I want but that didn't seem to go all well.
You could crate an address class with custom serialization, here is a full working example,
using System;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
public class Program
{
public static void Main()
{
var houseBefore = new House
{
Address = new FlatAddress
{
Street = "SomeStreet",
HouseNo = "123",
State = "Texas"
}
};
var serializer = new XmlSerializer(typeof(House));
string xml;
using (var writer = new StringWriter())
{
serializer.Serialize(writer, houseBefore);
xml = writer.ToString();
}
Console.WriteLine(xml);
House houseAfter;
using (var reader = new StringReader(xml))
{
houseAfter = serializer.Deserialize(reader) as House;
}
Debug.Assert(houseBefore.Address.Street == houseAfter.Address.Street);
Debug.Assert(houseBefore.Address.HouseNo == houseAfter.Address.HouseNo);
Debug.Assert(houseBefore.Address.State == houseAfter.Address.State);
}
}
public sealed class House
{
public FlatAddress Address { get; set; }
}
public sealed class FlatAddress : IXmlSerializable
{
public string Street { set; get; }
public string HouseNo { set; get; }
public string State { set; get; }
public XmlSchema GetSchema()
{
return default;
}
public void ReadXml(XmlReader reader)
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case nameof(Street):
this.Street = reader.ReadElementContentAsString();
break;
case nameof(HouseNo):
this.HouseNo = reader.ReadElementContentAsString();
break;
case nameof(State):
this.State = reader.ReadElementContentAsString();
break;
}
}
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteElementString(nameof(Street), this.Street);
var houseNoXml = new XElement(nameof(HouseNo), this.HouseNo);
writer.WriteRaw(houseNoXml.ToString());
var stateXml = new XElement(nameof(State), this.State);
writer.WriteRaw(stateXml.ToString());
if (writer.Settings.NewLineHandling == NewLineHandling.Replace)
{
writer.WriteRaw(writer.Settings.NewLineChars);
}
if (writer.Settings.Indent)
{
writer.WriteRaw(writer.Settings.IndentChars);
}
}
}
Related
I am using the standard .NET XmlSerializer to deserialize the following xml:
<root>
<Element>
<Grouping1>
<Item1>First</Item1>
<Item2>Second</Item2>
</Grouping1>
<Grouping2>
<Item3>Third</Item3>
</Grouping2>
</Element>
</root>
I would like to serialize it into the following class:
class Element
{
[XmlElement("Item1")]
public string Item1 { get; set; }
[XmlElement("Item2")]
public string Item2 { get; set; }
[XmlElement("Item3")]
public string Item3 { get; set; }
}
Which of course doesn't work, because - for instance - <Item1> isn't located in <Element> but in the logical container <Grouping1>.
The question:
Is there a way of telling the XmlSerializer to look for the Item1 in the <Grouping1> element?
Something downs the lines of [XmlElement("Grouping1.Item1")]
PS: I don't want to create a Grouping1 class (as suggested here) because the groupings are only logical containers and shouldn't have their own class.
Using Xml Linq. A custom serializer would be much more complicated
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication4
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<Element> elements = doc.Descendants("Element").Select(x => new Element()
{
Item1 = (string)x.Descendants("Item1").FirstOrDefault(),
Item2 = (string)x.Descendants("Item2").FirstOrDefault(),
Item3 = (string)x.Descendants("Item3").FirstOrDefault()
}).ToList();
}
}
class Element
{
public string Item1 { get; set; }
public string Item2 { get; set; }
public string Item3 { get; set; }
}
}
Here is what serializer would look like
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.Schema;
namespace ConsoleApplication4
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(Root));
Root root = (Root)serializer.Deserialize(reader);
}
}
[XmlRoot("root")]
public class Root
{
[XmlElement("Element")]
public List<Element> Element { get; set; }
}
public class Element : IXmlSerializable
{
private string Item1 { get; set; }
private string Item2 { get; set; }
private string Item3 { get; set; }
public void WriteXml(XmlWriter writer)
{
XElement element = new XElement("Element", new object[] {
new XElement("Grouping1", new object[] {
new XElement("Item1", Item1),
new XElement("Item2", Item2)
}),
new XElement("Grouping2", new XElement("Item3", Item3))
});
element.WriteTo(writer);
}
public void ReadXml(XmlReader reader)
{
XElement element = (XElement)XElement.ReadFrom(reader);
Item1 = (string)element.Descendants("Item1").FirstOrDefault();
Item2 = (string)element.Descendants("Item2").FirstOrDefault();
Item3 = (string)element.Descendants("Item3").FirstOrDefault();
}
public XmlSchema GetSchema()
{
return (null);
}
}
}
I don't want to create a Grouping1 class...
Not sure whether that's possible using serialization without creating the objects that output this XML.
An alternative to serialization is using the XmlReader to extract the properties in question (Item1, Item2, and Item3) and create a list of Element type, and XmlWriter to generate the whole XML file. Both classes provide fast, non-cached, and forward-only way to read and write XML files.
Assuming your XML file has multiple Element entries like:
<root>
<Element>
<Grouping1>
<Item1>First1</Item1>
<Item2>Second1</Item2>
</Grouping1>
<Grouping2>
<Item3>Third1</Item3>
</Grouping2>
</Element>
<Element>
<Grouping1>
<Item1>First2</Item1>
<Item2>Second2</Item2>
</Grouping1>
<Grouping2>
<Item3>Third2</Item3>
</Grouping2>
</Element>
</root>
... and a class named Element:
//The serializable attribute is not required here...
public class Element
{
public Element() { }
public string Item1 { get; set; }
public string Item2 { get; set; }
public string Item3 { get; set; }
public override string ToString() => $"{Item1}, {Item2}, {Item3}";
}
Create a function to read the file, create and return a list of Element items:
public List<Element> ReadElements(string xmlFile)
{
var elements = new List<Element>();
Element ele = null;
using (var xr = XmlReader.Create(xmlFile))
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element)
{
if (xr.Name == "Element")
ele = new Element();
else if (xr.Name == "Item1")
{
xr.Read();
ele.Item1 = xr.Value;
}
else if (xr.Name == "Item2")
{
xr.Read();
ele.Item2 = xr.Value;
}
else if (xr.Name == "Item3")
{
xr.Read();
ele.Item3 = xr.Value;
}
}
else if (xr.NodeType == XmlNodeType.EndElement)
if (xr.Name == "Element")
elements.Add(ele);
}
return elements;
}
... and a method to write:
public void WriteElements(string xmlFile, IEnumerable<Element> elements)
{
var xmlSet = new XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = true,
WriteEndDocumentOnClose = true,
};
using (var xr = XmlWriter.Create(xmlFile, xmlSet))
{
xr.WriteStartElement("root");
foreach(var ele in elements)
{
xr.WriteStartElement("Element");
xr.WriteStartElement("Grouping1");
xr.WriteStartElement("Item1");
xr.WriteString(ele.Item1);
xr.WriteEndElement();
xr.WriteStartElement("Item2");
xr.WriteString(ele.Item2);
xr.WriteEndElement();
xr.WriteEndElement();
xr.WriteStartElement("Grouping2");
xr.WriteStartElement("Item3");
xr.WriteString(ele.Item3);
xr.WriteEndElement();
xr.WriteEndElement();
xr.WriteEndElement();
}
}
}
A test to read and write the file like:
private void TheCaller()
{
var xmlFile = "XmlFile.xml";
var elements = ReadElements(xmlFile);
elements.ForEach(x => Console.WriteLine(x));
//...
WriteElements(xmlFile, elements);
}
Prints in my output window:
First1, Second1, Third1
First2, Second2, Third2
I am parsing an XML document using the XMLSerialization tool. The sample XML file consists of paragraphs (string) and tables, which are a complex XML type. Tables consist of a series of row, which consists of a series of entry (string)
I need to keep track of the position of each table, relative to each paragraph. Is there a way of catching the position of each table as it is being parsed by the XMLSerialization tool? Or do I need to use a construct like [XMLAnyElement] and parse each paragraph and table sequentially in order to track the table position? I would prefer to avoid that approach, because my real XML files have many levels that would need manual parsing. I have a feeling that I am missing something really obvious here, but I've been scouring SO and trying multiple approaches, but with no straightforward solution.
Here is my basic code:
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace XMLDeserializeTest
{
class Program
{
static void Main(string[] args)
{
string file = Environment.CurrentDirectory + #"\test.xml";
test testClass = Deserialize(file);
}
static test Deserialize(string url)
{
XmlSerializer reader =
new XmlSerializer(typeof(test));
StreamReader stream = new StreamReader(url);
return reader.Deserialize(stream) as test;
}
}
public class test
{
[XmlElement("paragraph")]
public List<string> paragraphs { get; set; }
[XmlElement("table")]
public List<Table> tables { get; set; }
public test()
{
}
}
public class Table
{
[XmlElement("row")]
public List<Row> rows { get; set; }
public int nodeNumber { get; set; } // This is what needs to be tracked
public Table()
{
}
}
public class Row
{
[XmlElement("entry")]
public List<string> entries { get; set; }
public Row()
{
}
}
My sample XML:
<?xml version="1.0" encoding="utf-8" ?>
<test>
<paragraph>Here is some text.</paragraph>
<paragraph>Here is some more text. The table follows this paragraph.</paragraph>
<table>
<row>
<entry>1</entry>
<entry>2</entry>
<entry>3</entry>
</row>
<row>
<entry>4</entry>
<entry>5</entry>
<entry>6</entry>
</row>
</table>
<paragraph>This is the last paragraph. This comes after the table.</paragraph>
</test>
I came up with one solution, using XDocument, but it seems pretty clumsy:
XDocument Xdoc = XDocument.Load(file);
int numParagraphs = 0;
int tableNumber = 0;
foreach(XElement item in Xdoc.Root.Descendants())
{
if (item.Name.LocalName.Equals("paragraph"))
{
numParagraphs++;
}
else if (item.Name.LocalName.Equals("table"))
{
testClass.tables[tableNumber].nodeNumber = numParagraphs;
tableNumber++;
}
}
One option would just be to serialize the paragraph "index" of the table into your XML at the time of serialization. That way you wouldn't have to do anything custom.
However, to do what you are looking for with the XmlSerializer you could handle the deserialization yourself for certain element types using the UnknownElement event. Notice that the XmlElement attributes have been removed from the test class in order for the table and paragraph elements to be handled.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace XMLDeserializeTest
{
class Program
{
static int paragraphCount = 0;
static void Main(string[] args)
{
string file = Environment.CurrentDirectory + #"\test.xml";
paragraphCount = 0;
test testClass = Deserialize(file);
}
static test Deserialize(string url)
{
XmlSerializer serializer = new XmlSerializer(typeof(test));
serializer.UnknownElement += serializer_UnknownElement;
StreamReader stream = new StreamReader(url);
return serializer.Deserialize(stream) as test;
}
static void serializer_UnknownElement(object sender, XmlElementEventArgs e)
{
test t = (test)e.ObjectBeingDeserialized;
if (e.Element.Name == "table")
{
var s = new XmlSerializer(typeof(Table));
var sr = new StringReader(e.Element.OuterXml);
Table newTable = s.Deserialize(sr) as Table;
newTable.nodeNumber = paragraphCount;
t.tables.Add(newTable);
}
else if (e.Element.Name == "paragraph")
{
String paragraphText = e.Element.InnerText;
t.paragraphs.Add(paragraphText);
paragraphCount++;
}
}
}
public class test
{
public List<string> paragraphs { get; set; }
public List<Table> tables { get; set; }
public test()
{
}
}
[Serializable, XmlRoot("table")]
public class Table
{
[XmlElement("row")]
public List<Row> rows { get; set; }
public int nodeNumber { get; set; } // This is what needs to be tracked
public Table()
{
}
}
[Serializable, XmlRoot("row")]
public class Row
{
[XmlElement("entry")]
public List<string> entries { get; set; }
public Row()
{
}
}
}
I try to save and read multiple objects in one XML-File.
The function Serialize is not working with my existing List, but i dont know why. I already tried to compile it but i get an error wich says, that the methode needs an object refference.
Program.cs:
class Program
{
static void Main(string[] args)
{
List<Cocktail> lstCocktails = new List<Cocktail>();
listCocktails.AddRange(new Cocktail[]
{
new Cocktail(1,"Test",true,true,
new Cocktail(1, "Test4", true, true, 0)
});
Serialize(lstCocktails);
}
public void Serialize(List<Cocktail> list)
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Cocktail>));
using (TextWriter writer = new StreamWriter(#"C:\Users\user\Desktop\MapSample\bin\Debug\ListCocktail.xml"))
{
serializer.Serialize(writer, list);
}
}
private void DiserializeFunc()
{
var myDeserializer = new XmlSerializer(typeof(List<Cocktail>));
using (var myFileStream = new FileStream(#"C:\Users\user\Desktop\MapSample\bin\Debug\ListCocktail.xml", FileMode.Open))
{
ListCocktails = (List<Cocktail>)myDeserializer.Deserialize(myFileStream);
}
}
Cocktail.cs:
[Serializable()]
[XmlRoot("locations")]
public class Cocktail
{
[XmlElement("id")]
public int CocktailID { get; set; }
[XmlElement("name")]
public string CocktailName { get; set; }
[XmlElement("alc")]
public bool alcohol { get; set; }
[XmlElement("visible")]
public bool is_visible { get; set; }
[XmlElement("counter")]
public int counter { get; set; }
private XmlSerializer ser;
public Cocktail() {
ser = new XmlSerializer(this.GetType());
}
public Cocktail(int id, string name, bool alc,bool vis,int count)
{
this.CocktailID = id;
this.CocktailName = name;
this.alcohol = alc;
this.is_visible = vis;
this.counter = count;
}
}
}
Ii also think I messed something up with the DiserializeFunc().
You are very close to implementing the Cocktail class correctly, but I think you're confused about how to serialize Lists. Your implementation of a Cocktail object class is completely fine, just get rid of the list related functions.
using System;
using System.Xml.Serialization;
namespace Serialization_Help
{
[Serializable()]
[XmlRoot("locations")]
public class Cocktail
{
[XmlElement("id")]
public int CocktailID { get; set; }
[XmlElement("name")]
public string CocktailName { get; set; }
[XmlElement("alc")]
public bool alcohol { get; set; }
[XmlElement("visible")]
public bool is_visible { get; set; }
[XmlElement("counter")]
public int counter { get; set; }
public Cocktail() {
}
public Cocktail(int id, string name, bool alc, bool vis, int count)
{
this.CocktailID = id;
this.CocktailName = name;
this.alcohol = alc;
this.is_visible = vis;
this.counter = count;
}
}
}
Now in your new function you want to serialize the list directly.
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace Serialization_Help
{
class Program {
static void Main(string[] args) {
List<Cocktail> list = new List<Cocktail> {
new Cocktail(01, "rum and coke", true, true, 5),
new Cocktail(02, "water on the rocks", false, true, 3)
};
Serialize(list);
List<Cocktail> deserialized = DiserializeFunc();
}
public static void Serialize(List<Cocktail> list) {
XmlSerializer serializer = new XmlSerializer(typeof(List<Cocktail>));
using (TextWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + #"\ListCocktail.xml")) serializer.Serialize(writer, list);
}
private static List<Cocktail> DiserializeFunc() {
var myDeserializer = new XmlSerializer(typeof(List<Cocktail>));
using (var myFileStream = new FileStream(Directory.GetCurrentDirectory() + #"\ListCocktail.xml", FileMode.Open)) return (List<Cocktail>)myDeserializer.Deserialize(myFileStream);
}
}
}
Doing so should correctly print out the following .xml output:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCocktail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Cocktail>
<id>1</id>
<name>rum and coke</name>
<alc>true</alc>
<visible>true</visible>
<counter>5</counter>
</Cocktail>
<Cocktail>
<id>2</id>
<name>water on the rocks</name>
<alc>false</alc>
<visible>true</visible>
<counter>3</counter>
</Cocktail>
</ArrayOfCocktail>
Keep in mind that I have not provided implementation of any of the standard safety or null checks for the file. You'll have to check if the file exists yourself by using File.Exists(...) (see here for File.Exists implementation) and implement the correct try and catch cases and what your code will chose to do if it runs into serialization or input/outut errors.
You'd better use ExtendedXmlSerializer to serialize and deserialize.
Instalation
You can install ExtendedXmlSerializer from nuget or run the following command:
Install-Package ExtendedXmlSerializer
Serialization:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var list = new List<Cocktail>();
var xml = serializer.Serialize(list);
Deserialization
var list = serializer.Deserialize<List<Cocktail>>(xml);
Standard XML Serializer in .NET is very limited.
Does not support serialization of class with circular reference or class with interface property,
Does not support Dictionaries,
There is no mechanism for reading the old version of XML,
If you want create custom serializer, your class must inherit from IXmlSerializable. This means that your class will not be a POCO class,
Does not support IoC.
ExtendedXmlSerializer can do this and much more.
ExtendedXmlSerializer support .NET 4.5 or higher and .NET Core. You can integrate it with WebApi and AspCore.
http://rtt.metroinfo.org.nz/RTT/Public/Utility/File.aspx?ContentType=SQLXML&Name=JPPlatform.xml
http://rtt.metroinfo.org.nz/RTT/Public/Utility/File.aspx?Name=JPRoutePositionET.xml&ContentType=SQLXML&PlatformTag=536
Both of those are my sample data which I am pulling down using HttpClient, I want to grab the attributes(?) such as PlatformTag, ETA and the like.
This is using Univeral Apps for Windows 10 mobile and Desktop. But I can't figure out what's going on to do it.
XDocument Document = XDocument.Parse(RESPONSE_CONSTANT);
var Stops = from Stop in Document.Descendants("Platform")
select new
{
Platformtag = (string)Stop.Attribute("PlatformTag"),
Platformno = (string)Stop.Attribute("PlatformNo")};
foreach (var item in Stops)
BusData.Text = item.Platformtag;
}
Is what I currently have, but nothing comes from it, it just sits there like it sees nothing, from here I don't know enough about XML Parsing to find a next step.
Note: Response_Constant contains data like this: http://rtt.metroinfo.org.nz/RTT/Public/Utility/File.aspx?Name=JPRoutePositionET.xml&ContentType=SQLXML&PlatformTag=536
Try following. Had to modify xml to replace ampersand.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.IO;
namespace ConsoleApplication14
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
string xml = File.ReadAllText(FILENAME);
xml = xml.Replace("&", "&");
StringReader sReader = new StringReader(xml);
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
XmlReader reader = XmlReader.Create(sReader);
List<Platform> platforms = new List<Platform>();
while (!reader.EOF)
{
if (reader.Name != "Platform")
{
reader.ReadToFollowing("Platform");
}
if (!reader.EOF)
{
XElement platform = (XElement)XElement.ReadFrom(reader);
platforms.Add(new Platform()
{
tag = (int)platform.Attribute("PlatformTag"),
no = (int?)platform.Attribute("PlatformNo"),
name = (string)platform.Attribute("Name"),
bearingToRoad = (double?)platform.Attribute("BearingToRoad"),
roadName = (string)platform.Attribute("RoadName"),
lat = (double)platform.Element(platform.Name.Namespace + "Position").Attribute("Lat"),
_long = (double)platform.Element(platform.Name.Namespace + "Position").Attribute("Long")
});
}
}
}
}
public class Platform
{
public int tag { get; set; }
public int? no { get; set; }
public string name { get; set; }
public double? bearingToRoad { get; set; }
public string roadName { get; set; }
public double lat { get; set; }
public double _long { get; set; }
}
}
I am using a DataContractSerializer to serialize an object to XML. The main object is SecurityHolding with the namespace "http://personaltrading.test.com/" and contains a property called Amount that's a class with the namespace "http://core.test.com". When I serialize this to XML I get the following:
<ArrayOfSecurityHolding xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<Amount xmlns:d3p1="http://core.test.com/">
<d3p1:Amount>1.05</d3p1:Amount>
<d3p1:CurrencyCode>USD</d3p1:CurrencyCode>
</Amount>
<BrokerageID>0</BrokerageID>
<BrokerageName i:nil="true" />
<RecordID>3681</RecordID>
</SecurityHolding></ArrayOfSecurityHolding>
Is there anyway I can control the d3p1 prefix? Am I doing something wrong or should I be doing something else?
Firstly, the choice of namespace alias should make no difference to a well-formed parser.
But; does it have to be DataContractSerializer? With XmlSerializer, you can use the overload of Serialize that accepts a XmlSerializerNamespaces. This allows you to pick and choose the namespaces and aliases that you use.
Ultimately; DataContractSerializer is not intended to give full xml control; that isn't its aim. If you want strict xml control, XmlSerializer is a better choice, even if it is older (and has some nuances/foibles of its own).
Full example:
using System;
using System.Xml.Serialization;
public class Amount
{
public const string CoreNamespace = "http://core.test.com/";
[XmlElement("Amount", Namespace=CoreNamespace)]
public decimal Value { get; set; }
[XmlElement("CurrencyCode", Namespace = CoreNamespace)]
public string Currency { get; set; }
}
[XmlType("SecurityHolding", Namespace = SecurityHolding.TradingNamespace)]
public class SecurityHolding
{
public const string TradingNamespace = "http://personaltrading.test.com/";
[XmlElement("Amount", Namespace = Amount.CoreNamespace)]
public Amount Amount { get; set; }
public int BrokerageId { get; set; }
public string BrokerageName { get; set; }
public int RecordId { get; set; }
}
static class Program
{
static void Main()
{
var data = new[] {
new SecurityHolding {
Amount = new Amount {
Value = 1.05M,
Currency = "USD"
},
BrokerageId = 0,
BrokerageName = null,
RecordId = 3681
}
};
var ser = new XmlSerializer(data.GetType(),
new XmlRootAttribute("ArrayOfSecurityHolding") { Namespace = SecurityHolding.TradingNamespace});
var ns = new XmlSerializerNamespaces();
ns.Add("foo", Amount.CoreNamespace);
ser.Serialize(Console.Out, data, ns);
}
}
Output:
<ArrayOfSecurityHolding xmlns:foo="http://core.test.com/" xmlns="http://personaltrading.test.com/">
<SecurityHolding>
<foo:Amount>
<foo:Amount>1.05</foo:Amount>
<foo:CurrencyCode>USD</foo:CurrencyCode>
</foo:Amount>
<BrokerageId>0</BrokerageId>
<RecordId>3681</RecordId>
</SecurityHolding>
</ArrayOfSecurityHolding>
I have solved this problem slightly differently to Marc that can be implemented in a base class.
Create a new attribute to define the additional XML namespaces that you will use in your data contract.
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public sealed class NamespaceAttribute : Attribute
{
public NamespaceAttribute()
{
}
public NamespaceAttribute(string prefix, string uri)
{
Prefix = prefix;
Uri = uri;
}
public string Prefix { get; set; }
public string Uri { get; set; }
}
Add the attribute to your data contracts.
[DataContract(Name = "SomeObject", Namespace = "http://schemas.domain.com/namespace/")]
[Namespace(Prefix = "a", Uri = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
[Namespace(Prefix = "wm", Uri = "http://schemas.datacontract.org/2004/07/System.Windows.Media")]
public class SomeObject : SerializableObject
{
private IList<Color> colors;
[DataMember]
[DisplayName("Colors")]
public IList<Colors> Colors
{
get { return colors; }
set { colours = value; }
}
}
Then in your Save method, use reflection to get the attributes and then write them to the file.
public static void Save(SerializableObject o, string filename)
{
using (Stream outputStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
{
if (outputStream == null)
throw new ArgumentNullException("Must have valid output stream");
if (outputStream.CanWrite == false)
throw new ArgumentException("Cannot write to output stream");
object[] attributes;
attributes = o.GetType().GetCustomAttributes(typeof(NamespaceAttribute), true);
XmlWriterSettings writerSettings = new XmlWriterSettings();
writerSettings.Indent = true;
writerSettings.NewLineOnAttributes = true;
using (XmlWriter w = XmlWriter.Create(outputStream, writerSettings))
{
DataContractSerializer s = new DataContractSerializer(o.GetType());
s.WriteStartObject(w, o);
foreach (NamespaceAttribute ns in attributes)
w.WriteAttributeString("xmlns", ns.Prefix, null, ns.Uri);
// content
s.WriteObjectContent(w, o);
s.WriteEndObject(w);
}
}
}
I have struggled with this problem also. The solution I present below is not optimal IMHO but it works. Like Marc Gravell above, I suggest using XmlSerializer.
The trick is to add a field to your class that returns a XmlSerializerNamespaces object. This field must be decorated with a XmlNamespaceDeclarations attribute. In the constructor of your class, add namespaces as shown in the example below. In the xml below note that the root element is prefixed correctly as well as the someString element.
More info on XmlSerializerNamespaces
Schemas reference
[XmlRoot(Namespace="http://STPMonitor.myDomain.com")]
public class CFMessage : IQueueMessage<CFQueueItem>
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns;
[XmlAttribute("schemaLocation", Namespace=System.Xml.Schema.XmlSchema.InstanceNamespace)]
public string schemaLocation = "http://STPMonitor.myDomain.com/schemas/CFMessage.xsd";
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("username")]
public string UserName { get; set; }
[XmlAttribute("somestring", Namespace = "http://someURI.com")]
public string SomeString = "Hello World";
public List<CFQueueItem> QueueItems { get; set; }
public CFMessage()
{
xmlns = new XmlSerializerNamespaces();
xmlns.Add("myDomain", "http://STPMonitor.myDomain.com");
xmlns.Add("xyz", "http://someURI.com");
}
}
<?xml version="1.0" encoding="utf-16"?>
<myDomain:CFMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xyz="http://someURI.com"
xsi:schemaLocation="http://STPMonitor.myDomain.com/schemas/CFMessage.xsd"
xyz:somestring="Hello World" type="JOIN" username="SJ-3-3008-1"
xmlns:myDomain="http://STPMonitor.myDomain.com" />
Add "http://www.w3.org/2001/XMLSchema" namespace by:
private static string DataContractSerialize(object obj)
{
StringWriter sw = new StringWriter();
DataContractSerializer serializer = new DataContractSerializer(obj.GetType());
using (XmlTextWriter xw = new XmlTextWriter(sw))
{
//serializer.WriteObject(xw, obj);
//
// Insert namespace for C# types
serializer.WriteStartObject(xw, obj);
xw.WriteAttributeString("xmlns", "x", null, "http://www.w3.org/2001/XMLSchema");
serializer.WriteObjectContent(xw, obj);
serializer.WriteEndObject(xw);
}
StringBuilder buffer = sw.GetStringBuilder();
return buffer.ToString();
}