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.
Related
I have a Model populated and I wish to serlise to an xml document.
Due to naming conventions I have to over ride the class names for my XML document,
This is my Model(s):
[Serializable]
[XmlRoot("preferences")]
public class PreferencesModel
{
[XmlIgnore]
public string MessageToUser { get; set; }
[XmlElement(ElementName = "sectiondivider")]
public List<SectionDivider> SectionDivider { get; set; }
}
[Serializable]
[XmlRoot(ElementName = "sectiondivider")]
public class SectionDivider
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlElement("preference")]
public List<PreferenceModel> PreferenceModel { get; set; }
}
[Serializable]
[XmlRoot("preference")]
public class PreferenceModel
{
[XmlAttribute("type")]
public string Type { get; set; }
public string Name { get; set; }
[XmlAttribute("value")]
public string Value { get; set; }
[XmlElement("options")]
public List<Option> Options { get; set; }
}
this is how I serialize:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(ObjectToXmlString(obj, includeNameSpace, includeStartDocument, rootAttribute));
return xDoc;
public static string ObjectToXmlString(Object obj, bool includeNameSpace, bool includeStartDocument, XmlRootAttribute rootAttribute)
{
SpecialXmlWriter stWriter = null;
XmlSerializer xmlSerializer = default(XmlSerializer);
string buffer = null;
try
{
if (rootAttribute == null)
{
xmlSerializer = new XmlSerializer(obj.GetType());
}
else
{
xmlSerializer = new XmlSerializer(obj.GetType(), rootAttribute);
}
MemoryStream memStream = new MemoryStream();
StringWriter writer = new StringWriter();
stWriter = new SpecialXmlWriter(memStream, new UTF8Encoding(false), includeStartDocument);
if (!includeNameSpace)
{
System.Xml.Serialization.XmlSerializerNamespaces xs = new XmlSerializerNamespaces();
//To remove namespace and any other inline
//information tag
xs.Add("", "");
xmlSerializer.Serialize(stWriter, obj, xs);
}
else
{
xmlSerializer.Serialize(stWriter, obj);
}
buffer = Encoding.UTF8.GetString(memStream.ToArray());
}
catch (Exception e)
{
string msg = e.Message;
throw;
}
finally
{
if (stWriter != null)
stWriter.Close();
}
return buffer;
}
I call it like this:
XmlDocument preferencesxml = Codec.ObjectToXml(m.SectionDivider,false,
false, new XmlRootAttribute("preferences"));
My m value is:
and my resulting XML is this:
XmlRootAttribute, as the name suggests, only applies to the root element of the XML being serialised.
You need to use XmlTypeAttribute in this context:
[XmlType("sectiondivider")]`
public class SectionDivider
{
//...
}
As an aside, the [Serializable] attribute is not relevant to XmlSerializer - it can be removed unless you need it for some other purpose.
I want to consume a restful web service with xml. I have to create this template in order to create a valid request:
<WS_IN_GetAccountCredit xmlns="http://schemas.datacontract.org/2004/07/WcfWebService">
<GetAccountCreditParams>
<Password>String content</Password>
<UserName>String content</UserName>
</GetAccountCreditParams>
<WSIdentity>
<WS_PassWord>String content</WS_PassWord>
<WS_UserName>String content</WS_UserName>
</WSIdentity>
</WS_IN_GetAccountCredit>
My serializer method is like this:
XmlSerializerNamespaces xmlNameSpace = new XmlSerializerNamespaces();
xmlNameSpace.Add("", "http://schemas.datacontract.org/2004/07/WcfWebService");
XmlSerializer xmlSerializer = new XmlSerializer(instance.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, instance, xmlNameSpace); ; return textWriter.ToString();
}
My output looks like this:
<?xml version="1.0" encoding="utf-16"?>
<WS_IN_GetAccountCredit xmlns:xmlns="http://schemas.datacontract.org/2004/07/WcfWebService">
<WSIdentity>
<WS_UserName>String content</WS_UserName>
<WS_PassWord>String content</WS_PassWord>
</WSIdentity>
<GetAccountCreditParams>
<UserName>String content</UserName>
<Password>String content</Password>
</GetAccountCreditParams>
</WS_IN_GetAccountCredit>
As you can see, the namespace and xml version is in wrong format. I also found this, this and this article but non of them could solve my problem.
How can create a valid request?
Here we go:
[XmlRoot(ElementName = "WS_IN_GetAccountCredit", Namespace = "http://schemas.datacontract.org/2004/07/WcfWebService")]
public class WS_IN_GetAccountCredit
{
private WS_IN_WebServiceIdentity wsIdentity;
private WS_IN_GetAccountCreditParams getAccountCreditParams;
public WS_IN_WebServiceIdentity WSIdentity { set { this.wsIdentity = value; } get { return this.wsIdentity; } }
public WS_IN_GetAccountCreditParams GetAccountCreditParams
{
set { this.getAccountCreditParams = value; }
get { return this.getAccountCreditParams; }
}
}
public class WS_IN_WebServiceIdentity
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class WS_IN_GetAccountCreditParams
{
public string UserName { get; set; }
public string Password { get; set; }
}
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "http://schemas.datacontract.org/2004/07/WcfWebService");
var ser = new XmlSerializer(typeof(WS_IN_GetAccountCredit));
using (var writer = new StringWriter())
{
ser.Serialize(writer, new WS_IN_GetAccountCredit
{
GetAccountCreditParams = new WS_IN_GetAccountCreditParams { Password = "pass", UserName = "use" },
WSIdentity = new WS_IN_WebServiceIdentity { Password = "pass", UserName = "use" }
},
namespaces);
var xml = writer.ToString();
}
The result is:
<?xml version="1.0" encoding="utf-16"?>
<WS_IN_GetAccountCredit xmlns="http://schemas.datacontract.org/2004/07/WcfWebService">
<WSIdentity>
<UserName>use</UserName>
<Password>pass</Password>
</WSIdentity>
<GetAccountCreditParams>
<UserName>use</UserName>
<Password>pass</Password>
</GetAccountCreditParams>
</WS_IN_GetAccountCredit>
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">
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();
}