Map values to class properties dynamically - c#

this is my scenario:
I have an application that downloads xml files and extracts data from them. To achieve this I've created a xml configuration file like this:
<?xml version="1.0" encoding="utf-8" ?>
<labelsSets>
<labelsSet getUrl="https://www.address.com/services/rest/product-display.xml?expand_entities=0&limit=100000000">
<fields>
<field name="item" required="true" xPath="result/item" doGet="false" isRoot="true" />
<field name="Title" required="true" xPath="title" doGet="false" />
</fields>
</labelsSet>
</labelsSets>
Where the "field" nodes are the values that I have to extract from the downloaded xml.
So, I have to insert the data into a database and I generated class like this with EF DbContext Generator:
[Table("Winery")]
public partial class Winery
{
public Winery()
{
Wine = new HashSet<Wine>();
}
[StringLength(10)]
public string Id { get; set; }
[Required]
[StringLength(200)]
public string Name { get; set; }
public DateTime? LastUpdate { get; set; }
public virtual ICollection<Wine> Wine { get; set; }
}
Is there a way to map the extracted values from xml to db table class?
I thought to add "table" and "tableField" attributes to "field" node in configuration, but I cannot find the way to achieve the mapping between the extracted values and db classes.

Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
//alternate method
//DataSet ds = new DataSet();
//ds.ReadXml(FILENAME);
XmlSerializer xs = new XmlSerializer(typeof(LabelsSets));
XmlTextReader reader = new XmlTextReader(FILENAME);
LabelsSets labelSets = (LabelsSets)xs.Deserialize(reader);
}
}
}
[XmlRoot("labelsSets")]
public class LabelsSets
{
[XmlElement("labelsSet")]
public List<LabelsSet> labelSet {get;set;}
}
[XmlRoot("labelsSet")]
public class LabelsSet
{
[XmlAttribute("getUrl")]
public string getUrl {get;set;}
[XmlElement("fields")]
public Fields fields {get;set;}
}
[XmlRoot("fields")]
public class Fields
{
[XmlElement("field")]
public List<Field> fields {get;set;}
}
[XmlRoot("field")]
public class Field
{
[XmlAttribute("name")]
public string name {get;set;}
[XmlAttribute("required")]
public Boolean required {get;set;}
[XmlAttribute("xPath")]
public string xPath {get;set;}
[XmlAttribute("doGet")]
public Boolean doGet {get;set;}
[XmlAttribute("isRoot")]
public Boolean isRoot {get;set;}
}

Related

How to XML deserialize Dictionary, that hidden in other element?

ACTUAL code and xml file
Code of program
class Program
{
static void Main(string[] args)
{
string path = AppDomain.CurrentDomain.BaseDirectory + "file.xml";
//string path2 = #"F:\fd.xml";
FileStream fs = new FileStream(path, FileMode.Open);
XmlReader reader = XmlReader.Create(fs);
SaveGame sav = new XmlSerializer(typeof(SaveGame)).Deserialize(reader) as SaveGame;
Console.WriteLine(sav.player.friendshipData[0].key);
Console.WriteLine(sav.player.friendshipData[0].value.Points);
fs.Close();
Console.ReadKey();
}
}
public class SaveGame
{
public Player player { get; set; }
}
public class Player
{
public item[] friendshipData { get; set; }
}
public class item
{
public string key { get; set; }
public Friendship value { get; set; }
}
public class Friendship
{
public int Points {get;set;}
}
}
XML File to work with:
<SaveGame>
<player>
<friendshipData>
<item>
<key>
<string>Name1</string>
</key>
<value>
<Friendship>
<Points>324</Points>
</Friendship>
</value>
</item>
<item>
<key>
<string>Name2</string>
</key>
<value>
<Friendship>
<Points>98</Points>
</Friendship>
</value>
</item>
</friendshipData>
</player>
</SaveGame>
I tried other posts, and that's not working, cause all readen values are null.
Please, how to deserialize this document? And with explanation, please.
If i set {get;set;} to variables, it won't read next item, if i set {get;} it read every item, but every item have null value.
Just in case, i can't edit XML File for some reasons. XML File is alright.
Your data-structure doesn´t really fit your xml well. If you have a simple data-type such as int or string you can serialize and de-serialize directly into an xml-node. If you have some more complex data-structure like your Firnedship-node you need a nested node.
Having said this your data-structure should be similar to thie following:
public class SaveGame
{
public Player player { get; set; }
}
public class Player
{
public item[] friendshipData { get; set; }
}
public class item
{
public Key key { get; set; }
public Friendship value { get; set; }
}
// add this class with a single string-field
public class Key
{
public string #string { get; set; }
}
public class Friendship
{
public int Points { get; set; }
}
As an aside consider following naming-conventions, which is giving classes and members of those classes PascaleCase-names, e.g. FriendshipData, Item, and Key. This however assumes you have some mapping from those names to your names within the xml. This can be done by using XmlElementAttribute:
public class Player
{
[XmlElement("friendshipData ")] // see here how to change the name within the xml
public item[] FriendshipData { get; set; }
}
Since you only need two values from Xml I would not use serialization. You can get a dictionary with one linq instruction.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication51
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
Dictionary<string, int> players = doc.Descendants("item")
.GroupBy(x => (string)x.Descendants("string").FirstOrDefault(), y => (int)y.Descendants("Points").FirstOrDefault())
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
}
}
}

Adding Qualified NameSpace in XML using C#

How can I create XML given below in c#
<v1:Request ProductId="string" xmlns:v1="com/user/service/core/services/v1" xmlns:v11="com/user/ds/sch/pii/v1" xmlns:enum="com/uaer/schema/database/tcps/enumerations">
<v1:Name>string</v1:Name>
<v1:ID>string</v1:ID>
<v1:Code>string</v1:Code>
<v1:Key>string</v1:Key>
<v1:RequestKey>string</v1:RequestKey>
<v1:PartnerId>string</v1:PartnerId>
<v1:CustomerInfo>
<v1:Name>
<v11:Title>string</v11:Title>
<v11:Forename>string</v11:Forename>
<v11:Surname>string</v11:Surname>
<v11:Suffix>string</v11:Suffix>
</v1:Name>
<v1:Number>
<v11:Name>string</v11:Name>
<v11:Id>string</v11:Id>
</v1:Number>
</v1:Request>
Code snippet that i am using to generate the XML is this,
[XmlRoot("Request")]
public class Request{
[XmlAttribute]
public string ProductId{get;set;}
[XmlElement("Name")]
public string Name{get;set;}
[XmlElement("ID")]
public string ID{get;set;}
[XmlElement("Code")]
public string Code{get;set;}
[XmlElement("Key")]
public string Key{get;set;}
[XmlElement("RequestKey")]
public string RequestKey{get;set;}
[XmlElement("PartnerId")]
public string PartnerId{get;set;}
[XmlElement("CustomerInfo")]
public CustomerInfo CustomerInfo= new CustomerInfo();
//this(CustomerInfo) is a class which contains the reference of NAME and NUMBER class.
//NAME and NUMBER class contains their respective properties.
}
I am able to generate this XML using the above code
<Request ProductId="string" xmlns:v1="com/user/service/core/services/v1" xmlns:v11="com/user/ds/sch/pii/v1" xmlns:enum="com/uaer/schema/database/tcps/enumerations">
<Name>string</Name>
<ID>string</ID>
<Code>string</Code>
<Key>string</Key>
<RequestKey>string</RequestKey>
<PartnerId>string</PartnerId>
<CustomerInfo>
<Name>
<Title>string</Title>
<Forename>string</Forename>
<Surname>string</Surname>
<Suffix>string</Suffix>
</Name>
<Number>
<Name>string</Name>
<Id>string</Id>
</Number>
</Request>
I am adding namespace using XmlSerializerNamespaces
I am trying to consume SOAP Api and i have to pass the xml as request.
You can combine multiple namespaces by adding them to XML attributes:
[XmlRoot("Request", Namespace = "com/user/service/core/services/v1")]
public class Request
{
[XmlAttribute]
public string ProductId { get; set; }
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("CustomerInfo")]
public CustomerInfo CustomerInfo = new CustomerInfo();
}
public class CustomerInfo
{
[XmlElement("Name")]
public Name Name { get; set; }
}
public class Name
{
[XmlElement("Title", Namespace = "com/user/ds/sch/pii/v1")]
public string Title { get; set; }
}
The namespace prefixes are provided to the XML serializer:
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("v1", "com/user/service/core/services/v1");
ns.Add("v11", "com/user/ds/sch/pii/v1");
XmlSerializer xser = new XmlSerializer(typeof(Request));
xser.Serialize(Console.Out, new Request(), ns);

Remove unnecessary nesting when deserializing XML or access data in parent node?

The XML I'm attempting to deserialize has a lot of fields that have the data nested in a subfield and I'd like to assign this data into the parent property on the class rather than create additional unneeded structure.
<?xml version="1.0" encoding="UTF-8"?>
<x:EventRecord eventid="EVR-1000">
<x:Postcode>
<x:ID>ABC123</x:ID>
</x:Postcode>
</x:EventRecord>
I've created an EventRecord class with a Postcode string property:
public class EventRecord
{
public string EventID { get; set; }
public string Postcode { get; set; }
}
Is there an attribute that can decorate the property that can tell the deserializer to take the value out of the nested ID field? There will never be any other fields in <x:Postcode> aside from <x:ID>.
Also is there a way to assign the eventid XML attribute on the parent x:EventRecord node to the EventID property inside itself?
Using following xml :
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:x="abc">
<x:EventRecord eventid="EVR-1000">
<x:Postcode>
<x:ID>ABC123</x:ID>
</x:Postcode>
</x:EventRecord>
</root>
Code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication16
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
XNamespace ns = doc.Root.GetNamespaceOfPrefix("x");
EventRecord record = doc.Descendants(ns + "EventRecord").Select(x => new EventRecord() {
EventID = (string)x.Attribute("eventid"),
Postcode = (string)x.Descendants(ns + "ID").FirstOrDefault()
}).FirstOrDefault();
}
}
public class EventRecord
{
public string EventID { get; set; }
public string Postcode { get; set; }
}
}

Have both DataType and additional Attribute during Xml Serialization

I am attempting to create an XML document through the application of attributes on fields/properties ([XmlAttribute], [XmlElement], etc.) My problem is that I have a requirement that I attach an additional attribute to a primitive datatype in the style of:
<document xmlns:dt="urn:schemas-microsoft-com:datatypes" >
<binary addAttribute="X" dt:dt="bin.base64">
[... binary ...]
</binary>
</document>
I'm making use of code like the following:
[Serializable]
public class Document {
[XmlElement]
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
public class XmlExample {
public static void Main(string[] args)
{
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = File.ReadAllBytes(#"FileName");
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document);
Console.ReadLine();
}
}
This, however, provides the following output:
<document>
<binary addAttribute="X">
[... binary ...]
</binary>
</document>
If I attempt to move the byte[] Binary to the Document class instead I can get the xmlns:dt="..." as expected but I cannot attach the arbitrary addAttribute when I do so (unless I missed something obvious.) This was incorrect; I misread something in the XML that I was getting out of the XML. The xmlns:dt element was not added in this case.
The question is: Can I do this (have both the DataType and the addAttribute) exclusively through C# attributes?
The answer to this question came partially from here: XmlSerializer attribute namespace for element type dt:dt namespace. The DataType = "base64Binary" does not apply the xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="bin.base64" to the element that it is attached to. An attribute has to be added to the BinaryObject that provides the dt:dt = "bin.base64" with the correct name space.
Final Code
[Serializable]
public class Document {
public BinaryObject Binary { get; set; }
}
[Serializable]
public class BinaryObject {
[XmlText]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
// Adds the dt:dt object to the correct name space.
[XmlAttribute("dt", Namespace = "urn:schemas-microsoft-com:datatypes")]
public string DataType { get; set; }
public BinaryObject() { DataType = "bin.base64"; }
}
public class XmlExample {
public static void Main(string[] args)
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
// Adds the needed namespace to the document.
namespaces.Add("dt", "urn:schemas-microsoft-com:datatypes");
Document document = new Document();
document.Binary = new BinaryObject();
document.Binary.Binary = new byte[]{0,1,2,3,4,5,6,7,8,9};
document.Binary.AddAttribute = 0;
XmlSerializer serializer = new XmlSerializer(typeof(Document));
serializer.Serialize(Console.Out, document, namespaces);
Console.ReadLine();
}
}
Final Output
<Document xmlns:dt="urn:schemas-microsoft-com:datatypes">
<Binary AddAttribute="0" dt:dt="bin.base64">AAECAwQFBgcICQ==</Binary>
</Document>
Try this
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
}
}
[XmlRoot("Document")]
public class Document
{
[XmlText(DataType = "base64Binary")]
public byte[] Binary { get; set; }
[XmlAttribute]
public int AddAttribute { get; set; }
}
}
​

Deserialize XML to object (need to return a list of objects)

Started practicing with XML and C# and I have an error message of "There is an error in XML document (3,2)". After looking at the file, I can't see anything wrong with it (Mind you, I probably missed something since I'm a noob). I'm using a Console Application for C# right now. I'm trying to return a list of Adventurers and just a side note, the GEAR element is optional. Here is what I have so far:
XML File - Test1
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurer>
<ID>001</ID>
<Name>John Smith</Name>
<Address>123 Fake Street</Address>
<Phone>123-456-7890</Phone>
<Gear>
<Attack>
<Item>
<IName>Sword</IName>
<IPrice>15.00</IPrice>
</Item>
<Item>
<IName>Wand</IName>
<IPrice>20.00</IPrice>
</Item>
</Attack>
<Defense>
<Item>
<IName>Shield</IName>
<IPrice>5.00</IPrice>
</Item>
</Defense>
</Gear>
</Adventurer>
<Adventurer>
<ID>002</ID>
<Name>Guy noone likes</Name>
<Address>Some Big House</Address>
<Phone>666-666-6666</Phone>
<Gear></Gear>
</Adventurer>
</Catalog>
C# Classes
public class Catalog
{
List<Adventurer> Adventurers { get; set; }
}
public class Adventurer
{
public int ID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public Gear Gear { get; set; }
}
public class Gear
{
public List<Item> Attack { get; set; }
public List<Item> Defense { get; set; }
}
public class Item
{
public string IName { get; set; }
public decimal IPrice { get; set; }
}
Serialize Function - Where the Problem Occurs at Line 5
Catalog obj = null;
string path = #"C:\Users\Blah\Desktop\test1.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Catalog));
StreamReader reader = new StreamReader(path);
obj = (Catalog)serializer.Deserialize(reader);
reader.Close();
Console.ReadLine();
The issue is the list of Adventurers in Catalog:
<?xml version="1.0" encoding="utf-8"?>
<Catalog>
<Adventurers> <!-- you're missing this -->
<Adventurer>
</Adventurer>
...
<Adventurer>
</Adventurer>
</Adventurers> <!-- and missing this -->
</Catalog>
You don't have the wrapping element for the Adventurers collection.
EDIT: By the way, I find the easiest way to build the XML structure and make sure it's compatible is to create the object(s) in C#, then run through the built-in XmlSerializer and use its XML output as a basis for any XML I create rather than forming it by hand.
First, "Adventurers" property is not public, it's inaccessible, I think that the best way to find the error is to serialize your object and then compare the result with your xml file.
Your XML doesn't quite line up with your objects... namely these two...
public string City { get; set; }
and
<Address>123 Fake Street</Address>
Change City to Address or vice versa and it should fix the problem.
Edit: Got this to work in a test project, combination of all our answers...
Add <Adventurers> tag after <Catalog> (and </Adventurers> before </Catalog>) and change
List<Adventurer> Adventurers { get; set; }
to
public List<Adventurer> Adventurers { get; set; }
and it works properly for me.
I was able to get your xml to deserialize with a couple minor changes (namely the public qualifier on Adventurer).
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
namespace TempSandbox
{
[XmlRoot]
public class Catalog
{
[XmlElement("Adventurer")]
public List<Adventurer> Adventurers;
private readonly static Type[] myTypes = new Type[] { typeof(Adventurer), typeof(Gear), typeof(Item) };
private readonly static XmlSerializer mySerializer = new XmlSerializer(typeof(Catalog), myTypes);
public static Catalog Deserialize(string xml)
{
return (Catalog)Utils.Deserialize(mySerializer, xml, Encoding.UTF8);
}
}
[XmlRoot]
public class Adventurer
{
public int ID;
public string Name;
public string Address;
public string Phone;
[XmlElement(IsNullable = true)]
public Gear Gear;
}
[XmlRoot]
public class Gear
{
public List<Item> Attack;
public List<Item> Defense;
}
[XmlRoot]
public class Item
{
public string IName;
public decimal IPrice;
}
}
I'm using [XmlElement("Adventurer")] because the xml element names don't exactly match the class property names.
NOTE: I'm using a generic deserialization utility i already had on hand .

Categories