Background :
I am working on this integration solution where I have to implement a WCF service (BizTalk) with a custom fault contract. The fault message should look like follows,
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode>002</faultcode>
<faultstring>some fault</faultstring>
<detail>
<wor:invalidMessageFault xmlns:wor="somenamespace">
<faultCode>002</faultCode>
<faultReference>Client WebService</faultReference>
<faultText>some fault</faultText>
</wor:invalidMessageFault>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
So far: I have created a custom fault inspector to intercept the fault message and send back the fault.
Problem : I need to construct the <detail> section of the fault message and as far as I figured out only way to do it is to dump raw xml into it, because in the fault message construction,
var faultException = new FaultException<RawXMLString>(raw, fault.faultText, new FaultCode(fault.faultCode)).CreateMessageFault();
It only accept an object (which can be serialized) as detail, and I tried different things but I could construct the same message with object.
Finally I thought of using a custom serialization to generate the exact message,
public class RawXMLString : IXmlSerializable
{
private string xmlTemplate = #"<wor:invalidMessageFault xmlns:wor="some namespace">
<faultCode>{0}</faultCode>
<faultReference>Client WebService</faultReference>
<faultText>{1}</faultText>
</wor:invalidMessageFault>";
public string FaultCode { get; set; }
public string FaultText { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteRaw(string.Format(xmlTemplate,FaultCode,FaultText));
}
}
Now there is another issue, because I don't want <RawXMLString> tag, is there any way to force serializer to ignore the root?
Does this fit the bill?
[XmlRoot(Namespace = "somenamespace",
ElementName = "invalidMessageFault")]
public class InvalidMessageFault : IXmlSerializable
{
public string FaultCode { get; set; }
public string FaultText { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("faultCode", FaultCode);
writer.WriteElementString("faultReference", "Client WebService");
writer.WriteElementString("faultText", FaultText);
}
}
Change the code to:
[XmlRoot("wor:invalidMessageFault xmlns:wor=\"some namespace\"")]
public class RawXMLString : IXmlSerializable
{
private string xmlTemplate = #"<faultCode>{0}</faultCode>
<faultReference>Client WebService</faultReference>
<faultText>{1}</faultText>";
public string FaultCode { get; set; }
public string FaultText { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteRaw(string.Format(xmlTemplate,FaultCode,FaultText));
}
}
Related
I am facing a challenge deserializing an XML to appropriate type. Request your help.
I have two xml files. oldXML.xml and newXML.xml below respectively
<?xml version="1.0"?>
<root>
<elementOne>101</elementOne>
<elementTwo>10</elementTwo>
</root>
And
<?xml version="1.0"?>
<root>
<elementOne>101</elementOne>
<elementTwo>10</elementTwo>
<elementThree>10</elementThree>
</root>
newXML.xml has an additional attribute "elementThree"
I have written 3 classes to desirialize the XMLs into
public abstract class ResponseBase
{
public abstract void PrintResult();
}
public class OldXML : ResponseBase
{
[XmlElement("elementOne")]
public string ElementOne { get; set; }
[XmlElement("elementTwo")]
public string ElementTwo { get; set; }
public override void PrintResult()
{
Console.WriteLine("Result is of type 'OldXML': {0}, {1}", ElementOne, ElementTwo);
}
}
public class NewXML : ResponseBase
{
[XmlElement("elementOne")]
public string ElementOne { get; set; }
[XmlElement("elementTwo")]
public string ElementTwo { get; set; }
[XmlElement("elementThree")]
public string ElementThree { get; set; }
public override void PrintResult()
{
Console.WriteLine("Result is of type 'NewXML': {0}, {1}, {2}", ElementOne, ElementTwo, ElementThree);
}
}
And I want to deserialize them as below
ResponseBase result1= MethodToDeserializeToBeWritten(File.ReadAllText("oldXML.json"))
ResponseBase result2= MethodToDeserializeToBeWritten(File.ReadAllText("newXML.json"))
result1.PrintResult()
result2.PrintResult()
When I Invoke PrintResult method, at the mercy of polymorphism in OOPS, child class implementation should be invoked (Not working, throws an error that abstract class cannot be instantiated). Please note that these XMLs are just examples and the code should work for any such XMLs.
Also, the XML is received from a client and hence we cannot change the XML.
The reason for doing this is, in future, we might get a new XML with new attribute say "elementFour". For that, we will be adding a new class and not touching the existing implementation.
Thanks in advance.
You need to create a generic MethodToDeserializeToBeWritten method.
public class XML
{
public static T Deserialize<T>(string input)
{
var serializer = new XmlSerializer(typeof(T));
using (TextReader textReader = new StringReader(input))
{
return (T)serializer.Deserialize(textReader);
}
}
}
public abstract class ResponseBase
{
public abstract void PrintResult();
}
[XmlRoot("root")]
public class OldXML : ResponseBase
{
[XmlElement("elementOne")]
public string ElementOne { get; set; }
[XmlElement("elementTwo")]
public string ElementTwo { get; set; }
public override void PrintResult()
{
Console.WriteLine("Result is of type 'OldXML': {0}, {1}", ElementOne, ElementTwo);
}
}
[XmlRoot("root")]
public class NewXML : ResponseBase
{
[XmlElement("elementOne")]
public string ElementOne { get; set; }
[XmlElement("elementTwo")]
public string ElementTwo { get; set; }
[XmlElement("elementThree")]
public string ElementThree { get; set; }
public override void PrintResult()
{
Console.WriteLine("Result is of type 'NewXML': {0}, {1}, {2}", ElementOne, ElementTwo, ElementThree);
}
}
class Program
{
static void Main(string[] args)
{
string rootFilePath = #"C:\test\";
ResponseBase result1 = MethodToDeserializeToBeWritten<OldXML>(File.ReadAllText($"{rootFilePath}oldXML.xml"));
ResponseBase result2 = MethodToDeserializeToBeWritten<NewXML>(File.ReadAllText($"{rootFilePath}newXML.xml"));
result1.PrintResult();
result2.PrintResult();
}
static ResponseBase MethodToDeserializeToBeWritten<T>(string fileContent) where T : ResponseBase
{
return XML.Deserialize<T>(fileContent);
}
}
OUTPUT:
Result is of type 'OldXML': 101, 10
Result is of type 'NewXML': 101, 10, 10
Or you can get rid off MethodToDeserializeToBeWritten method and simplify your code as follows:
ResponseBase result1 = XML.Deserialize<OldXML>(File.ReadAllText($"{rootFilePath}oldXML.xml"));
ResponseBase result2 = XML.Deserialize<NewXML>(File.ReadAllText($"{rootFilePath}newXML.xml"));
Also note that you need to mark OldXML and NewXML with a XmlRoot attribute. Otherwise, you get an exception.
I have xml like this:
<data>
<audit_values>
<audit_value>
<channel>2</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
<audit_value>
<channel>1</channel>
<week>
<mo_th>6,501698000000</mo_th>
<fr>8,414278000000</fr>
<sa>9,292674000000</sa>
<sun>8,551982000000</sun>
<holid>7,164605000000</holid>
</week>
</audit_value>
</audit_values>
</data>
And I need to deserialize it to class. But the problem is, that week will be change in future(it will be contains more elements, and name of them I dont know)
Data:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
AuditValue:
[XmlRoot("audit_value")]
public class AuditValue
{
[XmlElement("week", typeof(TVR))]
public Week Week;
}
Week:
[XmlRoot("week")]
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.Read();
var sub = reader.ReadSubtree();
do
{
if (sub.NodeType == XmlNodeType.Element)
{
string name = sub.Name;
string val = sub.ReadElementContentAsString();
Values.Add(name, Convert.ToDouble(val));
}
} while (sub.Read());
}
public void WriteXml(XmlWriter writer)
{
}
}
But after deserialization I have only one element with only one recored in dictionary Values. What I'm doing wrong?
GitHub:
https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead
I tweaked your ReadXml method based on #Matthew Whited's idea in the comments section and the following method does the job:
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
As a side note you only need XmlRoot on the actual root element not on every class so I removed it from AuditValue and Week. Also I don't know what TVR is. It didn't compile with "typeof(TVR)" so I removed it as well.
For the sake of completeness here's my version of the classes:
[XmlRoot("data")]
public class Data
{
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
}
public class AuditValue
{
[XmlElement("week")]
public Week Week;
}
public class Week : IXmlSerializable
{
public Dictionary<string, double> Values = new Dictionary<string, double>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Values = XElement.Parse(reader.ReadOuterXml())
.Elements()
.ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}
public void WriteXml(XmlWriter writer)
{
}
}
You should consider using the DataContractSerializer instead of XmlSerializer and implement the IExtensibleDataObject interface on your DataContract class.
Implementing IExtensibleDataObject allows the DataContract class to persist unknown information in the ExtensionData field, which prevents it from being lost if the XML being deserialized contains unknown elements, and then is re-serialized and saved.
Your Audit class would look something like this:
[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
[DataMember(Name = "channel")]
public int Channel { get; set; }
[DataMember(Name = "week")]
public Week Week { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
[DataMember(Name = "mo_th")]
public Decimal MondayThroughThursday { get; set; }
public ExtensionDataObject ExtensionData { get; set; }
}
You'll still need to update your code to deserialize the additional elements as POCOs, but at least the underlying data will be persisted until you get around to it.
Try this. XmlElement eliminates a lay of tags which you don't have. You have an extra 's' at end of values.
From
[XmlArray("audit_values")]
[XmlArrayItem("audit_value", IsNullable = true)]
public AuditValue[] AuditValues { get; set; }
To
[XmlElement("audit_value")]
public AuditValue[] AuditValues { get; set; }
The error was in ReadXml method, now I changed it to this:
public void ReadXml(XmlReader reader)
{
reader.Read();
do
{
if (!reader.IsEmptyElement)
{
var name = reader.Name;
var val = Convert.ToDouble(reader.ReadElementContentAsString());
Values.Add(name, val);
}
else
{
reader.Skip();
}
} while (reader.Name != "week");
if (reader.NodeType == XmlNodeType.EndElement)
{
reader.ReadEndElement();
}
}
And I works fine. This Solution without using XElement and Linq, #Volkan Paksoy offered method with XElement which is easier to understand
When I try to deserialize from MyConfig.xml I get an out of memory exception at
System.Net.IPAddress.InternalParse(String ipString, Boolean tryParse)
System.Net.IPAddress.Parse(String ipString)
MyNamespace.IPRange.ReadXml(XmlReader reader)
IPRange.cs
public class IPRange : IXmlSerializable
{
public IPRange () { }
public IPAddress StartIP { get; set; }
public IPAddress EndIP { get; set; }
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
this.StartIP = IPAddress.Parse(reader.GetAttribute("StartIP"));
this.EndIP = IPAddress.Parse(reader.GetAttribute("EndIP"));
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("StartIP", this.StartIP.ToString());
writer.WriteAttributeString("EndIP", this.EndIP.ToString());
}
}
MyConfig.cs
public class MyConfig
{
[XmlArrayItem("IPRange")]
public List<IPRange> DMZ { get; set; }
}
MyConfig.xml
<?xml version="1.0" encoding="utf-8" ?>
<MyConfig>
<DMZ>
<IPRange StartIP="{some start ip}" EndIP="{some end ip}" />
<IPRange StartIP="{some start ip}" EndIP="{some end ip}" />
</DMZ>
</MyConfig>
I don't know what I'm doing wrong.
Please help me with this problem.
Thanks!
I've fixed it by writing reader.Read() at the end of the function...
public void ReadXml(XmlReader reader)
{
this.StartIP = IPAddress.Parse(reader.GetAttribute("StartIP"));
this.EndIP = IPAddress.Parse(reader.GetAttribute("EndIP"));
reader.Read();
}
I have a XML file:
<Hand cards="C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4">
</Hand>
I define a class
[Serializable()]
[XmlRoot("Hand")]
public class Hand
{
[XmlAttribute("cards")]
public List<string> Cards{get;set;}
}
How to deserialize a XML to object in this case? Hand object result must have Cards = {C5,SQ,DQ,H8,C9,H7,S9,D5,DA,CJ,S6,HK,D4}.
You cannot.
What you can do is to create a property which will do this conversion in its getter/setter
[XmlIgnore]
public List<string> CardList { get; private set; }
[XmlAttribute("cards")]
public string Cards {
get { return String.Join(",", CardList); }
set { CardList = value.Split(",").ToList(); }
}
You can do this with the help of IXmlSerializable. Read more about it on MSDN.
This way
[Serializable()]
[XmlRoot("Hand")]
public class Hand : IXmlSerializable {
[XmlAttribute("cards")]
public List<string> Cards { get; set; }
public System.Xml.Schema.XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) {
this.Cards = new List<string>(reader.GetAttribute("cards").Split(','));
}
public void WriteXml(XmlWriter writer) {
writer.WriteAttributeString("cards",
string.Join(",",
this.Cards != null ? this.Cards : new List<string>()));
}
}
Hope this helps you.
I have an xml file looking somewhat like this:
<xml>
<A>value</A>
<B>value</B>
<listitems>
<item>
<C>value</C>
<D>value</D>
</item>
</listitems>
</xml>
And I have a two objects representing this xml:
class XmlObject
{
public string A { get; set; }
public string B { get; set; }
List<Item> listitems { get; set; }
}
class Item : IXmlSerializable
{
public string C { get; set; }
public string D { get; set; }
//Implemented IXmlSerializeable read/write
public void ReadXml(System.Xml.XmlReader reader)
{
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("C", this.C);
writer.WriteElementString("D", this.D);
}
}
I use the XmlSerializer to serialize/deserialize the XmlObject to file.
The problem is that when I implemented the custom IXmlSerializable functions on my "sub-object" Item I always only get one item(the first) in my XmlObject.listitems collection when deserializing the file.
If I remove the : IXmlSerializable everything works as expected.
What do I do wrong?
Edit: I have have implemented IXmlSerializable.GetSchema and I need to use IXmlSerializable on my "child-object" for doing some custom value transformation.
Modify your code like this:
public void ReadXml(System.Xml.XmlReader reader)
{
reader.Read();
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
reader.Read();
}
First you skip the start of the Item node, read the two strings, then read past the end node so the reader is at the correct place. This will read all nodes in the array.
You need to pay attention when modifying xml yourself :)
You don't need to use IXmlSerializable. But if you want you should implement GetShema() method. After some modification code that works looks like that:
[XmlRoot("XmlObject")]
public class XmlObject
{
[XmlElement("A")]
public string A { get; set; }
[XmlElement("B")]
public string B { get; set; }
[XmlElement("listitems")]
public List<Item> listitems { get; set; }
}
public class Item : IXmlSerializable
{
[XmlElement("C")]
public string C { get; set; }
[XmlElement("D")]
public string D { get; set; }
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(System.Xml.XmlReader reader)
{
this.C = reader.ReadElementString();
this.D = reader.ReadElementString();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("C", this.C);
writer.WriteElementString("D", this.D);
}
#endregion
}
Results for 2 items in itemlist will look like that:
<?xml version="1.0" encoding="utf-8"?>
<XmlObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<A>value</A>
<B>value</B>
<listitems>
<C>value0</C>
<D>value0</D>
</listitems>
<listitems>
<C>value1</C>
<D>value1</D>
</listitems>
</XmlObject>