I can't seem to point and read the correct information. I'm new to using Linq and have tried (after loading the document as XDocument and XElement) select, root.xelement, descendant, element, node etc. and haven't found the proper way of pointing to what I'm trying to target.
I have an XML document that looks like this for now.
<Contacts>
<EntryName>
<Name>NAME1</Name>
<Email>EMAIL</Email>
<EIL>1</EIL>
<Notes>Notes</Notes>
</EntryName>
</Contacts>
I need to pull up a list of all EntryNames and place them in listBox1.
When a user selects one, it gathers it takes the "listBox1.SelectedItem" and
gather's the email address associated and places it in a textBox.
"EntryName" during runtime is replaced by a textfield.
My most recent try was this:
var xml = XDocument.Load(apppath + #"\Contacts.clf");
var entries = xml.Element("Contacts").Value.ToString();
foreach (var entry in entries)
{
listBox1.Items.Add(entry.ToString());
}
Which gets me nothing but characters at a time of the complete file due to the
foreach function. What I'm looking for is in the listBox from Contacts:
EntryName
EntryName2
EntryName2...etc
and when selected (from say EntryName2) it pulls the email field and places it in a textbox. Please forgive and obvious or dumb mistake, very new to this. Thanks.
I wrote a quick example on how to achieve this
public partial class Form1 : Form
{
XDocument doc;
public Form1()
{
InitializeComponent();
doc = XDocument.Load(apppath + #"\Contacts.clf");
var entryNames = doc.Root.Elements("EntryName")
.Select(elem => elem.Element("Name").Value ).ToArray();
listBox1.Items.AddRange(entryNames);
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
textBox1.Text = doc.Root.Elements("EntryName")
.FirstOrDefault(node => node.Element("Name").Value == listBox1.SelectedItem.ToString())
.Element("Email").Value;
}
}
However That seems like too much trouble to find the Email. I would, instead handle it like this:
public partial class Form1 : Form
{
XDocument doc;
public Form1()
{
InitializeComponent();
String apppath = ".";
doc = XDocument.Load(apppath + #"\Contacts.clf");
var contacts = doc.Root.Elements("EntryName")
.Select( elem =>
new Contact {
Name = elem.Element("Name").Value,
Email = elem.Element("Email").Value,
EIL = elem.Element("EIL").Value,
Notes = elem.Element("Notes").Value
}
).ToList();
listBox1.DataSource = contacts;
listBox1.DisplayMember = "Name";
}
private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
textBox1.Text = (listBox1.SelectedItem as Contact).Email;
}
}
public class Contact
{
public String Name { get; set; }
public String Email { get; set; }
public String EIL { get; set; }
public String Notes { get; set; }
}
Try this. I believe you were trying to query the Name elements in your XML document.
var xml = XDocument.Load(apppath + #"\Contacts.clf");
var entries = from entryName in xml.Descendants("EntryName") select (string)entryName.Element("Name");
foreach (var entry in entries)
{
listBox1.Items.Add(entry);
}
Related
I have this kind of xml file. Root node is Sim and there are multiple "test" subnodes. Also each "test" has an instructor. Finally each instructor has own name,surname and rank values.
<Sim>
<Test ID="1" Description="test1" Date="17/01/2023">
<Instructor Num="1">
<Name>instructor1</Name>
<Surname></Surname>
<Rank></Rank>
</Instructor>
</Test>
<Test ID="2" Description="test2" Date="16/01/2023">
<Instructor Num="22">
<Name>instructor22</Name>
<Surname></Surname>
<Rank></Rank>
</Instructor>
</Test>
</Sim>
I want to feed the combo box with unique instructor IDS. With this code, I can get all instructor ids, but they are not unique. For example, if two tests have the same instructor, it shows 2 instructor on combobox. Also, after combobox selection, I want to store selected instructor's name, surname and rank in temporary variables. How can I solve this?
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlNodeList insList = doc.SelectNodes("Sim/Test/Instructor");
foreach (XmlNode xn in insList)
cBox_ins.Items.Add(xn.Attributes["Num"].Value);
Ask the right questions. Your task is not just to get unique instructor numbers, but to save the data for future work.
Let's do that.
Сreate a model class for storing data:
public class Instructor
{
public int Num { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Rank { get; set; }
}
And an auxiliary comparator class:
public class InstructorComparer : IEqualityComparer<Instructor>
{
public bool Equals(Instructor x, Instructor y)
{
return x.Num == y.Num;
}
public int GetHashCode(Instructor obj)
{
return obj.Num;
}
}
Here is a complete sample application:
public partial class Form1 : Form
{
ComboBox _comboBox;
List<Instructor> _instructors;
public Form1()
{
//InitializeComponent();
var set = new HashSet<Instructor>(new InstructorComparer());
var xml = XElement.Load("test.xml");
foreach (var node in xml.Elements("Test").Elements("Instructor"))
{
var instructor = new Instructor
{
Num = (int)node.Attribute("Num"),
Name = node.Element("Name").Value,
Surname = node.Element("Surname").Value,
Rank = node.Element("Rank").Value
};
set.Add(instructor);
}
_instructors = set.ToList();
_comboBox = new ComboBox { Parent = this, Dock = DockStyle.Left };
_comboBox.SelectedValueChanged += _comboBox_SelectedValueChanged;
_comboBox.DisplayMember = nameof(Instructor.Num);
_comboBox.DataSource = _instructors;
}
private void _comboBox_SelectedValueChanged(object sender, EventArgs e)
{
var comboBox = (ComboBox)sender;
var instructor = (Instructor)comboBox.SelectedValue;
Text = instructor.Name + " " + instructor.Surname + " " + instructor.Rank;
}
}
Load data from xml file. I'm using linq to xml, but it can be done with deserialization as well.
We use an intermediate HashSet with a comparator to get distinct data.
We save the data as List in the form field so that we can access it later.
Bind data to the combobox. Specify what will be display in the combobox.
In the event, we receive the bound data from the combobox and somehow use it. I just put them in the form's Text property.
Namespaces:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
To select unique instructors from the xml nodes, you can do something like this:
...your code to load XML
var instructors = insList.Cast<XmlNode>().DistinctBy(x => x.Attributes["Num"].Value);
foreach (XmlNode node in instructors)
{
cBox_ins.Items.Add(xn.Attributes["Num"].Value);
}
To solve your second problem for storing the selected instructor details, you probably want to have a lookup table that stores all the instructors, then you can retrieve an instructor by the selected Id.
here is the implementation of Client Code
cBox_ins.Items.AddRange(doc.SelectNodes("Sim/Test/Instructor")
.OfType<XmlNode>().Select(x => x.Attributes["Num"].Value)
.Distinct().ToArray());
Step 1: I have created a C# application called : Student details
Step 2: Added four TextBoxes and named them as :
Image below to refer:
Studentname.Text
StudentSurname.Text
StudentCity.Text
StudentState.Text
DATA INSIDE CSV FILE
vikas,gadhi,mumbai,maharashtra
prem,yogi,kolkata,maha
roja,goal,orissa,oya
ram,kala,goa,barka
Issue is How do I fetch all the data(surname,city,state) of user prem into above textboxes studentsurname,studentcity,studentstate from csv file when I search the name in textbox 1 => studentname.Text as prem
Below is the Code where I am stuck at return null and code inside Load_Script_Click
void Connection_fetch_details(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
if (line.Split(',')[0].Equals(searchName))
{
Connection_fetch_details cd = new Connection_fetch_details()
{
username = line.Split(',')[1]
};
}
}
return;
}
private void Load_Script_Click(object sender, EventArgs e)
{
// load script is button
String con_env = textenv.Text.ToString();
//Address Address = GetAddress("vikas");
//textsurname.text = Address.Surname
Connection_fetch_details cd = Connection_fetch_details(con_env);
textusername.Text = cd.username;
}
==============================================================
Class file name : Address.class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DDL_SCRIPT_GENERATOR
{
public class Connection_fetch_details
{
public string username { get; set; }
}
}
The main problem is that your method is void, which means it doesn't return any value. So even though you may be finding a match, and creating a Connection_fetch_details object, you aren't returning that result back to the calling method.
This will fix that problem:
Connection_fetch_details Connection_fetch_details(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
if (line.Split(',')[0].Equals(searchName))
{
Connection_fetch_details cd = new Connection_fetch_details()
{
username = line.Split(',')[1]
};
return cd; //return the object containing the matched username
}
}
return null;
}
Now it will return a Connection_fetch_details object if there is a match, or null if there is no match.
Next, you asked about returning all the fields, not just one. For that you would need to
a) add more properties to your object
b) add more code to populate those properties from the CSV
c) add code to populate the textboxes with the results from the object.
I'm also going to rename "username" to something more relevant, since none of the field names you described in the question match that. I'm also going to rename your class to "Student", and rename your search method, for the same reason.
Here's an example:
Student searchStudent(String searchName)
{
var strLines = File.ReadLines(filePath);
foreach (var line in strLines)
{
var split = line.Split(',');
if (split[0].Equals(searchName))
{
Student s = new Student()
{
firstname = searchName,
surname = split[1],
city = split[2],
state = split[3]
};
return s; //return the object containing the matched name
}
}
return null;
}
private void Load_Script_Click(object sender, EventArgs e)
{
// load script is button
String con_env = textenv.Text.ToString();
//Address Address = GetAddress("vikas");
//textsurname.text = Address.Surname
Student st = searchStudent(con_env);
textsurname.Text = st.surname;
txtcity.Text = st.city;
txtstate.Text = st.state;
}
namespace DDL_SCRIPT_GENERATOR
{
public class Student
{
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
}
To accomplish your goal you have to further separate your problem in more granular steps and also distinguish between what you show in your UI and what informations you hold in the background in which format.
Create a class with the desired properties
public class Student { public string Name { get; set; } ... }
Learn how to read a csv file into such an object by using an existing library like CsvHelper or CsvReader.
When you have something like List<Student> from this part. Learn how you can visualize such a thing by using some Binding (also depends on the visualization you use Winforms, WPF, etc.).
Depending on the visualization component it already supports filtering or you need to filter by yourself by using e.g. LINQ to get the matching elements students.Where(student => student.Name.StartsWith(search)).
So far a lot of smaller problems which is simply to much to answer in a single one. Please try to break down your problems into smaller ones and search for their solutions. If you get stuck, ask a new question. That's all I can do for you now.
I am trying to send form data to a confirmation page where you would enter your cc info to complete the transaction. I add all the information to a list that i will later code as hidden fields on the new form to send the information off as paid. However when I do a repsonse.write to see what the information stored is. i always get the same name over and over. not exactly sure where i am missing it. Thanks for your help!
protected void Page_Load(object sender, EventArgs e)
{
if (Request.HttpMethod == "POST"){
formData attendee = new formData();
NameValueCollection nvc = Request.Form;
List<formData> attendees = new List<formData>();
if (!string.IsNullOrEmpty(nvc["fn1"]))
{
var fn = nvc["fn1"];
var ln = nvc["ln1"];
var email = nvc["email1"];
var wife = nvc["wife1"];
var luncheon = nvc["lucheon1"];
attendee.firstName = fn; attendee.lastName = ln; attendee.email = email; attendee.wife = wife; attendee.luncheon = luncheon;
attendees.Add(attendee);
}
if (!string.IsNullOrEmpty(nvc["fn2"]))
{
var fn = nvc["fn2"];
var ln = nvc["ln2"];
var email = nvc["email2"];
var wife = nvc["wife2"];
var luncheon = nvc["lucheon2"];
attendee.firstName = fn; attendee.lastName = ln; attendee.email = email; attendee.wife = wife; attendee.luncheon = luncheon;
attendees.Add(attendee);
}
foreach(var person in attendees)
{
Response.Write(person.firstName.ToString());
Response.Write(person.lastName.ToString());
Response.Write(person.email.ToString());
Response.Write(person.wife.ToString());
Response.Write(person.luncheon.ToString());
}
}
}
public class formData
{
public string firstName { get; set; }
public string lastName { get; set; }
public string email { get; set; }
public string wife { get; set; }
public string luncheon { get; set; }
}
}
I assume your array of Attendees is going to be of a data type like this... each attendee has a {fn, ln, email, wife, and luncheon} property. Looks like they're stored in the POST like {fn1, ln1, email1, wife1, luncheon1} is attendee(0), {fn2, ln2, email2, wife2, luncheon2} is attendee(1), etc.
You might try looping like this
var count = 1;
while (!string.IsNullOrEmpty(nvc["fn" + count]))
{
attendee(count).firstname=nvc["fn" + count]
attendee(count).lastname=nvc["ln" + count]
//...for the rest of the fields
count++; //be sure and increment count
}
Until there's data fn# that is null, it keeps looping through and creating attendees.
You are creating the list in the Page_Load event. The Page_Load event fires each time the page loads, postback or not and recreating the list every time that event fires.
You can create and use a session variable. As an example:
protected void Page_Load(object sender, EventArgs e)
{
List<formData> attendees;
if(Session["attendees"] == null)
{
attendees = new List<formData>();
// your logic logic ...
Session["attendees"] = attendees;
}
else
{
var attendees = (List<formData>)Session["attendees"];
// your logic ...
Session["attendees"] = attendees;
}
}
I'd also consider breaking your code out a bit. Perhaps putting the code that adds to the list in a separate method.
ASP.NET Session State Overview.
I want to get all of my information from the XML to the Datagrid.
Currently I have this:
class ColumnNames
{
public string id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Computer { get; set; }
public ColumnNames(
string id,
string Name,
string Surname,
string Computer,
{
this.id = id;
this.Name = Name;
this.Surname = Surname;
this.Computer = Computer;
}
}
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
XDocument getElement = XDocument.Load("details.xml");
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
var items = new List<ColumnNames>();
items.Add(new ColumnNames(id, Name, Surname, Computer));
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
}
This is my XML
<info>
<user id="1" Name="Max0" Surname="0test" Computer="0" />
<user id="2" Name="Max1" Surname="1test" Computer="1" />
<user id="3" Name="Max2" Surname="2test" Computer="2" />
<user id="4" Name="Max3" Surname="3test" Computer="3" />
<user id="5" Name="Max4" Surname="4test" Computer="4" />
<user id="6" Name="Max5" Surname="5test" Computer="5" />
</info>
What I am trying to do is put all of the data in the root element into a datagrid with the columns id, name, surname and computer. My problem is that at the moment it only gets the first entry and when I tried to use the following:
foreach (var npc in getElement.Root("info"))
I got an error saying
"Non-invocable member 'System.Xml.Linq.XDocument.Root' cannot be used
like a method."
I'm not sure how I can make this work, It's been quite some time that I have been trying to get this fixed, some help with be nice. Thanks.
UPDATE:
This is the new code after David generously helped me. Although it is still not working.
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
// getElement is a weird name for this object :)
XDocument getElement = XDocument.Load("details.xml");
// Create your List of ColmNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Root.Elements("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
You're close...
foreach (var npc in getElement.Descendants("user"))
Actually does work, and is correct, with the given XML structure. See the bottom of my answer for an important distinction, though.
The issue is that you are setting grid.ItemsSource = items; inside your foreach loop, thereby only adding one item each time the foreach loops around, and you always end up with just the last item in your datagrid.
Additionally, you need to hold the list of items outside the foreach loop as well, otherwise it only exists (only has scope) inside the foreach.
To fix that, you need to move grid.ItemsSource = items; to be after the foreach loop, and move the list creation to before the foreach loop:
// Your code
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
// getElement is a weird name for this object :)
XDocument getElement = XDocument.Load("details.xml");
// Create your List of ColumnNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
// You are adding an item to the existing List<ColumnNames> now, not a new one each time
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
Hope this helps!
With regard to your attempt at changing the XDocument call in your foreach loop, as I mentioned, you actually had it working correctly ... but to explain why the changed version doesn't work, since I wrote it already, see the below:
Your main issue is that XDocument has a Root element, in this case, <info>
The syntax you're using, getElement.Root("info") tells the compiler you want to call a method called Root of XDocument - but that method doesn't exist. It's a property. That's what your error message is telling you.
To fix the problem,
Change your foreach to use the .Elements() method of XDocument and all will be well:
foreach (var npc in getElement.Root.Elements("user"))
Or, as you already had it
foreach (var npc in getElement.Descendants("user"))
The main difference here is .Descendants() will get any node named "user" regardless of how deeply it is nested; .Elements() will only get the next level down from the current node (direct children).
So while with your XML structure they appear to work the same, it is important to understand this distinction in the event your XML structure changes (or the scenario/project is different, etc.).
As a side note, getElement is a weird name for an XDocument object; I usually just use something like xDoc...
One More Update - Why isn't this working?
As I realized this is a WPF datagrid, and you're loading a local XML file, it dawned on me that your grid shows nothing because XDocument.Load() fails to find the file.
I checked this with a try/catch in an empty WPF app with a DataGrid.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
XDocument getElement = null;// = XDocument.Load(#"c:\dev\stackoverflow\DataGridWpf\DataGridWpf\details.xml");
try
{
getElement = XDocument.Load(#"details.xml");
} catch (Exception ex) {
var test = ex.ToString();
}
// Create your List of ColumnNames outside the foreach to use later
var items = new List<ColumnNames>();
foreach (var npc in getElement.Descendants("user"))
{
string id = npc.Attribute("id").Value;
string Name = npc.Attribute("Name").Value;
string Surname = npc.Attribute("Surname").Value;
string Computer = npc.Attribute("Computer").Value;
// You are adding an item to the existing List<ColumnNames> now, not a new one each time
items.Add(new ColumnNames(id, Name, Surname, Computer));
}
// Finally, get a reference to the grid and set the ItemsSource property to use
// your full list containing all the items
var grid = sender as DataGrid;
grid.ItemsSource = items;
}
This code, if you step through, will throw the FileNotFound exception, which if unhandled just dies and you end up with an empty window!
In my case the full path did the trick and the grid loads up nicely:
Larry Wall, the author of Perl, describes laziness as the 1st great virtue of a programmer. Don't do more work than you need to (somebody will have to maintain it...and it might be you). So...
Why don't you decorate your data structure with XML serialization attributes, something like:
[XmlRoot("info")]
public class InfoList
{
[XmlElement("user")]
public User[] Users { get ; set ; }
public class User
{
[XmlAttribute] public string id { get; set; }
[XmlAttribute] public string Name { get; set; }
[XmlAttribute] public string Surname { get; set; }
[XmlAttribute] public string Computer { get; set; }
}
}
Wrap the deserialization in a method:
static User[] LoadUserDetails()
{
XmlSerializer serializer = new XmlSerializer(typeof(InfoList)) ;
using ( Stream s = File.Open("details.xml",FileMode.Open,FileAccess.Read,FileShare.Read) )
{
InfoList instance = (InfoList) serializer.Deserialize(s) ;
return instance.Users ;
}
}
And your method then looks something like:
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
DataGrid grid = (DataGrid) sender ;
grid.ItemsSource = LoadUserDetails() ;
return ;
}
Which is easier to test and/or maintain?
I'm trying to create a collection of business objects from the following xml document using .net 4.0/c#
<WebServices ErrorFound="False" ServerDateTime="30/11/2010 14:58:58">
<Results>
<Users TotalResults="5">
<UserName UserID="2">John</UserName>
<UserName UserID="3">Dave</UserName>
<UserName UserID="4">Jim</UserName>
<UserName UserID="5">Bob</UserName>
</Users>
</Results>
</WebServices>
This is the class I need to create
public class User
{
public string Name { get; set; }
public int ID { get; set; }
}
Each of the Users child elements should be a new instance of the class.
So far I have this method which accepts an XmlDocument.
public static IEnumerable<User> GetUser(XmlDocument xmlDoc)
{
XmlReader reader = XmlReader.Create(new StringReader(xmlDoc.OuterXml));
XDocument doc = XDocument.Load(reader);
var user = from u in doc.Descendants("WebServices").DescendantsAndSelf("Users")
select new User()
{
Name = u.Element("UserName").Value,
ID = int.Parse(u.Element("UserName").Attribute("UserID").Value)
};
List<User> userInstance = user.ToList();
IEnumerable<User> users= from u in userInstance
select u;
return users;
}
This works fine as far as producing one instance of the object from the first child element is concerned but I am unsure as to how to create multiple instances from all the elements.
I need to be able to return a collection of the User objects eg Collection<User> users = new Collection<User>()
I could be barking up completely the wrong tree. Any help is much appreciated.
Using XPath you can write the code like this:
public class User
{
public string Name { get; set; }
public int ID { get; set; }
}
static void Main(string[] args)
{
string xml =
"<WebServices ErrorFound='False' ServerDateTime='30/11/2010 14:58:58'>" +
"<Results>" +
"<Users TotalResults='5'>" +
"<UserName UserID='2'>John</UserName>" +
"<UserName UserID='3'>Dave</UserName>" +
"<UserName UserID='4'>Jim</UserName>" +
"<UserName UserID='5'>Bob</UserName>" +
"</Users>" +
"</Results>" +
"</WebServices>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
var users = from userNameElement in doc.SelectNodes("/WebServices/Results/Users/UserName").OfType<XmlElement>()
select new User
{
Name = userNameElement.InnerText,
ID = Int32.Parse(userNameElement.GetAttribute("UserID"))
};
foreach (var user in users)
{
Console.WriteLine(user.ID.ToString() + ": " + user.Name);
}
}
Ok, so I finally figured out what was wrong with my code.
Instead of
var user = from u in doc.Descendants("WebServices").DescendantsAndSelf("Users")
select new User()
{
Name = u.Element("UserName").Value,
ID = int.Parse(u.Element("UserName").Attribute("UserID").Value)
};
I now have
var user = (from u in doc.Descendants("UserName")
select new Provider()
{
Name = u.Value,
ID = int.Parse(u.Attribute("UserID").Value)
});
the statement: doc.Descendants("UserName") basically produces an array of the UserName elements which can be iterated through, I can then directly access the value of that element and set it to my class properties.