I want to create custom xml serialization by implementing IXmlSerializable.
I've got this test class that implements IXmlSerializable interface:
[Serializable]
public class Employee : IXmlSerializable
{
public Employee()
{
Name = "Vyacheslav";
Age = 23;
}
public string Name{get; set;}
public int Age { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
this.Name = reader["Name"].ToString();
this.Age = Int32.Parse(reader["Age"].ToString());
}
public void WriteXml(XmlWriter writer)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.ConformanceLevel = ConformanceLevel.Fragment;
XmlWriter newWriter = XmlWriter.Create(writer, settings);
newWriter.WriteAttributeString("Name", this.Name);
newWriter.WriteAttributeString("Age", this.Age.ToString());
}
}
What I want to do is to omit xml declaration. For that I create proper instance of XmlWriterSettings and pass it as second parameter to create new XmlWriter.
But when I debug this piece of code, I see that newWriter.Settings.OmitXmlDeclaration is set to false and serialized data contains tag. What am I doing wrong?
The actual serialization looks like this:
var me = new Employee();
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
TextWriter writer = new StreamWriter(#"D:\file.txt");
serializer.Serialize(writer, me);
writer.Close();
And the second question is - if I want to serialize type Employee that has cutom type ContactInfo at field to be serialized, do I need to implement IXmlSerializable on ContactInfo too?
The writer-settings is a function of the outermost writer; you should be applying that to the code that creates the file, i.e.
using(var file = File.Create("file.txt"))
using(var writer = XmlWriter.Create(file, settings))
{
serializer.Serialize(writer, me);
}
additionally, then, you don't need to implement IXmlSerializable. You cannot do this at the inner level - it is too late.
For example:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
public class Employee
{
[XmlAttribute] public string Name { get; set; }
[XmlAttribute] public int Age { get; set; }
}
class Program
{
static void Main()
{
var settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
var me = new Employee {
Name = "Vyacheslav", Age = 23
};
var serializer = new XmlSerializer(typeof (Employee));
using (var file = File.Create("file.txt"))
using (var writer = XmlWriter.Create(file, settings))
{
serializer.Serialize(writer, me);
}
}
}
and if you don't want the extra namespaces, then:
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(writer, me, ns);
which generates the file:
<Employee Name="Vyacheslav" Age="23" />
Related
I have a class that I need to serialize to XML with a specific format for a WCF service I'm using.
The class structure is this:
[XmlRoot(Namespace="http://schemas.datacontract.org/2004/07/XXX.IT.Messaging.Common.Entities")]
public class PushBroadcastingParams
{
[XmlElement]
public string appId { get; set; }
[XmlElement]
public string message { get; set; }
[XmlArray(Namespace = "http://schemas.datacontract.org/2004/07/XXX.IT.Messaging.Common.Entities.KMS.Requests")]
public List<CustomKeyValuePair> customData { get; set; }
}
[XmlRoot(Namespace = "http://schemas.datacontract.org/2004/07/XXX.IT.Messaging.Common.Entities")]
public class PushNotificationParams : PushBroadcastingParams
{
[XmlArray(Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
public string[] subscribersIDs { get; set; }
}
This is my serialization code:
public static string SerializeXML<T>(T obj, XmlSerializerNamespaces namespaces = null)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", "");
using (StringWriter sw = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { OmitXmlDeclaration = true }))
{
new XmlSerializer(typeof(T)).Serialize(writer, obj, ns);
return sw.ToString();
}
}
}
I need the XML output to be exactly like this:
<PushNotificationParams xmlns="http://schemas.datacontract.org/2004/07/XXX.IT.Messaging.Common.Entities" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<appId>123</appId>
<customData xmlns:orb="http://schemas.datacontract.org/2004/07/XXX.IT.Messaging.Common.Entities.KMS.Requests">
<orb:CustomKeyValuePair>
<orb:Key>HEADER</orb:Key>
<orb:Value>FOCUS</orb:Value>
</orb:CustomKeyValuePair>
<orb:CustomKeyValuePair>
<orb:Key>TITLE</orb:Key>
<orb:Value>title of message</orb:Value>
</orb:CustomKeyValuePair>
<orb:CustomKeyValuePair>
<orb:Key>DESC</orb:Key>
<orb:Value>desc of message</orb:Value>
</orb:CustomKeyValuePair>
<orb:CustomKeyValuePair>
<orb:Key>MESSAGE_TYPE</orb:Key>
<orb:Value>POD</orb:Value>
</orb:CustomKeyValuePair>
</customData>
<message>test</message>
<subscribersIDs xmlns:orb="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<orb:string>USER-NAME</orb:string>
</subscribersIDs>
</PushNotificationParams>
But I can't get it to be with the name space and the prefix exectly like this and I can't get rid of the xmlns:xsd namespace.
I've created my own XmlTextWriter from another example, so that I can remove namespaces.
public class NoNamespaceXmlWriter : XmlTextWriter
{
//Provide as many contructors as you need
public NoNamespaceXmlWriter(System.IO.TextWriter output)
: base(output) { Formatting = System.Xml.Formatting.Indented; }
public override void WriteStartDocument() {}
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement("", localName, "");
}
}
When I use this it works OK but for some reason it skips the <xml ... > </xml> tags. WriteStartDocument() is never called.
I can't specify settings properly since they have a private setter and usually you use the static method XmlWriter.Create which I cannot override. I tried overriding the settings themselves, but no avail:
public override XmlWriterSettings Settings
{
get { return new XmlWriterSettings() {OmitXmlDeclaration = false}; }
}
my code to serialize is:
using (StringWriter textWriter = new StringWriter())
{
using (NoNamespaceXmlWriter xmlWriter = new NoNamespaceXmlWriter(textWriter))
{
xs.Serialize(xmlWriter, p);
}
xml = textWriter.ToString();
}
Any idea how I can get the xml tag to appear, or why it's disappearing?
If the goal here is to remove the namespaces from the serialized XML, I'd suggest an easier way: You can specify XmlSerializerNamespaces with empty namespace as illustrade in the sample application below
class Program
{
static void Main(string[] args)
{
Player player = new Player() { Id = 102, FirstName = "Danny", LastName = "TopScorer", AverageGoalsPerGame = 3.5, TotalGoalsScored = 150 };
XmlSerializer serializer = new XmlSerializer(typeof(Player));
XmlWriterSettings settings = new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, Encoding = Encoding.UTF8 };
StringBuilder output = new StringBuilder();
XmlWriter writer = XmlWriter.Create(output, settings);
XmlSerializerNamespaces xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
serializer.Serialize(writer, player, xns);
Console.WriteLine(output.ToString());
Console.ReadLine();
}
}
public class Player
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int TotalGoalsScored { get; set; }
public double AverageGoalsPerGame { get; set; }
}
It will output:
<Player>
<Id>102</Id>
<FirstName>Danny</FirstName>
<LastName>TopScorer</LastName>
<TotalGoalsScored>150</TotalGoalsScored>
<AverageGoalsPerGame>3.5</AverageGoalsPerGame>
</Player>
Otherwise the Player element would look like this with the defaults:
<Player xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://
www.w3.org/2001/XMLSchema">
Please, is any convenience way to avoid namespace attributes for nested member Request when serializing ResponseMessageEnvelope class? Result xml will not be used for deserialization.
[XmlInclude(typeof(AuthenticateRequest))]
[XmlRoot("REQUEST",Namespace="")]
public abstract class BaseRequest
{
...
}
[XmlRoot("REQUEST", Namespace = "")]
public class AuthenticateRequest : BaseRequest
{
...
}
[XmlRoot("EXTSYSTEM", Namespace="")]
public class ResponseMessageEnvelope
{
public ResponseMessageEnvelope(BaseRequest request, BaseReponse response)
{
Request = request;
Response = response;
}
[XmlElement("REQUEST", Namespace = "")]
public Request.BaseRequest Request
{
get;
set;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
using (XmlWriter sWriter = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true ,Indent = true }))
{
XmlSerializer serializer = new XmlSerializer(typeof(ResponseMessageEnvelope));
serializer.Serialize(sWriter, this, ns);
return sb.ToString();
}
}
}
I tried empty namespace for XmlRoot attribute on base and derived class, also for XmlElement on member but without success. Thanks for advice.
<EXTSYSTEM>
<REQUEST xmlns:p2="http://www.w3.org/2001/XMLSchema-instance" p2:type="AuthenticateRequest">
...
</REQUEST>
....
</EXTSYSTEM>
This I would like to have:
<EXTSYSTEM>
<REQUEST>
...
</REQUEST>
....
</EXTSYSTEM>
The base class property must be decorated with the know types to avoid those declarations in the output. Change the request attribute inside ResponseMessageEnvelope to
[XmlElement("REQUEST")]
[XmlElement(Type = typeof(AuthenticateRequest))]
public Request.BaseRequest Request
{
get;
set;
}
Also Namespace = "" is not needed at the element/root level
Finally I solve problem by XmlAttributeOverrides:
[Serializable()]
public abstract class BaseRequest
{
...
}
[XmlRoot("EXTSYSTEM", Namespace="")]
public class ResponseMessageEnvelope
{
[XmlElement("REQUEST")]
public Request.BaseRequest Request
{
get;
set;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
XmlAttributes attrs = new XmlAttributes();
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "REQUEST";
attr.Type = this.Request.GetType();
attr.Namespace = "";
attrs.XmlElements.Add(attr);
attrOverrides.Add(typeof(ResponseMessageEnvelope), "Request", attrs);
using (XmlWriter sWriter = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true ,Indent = true }))
{
XmlSerializer serializer = new XmlSerializer(typeof(ResponseMessageEnvelope), attrOverrides);
serializer.Serialize(sWriter, this, ns);
return sb.ToString();
}
}
I need to generate the following XML, but have thus far been unsuccessful in injecting my custom header data:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>
How can i generate the above XML, from within C#?
Currently we have :
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
StringWriter sww = new StringWriter();
XmlWriter writer = XmlWriter.Create(sww);
serializer.Serialize(writer, employee);
Employee Class:
public class Employee
{
public string personnel_id { get; set; }
public string dob { get; set; }
}
Making some reasonable guesses about what your Employee class looks like, you need to do the following:
Decorate your Employee class with [XmlRoot("MT_Get_Name_Req", Namespace = "http://hsd.sd.com")]. This will give your XML root element the correct name and namespace.
While the root is in the http://hsd.sd.com namespace, its child elements are not in any namespace. Thus their namespace will need to be overridden using an XmlElement attribute.
Setting the XmlRoot namespace will use the specified namespace as the default namespace. If you must use a prefixed namespace, you need to pass a XmlSerializerNamespaces with all desired namespaces to XmlSerializer.Serialize().
Thus:
[XmlRoot("MT_Get_Name_Req", Namespace = Employee.XmlNamespace)]
public class Employee
{
public const string XmlNamespace = "http://hsd.sd.com";
public const string XmlNamespacePrefix = "ns0";
const string xsi = "http://www.w3.org/2001/XMLSchema-instance";
const string xsd = "http://www.w3.org/2001/XMLSchema";
public static XmlSerializerNamespaces XmlSerializerNamespaces
{
get
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add(XmlNamespacePrefix, XmlNamespace);
// namespaces.Add("xsi", xsi); Uncomment to add standard namespaces.
// namespaces.Add("xsd", xsd);
return namespaces;
}
}
[XmlElement("personnel_id", Namespace="")]
public string personnel_id { get; set; }
[XmlElement("dob", Namespace = "")]
public string dob { get; set; }
}
And use it like
var xml = XmlSerializationHelper.GetXml(employee, Employee.XmlSerializerNamespaces, Encoding.UTF8);
Using the extension methods:
public static class XmlSerializationHelper
{
public sealed class StringWriterWithEncoding : StringWriter
{
private readonly Encoding encoding;
public StringWriterWithEncoding(Encoding encoding)
{
this.encoding = encoding;
}
public override Encoding Encoding
{
get { return encoding; }
}
}
public static string GetXml<T>(T obj, XmlSerializerNamespaces ns, Encoding encoding)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), ns, encoding);
}
public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns, Encoding encoding)
{
using (var textWriter = new StringWriterWithEncoding(encoding))
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // For cosmetic purposes.
settings.IndentChars = " "; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
if (ns != null)
serializer.Serialize(xmlWriter, obj, ns);
else
serializer.Serialize(xmlWriter, obj);
}
return textWriter.ToString();
}
}
}
To get
<?xml version="1.0" encoding="utf-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>
I did a small example for you. I included an XmlRoot Attribute to the Employee.class and added a namespace object when serializing the employee.
see Add prefixes and namespaces to xml Serialization
and SO Question How to set xml prefixes
Employee.cs
[XmlRoot("MT_Get_Name_Req", Namespace = "http://hsd.sd.com")]
public class Employee
{
[XmlElement(Namespace="")]
public string personnel_id { get; set; }
[XmlElement(Namespace = "")]
public string dob { get; set; }
}
demoFile.cs
private static void customXmlSerialization()
{
Employee employee = new Employee() { personnel_id = "1202", dob = "19470906" };
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("ns0", "http://hsd.sd.com");
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
string path = #"e:\temp\data.xml";
XmlTextWriter writer = new XmlTextWriter(path, Encoding.UTF8);
serializer.Serialize(writer, employee,ns);
}
output
<?xml version="1.0" encoding="utf-8"?>
<ns0:MT_Get_Name_Req xmlns:ns0="http://hsd.sd.com">
<personnel_id>1202</personnel_id>
<dob>19470906</dob>
</ns0:MT_Get_Name_Req>
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();
}