How to read an xml subnode into ASP.NET repeater - c#

I am reading XML using xmlreader, and then binding the xml to a Repeater
Here's the code behind:
XmlReaderSettings settings = new XmlReaderSettings();
settings.ProhibitDtd = false;
XmlReader xmlData = XmlReader.Create(webClient.OpenRead(requestUrl), settings);
try
{
xmlData.ReadToFollowing("SearchResults");
Label1.Text = xmlData.GetAttribute("TotalCount");
int numberResults = Convert.ToInt32(xmlData.GetAttribute("TotalCount"));
if (numberResults > 0)
{
DataSet ds = new DataSet();
ds.ReadXml(xmlData);
Repeater1.DataSource = ds.Tables[1];
Repeater1.DataBind();
}
else
{
Repeater1.DataSource = null;
Repeater1.DataBind();
}
}
Here's the repeater
<asp:Repeater ID="Repeater1" runat="server">
<HeaderTemplate><b>Results</b><br /><br /></HeaderTemplate>
<ItemTemplate>
<a href="<%#DataBinder.Eval(Container.DataItem, "Url")%>">
<%#DataBinder.Eval(Container.DataItem, "Title")%></a><br />
</ItemTemplate>
</asp:Repeater>
And the xml looks like:
<SearchResults PageSize="1" PageIndex="0" TotalCount="155">
<SearchResult>
<ContentId>2458</ContentId>
<Title>Component description</Title>
<Url>http://whatever/19/p/1537/2458.aspx</Url>
<Date>2009-06-10T09:34:00+01:00</Date>
<ContentType>forum</ContentType>
<Tags>
<Tag>Component</Tag>
</Tags>
<Users>
<User>
<Id>2533</Id>
<DisplayName>Haubent</DisplayName>
<Username>Haubent</Username>
</User>
</Users>
<IndexedAt>2010-07-29T15:40:52.414+01:00</IndexedAt>
</SearchResult>
So far so good.
But - I want to be able to show the contents of the DisplayName node in my repeater item template.
I tried
<%#DataBinder.Eval(Container.DataItem, "DisplayName")%>
and
<%#DataBinder.Eval(Container.DataItem, "Users.User.DisplayName")%>
but I get an error:
System.Web.HttpException: DataBinding: 'System.Data.DataRowView' does not contain a property with the name 'Users'.
How to get at the DisplayName?

I think the best (and most natural) way to do this is by using Linq To XML.
You can change your try block in this way:
try
{
xmlData.ReadToFollowing("SearchResults");
Label1.Text = xmlData.GetAttribute("TotalCount");
int numberResults = Convert.ToInt32(xmlData.GetAttribute("TotalCount"));
if (numberResults > 0)
{
XDocument xml = XDocument.Parse(xmlData.ReadOuterXml());
Repeater1.DataSource = xml.Element("SearchResults").Elements("SearchResult");
Repeater1.DataBind();
}
else
{
Repeater1.DataSource = null;
Repeater1.DataBind();
}
}
catch
{
// do some catch
}
passing an XElement collection to de repeater. Then you can declare an OnRepeaterDataBound method similar to this:
protected void Repeater1_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
if ((e.Item.ItemType == ListItemType.Item) || ((e.Item.ItemType == ListItemType.AlternatingItem)))
{
XElement User = ((XElement)e.Item.DataItem).Element("Users").Element("User");
HyperLink hlUrl = ((HyperLink)e.Item.FindControl("hlUrl"));
hlUrl.NavigateUrl = ((XElement)e.Item.DataItem).Element("Url").Value;
hlUrl.Text = User.Element("DisplayName").Value;
}
}
Having the repeater as:
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_OnItemDataBound">
<HeaderTemplate>
<b>Results</b><br />
<br />
</HeaderTemplate>
<ItemTemplate>
<asp:HyperLink ID="hlUrl" runat="server" />
</ItemTemplate>
</asp:Repeater>
On the Repeater1_OnItemDataBound you will be able to parse your xml in the way you want.

If you inspect (by setting a breakpoint at ds.ReadXml(xmlData);) the dataset you'll see that the Users and User elements actually ends up as records in their own tables. The records have id columns that enables you to relate the rows back to the containing SearchResult row.
In your sample your are binding Tables[1], which is the SearchResult table, to the repeater. You should rather make a selection into the dataset joining the three tables together. LINQ should prove useful here.

You can use xml deserialization to helper class, that copies the structure of xml nodes and inside the repeater create another repeater for Users collection like this:
<asp:Repeater runat="server" DataSource='<%# Eval("Users") %>' >...

Related

Nested CMS.Repeater not returning the DocumentName property value correctly

I have implemented web part to support nested repeaters in order to implement mega menu using kentico 8
What I did was created following repeaters in web part
<div>
<cms:CMSRepeater ID="CMSRepeater6" runat="server" Path="/%" ClassNames="CMS.MenuItem" OrderBy="NodeOrder" WhereCondition="DocumentMenuItemHideInNavigation = 0 AND Published = 1 AND MenuItemGroup='Top'" MaxRelativeLevel="1" DelayedLoading="true" OnItemDataBound="CMSRepeater6_ItemDataBound" >
<ItemTemplate>
<div>
<li>
<h3><%# Eval("DocumentName")%></h3>
</li>
</div>
<cms:CMSRepeater ID="repMegaMenuLevel1" runat="server" Path="/%" ClassNames="CMS.MenuItem" OrderBy="NodeOrder">
<ItemTemplate>
</ItemTemplate>
</cms:CMSRepeater>
</ItemTemplate>
</cms:CMSRepeater>
</div>
And tried to bind the transformation in code level
protected void CMSRepeater6_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
RepeaterItem item = e.Item;
if (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)
{
var dr = item.DataItem as DataRowView;
string menuType = dr["MenuItemType"].ToString();
if (!string.IsNullOrEmpty(menuType))
{
var rptMegaMenuItem = item.FindControl("repMegaMenuLevel1") as CMSRepeater;
if (menuType.Equals("1"))
{
rptMegaMenuItem.TransformationName = "Content.PageProductCategory.MagaMenu11";
rptMegaMenuItem.WhereCondition = "MenuItemType = 1 AND MenuItemGroup='Top' AND DocumentMenuItemHideInNavigation = 0 ";
rptMegaMenuItem.NestedControlsID = "repMegaMenuLevel3";
rptMegaMenuItem.DelayedLoading = true;
}
}
}
}
Transformation code:
<cms:CMSRepeater ID="repMegaMenuLevel3" runat="server" DataBindByDefault="false" EnableViewState="false" ClassNames="Content.PageProductCategory;CMS.MenuItem" MaxRelativeLevel="1" Path="/What-we-do/%">
<ItemTemplate>
<li><%# Eval("DocumentName")%></li>
<li><%# Eval("DocumentNamePath")%></li>
<li><%# Eval("ClassName")%></li>
</ItemTemplate>
</cms:CMSRepeater>
<script runat="server">
protected override void OnInit(EventArgs e) {
repMegaMenuLevel3.Path = "/What-we-do/%";
repMegaMenuLevel3.ClassNames="Content.PageProductCategory";
repMegaMenuLevel3.ReloadData(true);
}
</script>
The issue is that the repeater repMegaMenuLevel3 always returned the Document Name as the parents (CMS.MenuItem) DocumentName i.e "What we do" not from the content.PageProductCategory, I tried by setting the paths in different manner, but no luck
I have a feeling that you unnecessarily set too much properties :)
Use NestedControlsID correctly - when a parent control has it set avoid setting Path on child controls.
Also avoid calling ReloadData(true) on child controls - parent control will do that for you
Consolidate usage of ClassNames (I'd recommend to set it only in ASPX markup)
Get rid of custom _ItemDataBound - try to move your code to the transformation
If you want to try different approach I'd recommend using hierarchical transformations.
Resources:
Using nested controls in Kentico
Using Hierarchical transformations in Kentico

Repeater bound with two tables

I am working on a project in asp.net. I have used two nested repeaters to show status and comments on that status. The nested repeater is bound to a data source which has two tables. Now when I #Eval the value of column of second table it is showing a not contain property name error.
<ItemTemplate>
<div style="height:285px;">
<img src='ProfilePic/<%#Eval("ProfilePic")%>' width="100" height="100" alt="" />
<asp:LinkButton ID="lnkfrndname" OnClick="lnkfrndname_Click" CommandName="frndname" CommandArgument='<%#Eval("UserName") %>' runat="server"> <u><%#Eval("Firstname")%> <%#Eval("LastName")%></u></asp:LinkButton>
<br />
<%#Eval("StatusText")%>
<br />
<asp:HiddenField ID="hfstatusid" Value='<%# Eval("StatusId") %>' runat="<asp:Repeater ID="replike" runat="server">
< <asp:Literal ID="ltlstatuscomm" Text='<%#Eval("CommentText") %>' runat="server"></asp:Literal>
</ItemTemplate>
</ </ItemTemplate>
</asp:Repeater>
.cs
protected void rephomecontent_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if ((e.Item.ItemType == ListItemType.Item) || (e.Item.ItemType == ListItemType.AlternatingItem))
{
HiddenField hf = e.Item.FindControl("hfstatusid") as HiddenField;
if (hf != null)
{
Repeater rep = e.Item.FindControl("replike") as Repeater;
if (rep != null)
{
int statusid = int.Parse(hf.Value.ToString());
DataSet ds = new StatusLikeInfoAction().ViewStatusLike(statusid);
rep.DataSource = ds;
rep.DataBind();
}
}
}
}
It wound be good if you bind the repeater with Datatable instead of DataSet containing multiple table.
First Analyze whether you want to bind
ds.Tables[0]
Or
ds.Tables[1]
then bind the repeater as follows
rep.DataSource = ds.Tables[0];
Or
rep.DataSource = ds.Tables[1];
as per your choices

Gridview rowdatabound error : Multiple controls with the same ID 'hlLink' were found. FindControl requires that controls have unique ID

I have a gridview that has link and description to be rendered on the page.
written the below code in gridview in .aspx
<Columns>
<asp:TemplateField>
<ItemTemplate>
<p>
<asp:HyperLink ID="hlLink" runat="server" Target="_self"></asp:HyperLink></p>
</ItemTemplate>
<ItemTemplate>
<p>
<asp:Literal ID="litSummary" runat="server"></asp:Literal></p>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<AlternatingItemTemplate>
<p>
<asp:HyperLink ID="hlLink" runat="server" Target="_self"></asp:HyperLink></p>
</AlternatingItemTemplate>
<AlternatingItemTemplate>
<p>
<asp:Literal ID="litSummary" runat="server"></asp:Literal></p>
</AlternatingItemTemplate>
</asp:TemplateField>
</Columns>
and below in .aspx.csin gridview rowdataboundevent
protected void gvResults_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
SearchResultItem data = (SearchResultItem)e.Row.DataItem;
HyperLink hlLink = (HyperLink)e.Row.FindControl("hlLink");
Literal litSummary = (Literal)e.Row.FindControl("litSummary");
if (data.Description != null)
{
hlLink.Text = data.Title;
hlLink.NavigateUrl = data.Path.Replace("&", "&");
litSummary.Text = data.Description;
}
else
{
hlLink.Text = data.Path;
hlLink.NavigateUrl = data.Path.Replace("&", "&");
litSummary.Text = data.Path;
}
}
here SearchResultItem: is the result item that has link and description details.
First time when row bound event is called, it binds the data correctly, second time when called throws error "Multiple controls with the same ID 'hlLink' were found. FindControl requires that controls have unique IDs.
Please let me know whats error with the code.
Thanks
Problem : you are trying to create the same controls with same ID multiple times.
Solution : you need to remove the controls before creating them.
Try This:
void RemoveControls()
{
HyperLink l1 = (HyperLink)Page.FindControl("hlLink");
Literal l2 = (Literal)Page.FindControl("litSummary");
if(l1!= null)
Page.Controls.Remove(l1);
if(l2!= null)
Page.Controls.Remove(l2);
}
Solution 2: Pagination for Repeater control.
for implementing pagination in Repeater control you need to create PagedDataSource.
Try This:
PagedDataSource pds = new PagedDataSource();
pds.DataSource = ds.Tables[0].DefaultView;
pds.AllowPaging = true;
pds.PageSize = 8;//page sizes

Databind repeater using Linq with group by

I need to bind a repeater with hierarchical data as follows:
Category1
- Item1
- Item2
Category2
- Item3
- Item4
I currently have a single dataset with the items as well as the category that each item belongs to.
I'm trying to learn Linq and was wondering if there was a way I can do the same using Linq?
Below is what I tried:
var groupbyfilter = from dr in dtListing.AsEnumerable()
group dr by dr["Category"];
DataTable dtFinal = dtListing.Clone();
foreach (var x in groupbyfilter)
x.CopyToDataTable(dtFinal, LoadOption.OverwriteChanges);
rptList.DataSource = dtFinal;
rptList.DataBind();
But trouble with it is it repeats category for each item.
You'll need a repeater nested inside another.
Do a distinct on the dtlisting selecting only the category field. Bind this to the outer repeater.
In the 2nd repeater, select data whose where condition has category field which equals to the value that is being databound to the repeater item. You'd have to handle this in the repeater's onitem_databound event.
Here is an example.
<%# Import Namespace="System.Data" %>
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<div>
Category: <b><%# Container.DataItem%></b>
<asp:Repeater ID="Repeater2" runat="server">
<FooterTemplate>
<%="</ul>" %>
</FooterTemplate>
<HeaderTemplate>
<%= "<ul>"%>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# ((Data.DataRow)Container.DataItem)[1] %>, <%# ((Data.DataRow)Container.DataItem)[0] %>
</li>
</ItemTemplate>
</asp:Repeater>
</div>
</ItemTemplate>
</asp:Repeater>
For this sample I used a csv as my datasource, and created a datatable using it. So my codebehind looks like:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class _Default : System.Web.UI.Page
{
DataTable csvData;
protected void Page_Load(object sender, System.EventArgs e)
{
csvData = Utils.csvToDataTable("data.csv", true);
GridView1.DataSource = csvData;
GridView1.DataBind();
Repeater1.DataSource =
(from x in csvData.AsEnumerable() select x["category"]).Distinct();
Repeater1.DataBind();
}
protected void Repeater1_ItemDataBound(object sender, System.Web.UI.WebControls.RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item |
e.Item.ItemType == ListItemType.AlternatingItem) {
Repeater rptr = (Repeater)e.Item.FindControl("Repeater2");
rptr.DataSource =
csvData.AsEnumerable().Where(x => x["category"].Equals(e.Item.DataItem));
rptr.DataBind();
}
}
}
You can either use nested repeaters like deostroll says, or specify a grouping field and have a group header in your ItemTemplate that's dynamically hidden (or rendered) every time your grouping field changes.
More detail in fernan's answer at http://forums.asp.net/t/1252235.aspx

How to nest repeaters in asp.net

I need to know how to nest repeaters within a user control. The html side of things is fine, it's the binding and code behind I need help with. I've only been able to find examples using an sql data source, which doesn't really help.
My repeaters look like this:
<asp:Panel ID="pnlDiscipline" runat="server" CssClass="">
<asp:Repeater ID="rptDiscipline" runat="server">
<ItemTemplate>
<h4><%#Eval("Discipline")%></h4>
<ul>
<asp:Repeater ID="rptPrograms" runat="server">
<ItemTemplate>
<li><asp:HyperLink runat="server" Text='<%#Eval("Name") %>' NavigateUrl='<%#Eval("Link") %>'></asp:HyperLink> <%#Eval ("Notation") %></li>
</ItemTemplate>
</asp:Repeater>
</ul>
</ItemTemplate>
</asp:Repeater>
What I need to do is hopefully reasonably clear - the h4 discipline should appear once, all the entries that belong to the discipline are listed below, then the next h4, then the appropriate list, the next h4 and so on.
The datasource is a dataview created in the codebehind where each row has 'Name', "Link', 'NOtation' and 'Discipline'. I've bound the dataview to the outermost repeater, and it behaves as expected - lists the discipline name for each entry, but shows no data in the inner repeater.
How do I go about making this work?
EDIT: Just to clarify, I have one datatable in the codebehind. Each row in that table is an item, each item belongs to a discipline. I want to use the outer repeater to list the disciplines, the inner to list the items grouped under each discipline. Like so:
<h4>DISCIPLINE 1</h4>
<ul>
<li>Item</li>
<li>Item</li>
<li>Item</li>
</ul>
<h4>DISCIPLINE 2</h4>
<ul>
<li>Item</li>
<li>Item</li>
</ul>
<h4>DISCIPLINE 3</h4>
<ul>
<li>Item</li>
<li>Item</li>
</ul>
At present, binding the datatable to the outer repeater gives this (example uses the data above):
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 1</h4>
<h4>DISCIPLINE 2</h4>
<h4>DISCIPLINE 2</h4>
<h4>DISCIPLINE 3</h4>
<h4>DISCIPLINE 3</h4>
I've used OnItemDataBound on the outer repeater as suggested, and as a test case am able to access the data:
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
DataRowView drView = (DataRowView) e.Item.DataItem;
string name = drView["Name"] as string;
string link = drView["Link"] as string;
string notation = drView["Notation"] as string;
Response.Write(name + link + notation + "<br />")
}
So the data is there, it is exactly what I would expect to see, I just can't get it bound to the inner repeater. If there is a more performant way to achieve the same, I'm happy to rework my solution.
On the outer control, use the ItemDataBound event, like this:
<asp:Repeater ID="rptDiscipline" runat="server"
OnItemDataBound="rptDiscipline_ItemDataBound">
...
Then, in the code-behind, handle the rptDiscipline_ItemDataBound event and manually bind the inner repeater. The repeater's ItemDataBound event fires once for each item that is repeated. So you'll do something like this:
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
// To get your data item, cast e.Item.DataItem to
// whatever you're using for the data object; for example a DataRow.
// Get the inner repeater:
Repeater rptPrograms = (Repeater) e.Item.FindControl("rptPrograms");
// Set the inner repeater's datasource to whatever it needs to be.
rptPrograms.DataSource = ...
rptPrograms.DataMember = ...
rptPrograms.DataBind();
}
EDIT: Updated to match your question's update.
You need to bind the outer repeater to a data source that has only one record per item you want the repeater to render. That means the data source needs to be a collection/list/datatable/etc that has only the disciplines in it. In your case, I would recommend getting a List<string> of disciplines from the DataTable for the inner collection, and bind the outer repeater to that. Then, the inner repeater binds to a subset of the data in the DataTable, using the ItemDataBound event. To get the subset, filter the DataTable through a DataView.
Here's code:
protected void Page_Load(object sender, EventItems e)
{
// get your data table
DataTable table = ...
if ( !IsPostBack )
{
// get a distinct list of disciplines
List<string> disciplines = new List<string>();
foreach ( DataRow row in table )
{
string discipline = (string) row["Discipline"];
if ( !disciplines.Contains( discipline ) )
disciplines.Add( discipline );
}
disciplines.Sort();
// Bind the outer repeater
rptDiscipline.DataSource = disciplines;
rptDiscipline.DataBind();
}
}
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
// To get your data item, cast e.Item.DataItem to
// whatever you're using for the data object
string discipline = (string) e.Item.DataItem;
// Get the inner repeater:
Repeater rptPrograms = (Repeater) e.Item.FindControl("rptPrograms");
// Create a filtered view of the data that shows only
// the disciplines needed for this row
// table is the datatable that was originally bound to the outer repeater
DataView dv = new DataView( table );
dv.RowFilter = String.Format("Discipline = '{0}'", discipline);
// Set the inner repeater's datasource to whatever it needs to be.
rptPrograms.DataSource = dv;
rptPrograms.DataBind();
}
If you don't want to do it on the ItemDataBound event, you can also do it inline in your page by binding to a child property of the parent item if the child property is a collection like so:
<asp:Repeater runat="server" ID="OuterRepeater" >
<ItemTemplate>
Outer Content: <%# DataBinder.Eval(Container.DataItem, "ParentProperty")%>
<asp:Repeater runat="server" ID="InnerRepeater" DataSource='<%# DataBinder.Eval(Container.DataItem, "ChildCollection")%>' >
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "ChildProperty")%>
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
First you need two lists, the list of disciplines, and then the list of all your data.
Data bind the list of disciplines to the outer repeater. If there are 6 disciplines, the repeater should repeat 6 times.
<asp:Repeater ID="rptDiscipline" runat="server" OnItemDataBound="rptDiscipline_ItemDataBound">
<ItemTemplate>
<h4><%# Eval("Discipline")%></h4>
<ul>
<asp:Repeater runat="server" ID="InnerRepeater" >
<ItemTemplate>
<li>
<asp:Label runat="server" ID="lbl" />
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</ItemTemplate>
</asp:Repeater>
protected void rptDiscipline_ItemDataBound(Object Sender, RepeaterItemEventArgs e)
{
Repeater inner= (Repeater) e.Item.FindControl("InnerRepeater");
//You want to bind all the data related to this discipline
DataRowView drView = (DataRowView) e.Item.DataItem;
string discipline= drView["Discipline"] as string;
//filter your total data with ones that match the discipline
inner.DataSource = //data bind the filtered list here
inner.DataBind();
}

Categories