I am building an ASP.NET website that allows users to create and take tests. Tests can contain various types of questions (multiple choice, true/false, essay, etc.). Because of the dynamic nature of the tests, I am creating the "Take Test" page with repeaters.
My problem now is: how can I get the user's answers? With a fixed number/type of questions this would be simple, but I'm not sure how to grab answers from items with dynamically created IDs or how to pass a variable number of answers back to my database.
Edit:
I found my answer here.
You can use Request.Form
But Here is Another approach using FindControl and Repeater:
For Each item As RepeaterItem In Me.RptItems.Items
Dim value = CType(item.FindControl("TxtName"), TextBox).Text
Next
you can use FindControl method with each RepeaterItem and find desired control inside it by ID.
ASPX file:
<asp:Repeater ID="RptItems" runat="server">
<HeaderTemplate>
<table>
<tr>
<td>
Name
</td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td>
<asp:TextBox ID="TxtName" runat="server" Text='<%# Eval("Name")%>'></asp:TextBox>
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
If you are using Asp.Net MVC you can reference this article. Model Bind To Collection
If its webforms you can always access each of the inputs submitted via the Request.Form collection.
Here is what I ended up doing for each question type, based on links including this one.
foreach (RepeaterItem item in myRptr.Items)
{
if (item.ItemType == ListItemType.Item || item.ItemType == ListItemType.AlternatingItem)
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
cmd.CommandText = "myPackage.myProcedure";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("user_id", OracleType.VarChar).Value = Session["UserId"].ToString();
cmd.Parameters.Add("question_id", OracleType.Number).Value = ((HiddenField)item.FindControl("myHidden")).Value;
cmd.Parameters.Add("answer", OracleType.VarChar).Value = ((TextBox)item.FindControl("myTxt")).Text;
cmd.ExecuteNonQuery();
}
}
Related
I got a datatable in list view.
ID TelcoName Reload Value(RM) Quantity Total(RM)
1 Maxis 5 1 5 Delete
2 Digi 5 1 5 Delete
This is basically what my table looks like.
My html code is this:
<asp:ListView ID="ListView1" runat="server" OnSorting="ListView1Sorting" OnItemCommand="ListView1_ItemCommand">
<LayoutTemplate>
<table border="0" cellpadding="1">
<tr style="background-color:#FFFFFF">
<th align="center"><asp:Label ID="lblId" runat="server">Id </asp:Label></th>
<th align="center"><asp:Label ID="lblName" runat="server">TelcoName </asp:Label></th>
<th align="center"><asp:Label ID="lblReloadValue" runat="server">Reload Value(RM) </asp:Label></th>
<th align="center"><asp:Label ID="lblQuantity" runat="server">Quantity </asp:Label></th>
<th align="center"><asp:Label ID="lblTotal" runat="server">Total (RM) </asp:Label></th>
<th></th>
</tr>
<tr id="itemPlaceholder" runat="server"></tr>
</table>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td align="center"><asp:Label runat="server" ID="lblId"><%#Eval("ID") %></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblTelcoName"><%#Eval("TelcoName") %></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblReloadValue"><%#Eval("ReloadValue")%></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblQuantity"><%#Eval("Quantity")%></asp:Label></td>
<td align="center"><asp:Label runat="server" ID="lblTotal"><%#Eval("Total")%></asp:Label></td>
<td align="center"><asp:LinkButton ID="lnkDelete" runat="server" CommandName="Sort" CommandArgument="Delete">Delete</asp:LinkButton></td>
</tr>
</ItemTemplate>
</asp:ListView>
This is my behind code for deleting:
Protected Sub ListView1_ItemCommand(ByVal sender As Object, ByVal e As ListViewCommandEventArgs)
If (e.CommandName) = "Sort" Then
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
Dim deletecommand As String = "delete from dt where ID=" & Convert.ToInt32(txteno.text)
Session("dt").DeleteCommand = deletecommand
End If
End Sub
My problem here is i coud'nt get the ID of the row which the user choose. The "ID" is the name of the first column but when i run the website, this statement return nothing.
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
My question is how do i retrieve the value of the column("Id") to use for the deleting data? i ask around and someone suggest to me to use e.item.DataItemIndex but i coudnt find this command anywhere. Any idea how to solve this?
This has just been added
Dim dt As New DataTable
Dim i = 0
Dim ID As New DataColumn("ID")
dt.Columns.Add(ID)
Dim TelcoName As New DataColumn("TelcoName")
dt.Columns.Add(TelcoName)
Dim ReloadValue As New DataColumn("ReloadValue")
dt.Columns.Add(ReloadValue)
Dim Quantity As New DataColumn("Quantity")
dt.Columns.Add(Quantity)
Dim Total As New DataColumn("Total")
dt.Columns.Add(Total)
ListView1.DataSource = dt
ListView1.DataBind()
Session("dt") = dt
After i binded to the listview, do i need to delcare a name for the datatable so that i can use it as a reference for sql statement or i can just leave it as dt and in sql statement, when i refer to dt, it refer to the table in the listview?
EDIT: Addressing two questions separately - one is that a control handle could not be grabbed, the other question is around deleting from a DataTable
Control Reference
If you're trying to get a handle on the control using this:
Dim txteno As Label = DirectCast(e.Item.FindControl("ID"), Label)
Then I think the control ID is incorrect as in your markup you've defined it as "lblId". So perhaps try:
Dim txteno As Label = DirectCast(e.Item.FindControl("lblId"), Label)
Also, I'm not sure what impact it would have but I would give the controls in the LayoutTemplate (so I am guessing the header for your ListView) different ID's to the controls in your ItemTemplate, if only for maintenance/cleanliness if nothing else.
The label should also be bound like:
<asp:Label id="lblId" runat="server" Text='<%# Eval("ID") %>'></asp:Label>
So that in code-behind the text can be retrieved.
Deleting from DataTable
Based on the poster's comments, the DataTable's data is not coming from a database.
Since you aren't connecting back to a database, you don't have to write SQL to remove items from the DataTable.
It looks like you're storing the DataTable in session. And also that you are binding totally in code. As such, what you could do in your ItemCommand method is:
Iterate over the data table to find the item to delete
Delete the item (you could consult, for example, this forum post)
Once the item has been removed from the DataTable, re-bind it to the list view. As an example of someone who seems to have been in a similar situation please see this
HTH,
Nathan
Sort listview using column headings in the LayoutTemplate
I am able to sort a basic list view using asp:SqlDataSource and setting the list view property DataSourceID by pointing it to the asp:SqlDataSource ID. I am having an issue when sorting when not using the asp:SqlDataSource and just DataBinding from the code behind.
SqlDataSource Example:
<asp:ListView ID="ContactsListView" DataSourceID="ContactsDataSource" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
<asp:SqlDataSource ID="ContactsDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:MainConnString %>"
SelectCommand="SELECT * FROM TableName">
</asp:SqlDataSource>
DataBind Example:
<asp:ListView ID="ContactsListView" DataSourceID="ContactsDataSource" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
protected void Page_Load(object sender, EventArgs e)
{
String SQL = "SELECT * FROM Customer";
SqlDataAdapter da= new SqlDataAdapter(SQL, ConnStr);
DataSet ds = new DataSet();
da.Fill(ds);
ContactsListView.DataSource = ds.Tables[0];
ContactsListView.DataBind();
}
Both code samples populate the list view, but the second example data binding does not work for sorting. With the first example, the sorting just works with the added asp:LinkButton in the LayoutTemplate adding the CommandName="sort" and setting the CommandArugment="ColumnName", but it does not work with the second example.
Can anyone please explain why and how to get the sorting working using the code behind DataBind method?
Thanks!
I solved my issue.
I added an event to handle the sorting. The event grabs the command name (Data column) and passes it and the sorting direction into a function which will make another call to the database sort the returned results and rebind to the List View.
I had to create a view state to hold the List View Sort Direction because for some reason, the onsorting event handler kept saying that the sort direction was ascending.
Front End
<asp:ListView ID="ContactsListView" OnSorting="ContactsListView_Sorting" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
Back End
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindContacts(string.Empty);
}
}
protected SortDirection ListViewSortDirection
{
get
{
if (ViewState["sortDirection"] == null)
ViewState["sortDirection"] = SortDirection.Ascending;
return (SortDirection)ViewState["sortDirection"];
}
set { ViewState["sortDirection"] = value; }
}
protected void ContactsListView_Sorting(Object sender, ListViewSortEventArgs e)
{
BindContacts(e.SortExpression + " " + ListViewSortDirection.ToString());
// Check the sort direction to set the image URL accordingly.
string imgUrl;
if (ListViewSortDirection == SortDirection.Ascending)
ListViewSortDirection = SortDirection.Descending;
else
ListViewSortDirection = SortDirection.Ascending;
}
private void BindContacts(string sortExpression)
{
sortExpression = sortExpression.Replace("Ascending", "ASC");
sortExpression = sortExpression.Replace("Descending", "DESC");
using (SqlConnection conn = new SqlConnection(_connStr))
{
conn.Open();
using (SqlDataAdapter dAd = new SqlDataAdapter("SELECT * FROM Customer", conn))
{
DataTable dTable = new DataTable();
dAd.Fill(dTable);
// Sort now
dTable.DefaultView.Sort = sortExpression;
// Bind data now
ContactsListView.DataSource = dTable;
ContactsListView.DataBind();
}
conn.Close();
}
}
I guess you could try to add a handler for ListView's Sorting event. There you are given the sorting column and the sort order in the event arguments. This could be easily usable to build a specific query and bind it to the list.
Because depending on the sort expression (which you defined in your markup) SqlDataSource will very likely do something like this (I'm not sure this is exactly what it does) for you behind the scenes:
Expression<Func<DataRow,object>> myExpression = row => row["SortExpressionYouDefinedForTheColumn"];
IEnumerable<DataRow> ex = ds.Tables[0].AsEnumerable().OrderBy(myExpression.Compile());
Or SqlDataSource may be using a DataView to sort the DataTable.
SqlDataSource can do this because by default it uses a DataSet to store the result set (or so says the documentation):
The data retrieval mode identifies how a SqlDataSource control retrieves data from the underlying database.
When the DataSourceMode property is set to the DataSet value, data is
loaded into a DataSet object and stored in memory on the server. This
enables scenarios where user interface controls, such as GridView,
offer sorting, filtering, and paging capabilities..
Since you chose to bind manually to a DataSet, you need to do the "magic" yourself; in other words, you handle the OnSort command, get the sort expression, get your data source again (however you do it, from Session or by calling the database again) and do your sort similarly to the lines shown above and rebind to your Gridview.
I am trying to iterate over the contents of a Repeater containing an html table, and use the contents of each cell. This works fine for standard HTML components, but blows up when I try to use a control. The code below will print the value of the first cell, "Item #1", but will throw an HttpException when trying to access .InnerText of the second cell. The error is as follows:
Cannot get inner content of because the contents are not literal.
I have tried to use RenderControl via this solution found elsewhere, which failed for two reasons; 1) it rendered the entire HTML of the first cell and 2) it still blew up when trying to access the second cell with the following message:
'LinkButton1' of type 'LinkButton' must be placed inside
a form tag with runat=server
Is there an easy way to get the LinkButton text I'm after? Repeater code and C# can be found below.
The repeater code:
<asp:Repeater ID="Rep1" runat="server">
<HeaderTemplate>
<table id="Table1" class="data">
</HeaderTemplate>
<ItemTemplate>
<tr id="Row" runat="server">
<td>Item #1</td>
<td><asp:LinkButton ID="LinkButton1" OnClick="DoSomething"
Text="Item #2" runat="server" /></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
The C#:
foreach (RepeaterItem item in Rep1.Items)
{
HtmlTableRow row = item.Controls[0].FindControl("Row") as HtmlTableRow;
foreach (HtmlTableCell cell in row.Cells)
{
if (cell.InnerText != string.Empty)
{
Console.WriteLine(cell.InnerText);
}
}
}
For anyone else who arrives here:
You cannot get InnerHtml or InnerText on a Control unless its contents are literal, i.e. there are not any server controls or controls with runat="server" inside it that required rendering
Iterate/recurse through any child controls in the Controls collection. When you get to a leaf node (no children), then access that control's InnerText property.
RenderControl C#
StringWriter iSW = new StringWriter();
HtmlTextWriter iHTW = new HtmlTextWriter(iSW);
iDiv.RenderControl(iHTW);
string iS = iSW.GetStringBuilder().ToString();
Include
public override void VerifyRenderingInServerForm(Control control)
{
return;
}
RenderControl VB
Dim iSW As New StringWriter
Dim iHTW As New HtmlTextWriter(iSW)
iDiv.RenderControl(iHTW)
Dim iS As String = iSW.GetStringBuilder().ToString()
Include
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
Return
End Sub
Also, make sure that you do not have anything such trying to access another view control while another view control is activated in a multi view control.
string value = ((Literal)(cell.Controls[0])).Text
How can I get around this exception?
Dim imagepathlit As Literal = DownloadsRepeater.FindControl("imagepathlit")
imagepathlit.Text = imagepath
Here is the repeater:
<asp:Repeater ID="DownloadsRepeater" runat="server">
<HeaderTemplate>
<table width="70%">
<tr>
<td colspan="3"><h2>Files you can download</h2></td>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td width="5%">
<asp:Literal ID="imagepathlit" runat="server"></asp:Literal></td>
<td width="5%"></td>
<td> </td>
</tr>
</table>
</ItemTemplate>
</asp:Repeater>
Here is the code that gets the data for the repeater:
c.Open()
r = x.ExecuteReader
While r.Read()
If r("filename") Is DBNull.Value Then
imagepath = String.Empty
Else
imagepath = "<img src=images/" & getimage(r("filename")) & " border=0 align=absmiddle>"
End If
End While
c.Close()
r.Close()
My guess is that there is no control found in the DownloadsRepeater control called imagepathlit, therefore the imagepathlit control is null after the call.
Remember that Control.FindControl() looks up the control based on ID, not the name of the control. Therefore, to find the control in the collection...you would have to had something like this earlier in the application:
Dim imagepathlit As Literal = new Literal()
imagepathlit.ID = "imagepathlit"
UPDATE
Since you're using a repeater, the child controls get layed out a bit differently. You're going to have an instance of the Literal for each Item in the Repeater. Therefore, to get each instance of the control, you have to loop through the Items in the Repeater and call FindControl() on each Item:
For Each item As Item In DownloadsRepeater.Items
Dim imagepathlit As Literal = item.FindControl("imagepathlit")
Next
Assuming the code you posted is where the exception is indeed thrown, I would say that the DownloadRepeater does not have a control in it that has an ID of imagepathlit.
Check your aspx.
Because the control is within the ItemTemplate, you cannot use repeater.findcontrol; you have to loop through the items of the repeater to look for the control, as the itemtemplate is repeatable. So you have to loop through each one to look for the control as in:
foreach (var item in repeater.Items)
{
var control = item.FindControl("ID") as Type;
}
Use that syntax.
<asp:TemplateField>
<ItemTemplate>
<table width="540" cellpadding="5">
<tr>
<td align="left" style="width:60%;">
<img src='PurchaseHandler.ashx?ProductID=<%# Eval("ProductID")%>'
alt="<%# Eval("ProductName") %>" />
</td>
<td align="left">
<h3 style="text-align:left;">
<asp:Label ID="nameLabel" runat="server"
Text='<%# Eval("ProductName") %>' />
</h3>
<asp:Label ID="priceLabel" runat="server" Text='<%# Eval("Price") %>' />
<br />
<asp:LinkButton ID="cartLink" runat="server" Text="<b>Add to Cart</b>"
CommandName="Add" CommandArgument='<%# Eval("ProductID") %>' />
</td>
</tr>
</table>
</ItemTemplate>
</asp:TemplateField>
I'm using a shopping cart business object which contains fields not used for display in the GridView. What I'm attempting to do next in the RowCommand event handler is to retrieve the rest of the data fields from the selected row. This gives me the correct product ID from the selected row:
if (e.CommandName == "Add")
{
int productID = 0;
int.TryParse(e.CommandArgument as string, out productID);
// Big blank!
}
How can I grab the rest of the data fields from the selected row to populate my cart? By way of explanation, I can probably use the productID to dig into the DataSet pulled from Session state, and get the data that way. However, what I'm trying to determine is if there is a syntax similar to this that can be used in this situation?
DataRow[] rows = ds.Tables[0].Select("ProductID=" +
gridProducts.SelectedDataKey.Values["ProductID"].ToString());
DataRow row = rows[0];
One way to do this would be to use the command argument to store the row index (perhaps setting it in the on row created event).
You could then use code as below to access your row (code snipped from MSDN):
// Convert the row index stored in the CommandArgument
// property to an Integer.
int index = Convert.ToInt32(e.CommandArgument);
// Retrieve the row that contains the button clicked
// by the user from the Rows collection. Use the
// CommandSource property to access the GridView control.
GridView customersGridView = (GridView)e.CommandSource;
GridViewRow row = customersGridView.Rows[index];
If you need to keep your command argument as the product id then you can use the code below to access the row:
GridViewRow row = (GridViewRow)(((LinkButton)e.CommandSource).NamingContainer);
You can then use the FindControl() method of the GridViewRow to select the controls that you need.
Label priceLabel = row.FindControl("priceLabel") as Label;
Edit after comments from OP
So if I'm correct, you want to access the DataItem of your row?
I don't think this is possible within a RowCommand event handler - I did a little bit of digging on other forums and apparently this property is only not null during DataBind - MSDN has this to say:
The DataItem property is available only during and after the RowDataBound event of a GridView control.
Looks like your method or something similar is the only way of linking back to the original objects in the RowCommand (hopefully someone proves me wrong)