Optimize asp.net C# code for repeaters using a table - c#

On the aspx:
<table>
<tr>
<asp:Repeater ID="rptHeader" runat="server">
<ItemTemplate>
<th><%#Eval("Category")%></th>
</ItemTemplate>
</asp:Repeater>
</tr>
<tr>
<asp:Repeater ID="rptContents" runat="server">
<ItemTemplate>
<td valign="top">
<%#Eval("Content")%>
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</table>
On the code-behind:
protected void Page_Load(object sender, EventArgs e)
{
rptHeader.DataSource = DataSource;
rptHeader.DataBind();
rptContentBlocks.DataSource = DataSource;
rptContentBlocks.DataBind();
}
The problem here is that instead of using Two repeaters, can we use only one?
We actually need the header to be separated from the contents using a different table row...
Edit: changed rptHeader's ItemTemplate's html element from <td> to <th> to be a little clearer. :-)

IMO it's impossible without repeater hacking. Anyway, there is a rather irregular approach that maybe works. Also you can use two foreach statements instead of repeaters.
Code
protected string[] headers = { "A", "B", "C", "D" };
protected string[] contents = { "Alpha", "Beta", "Counter", "Door" };
//string[] headers = { "A", "B", "C", "D" };
//string[] contents = { "Alpha", "Beta", "Counter", "Door" };
//DataList1.RepeatColumns = headers.Length;
//DataList1.DataSource = headers.Concat(contents);
//DataList1.DataBind();
Html markup
<table>
<tr>
<%foreach (string item in headers)
{ %>
<th><%= item %></th>
<% } %>
</tr>
<tr>
<%foreach (string item in contents)
{ %>
<td><%= item %></td>
<% } %>
</tr>
</table>
<!--
<asp:DataList ID="DataList1" runat="server" RepeatDirection="Horizontal">
<ItemTemplate>
<%# Container.DataItem %>
</ItemTemplate>
</asp:DataList>
-->

An HTML table is always declared as cells nested within rows, i.e.
<table>
<tr>
<td>
...
</td>
</tr>
</table>
rather than
<table>
<td>
<tr>
...
</tr>
</td>
</table>
This means you won't be able to write a single Category followed by a single Content. You will have to repeate the Category values and then the Content values as you are already doing.
I see nothing wrong with the approach you are using. The use of 2 repeaters rather than one should be a negligible overhead.
The alternative would be to nest a table within each column. This would allow you to use just a single repeater but the resulting HTML would be rather contrived and bloated. I would not recommend this approach but somehow I can't stop myself from providing an example.
<table>
<tr>
<asp:Repeater ID="rptHeader" runat="server">
<ItemTemplate>
<td>
<table>
<tr>
<td valign="top">
<strong><%#Eval("Category")%></strong>
</td>
</tr>
<tr>
<td valign="top">
<%#Eval("Content")%>
</td>
</tr>
</table>
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</table>

If it's important to place the content in a table, then you could "rotate" your table into a different structure and bind on that structure. Or if it's not important that the data is in a table, you could place the items in divs and float them so they're side-by-side.
Edit:
Here's what I mean by rotating the data. If your data is currently in a structure like List<MyDataClass>, where MyDataClass is defined as something like:
class MyDataClass
{
public string Category { get; set; }
public string Content { get; set; }
public int OtherField { get; set; }
}
...then the logical layout when iterating through the structure would look like this:
MyDataClass[0]: Category, Content, OtherField
MyDataClass[1]: Category, Content, OtherField
...
When rotating the data, you'd turn those rows into columns and columns into rows. For example, you could populate a List<List<string>> with code such as this:
var rotated = new List<List<string>> {
new List<string>(), new List<string>(), new List<string>(),
};
for each (MyDataClass object in myCollection)
{
rotated[0].Add(object.Category);
rotated[1].Add(object.Content);
rotated[2].Add(object.OtherField.ToString("n0"));
}
Now your data structure looks like this:
rotated[0]: MyDataClass[0].Category, MyDataClass[1].Category, ...
rotated[1]: MyDataClass[0].Content, MyDataClass[1].Content, ...
rotated[2]: MyDataClass[0].OtherField, MyDataClass[1].OtherField, ...
Basically, you transform the data into a form that can be dropped right into your table.
Edit 2:
I actually have to do these sorts of rotations for reports often enough that I made an extension method for IEnumerable that will do the rotation in a somewhat more elegant manner. Here's the extension method:
public static class MyCollectionExtensionMethods
{
public static IEnumerable<IEnumerable<TResult>> Rotate<TOrig, TResult>(
this IEnumerable<TOrig> collection,
params Func<TOrig, TResult>[] valueSelectors)
{
return valueSelectors.Select(s => collection.Select(i => s(i)));
}
}
And here's how I'd use it:
IEnumerable<IEnumerable<string>> rotated = data.Rotate(
i => i.Category, i => i.Content, i => i.OtherField.ToString("n0"))

Correct me if i misunderstood but it sounds to me like you could use only one repeater, integrating your first repeater into a HeaderTeplate tag and the second into the ItemTemplate.

Related

Using Tuple to create a table structure

I have a List of tuple that i create in the following format
List<Tuple<String, String, String>> ExtendedSpecsList = new List<Tuple<String, String, String>>();
Theinputs to these tuple would be
{Audio, Type, Speakers - stereo - internal }
{Audio, Output Power / Channel, 2 Watt }
{General, Display Type, LCD monitor / TFT active matrix }
{General, Diagonal Size, 23.6" }
Based on this i am trying to create the following HTML structure for each top category/ Item1 of the tuple. Howe can i do this. Or should i use some other process other than tuple to do this...
<div class="ccs-ds-extendedSpec-group">
<div class="ccs-ds-extendedSpec-header">Audio</div>
<div class="ccs-ds-extendedSpec-body">
<table>
<tbody>
<tr>
<td class="ccs-ds-extendedSpec-item">Type</td>
<td class="ccs-ds-extendedSpec-value">Speakers - stereo - internal</td>
</tr>
<tr>
<td class="ccs-ds-extendedSpec-item" >Output Power/Channel</td>
<td class="ccs-ds-extendedSpec-value" >2 Watt</td>
</tr>
</tbody>
</table>
</div>
</div>
So the first issue is to have the structure of your data match how you want to display it. You want your data grouped on the first value, so you need to do that before binding the data:
var query = yourOriginalData.GroupBy(item => item.Item1)
.Select(group => new { Category = group.Key, Items = group });
With only that change it's ready to be bound to something that can render it.
To render it, a Repeater would be the right choice. Here, since you have a sub-collection within your main collection you'll need nested repeaters:
<asp:Repeater runat="server" ID="repeater">
<ItemTemplate>
<div class="ccs-ds-extendedSpec-group">
<div class="ccs-ds-extendedSpec-header"><%# Eval("Category") %></div>
<div class="ccs-ds-extendedSpec-body">
<table>
<tbody>
<asp:Repeater runat="server" DataSource='<%# Eval("Items") %>'>
<ItemTemplate>
<tr>
<td class="ccs-ds-extendedSpec-item">
<%# Eval("Item2") %>
</td>
<td class="ccs-ds-extendedSpec-value">
<%# Eval("Item3") %>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</tbody>
</table>
</div>
</div>
</ItemTemplate>
</asp:Repeater>
With that you only need to bind your query to this repeater and you're golden.
It's also worth noting that while Tuple certainly works here, the readability of the code would be improved dramatically by using a class that has meaningful property names.
You could use Linq to filter ExtendedSpecsList by the first component as follows.
var Audio = ExtendedSpecList.Where(iItem => iItem.First == "Audio");
var General = ExtendedSpecList.Where(iItem => iItem.First == "General");
Afterwards the different categories can be processed seperately. If the different Categories are not known beforehand, the list can be grouped as follows.
foreach(var Group in ExtendedSpecList.GroupBy(iItem => iItem.First))
{
// do some processing of the group
foreach (var iItem in Group)
{
// do something with an individual item
}
}

ListView DataBind Sorting Values

I want to have a DropdownMenu from which i can choose how i want to sort my ListView.
This is my current code for it :
<asp:DropDownList ID="DropDownSelect" runat="server" AutoPostBack="True"
OnSelectedIndexChanged="GetProducts">
<asp:ListItem Selected="True" Value="DesDate"> Descending Date </asp:ListItem>
<asp:ListItem Value="AsDate"> Ascending Date </asp:ListItem>
<asp:ListItem Value="AsAlp"> Ascending Alphabetical </asp:ListItem>
<asp:ListItem Value="DesAlp"> Decentind Alphabetical </asp:ListItem>
</asp:DropDownList>
And I have this ListView to display my data:
<asp:ListView ID="productList" runat="server"
DataKeyNames="NewsID" GroupItemCount="1"
ItemType="SiteStiri.Models.News" SelectMethod="GetProducts">
<EmptyDataTemplate>
<table>
<tr>
<td>No data was returned.</td>
</tr>
</table>
</EmptyDataTemplate>
<EmptyItemTemplate>
<td/>
</EmptyItemTemplate>
<GroupTemplate>
<tr id="itemPlaceholderContainer" runat="server">
<td id="itemPlaceholder" runat="server"></td>
</tr>
</GroupTemplate>
<ItemTemplate>
<td runat="server">
<table>
<tr>
<td>
<a href="NewsDetails.aspx?newsID=<%#:Item.NewsID%>">
<img src="/Catalog/Images/Thumbs/<%#:Item.ImagePath%>"
width="100" height="75" style="border: solid" /></a>
</td>
</tr>
<tr>
<td>
<a href="NewsDetails.aspx?newsID=<%#:Item.NewsID%>">
<p style="color: black;">
<%#:Item.NewsTitle%>
</p>
</a>
</td>
</tr>
<tr>
<td> </td>
</tr>
</table>
</p>
</td>
</ItemTemplate>
<LayoutTemplate>
<table style="width:100%;">
<tbody>
<tr>
<td>
<table id="groupPlaceholderContainer" runat="server"
style="width:100%">
<tr id="groupPlaceholder"></tr>
</table>
</td>
</tr>
<tr>
<td></td>
</tr>
<tr></tr>
</tbody>
</table>
</LayoutTemplate>
</asp:ListView>
The thing that i have no idea how to do is:
After selecting a sorting rule from the dropdown menu, i can't figure out how to write(or where to write) the method that would update my ListView as it should. My attemp is :
public IQueryable<News> GetProducts()
{
var _db = new SiteStiri.Models.NewsContext();
IQueryable<News> query = _db.News;
if (("DesDate").Equals(DropDownSelect.SelectedItem.Value))
{
query.OrderByDescending(u => u.ReleaseDate);
}
if (("AsDate").Equals(DropDownSelect.SelectedItem.Value))
{
query.OrderBy(u => u.ReleaseDate);
}
if (("AsAlp").Equals(DropDownSelect.SelectedItem.Value))
{
query.OrderBy(u => u.NewsTitle);
}
if (("DesApl").Equals(DropDownSelect.SelectedItem.Value))
{
query.OrderByDescending(u => u.NewsTitle);
}
return query;
}
which gives me a bunch of errors and it doesn't even work .... a little bit of help please? I am new to this (2 days).
Let's fix your code step by step.
Event handlers need to have a certain signature. In case of every ASP.NET control I can remember of, they need to receive two parameters, event arguments and event source object, and return void. Also note that just calling GetProduct is not going to update ListView, we need to trigger databinding for the control itself. We'll get to that later. For now let's introduce a proper handler:
public void DropDownSelect_SelectedIndexChanged(object sender, EventArgs e)
Don't forget to update markup as well:
OnSelectedIndexChanged="DropDownSelect_SelectedIndexChanged"
The conditions on how you show data in the ListView have changed. That means that you need to rebind it with calling DataBind, which should call GetProducts (as a one specified in SelectMethod):
public void DropDownSelect_SelectedIndexChanged(object sender, EventArgs e)
{
productList.DataBind();
}
Finally in GetProducts note that LINQ calls do not update the current object, but rather they produce new one every time. So you should have something like this:
if ("DesDate".Equals(DropDownSelect.SelectedItem.Value))
{
query = query.OrderByDescending(u => u.ReleaseDate);
}
I am guessing you want the data and not the query right? That code would look like this, all you have to do is parse the selected value and pass it into the method. If the ListView is bound to the data (and it should be) you're all set. This example shows the separation of concerns, whereby the view is just a framework for HOW to show the data, and the code behind is a framework for WHAT data is supplied based on triggers from the view. The trigger in this case is the "filter" parameter.
List<News> query = _db.News.ToList();
public List<News> GetProduct(string filter) {
if (filter == "DesDate") return query.OrderByDescending(u => ReleaseDate).ToList();
}
One last note: IQueryable is a class that lets you pass queries around, you can alter the query itself from method to method. But this class does not produce results until it is told to do so by using any of the To methods like ToList(), ToArray(), ToLookup() and or any attempt to iterate over it such as:
Foreach(var thing in MyQuerable){ //results are here }

Filtering values from a Datatable to be transferred to another Datatable

I have a Datatable that has a checkbox and a value, What I want is that when I click a button it would get all the checked boxes and their corresponding value and Add them to a blank Datatable. I not planning to save the data moved from 1 list to another, as there will be another functionality to confirm if I want to save the changes(not being asked in this question).
This is my html code:
<asp:ListView runat="server" ID="uoListViewAirportSaved" EmptyDataText="No Data Found">
<LayoutTemplate>
<table border="0" cellpadding="0" cellspacing="0" width="500px">
<tr>
<th runat="server"> </th>
<th runat="server">Airport</th>
</tr>
<asp:PlaceHolder runat="server" ID="itemPlaceHolder"></asp:PlaceHolder>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td class="leftAligned">
<asp:CheckBox ID="uoCheckBoxSelect" runat="server" />
</td>
<td class="leftAligned">
<asp:HiddenField ID="uoHiddenFieldAirport" runat="server" Value='<%# Eval("ColAirportCodeVarchar") %>' />
<asp:Label ID="uoLabelAirport" runat="server" Text='<%# Eval("colAirportFullName")%>' />
</td>
</tr>
</ItemTemplate>
<EmptyDataTemplate>
<table border="0" cellpadding="0" cellspacing="0" width="500px">
<tr>
<td>Airport</td>
</tr>
<tr>
<td colspan="3" class="leftAligned">No Record</td>
</tr>
</table>
</EmptyDataTemplate>
</asp:ListView>
And the code when I click the add button:
private void AddAirport() {
CheckBox uoCheckBoxSelect;
HiddenField uoHiddenFieldAirport;
Label uoLabelAirport;
DataTable dt = new DataTable();
DataTable dt2 = new DataTable();
DataTable dt3 = new DataTable();
string serviceProvider = GlobalCode.Field2String(Request.QueryString["pid"]);
///datatable with all list of airports
dt = VendorMaintenanceBLL.GetServiceProviderAirportbyBrand(serviceProvider);
///datatable with provider's current airports
dt2 = VendorMaintenanceBLL.GetServiceProviderAirportbyVendor(serviceProvider);
foreach(ListViewItem item in uoListViewAirport.Items)
{
uoCheckBoxSelect = (CheckBox)item.FindControl("uoCheckBoxSelect");
if (uoCheckBoxSelect.Checked == true)
{
uoHiddenFieldAirport = (HiddenField)item.FindControl("uoHiddenFieldAirport");
uoLabelAirport = (Label)item.FindControl("uoLabelAirport");
/// my new list should contain the values from the checked values of the checkboxes
???
}
}
}
My Datatable contains the AirportID and the AirportStringName columns.
I found a code that I'm trying to replicate, logic-wise. However it is designed for a list:
List<AirportDTO> listToBeAdded = new List<AirportDTO>();
List<AirportDTO> listAdded = new List<AirportDTO>();
listToBeAdded = GetAirportNotInUser(false, false);
listAdded = GetAirportInUser(false);
foreach (ListViewItem item in uoListViewAirport.Items)
{
uoCheckBoxSelect = (CheckBox)item.FindControl("uoCheckBoxSelect");
if (uoCheckBoxSelect.Checked == true)
{
uoHiddenFieldAirport = (HiddenField)item.FindControl("uoHiddenFieldAirport");
uoLabelAirport = (Label)item.FindControl("uoLabelAirport");
var listToAdd = (from a in listToBeAdded
where a.AirportIDString == GlobalCode.Field2String(uoHiddenFieldAirport.Value)
select new
{
AirportID = a.AirportIDString,
AirportName = a.AirportNameString,
}).ToList();
}
}
I do not know how I would get the necessary data from the datatable as it is different from a list. Is there a way for me to do the same thing with DataTables the way lists were tackled.
Since I really need to fix the problem yesterday, I decided to do the following.
What I did was just basic for loop, then inside compare each row and then just use Datatable.ImportRow and DataTable.RemoveAt. I also added a temporary Datatable for the changed lists.
foreach(ListViewItem item in uoListViewAirport.Items)
{
uoCheckBoxSelect = (CheckBox)item.FindControl("uoCheckBoxSelect");
if (uoCheckBoxSelect.Checked == true)
{
uoHiddenFieldAirport = (HiddenField)item.FindControl("uoHiddenFieldAirport");
uoLabelAirport = (Label)item.FindControl("uoLabelAirport");
string HiddenFieldAirport = GlobalCode.Field2String(uoHiddenFieldAirport.Value);
for (var f = 0; f < dt.Rows.Count; f++ )
{
if (dt.Rows[f]["colAirportIDInt"].ToString() == HiddenFieldAirport)
{
dt3.ImportRow(dt.Rows[f]);
dt.Rows.RemoveAt(f);
}
}
}
}
The code is dirty, plus I had to sort it as well, but since I really needed it, I can make do with this.

Use ListView control to display data but join a reference table

I'm really new to ASP.NET and I've looked at countless tutorials trying to figure this out but can't seem to get my head around it. I can successfully output records from tbProject using ListView and the Entity Framework but instead of the refDepartmentID, I want to display refDepartmentValue from the refDepartment table. Here is a quick table structure.
tbProject
-ProjectID
-refDepartmentID
-ProjectName
refDepartment
-refDepartmentID
-refDeparmentValue
I can write this in SQL no problem with a JOIN but I don't know where I'd put it. I've played around with LINQ a bit and it seems like this is how I'm going to accomplish this. Maybe write the LINQ in the code behind file then bind it to the ListView control, maybe?
Here's the ListView control:
<asp:ListView runat="server"
DataSourceID="EntityDataSource1">
<ItemTemplate>
<tr style="">
<td>
<asp:Label ID="ProjectIDLabel" runat="server" Text='<%# Eval("ProjectID") %>' />
</td>
<td>
<asp:Label ID="refDepartmentIDLabel" runat="server"
Text='<%# Eval("refDepartmentID") %>' />
</td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table runat="server">
<tr runat="server">
<td runat="server">
<table ID="itemPlaceholderContainer" runat="server" border="0" style="">
<tr runat="server" style="">
<th runat="server">
ProjectID</th>
<th runat="server">
refDepartmentID</th>
</tr>
<tr ID="itemPlaceholder" runat="server">
</tr>
</table>
</td>
</tr>
<tr runat="server">
<td runat="server" style="">
</td>
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
Here's how I'm using the EF to populate a dropdownlist so you can at least refer to how things are named.
// populates Departments dropdownlist
using (dbOrganizationEntities1 myEntities = new dbOrganizationEntities1())
{
var allDepartments = from refDepartments in myEntities.refDepartments
select refDepartments;
ddlDepartments.DataSource = allDepartments;
ddlDepartments.DataValueField = "refDepartmentID";
ddlDepartments.DataTextField = "refDepartmentValue";
ddlDepartments.DataBind();
}
Any help would be appreciated. Thanks!
Disclaimer, I did this in my head without Visual Studio, so it might have a minor syntax error. Let me know if it does.
You need to do two things, First you must join your tbProject to refDepartments Entites using an inner (or outer) join as needed, depending on your desired behavior. One you have the two tables joined in LINQ, you want to create a new anonymous class using "select new" operator which creates it. The members of this new class will be created based on the datatypes of the Entities they are selected from.
using (dbOrganizationEntities1 myEntities = new dbOrganizationEntities1())
{
var allDepartments = (from tbProject in myEntities.tbProjects
// inner join to department lookup table
from refDepartments in myEntities.refDepartments.Where(x=>x.refDepartmentID == tbProject.refDepartmentID) // to do a left join instead of an inner, append .DefaultIfEmpty() after this where clause
// select new anon type
select new {
refDepartmentID = tbProject.refDepartmentID,
ProjectName = tbProject.ProjectName,
refDepartmentValue = refDepartments.refDepartmentValue,
}).ToList(); // I chose to turn the result into a list to demonstrate something below, you can leave it as an enumerable.
// you can access the properties of the anon type like so
System.Diagnostics.Debug.Print(allDepartments[0].refDepartmentID);
System.Diagnostics.Debug.Print(allDepartments[0].refDepartmentValue);
// bind to your listview, make sure control name is accurate and ItemTemplates are defined for each data column.
MyListView.DataSource = allDepartments;
MyListView.DataBind();
}

Databinding in C#

Can someone help me databind? I'm new to .net and c# and am following tutorials that are only getting me half way there. The aspx is the following:
<asp:Repeater ID="rptContent" runat="server">
<HeaderTemplate>
<table>
<thead>
<tr>
<th>T</th>
<th>L</th>
<th>S</th>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("T") %></td>
<td><%# Eval("L")%></td>
<td><%# Eval("S")%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
But on the back end I don't know how to actually bind the data. If there is a tutorial someone can send me to follow for this part I'd appreciate it or if you can explain that would be great.
public List<Sample> Results()
{
List<Sample> List = new List<Sample>();
myList.Add(new Sample { Title = "Title
1", Link = "/item.aspx?id=1", Summary = "summary
for Item 1" });
return List;
}
public class Content
{
public string T
{
get;
set;
}
public string L
{
get;
set;
}
public string S
{
get;
set;
}
}
Can you bind directly the list of Sample? or you do need to bind it to the class Content?
The important here is: in the markup, when you use Eval(""), you have to provide the exact name of the property of the object you are binding.
If you can use the list of Sample I would do the following
ASPX:
<asp:Repeater ID="rptContent" runat="server">
<HeaderTemplate>
<table>
<thead>
<tr>
<th>T</th>
<th>L</th>
<th>S</th>
</tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("Title") %></td>
<td><%# Eval("Link")%></td>
<td><%# Eval("Summary")%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
and in Code-Behind:
protected void Page_Load(object sender, EventArgs e)
{
rptContent.DataSource = Results();
rptContent.DataBind();
}
public List<Sample> Results()
{
List<Sample> List = new List<Sample>();
myList.Add(new Sample { Title = "Title
1", Link = "/item.aspx?id=1", Summary = "summary
for Item 1" });
return List;
}
The collection you assign to the data source of your repeater needs to be a collection of items containing the properties you're intending to bind to.
The individual items in your Results collection do not directly possess L, T, & S properties so in binding this collection to your repeater, the repeater cannot find those properties. In your case, you'll need to bind to a collection of Content objects:
List<Content> contentResults = new List<Content>();
contentResults.Add(new Content(){L="el", T="tee", S="es"});
rptContent.DataSource = contentResults;
rptContent.DataBind();

Categories