I have a scenario where I may need to serialize an object as a root element into an XML file or if a file is provided that already has an array of objects I need to serialize the object into the array.
Because the object may be a root I have decorated it with the System.Xml.Serialization.XmlRootAttribute.
When I serialize the object to an XElement I get the root version of the object. When I add the XElement to the array it retains the namespace attributes and can not be deserialized properly.
Here is a portion of the class:
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.8.3928.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:www.agxml.org:schemas:all:4:0", TypeName = "CommodityMovement")]
[System.Xml.Serialization.XmlRootAttribute(ElementName ="CommodityMovement", Namespace="urn:www.agxml.org:schemas:all:4:0", IsNullable=false)]
public partial class CommodityMovementType {
...
}
When I create a file with a list it works just fine.
private static String CreateMessageFile<T>(String filePath, T message)
{
var xmlDoc = new XDocument();
// serialize XML to string to the xmlEntity document
using (var writer = new StringWriter())
{
var entitySerializer = new XmlSerializer(typeof(List<T>));
var list = new List<T>();
list.Add(message);
entitySerializer.Serialize(writer, list);
xmlDoc.Add(XElement.Parse(writer.ToString()));
}
xmlDoc.Save(filePath);
}
This will serialize an array with one message:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CommodityMovement>
...
</CommodityMovement>
</ArrayOfCommodityMovement>
However, I may read that file and add a message to that array.
private static String CreateMessageFile<T>(String filePath, T message)
{
var xmlDoc = new XDocument();
using var sourceStream = File.Open(filePath,FileMode.Open);
xmlDoc.Add(XElement.Load(sourceStream));
using (var writer = new StringWriter())
{
var entitySerializer = new XmlSerializer(entity.DotNetType);
// entitySerializer.Serialize(writer, list);
entitySerializer.Serialize(writer, message);
xmlDoc.Root.Add(XElement.Parse(writer.ToString()));
}
xmlDoc.Save(filePath);
}
This produces the the following XML:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CommodityMovement>
...
</CommodityMovement>
<CommodityMovement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:www.agxml.org:schemas:all:4:0">
...
</CommodityMovement>
</ArrayOfCommodityMovement>
When I attempt to deserialize the ArrayOfCommodityMovement it only deserializes the first CommodityMovement message.
If I open the generated XML file and remove the namespace attributes from the second CommodityMovement element then it will deserialize correctly. Here is the test I am using to define "correctly".
XDocument xDocument = XDocument.Load(filePath);
var descendants = xDocument.Descendants("CommodityMovement");
Assert.Equal(2, descendants.Count());
var entitySerializer = new XmlSerializer(typeof(List<CommodityMovementType>));
var commodityMovementList = entitySerializer.Deserialize(xDocument.CreateReader()) as List<CommodityMovementType>;
Assert.NotEmpty(commodityMovementList);
Assert.Equal(2, commodityMovementList.Count);
So how can I deserialize the object and insert the resulting element into an existing array and make sure the attributes aren't added?
BTW, I need to keep the System.Xml.Serialization.XmlRootAttribute because depending on configuration I need to be able to generate one message per file and the CommodityMovement then becomes a root element.
Note:
This isn't my exact code. It is a simplified example.
Thanks in advance for any help.
I found the solution is to remove the attributes and namespace if this is not the first element in the array. To see if it is the first I check to see if the document root is null:
if (xmlDoc.Root is null)
If it is not null, I serialize the element, remove it's attributes and namespace and add the element to the root element:
else
{
var element = SerializeEntityAsElement(entity, masterData);
// remove attributes and namespace so element can be added to existing array
element.RemoveAttributes();
element.Name = element.Name.LocalName;
xmlDoc.Root.Add(element);
}
Now my test will pass:
XDocument xDocument = XDocument.Load(filePath);
var descendants = xDocument.Descendants("CommodityMovement");
Assert.Equal(2, descendants.Count());
var entitySerializer = new XmlSerializer(typeof(List<CommodityMovementType>));
var commodityMovementList = entitySerializer.Deserialize(xDocument.CreateReader()) as List<CommodityMovementType>;
Assert.NotEmpty(commodityMovementList);
Assert.Equal(2, commodityMovementList.Count);
Related
I have a C# file which was auto generated by using the wsdl.exe utility.
This file contains the "MyObject" class used in the code example below.
I am now trying to Deserialize the raw response of the webservice to the object model manually.
The XML structure looks like this:
<?xml version="1.0">
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope">
<soap:Body>
<ns2:myResponse xmlns:ns2="http://myurl.com">
<return>...</return>
<return>...</return>
<return>...</return>
...
</ns2:myResponse>
</soap:Body>
</soap:Envelope>
The following code works for deserializing the "return" elements to "MyObject":
var file = #"C:\Data\example_response.xml";
var xdoc = XDocument.Load(file);
var myNamespace = "http://myurl.com";
var results = xdoc.Descendants(XName.Get("myResponse", myNamespace)).Elements();
var myObjects = new List<MyObject>();
foreach (var result in results)
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = "return";
xRoot.IsNullable = true;
var serializer = new XmlSerializer(typeof(MyObject), xRoot);
var output = (MyObject)serializer.Deserialize(result.CreateReader());
myObjects.Add(myObject);
}
The problem is the above code is terribly slow. It takes 6 secondes to iterate over 400 "return" elements.
It would probably be advised to run the XmlSerializer Deserialize method outside of the foreach-loop, but I am unable to get this working with the code below:
var file = #"C:\Data\example_response.xml";
var xdoc = XDocument.Load(file);
var myNamespace = "http://myurl.com";
var results = xdoc.Descendants(XName.Get("myResponse", myNamespace)).ElementAt(0).ToString();
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.Namespace = myNamespace;
xRoot.ElementName = "myResponse";
xRoot.IsNullable = true;
var serializer = new XmlSerializer(typeof(List<MyObject>), xRoot);
var output = (List<MyObject>)serializer.Deserialize(new StringReader(results));
The code above is resulting into an empty list (probably because the XmlSerializer does not recognize the "return" element to be a "MyObject", but I don't want to make changes to the generated C# file.
I feel like there's a terribly easy solution for my problem, but I can't seem to figure it out.
I'm fairly new to XML and whilst trying to load a series of XML files in through SSIS I've come up against the 'null' value issue when writing the data to a SQL table.
The XML is being provided by a 3rd party supplier and it can't be changed at their end - it is what it is and most of it works fine - it is only one bit of the document that returns this 'null' value problem.
I've read a few articles and found this is down to the XML needing to be wrapped in another root node Link
So my question is how can I add tags around some current tags? (Shoot me down for the awful wording).
The XML looks something like this:
<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<Child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
<Summary>
<text>1234</text>
</Summary>
</Parent>
And what I need to do is dynamically add tags around the child tags above to get something like this:
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<info_2>
<Child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
</info_2>
<Summary>
<text>1234</text>
</Summary>
</Parent>
The SSIS uses a foreach to loop through all of the XML files. My theory at this stage is to add a script task, take the current file variable and use it to load and edit the XML and then save it back before the data flow task picks it up and extracts the data.
I've manually added some tags to one file and it did eliminate the null value problem - so I'm confident it will work.
The current code in my script task is:
public void Main()
{
//Get the variable from the foreach which contains the current file name.
string filename = Convert.ToString(Dts.Variables["User::File_Name"]);
//Create a new XML document object and then load the current file into it.
XmlDocument doc = new XmlDocument();
doc.Load(filename);
}
But then I have no idea how to add those tags!
Find elements which name starts with 'child'
Create new parent element info_2 populated with copy of the found elements as its content
Remove original elements found in step 1 from the tree
Here is an example :
var raw = #"<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
<Summary>
<text>1234</text>
</Summary>
</Parent>";
var doc = XDocument.Parse(raw);
//step 1
var elements = doc.Root.Elements().Where(o => o.Name.LocalName.StartsWith("child"));
//step 2
var newElement = new XElement("info_2", elements);
doc.Root.Element("info").AddAfterSelf(newElement);
//step 3:
elements.Remove();
//print result
Console.WriteLine(doc.ToString());
Output :
<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<info_2>
<child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
</info_2>
<Summary>
<text>1234</text>
</Summary>
</Parent>
Great solution from har07.
I've edited the names, but here is the code which solved my problem:
public void Main()
{
string filename = Convert.ToString(Dts.Variables["User::File_Name"].Value);
XDocument xml = new XDocument();
var doc = XDocument.Load(filename);
var element1 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("bob"));
var element2 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("mice"));
var element3 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("car"));
var element4 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("dog"));
var element5 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("cat"));
var element6 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("number"));
var element7 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("thing"));
var newElement = new XElement("New_Tag", element1, element2, element3, element4, element5, element6, element7);
doc.Root.Element("Some_Tag").AddBeforeSelf(newElement);
element1.Remove();
element2.Remove();
element3.Remove();
element4.Remove();
element5.Remove();
element6.Remove();
element7.Remove();
doc.Save(filename);
Dts.TaskResult = (int)ScriptResults.Success;
}
I'm trying to write a XML-document programatically.
I need to add <xsd:schema> tag to my document.
Currently I have:
var xmlDoc = new XmlDocument();
var root = xmlDoc.CreateElement("root");
xmlDoc.AppendChild(root);
var xsdSchemaElement = xmlDoc.CreateElement("schema");
xsdSchemaElement.Prefix = "xsd";
xsdSchemaElement.SetAttribute("id", "root");
root.AppendChild(xsdSchemaElement);
However, this renders to:
<root>
<schema id="root" />
</root>
How do I get the tag to be <xsd:schema>?
Already tried var xsdSchemaElement = xmlDoc.CreateElement("xsd:schema"); which simply ignores the xsd:.
Edit #1
Added method
private static XmlSchema GetTheSchema(XmlDocument xmlDoc)
{
var schema = new XmlSchema();
schema.TargetNamespace = "xsd";
return schema;
}
which is called like xmlDoc.Schemas.Add(GetTheSchema(xmlDoc)); but does not generate anything in my target XML.
Using LINQ-to-XML, you can nest XElements and XAttributess in a certain hierarchy to construct an XML document. As for namespace prefix, you can use XNamespace.
Notice that every namespace prefix, such as xsd in your case, has to be declared before it is used, something like xmlns:xsd = "http://www.w3.org/2001/XMLSchema".
XNamespace xsd = "http://www.w3.org/2001/XMLSchema";
var doc =
new XDocument(
//root element
new XElement("root",
//namespace prefix declaration
new XAttribute(XNamespace.Xmlns+"xsd", xsd.ToString()),
//child element xsd:schema
new XElement(xsd + "schema",
//attribute id
new XAttribute("id", "root"))));
Console.WriteLine(doc.ToString());
output :
<root xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:schema id="root" />
</root>
I'm trying to modify an attribute of an XML string using Json in C#. Currently I'm doing the following:
XmlDocument serializedFormXml = new XmlDocument();
serializedFormXml.LoadXml(mySerializedForm);
string formJsonString = JsonConvert.SerializeXmlNode(serializedFormXml, Newtonsoft.Json.Formatting.None, true);
JObject formJsonObj = JObject.Parse(formJsonString);
formJsonObj["#code"] = "myNewValue";
var xml = JsonConvert.DeserializeXmlNode(formJsonObj.ToString()).ToString();
When I do this I get get an exception on the last line:
Unable to cast object of type 'Newtonsoft.Json.Converters.XmlDocumentWrapper' to type 'Newtonsoft.Json.Converters.IXmlElement'
Any ideas what I'm doing wrong and how I can fix modify my form attribute "code"?
This is the XML I'm using:
<Form code="XYZ">
<Info>Data</Info>
.....
Thanks!
That's going to be way, way easier with Linq-to-XML:
var doc = XDocument.Parse(mySerializedForm);
doc.Root.SetAttributeValue(doc.Root.Name.Namespace + "code", "myNewValue");
var xml = doc.ToString();
This drops the XML declaration. If you need the XML declaration included, you can use the following extension method:
public static class XObjectExtensions
{
public static string ToXml(this XDocument xDoc)
{
using (var writer = new StringWriter())
{
xDoc.Save(writer);
return writer.ToString();
}
}
}
And then write:
var xml = doc.ToXml();
If specifically you need to make the encoding string say "UTF-8", use Utf8StringWriter from this answer.
Update
The reason you code fails is that you stripped the XML root element name away when you converted to json by passing true here:
string formJsonString = JsonConvert.SerializeXmlNode(serializedFormXml, Newtonsoft.Json.Formatting.None, true);
Thus you need to add it back when converting back:
var xml = JsonConvert.DeserializeXmlNode(formJsonObj.ToString(), serializedFormXml.DocumentElement.Name).ToString();
I'm trying to write an XML file that will be picked up and parsed by another service. In order for this to happen the XML must be formatted in a very specific way, namely:
<?xml version="1.0"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<Feedback:MfgUnitID></Feedback:MfgUnitID>
<Feedback:MachineId></Feedback:MachineId>
<Feedback:OperationCode></Feedback:OperationCode>
<Feedback:ItemSeqNum></Feedback:ItemSeqNum>
<Feedback:OperDispositionCd></Feedback:OperDispositionCd>
<Feedback:ItemId></Feedback:ItemId>
<Feedback:ParentItemId></Feedback:ParentItemId>
<Feedback:ItemEndSize>1821</Feedback:ItemEndSize>
<Feedback:ItemDispositionCd></Feedback:ItemDispositionCd>
<Feedback:OperStartDate></Feedback:OperStartDate>
<Feedback:OperEndDate></Feedback:OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
with data of course between the innermost elements. Here's the issue though, no matter what I do, I can't get any of the C# classes to keep the semicolons on the innermost nodes. As far as I know these need to stay, so is there a way in C# to force it to format the nodes this way? I've tried all of the create methods that I could find in the XMLDocument class. I can get the outer nodes formatted fine, but the inner ones just keep creating problems.
Edit, sorry here's the code that makes the inner nodes.
private void AppendFile(string filename, string[] headers, Dictionary<string, string> values)
{
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlNode node = doc.GetElementsByTagName(headers[headers.Length - 2]).Item(0);
string[] hPieces = headers[headers.Length - 1].Split(':');
XmlElement appendee = doc.CreateElement(hPieces[0].Trim(), hPieces[1].Trim(), hPieces[0].Trim());
node.AppendChild(appendee);
foreach (KeyValuePair<string, string> pair in values)
{
string[] ePieces = pair.Key.Split(':');
//XmlElement element = doc.CreateElement(ePieces[0].Trim(), string.Empty, ePieces[1].Trim());
//XmlText text = doc.CreateTextNode(pair.Value);
XmlNode innerNode = doc.CreateNode(XmlNodeType.Element, ePieces[1].Trim(), ePieces[0].Trim());
node.InnerText = pair.Value;
// element.AppendChild(text);
appendee.AppendChild(innerNode);
}
doc.Save(filename);
}
The data for the inner nodes comes in as key value pairs in the dictionary. Where the keys contain the intended name.
Edit2: This is what the file output looks like
<?xml version="1.0" encoding="utf-8"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<MfgUnitID></MfgUnitID>
<MachineId></MachineId>
<OperationCode</OperationCode>
<ItemSeqNum></ItemSeqNum>
<OperDispositionCd></OperDispositionCd>
<ItemId></ItemId>
<ParentItemId></ParentItemId>
<ItemEndSize></ItemEndSize>
<ItemDispositionCd></ItemDispositionCd>
<OperStartDate></OperStartDate>
<OperEndDate></OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
You can accompish this easily with XLinq:
using System.Xml.Linq;
XNamespace ns1 = "Feedbacks";
XNamespace ns2 = "Feedback";
var doc = new XElement("Feedbacks",
new XAttribute(XNamespace.Xmlns+"Feedbacks", ns1));
doc.Add(new XElement(ns1 + "Elements",
new XElement(ns2 + "Feedback",
new XAttribute(XNamespace.Xmlns+"Feedback", ns2),
new XElement(ns2 + "Unit"))));
Gives
<Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:Feedback xmlns:Feedback="Feedback">
<Feedback:Unit />
</Feedback:Feedback>
</Feedbacks:Elements>
</Feedbacks>
Although I believe that your own output should be valid XML, relying on the parent namespcae.