I am trying to deserialize an xml to an object to use. We have created templates and would like to keep the xml the same standard if possible. The problem I am trying to figure out is how to look within a standard node in the xml and all subnodes are the same object type, just with different node names.
For example:
<Account>
<AccountNumber>12345</AccountNumber>
<Balance>12.52</Balance>
<LateFee>0</LateFee>
</Account>
The Account level is always within the template, but everything below that is variable. Is there a way to deserialize all nodes within the Account level to be the same object?
Public Class AccountNode
{
Public String Name { get; set; }
Public String Value { get; set; }
}
Based on my research, it appears, they have to have a standard naming schema and then you can have an attribute to assign to the Name value. I just haven't been able to confirm that. If someone has a link that I haven't been able to find, or is knowledgeable and can confirm whether or not this is a possibility, I would like to know.
EDIT:
I have a much larger xml than listed above, so I'm trying to see how I can deserialize it.
<AccountNumber>
<KeyWord Name="Customer Account" isRegex="False" errorAllowance="10" LookFor="Customer Account">
<Rectangle>
<Left>200</Left>
<Bottom>350</Bottom>
<Right>600</Right>
<Top>690</Top>
</Rectangle>
<Relations KwName="Charges">
<Relation>above.0</Relation>
</Relations>
</KeyWord>
<Capture DataType="String" FindIfKeywordMissing="false">
<Rectangle>
<Left>200</Left>
<Bottom>350</Bottom>
<Right>600</Right>
<Top>690</Top>
</Rectangle>
<Relations anchorName="ChargeSection">
<Relation>rightOf.0</Relation>
<Relation>below.-20</Relation>
<Relation>above.20</Relation>
<Relation>width.150</Relation>
</Relations>
<Regex>Customer account\s+(\S+)</Regex>
</Capture>
</AccountNumber>
So with this one, I assume it is similar, but basically the Account Number node is the variable one and everything above and below it is standard.
You could use a surrogate [XmlAnyElement] public XElement[] AccountNodesXml property in your Account class to manually convert your AccountNode objects from and to XML nodes. Marking the property with XmlAnyElement ensures that the elements will taken verbatim from the XML:
public class Account
{
public Account() { this.AccountNodes = new List<AccountNode>(); }
[XmlIgnore]
public List<AccountNode> AccountNodes { get; set; }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[XmlAnyElement]
public XElement[] AccountNodesXml
{
get
{
if (AccountNodes == null)
return null;
return AccountNodes.Select(a => new XElement((XName)a.Name, a.Value)).ToArray();
}
set
{
if (value != null)
AccountNodes = value.Select(e => new AccountNode { Name = e.Name.LocalName, Value = (string)e }).ToList();
}
}
}
Sample fiddle which successfully deserialized and re-serializes the following XML:
<Account>
<AccountNumber>12345</AccountNumber>
<Balance>12.52</Balance>
<LateFee>0</LateFee>
</Account>
Related
I want to Deserialize two different kind of Xml elements into one List as I want to preserve the order
<Step name="Login to Account" >
<Action name="LoginToWebsite" type="Login" />
<NewAction name="EnterTextInTextBox" type="SendKeysToTextBox" extraAttribute="Testvalue" />
</Step>
currently I am using the following to get the two actions into one list but I have to change the element name to NewAction (like above) instead of Action for the second one
[XmlRoot(ElementName = "Step")]
public class WorkflowStep
{
[XmlAnyElement("Action")]
public XmlElement[] Actions
{
get;
set;
}
}
As the XmlAnyElement is bound to "Action" Element name, how can I change to support two different Element Names but need to be Deserialized into one array
What you want is
[XmlRoot(ElementName = "Step")]
public class WorkflowStep
{
[XmlAnyElement("Action")]
[XmlAnyElement("NewAction")]
public XmlElement[] Actions
{
get;
set;
}
}
I have a DTO and a Domain project in my solution and an MVC front end with web api to expose data.
I have the web api controller set up and the action is getting my DTO object back from the DataService. That's all great, however, I want xml to be returned and I want some of the values to be in xml attributes e.g.
<root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lookups>
<lookup category="General" field="Alert Type" value="Lack of Transparency" entityid="2273"/>
<lookup category="General" field="Alert Type" value="Unfair Terms " entityid="2274"/>
<lookup category="General" field="Alert Type" value="Operator Concerns" entityid="2275"/>
...
</lookups>
<paymentmethods />
<affiliates />
</root>
The Lookup class is as follows:
[Serializable]
[XmlSerializerFormat]
public class Lookup
{
[XmlAttribute("category")]
public String Category { get; set; }
[XmlAttribute("field")]
public String Field { get; set; }
[XmlAttribute("value")]
public String Value { get; set; }
[XmlAttribute("entityid")]
public String EntityId { get; set; }
public Lookup(String Category, String Field, String Value, int? EntityId = null)
{
this.Category = Category;
this.Field = Field;
this.Value = Value;
this.EntityId = (EntityId != null ? EntityId.ToString() : null);
}
public Lookup() { }
}
initially I had my DTO objects (e.g Lookup, PaymentMethod and Affiliates) with my viewmodels, but moved them into my DTO project.
I have set UseXmlSerializer = true in my global.asax
Before moving the objects from hy viewmodels folder to the DTO project, it was working and I was getting the desired XML. AFTER moving, it appears to be ignoring the XmlSerializerFormat and using DataContractSerliazer.
So using DataMember attributes, I can format the xml, but obviously I can't set some properties to be serialised as xml attributes
Any thoughts on why it seems to be ignoring the [XmlSerializerFormat] and [XmlAttribute("field")] attributes?
I've read a few SO posts like:
XmlSerializer ignores [XmlAttribute] in WebApi and
How can you control .NET DataContract serialization so it uses XML attributes instead of elements?
I found the solution to this here:
http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization#xml_pertype
Setting the xml Serializer in the Global.asax to work on a specific type worked for me :
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
xml.SetSerializer<FullResponseRoot>(new XmlSerializer(typeof(FullResponseRoot)));
where FullResponseRoot is the name of the object that I am serialising (the classes mentioned above are properties of FullResponseRoot)
FullResponseRoot forms the root node of the generated xml
How I read in the specific value of an XML attribute from a node when my XML looks like the following:
<Settings>
<Display_Settings>
<Screen>
<Name Name="gadg" />
<ScreenTag Tag="asfa" />
<LocalPosition X="12" Y="12" Z="12" />
</Screen>
</Display_Settings>
</Settings>
I only know how to read in the inner text value of XML and not the attribute value. For instance, I want the value of X in LocalPosition. This is what I've tried so far;
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Screen");
foreach (XmlNode nodeInfo in nodeList)
{
XmlNodeList nodeContent = nodeInfo.ChildNodes;
foreach (XmlNode nodeItems in nodeContent)
{
if (nodeItems.Name == "Tag")
{
print("There is a name");
}
if (nodeItems.Name == "LocalPosition")
{
print("TEST");
}
}
}
Though for what I want to do, I think this is the wrong way to go about it. Can someone point in the right direction please?
You can use LINQ to XML:
var xdoc = XDocument.Load(path_to_xml);
int x = (int)xdoc.Descendants("LocalPosition").First().Attribute("X");
Or with XPath
int x = (int)xdoc.XPathSelectElement("//LocalPosition").Attribute("X");
string XValue= nodeItems.Attributes["X"].Value; // will solve your problem.
I was a little confused when I first started parsing XML with C# too! Instead of trying to write it all yourself .NET provides System.XML.Linq and System.XML.Serialization to help us with all of the hard stuff!
This approach is slightly different as we:
Load the XML document into a System.XML.Linq.XDocument,
Deserialize the XDocument into .NET objects that we can use as we please =]
I hope you don't mind, but I tweaked your XML example a little bit. Try to avoid using too many attributes within your elements, as they tend to reduce readability. The first line of the XML sample just highlights the version that is being used.
You should be able to copy and paste the code sample below into a new console application to get a feel for how it works. Just make sure your XML file is in the ..\bin\Debug folder (otherwise you will need to provide a full file path reference to it).
As you can see, in this example your XML elements (Display_Settings, Screen, Name, ScreenTag, and LocalPosition) are all classes that we decorate with XMLElement attributes. These classes just have public properties that are automatically populated when we call Deserialize(). Neat!
The same applies to the X, Y and Z attributes.
There are a bunch of tutorials that can explain this a lot better than me - enjoy! =]
XML Example
<?xml version="1.0"?>
<Settings>
<Display_Settings>
<Screen>
<Name>NameGoesHere</Name>
<ScreenTag>ScreenTagGoesHere</ScreenTag>
<LocalPosition X="12" Y="12" Z="12" />
</Screen>
</Display_Settings>
</Settings>
Complete Code Sample
using System;
using System.Xml.Linq;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create XDocument to represent our XML file
XDocument xdoc = XDocument.Load("XmlFile.xml");
// Create a deserializer and break down our XML into c# objects
XmlSerializer deserializer = new XmlSerializer(typeof(Settings));
// Deserialize into a Settings object
Settings settings = (Settings)deserializer.Deserialize(xdoc.CreateReader());
// Check that we have some display settings
if (null != settings.DisplaySettings)
{
Screen screen = settings.DisplaySettings.Screen;
LocalPosition localPosition = screen.LocalPosition;
// Check to make sure we have a screen tag before using it
if (null != screen.ScreenTag)
{
Console.WriteLine("There is a name: " + screen.ScreenTag);
}
// We can just write our integers to the console, as we will get a document error when we
// try to parse the document if they are not provided!
Console.WriteLine("Position: " + localPosition.X + ", " + localPosition.Y + ", " + localPosition.Z);
}
Console.ReadLine();
}
}
[XmlRoot("Settings")]
public class Settings
{
[XmlElement("Display_Settings")]
public DisplaySettings DisplaySettings { get; set; }
}
public class DisplaySettings
{
[XmlElement("Screen")]
public Screen Screen { get; set; }
}
public class Screen
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("ScreenTag")]
public string ScreenTag { get; set; }
[XmlElement("LocalPosition")]
public LocalPosition LocalPosition { get; set; }
}
public class LocalPosition
{
[XmlAttribute("X")]
public int X { get; set; }
[XmlAttribute("Y")]
public int Y { get; set; }
[XmlAttribute("Z")]
public int Z { get; set; }
}
}
Try with nodeItems.Attributes["X"].Value
My web api is returning a set of objects which are differ from the Domain object. Forexample, I my domain has an Employee class but I don't want to expose all the members of the Employee class in my api so I created another class called EmployeeApiModel.
Now my WebApi is returning a List of EmployeeApiModel but I want to be able to specify the name to which it should serialize to. That is instead of <EmployeeApiModel> tag in the xml, I want to get <Employee> but without changing the fact that the underlying class which is being serialized is EmployeeApiModel.
How can I achieve this?
Technically, Web Api support both json and xml based on content negotiation mechanism, Json is the default format, if you want to receive xml, just put on header:
Accept: application/xml
To understand more content negotiation, access this
Since you want your api support both json and xml, you should use DataContract and DataMember Attribute for serialization for your model: EmployeeApiModel, something like:
[DataContract(Name = "Employee")]
public class EmployeeApiModel
{
[DataMember(Name = "Name2")]
public string Name { get; set; }
[DataMember]
public string Email { get; set; }
}
See more on this blog-post
You can control the output of your serialized XML by using various Attribute tags.
[XmlRoot("Employee")]
Public class EmployeeApiModel
{
[XmlElement("fname")]
public string FirstName { get; set; }
public string LastName { get; set; }
public int age { get; set; }
}
this will produce XML like:
<Employee>
<fname>John</fname>
<LastName >Smith</LastName >
<age>24</age>
</RootElementsName>
You can read more about the various XML modifiers here: http://msdn.microsoft.com/en-us/library/e123c76w.
If you want to use existing XML modifiers for JSON, check out this post: Serialize .Net object to json, controlled using xml attributes
Just for some fun I was playing with the API of Last.fm. The XML file they return for top artists is structured like this:
<lfm status="ok">
<topartists user="xbonez" type="overall">
<artist rank="1">
<name>Evanescence</name>
<playcount>4618</playcount>
<mbid>f4a31f0a-51dd-4fa7-986d-3095c40c5ed9</mbid>
<url>http://www.last.fm/music/Evanescence</url>
<streamable>1</streamable>
<image size="small">http://userserve-ak.last.fm/serve/34/48488613.png</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/48488613.png</image>
<image size="large">http://userserve-ak.last.fm/serve/126/48488613.png</image>
<image size="extralarge">http://userserve-ak.last.fm/serve/252/48488613.png</image>
<image size="mega">http://userserve-ak.last.fm/serve/500/48488613/Evanescence++PNG.png</image>
</artist>
</topartists>
</lfm>
This is how I'm deserealizing it.
I have a class called lfmStatus:
[Serializable()]
[XmlRootAttribute("lfm")]
public class lfmStatus
{
[XmlElement("artist")]
public List<Artists> TopArtists { get; set; }
}
And a Class Artists:
[Serializable()]
public class Artists
{
[XmlElement("name")]
public string Name { get; set; }
[XmlElement("playcount")]
public int playcount { get; set; }
[XmlElement("url")]
public string url { get; set; }
[XmlElement("streamable")]
public int streamable { get; set; }
[XmlElement("image")]
public string image { get; set; }
}
And then I deserealize using this code:
string XmlFile = "artists.xml";
XmlSerializer serializer = new XmlSerializer(typeof(lfmStatus));
lfmStatus LoadFile;
using (Stream reader = new FileStream(XmlFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
try
{
Console.WriteLine("Beginning deserialization.");
// Call the Deserialize method to restore the object's state.
LoadFile = (lfmStatus)serializer.Deserialize(reader);
return LoadFile.TopArtists;
}
Now, this code works great for the XML if it did not have the topartists tag enveloping all the artists. But since it does, how do I change my code to handle that? I'm assuming I need to add another class.
You are missing the attribute(s) on a few types.
See XmlAttributeAttribute for more detail.
You are also missing the topartists element's type.
If I was you, I would get the XML schema and just use xsd.exe to generate the C# classes, and modify from there. It can also infer the schema based on XML if you really can't find it, this will give you a parsable result based on the input XML.
To see that you wrote correct code for deserializing the response XML you can use XSD. Open VS command prompt and give XSD LastFM.xml which generates and XSD file. Now give XSD LastFM.XSD which will generate a CS file. compare that one with the one you have written and check if you made any mistakes.