I am trying to make a simple grid view that is binded to a simple xml document but I must be missing something since I am keep getting error message:
The data source for GridView with id
'GridView1' did not have any
properties or attributes from which to
generate columns. Ensure that your
data source has content.
Code
<asp:GridView ID="GridView1" runat="server" DataSourceID="XmlDataSource1">
<Columns>
<asp:BoundField DataField="id" HeaderText="ID" SortExpression="id" />
</Columns>
</asp:GridView>
<asp:XmlDataSource ID="XmlDataSource1" runat="server"
DataFile="Notifications.xml" XPath="/data/node"></asp:XmlDataSource>
XML
<?xml version="1.0" encoding="utf-8" ?>
<data>
<node>
<id>1096</id>
<name>About Us</name>
<date>21/12/2009 17:03:43</date>
<user id="1">writer</user>
</node>
<node>
<id>1099</id>
<name>News</name>
<date>21/12/2009 17:03:47</date>
<user id="1">writer</user>
</node>
<node>
<id>1098</id>
<name>Another page</name>
<date>21/12/2009 17:03:52</date>
<user id="1">writer</user>
</node>
</data>
Is it perhaps my xpath that is wrong or am I making something fundamentally wrong here?
There are a number of ways to get this to work:
Use Brian's solution, which is to rewrite the XML to use attributes instead of sub-nodes.
Use an XSLT transform to dynamically convert the child nodes to attributes. See this SO question for an XSLT that can perform that operation.
Load the XML data into a DataSet, which internally does this conversion.
Here's an example of how to do #3:
DataSet ds = new DataSet();
ds.ReadXml(MapPath("~/App_Data/mydata.xml"));
GridView1.DataSource = ds;
GridView1.DataBind();
The limitation of this last approach is that you don't get automatic databinding as you would with a data source control. However, since the XmlDataSource is a read-only control anyway, that's not necessarily a serious limitation.
XmlDataSource works with attributes, not child entities. You need to do:
<node id="1096" name="About Us" ../>
Instead of using child elements. Unfortunately it is this way; I really wish it would work with the alternative; I like that approach much better.
try changeing the xpath to
look like XPath="data/node"
Dynamic Databind to XML Document
If your Xml is structured with more info, you will be able to iterate over the structure with more ease, as its easier to identify the exact node your looking for.
We have a web service that returns XML in a row/column structure (similar to your data example above)
For speed Ive copy/pasted our solution, but you should get the gist and be able to hack it to do your thing.
<response xmlns="">
<method name="ExecuteMethod">
<message>Query Successful</message>
<summary success="true" rowcount="2" />
<row>
<column name="ID"><![CDATA[SomeData]]></column>
<column name="NHS_NO"><![CDATA[SomeData]]></column>
<column name="HOSPITALNUMBER"><![CDATA[SomeData]]></column>
<column name="SURNAME"><![CDATA[SomeData]]></column>
<column name="FIRST_FORENAME"><![CDATA[SomeData]]></column>
<column name="TITLE"><![CDATA[SomeData]]></column>
<column name="SEX"><![CDATA[SomeData]]></column>
<column name="DOB">SomeData</column>
<column name="ADDRESS"><![CDATA[SomeData]]></column>
<column name="POSTCODE"><![CDATA[SomeData]]></column>
<column name="DOD" />
</row>
<row>
<column name="ID"><![CDATA[SomeData]]></column>
<column name="NHS_NO"><![CDATA[SomeData]]></column>
<column name="HOSPITALNUMBER"><![CDATA[SomeData]]></column>
<column name="SURNAME"><![CDATA[SomeData]]></column>
<column name="FIRST_FORENAME"><![CDATA[SomeData]]></column>
<column name="TITLE"><![CDATA[SomeData]]></column>
<column name="SEX"><![CDATA[SomeData]]></column>
<column name="DOB">SomeData</column>
<column name="ADDRESS"><![CDATA[SomeData]]></column>
<column name="POSTCODE"><![CDATA[SomeData]]></column>
<column name="DOD" />
</row>
</method>
</response>
Here's the c# implementation
we get the Column names out to pass to the data in to the Gridviews.Datakey names array
we loop over the rows, adding each row to the dataset as we go along
we set the gridviews datasounce to the dataset
we bind()
There's a bit of css and the control instance for your ease of copy/paste in the example below.
//In Code In Front...
Table.DataGridView{float:left; width:100%;}
Table.DataGridView tr{}
Table.DataGridView th{ background-color:Gray; font-weight:bold; color:White;}
Table.DataGridView td{ background-color:White; color:Black; font-weight:normal;}
<asp:GridView ID="DataGridView" runat="server" CssClass="DataGridView" GridLines="Both" Visible="false" />
//In Code Behind...
XmlNode myXmlNodeObject = myXmlDocService.GetData(_xmlDataString);
//Bind To GridView
//Create a DataSet To Bind To
DataSet ds = new DataSet();
ds.Tables.Add("XmlDataSet");
//Get Column Names as String Array
XmlDocument XMLDoc = new XmlDocument();
XMLDoc.LoadXml("<result>" +myXmlNodeObject.ChildNodes.Item(0).ChildNodes.Item(2).ParentNode.InnerXml + "</result>"); //Get Row/Columns
int colCount = myXmlNodeObject.ChildNodes.Item(0).ChildNodes.Item(2).SelectNodes("column").Count;
string[] ColumnNameArray = new string[colCount];
int iterator = 0;
foreach(XmlNode node in myXmlNodeObject.ChildNodes.Item(0).ChildNodes.Item(2).SelectNodes("column"))
{
ColumnNameArray.SetValue(node.Attributes["name"].Value ,iterator);
ds.Tables["XmlDataSet"].Columns.Add(node.Attributes["name"].Value); //Create individual columns in the dataset
iterator++;
}
//Get Data Row By Row to populate the DataSet.Rows
foreach(XmlNode RowNode in XMLDoc.ChildNodes.Item(0).SelectNodes("row"))
{
string[] rowArray = new string[colCount];
int iterator2 = 0;
foreach(XmlNode ColumnNode in RowNode.ChildNodes)
{
rowArray.SetValue(ColumnNode.InnerText, iterator2);
iterator2++;
}
ds.Tables["XmlDataSet"].Rows.Add(rowArray);
}
DataGridView.DataSource = ds.Tables["XmlDataSet"];
DataGridView.DataKeyNames = ColumnNameArray;
DataGridView.DataBind();
DataGridView.Visible = true;
Related
I'm trying to read an xml file into a datagridview using c# and im having this error (System.Data.DuplicateNameException: 'A Relation named 'database_table' already belongs to this DataSet.') on the line ds.ReadXml(opf.FileName);
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog opf = new OpenFileDialog();
opf.Filter = "XML File | *.xml";
opf.FileName = "";
if (opf.ShowDialog() == DialogResult.OK)
{
DataSet ds = new DataSet();
ds.ReadXml(opf.FileName);
dataGridView1.DataSource = ds.Tables["Record"];
}
}
I tried doing
DataSet ds = new DataSet();
ds.Tables.Remove("database_table");
ds.ReadXml(opf.FileName);
dataGridView1.DataSource = ds.Tables["Record"];
still received the same error
Here is the XML File
<!--
- Database: 'sialabact'
-->
<database name="sialabact">
<!-- Table studentinfo -->
<table name="studentinfo">
<column name="Stud_No">20-1399</column>
<column name="FirstName">Al-Sharief</column>
<column name="LastName">Dinglasa</column>
<column name="Section">SBIT3E</column>
</table>
<table name="studentinfo">
<column name="Stud_No">21-4874</column>
<column name="FirstName">Ryan</column>
<column name="LastName">Wibowo</column>
<column name="Section">SFIT4A</column>
</table>
<table name="studentinfo">
<column name="Stud_No">19-2317</column>
<column name="FirstName">Mark</column>
<column name="LastName">Gallego</column>
<column name="Section">BAENT3C</column>
</table>
<table name="studentinfo">
<column name="Stud_No">20-1114</column>
<column name="FirstName">Carlo</column>
<column name="LastName">Enrile</column>
<column name="Section">SBIT3M</column>
</table>
</database>
This XML file was exported from MySQL
I have a XML that has a structure similar to this one:
<?xml version="1.0" encoding="UTF-8"?>
<CompanyName>
<AttrContainer>
<Attr type="String">
<Name value="'Name'" />
<Value value="'AttrContainer'" />
</Attr>
<SubContainer>
<AttrContainer value="'WSSMetadata'" />
<AttrContainer>
<Attr type="String">
<Name value="'Name'" />
<Value value="'AttrContainer'" />
</Attr>
<SubContainer>
<WSSMetadata value="'afe2e194-0ce7-4bfc-b446-9623e4fe7189'" />
<AttrContainer>
<Attr type="String">
<Name value="'Name'" />
<Value value="'WSSMetadata'" />
</Attr>
<Attr type="Uuid">
<Name value="'scanID'" />
<Value value="afe2e194-0ce7-4bfc-b446-9623e4fe7189" />
</Attr>
<Attr type="String">
<Name value="'imagePath'" />
</Attr>
<Attr type="String">
<Name value="'imagePathHD'" />
</Attr>
<Attr type="String">
<Name value="'imagePathThumbnail'" />
</Attr>
<Attr type="String">
<Name value="'imagePathGrey'" />
<Value value="'Images/afe2e194-0ce7-4bfc-b446-9623e4fe7189_grey.jpg'" />
</Attr>
<Attr type="String">
<Name value="'imagePathGreyHD'" />
<Value value="'Images/afe2e194-0ce7-4bfc-b446-9623e4fe7189_grey_hd.jpg'" />
</Attr>
<Attr type="String">
<Name value="'imagePathGreyThumbnail'" />
<Value value="'Images/afe2e194-0ce7-4bfc-b446-9623e4fe7189_grey_thumbnail.jpg'" />
</Attr>
</AttrContainer>
</SubContainer>
</AttrContainer>
</SubContainer>
</AttrContainer>
</CompanyName>
and I am trying to parse it using this code (Linq to XML)
var xmlContent = File.ReadAllText(filePathName);
var doc = XDocument.Parse(xmlContent);
var attr = doc.Root.Elements("CompanyName");
var x = attr.ToList();
but it x has no element.
My questions:
What is wrong with this code that I am not able to get the CompanyName element?
How can I get list of all <SubContainer> elements?
When I got the list of <SubContainer> elements, how can I read read and change its content?
I think you want this instead:
var attr = doc.Root.Elements("AttrContainer");
.Elements returns child elements of that name. CompanyName is you root node, and you're trying to search for its children which are AttrContainer.
What is wrong with this code that I am not able to get the companyname element?
The root element of your xml is CompanyName. So what your code is doing, it's essentially asking 'give me all CompanyName elements that are children of my root CompanyName element'. Hence the list is empty.
how can I get list of all SubContainer elements.
You can use
var subContainers = doc.Root.Descendants("SubContainer");
when I got the list of SubContainer elements, how can I read read and change its content?
foreach (var subContainer in subContainers)
{
foreach (var attrContainer in subContainer.Elements("AttrContainer"))
{
var attr = attrContainer.Elements("Attr").FirstOrDefault();
if (attr != null)
{
var oldValue = attr.Attribute("type").Value;
attr.Attribute("type").Value = "something completely different";
}
}
}
This reads and changes the type on each first Attr element (assuming one exists) in all AttrContainers in all SubContainers - hopefully you can derive something meaningful out of that.
doc.Root returns the element <CompanyName>, so further selecting elements named CompanyName won't return any elements. You're effectively trying to select all <CompanyName> elements that are children of <CompanyName>.
This code will select all <SubContainer> elements no matter their depth. I'm suggesting this because your example XML has several <SubContainer> elements.
// Read all Attr elements
IEnumerable<XElement> subContainerElements = doc.Root.Descendants("SubContainer");
foreach (XElement subContainerElement in subContainerElements)
{
// Work with <SubContainer> element here
}
Hi I need to insert some lines into an xml file and save it how I should do it?
the xml file is
<?xml version="1.0" encoding="utf-8"?>
<Dashboard CurrencyCulture="en-US">
<Title Text="Dashboard" />
<DataConnections>
<DataConnection Name="Database1Connection" ProviderKey="Access2007" ConnectionString="XpoProvider=MSAccess;Provider=Microsoft.ACE.OLEDB.12.0;Mode=Share Deny None;data source=D:\Sina\Desktop\Omid\Database1.accdb;Jet OLEDB:Database Password=;">
<Parameters>
<Parameter Name="database" Value="D:\Sina\Desktop\Omid\Database1.accdb" />
<Parameter Name="read only" Value="1" />
<Parameter Name="generateConnectionHelper" Value="false" />
</Parameters>
</DataConnection>
</DataConnections>
<DataSources>
<DataSource Name="Data Source 1">
<DataProvider DataConnection="Database1Connection" SupportSql="true" />
</DataSource>
<DataSource Name="Query 2" />
</DataSources>
and I need to insert these lines
<Selection>
<Table Name="Query2">
<Columns>
<Column Name="PName" />
<Column Name="Prog" />
<Column Name="RDate" />
</Columns>
</Table>
</Selection>
between
<DataProvider DataConnection="Database1Connection" SupportSql="true">
.
.(here)
</DataProvider>
Here is a complete console application that will take the file you provided as input and build a new file with the nodes added.
NOTE: you will clearly have to make the code more dynamic as this is very static. For example, you would build the Column elements with another list and a loop likely.
class Program
{
static void Main(string[] args)
{
var doc = XDocument.Load("XMLFile1.xml");
var selection = new XElement("Selection");
var table = new XElement("Table");
table.Add(new XAttribute("Name", "Query2"));
var columns = new XElement("Columns");
var column = new XElement("Column");
column.Add(new XAttribute("Name", "PName"));
columns.Add(column);
column = new XElement("Column");
column.Add(new XAttribute("Name", "Prog"));
columns.Add(column);
column = new XElement("Column");
column.Add(new XAttribute("Name", "RDate"));
columns.Add(column);
table.Add(columns);
selection.Add(table);
var dataProvider = doc.Root.Descendants("DataProvider").First();
dataProvider.Add(selection);
doc.Save("XMLFile2.xml");
}
}
The output of the new file looks like this:
<DataSources>
<DataSource Name="Data Source 1">
<DataProvider DataConnection="Database1Connection" SupportSql="true">
<Selection>
<Table Name="Query2">
<Columns>
<Column Name="PName" />
<Column Name="Prog" />
<Column Name="RDate" />
</Columns>
</Table>
</Selection>
</DataProvider>
</DataSource>
<DataSource Name="Query 2" />
</DataSources>
Try this,
XmlDocument document = new XmlDocument();
document.Load(filename);
XmlElement childElement = document.CreateElement("child");
XmlNode parentNode = document.SelectSingleNode("root/firstLevel/parent");
parentNode.AppendChild(childElement);
A quick approach would be:
string file = "XMLFile1.xml";
string text = File.ReadAllText(file);
text = text.Replace("<DataProvider DataConnection=\"Database1Connection\" SupportSql=\"true\" />",
"<DataProvider DataConnection=\"Database1Connection\" SupportSql=\"true\">" +
"<Selection>" +
"<Table Name=\"Query2\">" +
"<Columns>" +
" <Column Name=\"PName\" />" +
"<Column Name=\"Prog\" />" +
"<Column Name=\"RDate\" />" +
"</Columns>" +
"</Table>" +
"</Selection>" +
"</DataProvider> ");
File.WriteAllText(file, text);
I try to get some specific values from an xml config. See example below.
<?xml version="1.0" encoding="utf-8"?>
<ExcelConfig>
<ExcelDocument name="Customer" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="Company" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="SomeOtherType" type="block">
<IdentityBlock>
<Column name="Period" col="A" />
<Column name="Period2" col="B" />
</IdentityBlock>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
</Validate>
</ExcelDocument>
</ExcelConfig>
I use the following code to get some information from the excel file.
"ValidationConfiguration" is the string with the previous configuration.
//Get Different NodeTypes of Excel documents
List<XPathNavigator> types = XmlHelper.GetNodeTypes(validationConfiguration, "/ExcelConfig/ExcelDocument");
List<XPathNavigator> flatTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='flat']");
List<XPathNavigator> blockTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='block']");
//First we check if the file is from the flat type and get the IdentityColumns
List<XPathNavigator> identityColumnsNode = XmlHelper.GetNodeTypes(validationConfiguration, "//ExcelConfig/ExcelDocument[#type='flat']/IdentityColumn");
You can find the XmlHelper class below.
public static class XmlHelper
{
public static List<XPathNavigator> GetNodeTypes(string xmlConfiguration,string xPath)
{
XPathDocument doc = new XPathDocument(new StringReader(xmlConfiguration));
XPathNavigator nav = doc.CreateNavigator();
XPathExpression expr = nav.Compile(xPath);
List<XPathNavigator> elements = new List<XPathNavigator>();
foreach (XPathNavigator node in nav.Select(expr))
{
elements.Add(node);
}
return elements;
}
public static List<string> GetIdentityColumnNames(List<XPathNavigator> xPathNavigators)
{
List<string> identityColumns = new List<string>();
foreach (XPathNavigator xPathNavigator in xPathNavigators)
{
foreach (XPathNavigator test in xPathNavigator.Select("//Column"))
{
identityColumns.Add(test.GetAttribute("name", ""));
}
}
return identityColumns;
}
}
Now i want to do the following. I selected the identityColumnsNodes(they contains the IdentityColumn from the exceldocuments that have the flat type).
The i get for al that types the colums. But when i try that, i get all columns back from the whole file. He don't only the items from the node that i use.
foreach (XPathNavigator identityColumNode in identityColumnsNode)
{
List<string> identityColumns = XmlHelper.GetIdentityColumnNames(identityColumnsNode);
}
The second problem/thing i want to do --> the best way to select the right validate node from the specific file. With the identityColumns (that i get back and my list of HeaderRow Cells i know what file it is. But how can i select that validate node?
Or are their better methods to do this stuff?
I have an XML that is laid out to be reformatted into nested HTML table headers. I am working on getting each tier of the XML document into it's own list. For example:
<column name="Total" size="0">
<column name="Users" size="0" />
</column>
<column name="Date" size="0" />
<column name="Unique" size="0">
<column name="Clicks" size="0">
<column name="RC" size="0" />
<column name="CB" size="0" />
</column>
</column>
From this example, columns "Total", "Date", and "Unique" should be in the first list. Columns "Users" and "Clicks" should be in the second list. And, columns "RC" and "CB" should be in the third list. This should be accomplished using recursion to make the method completely dynamic. Any help is greatly appreciated.
Here you go:
XElement root = XElement.Parse(#"
<doc>
<column1>
<column2 />
</column1>
<column3 />
<column4>
<column5>
<column6 />
<column7 />
</column5>
</column4>
</doc>");
List<List<XElement>> outerList = new List<List<XElement>>();
List<XElement> innerList = root.Elements().ToList();
while (innerList.Any())
{
outerList.Add(innerList);
innerList = innerList.SelectMany(element => element.Elements()).ToList();
}
Edit: If you want to strip ancestor XElement instances of their descendants within your list, then you could use the following:
XElement root = XElement.Parse(#"
<table>
<column name=""Total"" size=""0"">
<column name=""Users"" size=""0"" />
</column>
<column name=""Date"" size=""0"" />
<column name=""Unique"" size=""0"">
<column name=""Clicks"" size=""0"">
<column name=""RC"" size=""0"" />
<column name=""CB"" size=""0"" />
</column>
</column>
</table>");
List<List<XElement>> outerList = new List<List<XElement>>();
IEnumerable<XElement> innerList = root.Elements();
while (innerList.Any())
{
outerList.Add(innerList.Select(e => new XElement(e.Name, e.Attributes())).ToList());
innerList = innerList.SelectMany(element => element.Elements());
}
Note: For the record, your intuition that you should use recursion was correct. However, it is also well known that any recursive function can be converted to an iteration, typically by simulating the stack. Sometimes, this leads to bloated code; however, other times, the conversion lends itself naturally. In your case, if you were to recurse, your recursive parameter would have been the immediate children of the set of elements currently being considered – which already happens to be available in innerList, thus allowing us to use the innerList = innerList.<SequenceOperation> trick to substitute the recursion.