In a web application I am writing, I populate a HTML table with a button press; after the table is populated, then the user can click another button to export the table in a CSV file.
The issue is that, while the table is properly populated at he first button pressing, it appears to be empty when the application query it at the second button press.
A few code to explain the issue:
the HTML in ASPX page
<asp:Button ID="BTNPopulate" runat="server" Text="Populate" />
<table id="Table1" style="border-width: 1px; border-color: Black; padding: 5px" cellspacing="0" runat="server" />
<asp:Button ID="BTNExport" runat="server" Text="Export" />
now the first button
Protected Sub BTNTest_Click(sender As Object, e As EventArgs) Handles BTNTest.Click
Dim row As HtmlTableRow
Dim cell As HtmlTableCell
row = New HtmlTableRow()
row.BgColor = "Gray"
cell = New HtmlTableCell()
cell.Controls.Add(New LiteralControl("Test cell"))
row.Cells.Add(cell)
Table1.Rows.Add(row)
end sub
and the table is properly populated.
I was unable to obtain any info from it and in fact if I call the .Rows.Count method anyway in the second button click code-behind
Protected Sub BTNExport_Click(sender As Object, e As EventArgs) Handles BTNExport.Click
Response.ContentType = "text/csv"
Response.AddHeader("Content-Disposition", "attachment; filename='PFExport.csv'")
CSVBuilder.Append(Table1.Rows.Count)
Response.Write(CSVBuilder.ToString)
Response.Flush()
Response.End()
End Sub
the result in the file is always 0.
On the other hand, if I call the same .Rows.Count property at the end of the populate part of the code, it properly returns the result.
I tried to use the ASPTable instead of the HTMLTable, but the issue is the same.
I assume I am missing something basic here, not sure what.
ASP.NET (or anything on the web ftm) works over HTTP. HTTP is stateless. That means that whatever you do in one request is completely lost when you make a new request.
ASp.NET webforms tries to hide this from you by using something called ViewState. It's essentially a hidden field which it sends back and forth to repopulate your page every time you do a roundtrip. When the server reconstructs the page, it reads the info from the ViewState that was sent in and rebuilds the page from those values.
The problem you're having is that the page is not repopulated yet when your click-handler is triggered. There's two ways to fix this.
(preferred): Instead of reading the data from the table in the UI, just look it up in the database again.
Set a flag on the page in the click-handler and populate the CSV on the PreRender-event.
As explained by Kenneth, my error to think the information to be preserved between requests, while HTTP is stateless.
So I investigate this from a different perspecitve and I ended up using View State to store the information locally and retrieve it to generate the CSV at the Export button press.
So, to store the info I use
ViewState("TableResultsState") = CSVBuilder.ToString
immediately after the table is populated.
For the export button, I do
With Response
.ContentType = "text/csv"
.AddHeader("Content-Disposition", "attachment; filename='PFExport.csv'")
.Write(ViewState("TableResultsState").ToString)
.Flush()
.End()
End With
As the text quantity is limited, from my research I understand this should not have adverse effects.
Again, thanks to Kenneth for pointing out my very basic misunderstanding.
Related
I have this dropdownlist, this one load with the data that I get from a store procedure, as you can see the load is correct, but when I change the selected value in the debug the selected value doesnt change, it stays in the first loaded value, which in this case is 1. What can I do?
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim adaptador As New SqlDataAdapter
Dim datos As New DataTable
Dim ord As SqlDataReader
Conexiones.AbrirConexion()
Conexiones.Cnn.Open()
Dim cmd As SqlCommand = New SqlCommand("sp_devolver_empresas", Conexiones.Cnn)
cmd.CommandType = CommandType.StoredProcedure
ord = cmd.ExecuteReader
datos.Load(ord)
cbEmpresas.DataSource = datos
cbEmpresas.DataTextField = "Nombre"
cbEmpresas.DataValueField = "Identificador"
cbEmpresas.DataBind()
Conexiones.Cnn.Close()
End Sub
For EVERY web page that you build and write to the end of time?
You MUST always put the load and setup code inside of a check for post back.
every button, every drop down list, and EVERY time you click on a button or do anything, the page load will ALWAYS fire and run. Thus if you change a value, you will lose that value since the page setup and loading of say control (in this case your dropdown list) will fire EACH time - thus overwriteing.
Out of the last 100's of web pages, the FIRST thing I write is the check for postback.
So, your code should look like this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData
End If
End Sub
Sub LoadData()
Dim datos = New DataTable
Using onexiones.Cnn
Using cmd As SqlCommand = New SqlCommand("sp_devolver_empresas", onexiones.Cnn)
cmd.CommandType = CommandType.StoredProcedure
onexiones.Cnn.Open()
datos.Load(cmd.ExecuteReader())
End Using
End Using
cbEmpresas.DataTextField = "Nombre"
cbEmpresas.DataValueField = "Identificador"
cbEmpresas.DataSource = datos
cbEmpresas.DataBind()
End Sub
So, only on the "real" first page load do you want to load up your grids, dropdowns etc. Remember, even a simple button dropped on to the page, and you have a click event? (the page load event fires each time - and every time). So once again, you need the IsPostBack test - and I am quite pressed to find any of my web pages that does not have this all important code stub in the on-load event.
So, remember the above rule - always setup your setup code to run on first time - not every time a button or anything else occurs on the web page. So, page load (unlike desktop) fires each and every time you do ANY operations on that page.
Also, note how I setup the data text, and value field BEFORE we give the drop down list a data source. In fact, I would suggest you put the data text, and data value field in the markup and NOT code (since then multiple different code routines can use that setting - not hard coded in code).
eg do this:
<asp:DropDownList ID="cbEmpresas" runat="server"
DataTextField = "Nombre"
DataValueField = "Identificador" >
</asp:DropDownList>
So, now your code becomes :
cbEmpresas.DataSource = datos
cbEmpresas.DataBind()
I suppsoe some out of habt like to set the text + data settings for the dropdown list in code, but then again, in code you might want more then one palce that loads up the combo box - and thus you now have multiple places for those two settings. And better yet, you can even use the property sheet during design time to make the settings. (this is a "minor" issue, but I just perfer those settings in the markup as opposed to writing them in code for text/data settings).
At the end of the day? Just remember that golden rule: have that "test" for postback so you have a REAL first page load event code inside that if/then. In fact, you really can't even build a fnctional working web page if you break this rule. As noted, I count about 1-2 of my web pages out of 100's tht don't have that not postback code stub. It is quite much as important say compared to humans having to breathe air to function.
And why such a long post about this simple issue? Well on a near daily bases, both c# and vb.net questions are posted about how some combo box, grid view or just about anything else is "broken" and not working. And the answer 9 out of 10 times?
The developer forgot to use a Not IsPostBack code stub in their page load code.
If I was teaching asp.net, this would be the first lesson - ALWAYS use and consider the requirement for the Not IsPost back stub. You need that in 99% of your web pages. Or at the very least in ANY web page in which you have code to run on page load to setup grids, dropdowns, repeaters - don't matter what, you will NEED that code stub. So important, I often wonder if they should have create a even called first page load? It would have eliminated the daily posts and questions on SO that fail as a result of not heeding the above simple advice.
Edit: Now how to get selected value in code
Ok, now that we fixed up the code to setup the dropdown list, we now want to get in our code the value when the user makes a choice in the dropdown list.
You of course have code to fill the dropdown, BUT we also have to set autopostback = true for this dropdown.
So, say code to load:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData
End If
End Sub
Sub LoadData()
Using conn As New SqlConnection(My.Settings.TEST4)
Dim strSQL As String =
"SELECT ID, HotelName from tblHotels ORDER BY HotelName"
Using cmdSQL As SqlCommand = New SqlCommand(strSQL, conn)
conn.Open()
Dim rstData As New DataTable
rstData.Load(cmdSQL.ExecuteReader)
cboHotels.DataSource = rstData
cboHotels.DataBind()
End Using
End Using
End Sub
And now the selected index changed event:
so, our debug output would then look like:
output:
combo selected value = 82
combo selected Text = Canadian Rocky Mountain Resorts
And as noted, make sure you set auto-post back true in the property sheet, or in the markup. The combo box for above in markup thus is this:
<asp:DropDownList ID="cboHotels" runat="server"
DataValueField="ID"
DataTextField="HotelName" AutoPostBack="True">
</asp:DropDownList>
Label is not visible in the following code . I need to show a message please wait when the user clicks login.It shows before but after adding the time interval its not showing
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Label3.Visible = True
If TextBox1.Text = "Akilan" And TextBox2.Text = "123" Then
System.Threading.Thread.Sleep(5000)
Form2.Show()
Hide()
Else
MsgBox("Sorry, The Username or Password was incorrect.", MsgBoxStyle.Critical, "Information")
End If
End Sub
Thread.Sleep on the UI thread will cause your form to "freeze". It sounds like you want some sort of waiting indicator while the background code is running.
What you should do is execute your long running code asynchronously, show your label, wait for the asynchronous code to finish, then do something (e.g. hide the label).
Using your code, it'd look something like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Label3.Show()
If (TextBox1.Text = "Akilan" AndAlso TextBox2.Text = "123") Then
Dim t = Task.Run(Sub() Threading.Thread.Sleep(5000))
t.Wait()
Label3.Hide()
Else
MessageBox.Show("Sorry, the username or password was incorrect", "Invalid Credentials", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
End Sub
The way to do this is to hide/show the label text client side. Then when the page travels up to server and your code behind runs, the client side will still show that message. When you code behind is done, then the whole page now travels back down to the client side is re-load + re-display.
Grasping the above so called "round trip" is REALLY important here.
Since your code behind never interacts with the user, but only that copy of the web page that JUST traveled up to the server, and the code behind can play with, change and modif that page - but client side can ONLY see such updates AFTER the page travels all the way back down to client side. That's why this code can't work:
Label3.Visible = True
If TextBox1.Text = "Akilan" And TextBox2.Text = "123" Then
System.Threading.Thread.Sleep(5000)
Form2.Show()
Hide()
The above code will set Label.visible = true, but the whole page has not yet done its code, and the whole page is still on the server side. ONLY AFTER all your code is done, does the page travel back to client side, and show the label - but ALL code has to complete before page travels back down to client side.
However, this code will work just fine:
So lets look at above:
First up, we do NOT use visible=false for the label. The reason is that if you do that, then the label is NOT rendering in the markup.
Next up, we added a onclient click event to the button.
And we have our button code that has this code:
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Thread.Sleep(3000)
End Sub
So, when you run this, you see this for 3 seconds:
So what happens?
Well, you click on button - the OnClient code (javascrt client side) will run and THEN the post back of the page up to the server occurs.
So, since that label is now "show", then it will display. Now the web page is up on the server - your code behind runs. When code behind is done?
The WHOLE page now makes the trip back to browser client side. And this ALSO means our label message will automatic hide again - since the page is re-plotted with the whole new page that the server just send back to the client side.
Do note, that in above, I assumed jQuery is installed. If not then your script will have to be this:
function ShowLabel()
{
lbl = document.getElementById("lblMessage")
lbl.style.display = "inline"
}
Note also close how I set the id mode of lable = static - I often do that, since then referencing controls in js becomes a lot easier.
So, the real trick here?
We display the label client side, and then whatever and how ever long the server takes is the amount of time the label will display. And when that whole new fresh web page comes back down from the server, then the label will re-vert back to being hidden (display:none).
So, do keep in mind this VERY important concept of round trip. When you code runs behind and changes values or controls on the page? The end user does NOT see such changes until ALL code behind is done, AND THEN the page travels back down to the user. In fact, then your code behind to hide or show, can become before or after the delay in code - it will NOT update client side and in fact the order that that change label and delay will NOT matter (since the web page is STILL up on the server). So all your changes you make to the page remain up on the server until all code is done, and THEN the whole page comes back to client side to show such changes.
The other way would be to consider ajax calls - but baby steps here first, right?
You can make Button Click event asynchronous.
The following code works for me:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Label3.Visible = True
If TextBox1.Text = "Akilan" And TextBox2.Text = "123" Then
Label3.Text = "Please wait"
Await Task.Delay(500)
Label3.Text = ""
Form2.Show()
Hide()
Else
MsgBox("Sorry, The Username or Password was incorrect.", MsgBoxStyle.Critical, "Information")
End If
End Sub
Also check:
Async (Visual Basic)
When to use Task.Delay, when to use Thread.Sleep?
I have checklist with multiple selections now the user will preview the form in another webform after press preview button the issue here that the checklist not populated in the other form with all selections and only item selected
In first form I wrote the below code:
Dim Fruit as String = ChkFruit.SelectedValue
Redirect.response(home.aspx?"fruit=" + Fruit)
In second form I wrote the below code:
ChkFruit1.Selectedvalue = QueryString("Fruit")
Well in your example posted code, if we are to ONLY PASS one selected value, then what you have is a good start.
However, VERY NEW BIG LARGE MASSIVE different problem is now we allow "many" selections in the check box list. So with a NEW HUGE LARGE DIFFERENT kind of question and problem?
Then we will as a result need a VERY different solution. The large new big massive problem here is how to pass 1 value, or 15 choices? This of course as noted is quite a more difficult problem and challenge.
For passing a "list" or "array" of a whole bunch of possbile choices, then I suggest we do NOT use the URL parmaters (query parms in the URL).
So, what we need here is to build up some "list" of choices from the check box list, and pass that to the 2nd page. Now there are more ways to do this then there are flavors of ice cream, and we have a LOT of choices.
However, I going to suggest creating a List of chocies, and we pass that to the 2nd page we jump to.
So, our markup and button in the first page can look like this:
<asp:CheckBoxList ID="CheckBoxList1" runat="server">
<asp:ListItem>Apple</asp:ListItem>
<asp:ListItem>Grapes</asp:ListItem>
<asp:ListItem>Bananna</asp:ListItem>
<asp:ListItem>Cherry</asp:ListItem>
</asp:CheckBoxList>
<br />
<br />
<asp:Button ID="Button2" runat="server" Text="Submit" Width="158px" />
Ok, above is quite nice - not complex.
Now, for our button code? We need to build up, get/grab/obtain the list of selected values - that's what we will pass to the 2nd page.
So, above looks like this:
So, now our button/submit code on this web page:
Protected Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim MyCheckList As List(Of String) = New List(Of String)
For Each MyItem As ListItem In CheckBoxList1.Items
If MyItem.Selected Then
MyCheckList.Add(MyItem.Value)
End If
Next
Session("MyCheckList") = MyCheckList
Response.Redirect("Test2.aspx")
End Sub
So, in place of "url messay parmaters"?
We create a List type variable (they are somewhat the same as an array()
We then add all selected items to that new list
We then stuff/place the new list into Session()
We then jump to the 2nd web page with the Reponse.Redirect
Ok, so now, on the target web page, we have to get that list we passed.
Now, on the 2nd page, the code ONLY needs that list.
but, lets assume that we have the same check box list on that 2nd page - and we want to fill out - show/display/have the Check box list show the same choices.
WHAT IS IMPORTANT here is how we are free to use the "list" we passed to that 2nd page. I can do anything with that nice list , but we have this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
Dim MyCheckList As List(Of String) = Session("MyCheckList")
' display the list of passed check box values
For Each MySelected As String In MyCheckList
Debug.Print(MySelected)
Next
' or say fill out a check box list we have on this new page -
' process EACH check box list item
' check if in our list - if yes, then check box/select it
For Each MyItem As ListItem In CheckBoxList1.Items
MyItem.Selected = MyCheckList.Contains(MyItem.Value)
Next
End If
End Sub
So, in this 2nd page, we demonstrate two examples.
First part of code example - JUST display the list of previous selections. You are thus now free to for each loop and process the check box list of selections.
Second part of code example. We actually take that list and check box a whole new check box list we have on the 2nd target page. Now, I doubt the check box list would be the same and duplicated on that 2nd page, but this code example shows how we could display the check box list "again" on the 2nd new target page.
And the code also demonstrates how to loop/process the list of selections, which is really at a the end of the day the most valuable part of this example code
So, in our 2nd page, if we have this markup:
<asp:CheckBoxList ID="CheckBoxList1" runat="server">
<asp:ListItem>Apple</asp:ListItem>
<asp:ListItem>Grapes</asp:ListItem>
<asp:ListItem>Bananna</asp:ListItem>
<asp:ListItem>Cherry</asp:ListItem>
</asp:CheckBoxList>
<br />
Then we would see this:
So when you have MORE then just one value to pass to the next page?
Then you can use session() in place of messy and difficult to use URL "parmaters". And those URL paramters are not all that great for passing "many" values like this new problem required.
If we were to only pass ONE value, and ONE selection, then the URL parmaters would be ok to use. But now since we need to pass "many" values then using session() to pass that information to the 2nd target page becomes a whole lot less work and efforts on your part.
I have a div(runat=server) that contain repeater,this repeater is getting is value from datatable
when the datatable is empty I do this line of code in asp .net function
divname.innerHTML="<img src="...>"
on this page there is also a asp:button that fill the datatable with values.
when pressing the button ,the above line isn't excute,the page offcourse is doing postback, but the div content isn't changing back to his original value(the repeater).
What do I have to do in the postback in order to force the div to get his original values?
This is code example:
sub page_load(..
if ispostback=false then
run_div()
end if
end sub
sub run_div()
if datatable.Rows.Count=0 then
divname.innerHTML="<img src="...>"
end if
end sub
sub filldata(....
fill datatable
repeater.datasource=...
repeater.databind
run_div()
end sub
html look like this
<div runat=servver id=divname><asp:repeater>.....
<asp:button onclick="filldata">
Thanks for any help
Assuming the variable named datatable is a System.Data.DataTable, remember that it only exists and had data in the context of a sibgle run of the page lifecycle. if the page is re-generated (whether it's via a postback or an initial load of the page) the DataTable is empty until it's loaded.
However, the Repeater object is stored in teh ViewState. IT will have values on a postback if there were rows on the previous load.
Instead of looking at the number of rows in the table, you should be looking at the Repteater.Items.Count.
If repeater.Items.Count = 0 Then
divname.innerHTML="<img src="...>"
Else
...
End If
I am dynamically generating HTML which is stored in a string variable.
I would like to open a new window with a new page created from this HTML.
This seems too simple, but I just cannot find the solution.
I am using ASP.NET 3.5 and VS2008.
Thanks,
Paul.
Best idea would be to create an http handler, register it in your web.config file to handle the various request paths that you need to have dynamic content for, and then detect the content to display based on HttpContext.Current.Request.Path.
This way you don't have to save any files, and you write from your string variable to the output stream
You can try this in your new page:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
HttpContext.Current.Response.Clear()
HttpContext.Current.Response.ClearHeaders()
HttpContext.Current.Response.ClearContent()
HttpContext.Current.Response.ContentType = "text/html
HttpContext.Current.Response.Write(YourString)
HttpContext.Current.Response.Flush()
HttpContext.Current.Response.End()
End Sub
Create an .ashx page that takes a query string, e.g. pagebuilder.ashx?pageid=12345
The purpose of this page is simply to lookup in a session id based on the pageid query string. e.g.
var page = Session["PAGE_" + QueryString["pageid"]].ToString();
Response.Write(page);
On the page that generates the html in a variable, store the variable in Session at Page_Init
` ["PAGE_12345"] = generatedHtml;
Then on Page_Load, generate a javascript that opens to the url pagebuilder.ashx?pageid=12345.
That's it. You will be able to open your newly generated html in another window.