C# - XDocument / linq to object - c#

How can i import xml elements to object? My code below doesn't work, it fails at the SetValue and i can't figure out why.
But even then, i suspect that linq has a much cleaner way of doing this but i can't find any examples.
class Printers {
public List<Printer> list = new List<Printer>();
public Printers()
{
var xDoc = XDocument.Load(Properties.Settings.Default.XmlSetupPath).Root;
var xPrinters = xDoc.Element("printers").Elements();
foreach (var xPrinter in xPrinters)
{
var printer = new Printer();
foreach (var xEl in xPrinter.Elements())
{
printer.GetType().GetProperty(xEl.Name.ToString()).SetValue(printer, xEl.Value);
}
}
}
}
class Printer
{
public string name;
public string ip;
public string model;
public string infx86;
public string infx64;
public string location;
public string comment;
}
my XML:
<printers>
<printer>
<name>my Printer</name>
<ip>192.168.100.100</ip>
<model>Brother</model>
<driver>ab</driver>
<infx86>ab\cd.INF</infx86>
<comment>Copycenter</comment>
</printer>
<printer>
<name>my Printer</name>
<foobar>oh no!</foobar>
</printer>
</printers>
I want to

You're asking for properties - but your type only has fields. Either make them properties, like this:
public string name { get; set; }
... or use Type.GetField instead.
In terms of making it prettier, I'd personally add a static FromXElement method to your Printer class, at which point you could have:
list = xDoc.Element("printers")
.Elements()
.Select(Printer.FromXElement)
.ToList();
Or you could write a generic method to create a new instance and populate it via reflection, e.g.
public static T FromXElement<T>(XElement element) where T : class, new()
{
T value = new T();
foreach (var subElement in element.Elements())
{
var field = typeof(T).GetField(subElement.Name.LocalName);
field.SetValue(value, (string) subElement);
}
return value;
}
Then:
list = xDoc.Element("printers")
.Elements()
.Select(XmlReflection.FromXElement<Printer>)
.ToList();

Related

Detecting mismatch between constructor parameter names and property names with immutable objects

Consider the following mutable object:
class SomePoco
{
public int Id{get;set;}
public string Name{get;set;}
}
Let's round trip it through Json.NET:
var p=new SomePoco{Id=4,Name="spender"};
var json=JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
All is good.
Now, let's make out POCO immutable and feed values via a constructor:
class SomeImmutablePoco
{
public SomeImmutablePoco(int id, string name)
{
Id = id;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
... and round-trip the data again:
var p = new SomeImmutablePoco(5, "spender's immutable friend");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Still good.
Now, let's make a small change to our immutable class by renaming a constructor parameter:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
public int Id{get;}
public string Name{get;}
}
then:
var p = new SomeImmutablePoco(666, "diabolo");
var json = JsonConvert.SerializeObject(p);
var pr = JsonConvert.DeserializeObject<SomeImmutablePoco>(json);
Console.WriteLine($"Id:{pr.Id}, Name:{pr.Name}");
Oh dear... It looks like Json.NET is doing some reflective magic over the names of our constructor parameters and matching them to property names in our POCO/json. This means that our freshly deserialized object doesn't get an Id assigned to it. When we print out the Id, it's 0.
This is bad, and particularly troublesome to track down.
This problem might exist in a large collection of POCOs. How can I automate finding these problem POCO classes?
Here is the code that finds such classes using reflection:
var types = new List<Type>() { typeof(SomeImmutablePoco) }; // get all types using reflection
foreach (var type in types)
{
var props = type.GetProperties(bindingAttr: System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
foreach (var ctor in type.GetConstructors())
{
foreach (var param in ctor.GetParameters())
{
if (!props.Select(prop => prop.Name.ToLower()).Contains(param.Name.ToLower()))
{
Console.WriteLine($"The type {type.FullName} may have problems with Deserialization");
}
}
}
}
You can map your json property like that:
class SomeImmutablePoco
{
public SomeImmutablePoco(int pocoId, string name)
{
Id = pocoId;
Name = name;
}
[JsonProperty("pocoId")]
public int Id { get; }
public string Name { get; }
}

C# Adding multiple elements to a list using xdocument

<?xml version="1.0" encoding="utf-8" ?>
<root>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>configurationtestbucket</UploadBucket>
<FileType>
<type>*.txt</type>
<type>*.OpticomCfg</type>
</FileType>
</fileUploadSpecification>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>loguploadbucket</UploadBucket>
<FileType>
<type>*.Xml</type>
<type>*.Json</type>
</FileType>
</fileUploadSpecification>
</root>
This is the XML file I need to parse, I want to get each instance of fileUploadSpecification so that I can put each set of details into a list, I think some type of for loop would be appropriate, where I loop through and add the first set of upload details and then loop through and add the second. This is what I currently have, but it never gets to the second fileUploadSpecification element, it just returns the same one again.
The idea would be to create a new SettingsData for every set of fileUploadSpecification elements, whether it be two like shown above, or 10.
public interface ISettingsEngine
{
IEnumerable<SettingsData> GetSettings();
}
public class SettingsEngine : ISettingsEngine
{
public IEnumerable<SettingsData> GetSettings()
{
List<SettingsData> dataList = new List<SettingsData>();
try
{
var xDoc = XDocument.Load("File1.xml");
var instancesToParse = xDoc.Root.Elements().Count();
var fileCount = xDoc.Root.Elements("FileType").Count();
for (int x = 0; x < instancesToParse; x++)
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = xDoc.Root.Element("fileUploadSpecification").Element("UploadBucket").Value;
newSettingsData.Region = xDoc.Root.Element("fileUploadSpecification").Element("Region").Value;
newSettingsData.DirectoryPath = xDoc.Root.Element("fileUploadSpecification").Element("DirectoryPath").Value;
var query = xDoc.Root.Descendants("FileType").Elements("type");
foreach (XElement e in query)
{
newSettingsData.FileType.Add(e.Value);
}
dataList.Add(newSettingsData);
}
return dataList;
}
catch(Exception)
{
return dataList;
}
}
}
public class SettingsData
{
public List<string> FileType { get; set; }
public string DirectoryPath { get; set; }
public string Region { get; set; }
public string UploadBucket { get; set; }
public SettingsData()
{
FileType = new List<string>();
}
}
var dataList = (from fus in xDoc.Root.Elements("fileUploadSpecification")
select new SettingsData
{
UploadBucket = fus.Element("UploadBucket").Value,
Region = fus.Element("Region").Value,
DirectoryPath = fus.Element("DirectoryPath").Value,
FileType = fus.Element("FileType")
.Elements("type").Select(f =>f.Value).ToList()
}).ToList();
Each time through the loop, you're looking up the first fileUploadSpecification element all over again. You used the Elements() method already, in a few places. That's the one you want. Always favor foreach over for in C#, when you're looping over a collection. It's quicker (to code, not at runtime) and less error prone.
foreach (var uploadSpec in xDoc.Root.Elements("fileUploadSpecification"))
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = uploadSpec.Element("UploadBucket").Value;
newSettingsData.Region = uploadSpec.Element("Region").Value;
newSettingsData.DirectoryPath = uploadSpec.Element("DirectoryPath").Value;
var types = uploadSpec.Descendants("FileType").Elements("type").Select(e => e.Value);
foreach (var type in types)
{
newSettingsData.FileType.Add(type);
}
// Or if newSettingsData.FileType is List<String>...
//newSettingsData.FileType.AddRange(types);
dataList.Add(newSettingsData);
}
James Curran's answer is functionally the same, but it's better form.

Serialize list of objects

I need to serialize/deserialize some XML code and part of it looks like next example:
<CoordGeom>
<Curve rot="cw" chord="830.754618036885" crvType="arc" delta="72.796763873948" dirEnd="283.177582669379" dirStart="355.974346543327" external="169.661846548051" length="889.38025007632" midOrd="136.562611151675" radius="699.999999998612" tangent="516.053996536113">
<Start>4897794.2800513292 6491234.9390137056</Start>
<Center>4897096.0071489429 6491185.7968343571</Center>
<End>4897255.5861026254 6491867.3645547926</End>
<PI>4897758.0514541129 6491749.7197593488</PI>
</Curve>
<Spiral length="109.418078418008" radiusEnd="INF" radiusStart="699.999999999025" rot="cw" spiType="clothoid" theta="4.477995782709" totalY="2.849307921907" totalX="109.351261203955" tanLong="72.968738862921" tanShort="36.493923980983">
<Start>4897255.5861026254 6491867.3645547936</Start>
<PI>4897220.0531303799 6491875.6840722272</PI>
<End>4897147.9238984985 6491886.7208634559</End>
</Spiral>
<Spiral length="153.185309785019" radiusEnd="499.99999999993" radiusStart="INF" rot="ccw" spiType="clothoid" theta="8.776871734087" totalY="7.808812331497" totalX="152.826239431476" tanLong="102.249348442205" tanShort="51.176160975293">
<Start>4897147.9238985004 6491886.7208634559</Start>
<PI>4897046.8509311257 6491902.186455016</PI>
<End>4896998.0370401107 6491917.5553683294</End>
</Spiral>
<Curve rot="ccw" chord="936.510896488672" crvType="arc" delta="138.94725576785" dirEnd="66.423714388543" dirStart="287.476458620693" external="925.970149937768" length="1212.543549877849" midOrd="324.680762068264" radius="499.999999999181" tangent="1335.436583485725">
<Start>4896998.0370401107 6491917.5553683294</Start>
<Center>4897148.1939981515 6492394.4755796343</Center>
<End>4896948.2091376046 6492852.7397562303</End>
<PI>4895724.243644949 6492318.6055583945</PI>
</Curve>
</CoordGeom>
I've generated automatically classes using xsd.exe. Part of generated code looks like this:
public partial class CoordGeom
{
private List<object> _items;
private List<Feature> _feature;
private string _desc;
private string _name;
private stateType _state;
private string _oID;
public CoordGeom()
{
_feature = new List<Feature>();
_items = new List<object>();
}
[XmlElementAttribute("Chain", typeof(Chain))]
[XmlElementAttribute("Curve", typeof(Curve))]
[XmlElementAttribute("IrregularLine", typeof(IrregularLine))]
[XmlElementAttribute("Line", typeof(Line))]
[XmlElementAttribute("Spiral", typeof(Spiral))]
public List<object> Items
{
get { return this._items; }
set { this._items = value; }
}
[XmlElement("Feature")]
public List<Feature> Feature { get; set; }
[XmlAttribute()]
public string desc { get; set; }
[XmlAttribute()]
public string name { get; set; }
[XmlAttribute()]
public stateType state { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string oID
{
get{ return this._oID; }
set{ this._oID = value; }
}
}
And my code for deserialization look like this:
XmlSerializer mySerializer = new XmlSerializer(typeof(LandXML), new XmlRootAttribute(""));
TextReader myFileStream = new StreamReader("myFile.xml");
LandXML myObject = (LandXML)mySerializer.Deserialize(myFileStream);
var coordGeomItems = myObject.Alignments.Alignment[0].CoordGeom;
My problem is that, when I deserialize file, it is deserialized as list of items of type {LandXML.Curve}, {LandXML.Spiral} etc. and I don't know how to access their properties. It would be great if I can do this directly. Here is a screenshot:
EDIT 1
Here is inital screen
then I have items:
When I unfold this
And this is at the top layer of object - it has some InnerXml, InnerText... If I want to achieve CoordGeom, there is a lot object.Item(i).ChildNodes.Item(j).ChildNodes...
And all of that is because in some lines, lists of objects are made like List as for CoordGeom
Because there are multiple allowed types, the Items collection is typed as object. The simplest approach is to enumerate and cast each item:
foreach(var item in coordGeomItems.Items)
{
var curve = item as Curve;
if (curve != null)
{
// access curve properties here
}
var spiral = item as Spiral
if (spiral != null)
{
// access spiral properties here
}
// ...
}
You could build up a list of Curves and Spirals and access them using properties with custom getters:
class CoordGeom
{
public List<object> Items;
List<Curve> _curves;
public List<Curve> Curves
{
get
{
return _curves ?? (_curves = Items
.Where(item => item is Curve).Select(curve => (Curve)curve).ToList());
}
}
}
The null coalescing operator (??) will cause the Curves property to set and return the value of _curves as a list of curves if _curves is null. This basically causes it to initialize the list on the first get and on all subsequent gets it will return the already initialized list.
As you cannot change the generated class nor the XML.The best possible approach would be to write an extension method.
public static List<Curve> GetCurves(this CoordGeom cg)
{
return cg.Items.OfType<Curve>().ToList();
}
public static List<Spiral> GetSpirals(this CoordGeom cg)
{
return cg.Items.OfType<Spiral>().ToList();
}
Once you do this, you can get items like this
var coordGeomItems = myObject.Alignments.Alignment[0].CoordGeom;
var curves = coordGeomItems.GetCurves();
var spirals = coordGeomItems.GetSpirals();

Retrieve values of attributes from XML using XDocument

I am using XDocument.Parse method to load following XML :
<AuditMessage>
<Event Action="Read" DateTime="2013/26/7" EventID="100"/>
<User Role="Admin" UserID="12123"/User>
<SourceIdentification SourceID="TeamLondon" SourceType="3"/>
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="1" />
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="`2" />
<Participant ParticipantID="0001" ParticipantType ="2"/>
<Participant ParticipantID="0002" ParticipantType ="3"/>
<Participant ParticipantID="0003" ParticipantType ="3" ParticipantName = "Housh Mangrove"/>
</AuditMessage>
I need to retrieve the values of following attributes in the above XML.
-DateTime
-Role
-AccessPointID
-ParticipantID
-ParticipantName
I have used sourceXML.Root.Element(nodeName).Attribute(attributeToMatch).Value to read a single attribute. I am failing to understand how can I iterate the same thing over different nodes, provided some nodes might be missing.
Please notice :
<Network> and <Participant> nodes are repeating.
ParticipantName attribute exists only in one Instance of
Lastly, any node could be missing in different XMLs provided as Input. Therefore I need to write code in such a way that if a node is missing, the application doesn't throw OBJECT REFERENCE NOT FOUND Exception
here's a quick attempt, you can figure out the ones I didn't add in:
public static void Main()
{
GetAtts(xml);
}
public static Atts GetAtts(string xml)
{
Atts atts = new Atts();
XDocument doc = XDocument.Parse(xml);
if (doc.Root.Element("Event") != null)
atts.datetime = doc.Root.Element("Event").Attribute("DateTime").Value;
//...
foreach (XElement ele in doc.Descendants("Network"))
atts.accesspointid.Add(ele.Attribute("AccessPointID").Value);
return atts;
}
public class Atts
{
public string datetime { get; set; }
public string role { get; set; }
private List<string>_accesspointid = new List<string>();
public List<string> accesspointid { get { return _accesspointid; } set { _accesspointid = value; } }
public List<string> _participantid = new List<string>();
public List<string> participantid { get { return _participantid; } set { _participantid = value; } }
public string participantname { get; set; }
}
you'll have an object you can handle more easily
You can use the Elements method to get an enumeration of nodes of a given name.
You can then test if the enumeration returned any results and look for the appropriate attributes in them.
Something like this, if you want it as CSV:
var data = new List<string>();
var events = doc.Root.Elements("Event");
if (events.Any())
{
foreach (var evt in events)
{
data.Add(evt.Attribute("DateTime").Value);
}
}
var participants = doc.Root.Elements("Participant");
if (participants.Any())
{
foreach (var participant in participants)
{
data.Add(participant.Attribute("ParticipantID").Value);
}
}
var csv = string.Join(", ", data);
Quick and dirty solution - hopefully you can take it from here:
var participantID = String.Join(", ",
xdoc.Root.Elements("Participant")
.Select(e => e.Attribute("ParticipantID"))
.Where(a => a != null)
.Select(a => a.Value)
.Distinct());

Storing/restoring values from a class to/from a list of property names and values

I am not sure what the best and simplest way to do this, so any advice is appreciated.
I want to get all the fields on any/all/single domain entity class and add prefix/remove prefix dynamically when calling a particular method.
For example, I have entities such as:
public class Shop
{
public string TypeOfShop{get;set}
public string OwnerName {get;set}
public string Address {get;set}
}
public class Garage
{
public string Company {get;set}
public string Name {get;set}
public string Address {get;set}
}
and so on...
I want to get a list of the properties with a prefix:
public Class Simple
{
public class Prop
{
public string Name{get;set;}
public string Value{get;set;}
}
public ICollection list = new List<Prop>();
//set all prop
public void GetPropertiesWithPrefix(Garage mygarage, string prefix)
{
list.Add(new Prop{Name = prefix + "_Company", Value = mygarage.Company});
//so on... upto 50 props...
}
}
//to get this list I can simple call the list property on the Simple class
When reading each field I am using a switch statement and setting the value.
//Note I return a collection of Prop that have new values set within the view,lets say
//this is a result returned from a controller with the existing prop names and new values...
public MyGarage SetValuesForGarage(MyGarage mygarage, string prefix, ICollection<Prop> _props)
{
foreach (var item in _prop)
{
switch(item.Name)
{
case prefix + "Company":
mygarage.Company = item.Value;
break;
//so on for each property...
}
}
}
Is there a better, simpler or more elegant way to do this with linq or otherwise?
You could store props in a dictionary, then have:
mygarage.Company = _props[prefix + "_Company"];
mygarage.Address = _props[prefix + "_Address"];
//And so on...
in your SetValuesForGarage method instead of a loop with a switch inside.
EDIT
For more info on using Dictionary see MSDN.
You can define list something like:
Dictionary<string, string> list = new Dictionary<string, string>();
And have something like the following in your GetPropertiesWithPrefix method:
list.Add(prefix + "_Company", mygarage.Company);
list.Add(prefix + "_Address", mygarage.Address);
//And so on...
This would eliminate your Prop class.
Maybe the following method works for you. It takes any object, looks up its properties and returns a list with your Prop objects, each for every property.
public class PropertyReader
{
public static List<Prop> GetPropertiesWithPrefix(object obj, string prefix)
{
if (obj == null)
{
return new List<Prop>();
}
var allProps = from propInfo
in obj.GetType().GetProperties()
select new Prop()
{
Name = prefix + propInfo.Name,
Value = propInfo.GetValue(obj, null) as string
};
return allProps.ToList();
}
}

Categories