Open DataTable in Excel VB - asp.net

Ok, this is an interesting issue. I have been tasked with modifying an existing VB project. Currently the user selected from a series of dropdowns to select a sql query and then run that query. So the user selects and environment dropdown, the results of that dropdown populates the category dropdown. Once the category is selected, they get a dropdown of available queries. Once they select a query and hit the "Run" button, they get a gridview with the results of the query. Some of the query results are huge. The query I'm running as a test has 40 columns and 20,000 records. The query runs in less than 5 seconds but it takes over a minute to render the gridview. Once the gridview is done rendering, the user has the option to export the results to Excel. And by this, I mean the code opens an instance of Excel through gridview.RenderControl and displays the results in Excel. The user doesn't want to save the excel file and then navigate to the file, they want it to open right from the webform they are using which is what the code does currently.
However, the user doesn't care about the gridview. They don't care if they see it at all. They want to just open Excel. So instead of using gridview.RenderControl, I want to open Excel and populate it with the DataTable (or DataSet) in memory. Any thoughts on the best way to do that?
Here's how they are currently populating the gridview:
Dim MyConnection As SqlConnection
Dim MyCommand As SqlCommand
Dim MyDataTable As DataTable
Dim MyReader As SqlDataReader
MyConnection = New SqlConnection()
MyConnection.ConnectionString = ConfigurationManager.ConnectionStrings(Connection).ConnectionString
MyCommand = New SqlCommand()
MyCommand.CommandText = Sqlquery
MyCommand.CommandType = CommandType.Text
MyCommand.Connection = MyConnection
MyCommand.Connection.Open()
MyReader = MyCommand.ExecuteReader(CommandBehavior.CloseConnection)
MyDataTable = New DataTable()
MyDataTable.Load(MyReader)
If (MyDataTable.Rows.Count > 0) Then
QueryresultPanel.Visible = True
gvLineItems.DataSource = MyDataTable
gvLineItems.DataBind()
End If
MyDataTable.Dispose()
MyCommand.Dispose()
MyConnection.Dispose()
Here's how they're opening and populating the instance of Excel:
Protected Sub btnExportToExcel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnExportToExcel.Click
Response.Clear()
Response.Buffer = True
'
' Set the content type to Excel.
'
Response.AddHeader("content-disposition", "attachment;filename=GridViewExport.xls")
Response.Charset = ""
Response.ContentType = "application/vnd.ms-excel"
'
' Turn off the view state.
'
Me.EnableViewState = False
Dim oStringWriter As New System.IO.StringWriter()
Dim oHtmlTextWriter As New System.Web.UI.HtmlTextWriter(oStringWriter)
'
' Get the HTML for the control.
'
gvLineItems.RenderControl(oHtmlTextWriter)
'
' Write the HTML back to the browser.
'
Response.Write(oStringWriter.ToString())
Response.[End]()
End Sub
Obviously, there's no RenderControl for a DataTable or DataSet and can't figure out how to get this record set to render in an instance of Excel without saving it to a file first.

Alright, here's the solution I found (in case anyone is interested). It's pretty simple actually. I just looped through the datatable and used StringWriter.
Protected Sub WriteToExcelFile(dt As DataTable)
Dim sw As StringWriter
For Each datacol As DataColumn In dt.Columns
sw.Write(datacol.ColumnName + vbTab)
Next
Dim row As DataRow
For Each row In dt.Rows
sw.Write(vbNewLine)
Dim column As DataColumn
For Each column In dt.Columns
If Not row(column.ColumnName) Is Nothing Then
sw.Write(row(column).ToString() + vbTab)
Else
sw.Write(String.Empty + vbTab)
End If
Next column
Next row
Response.Clear()
Response.ContentType = "application/vnd.ms-excel"
Response.AddHeader("Content-Disposition", "attachment;filename=DataTable.xls")
Response.Output.Write(sw.ToString())
Response.Flush()
System.Web.HttpContext.Current.Response.Flush()
System.Web.HttpContext.Current.Response.SuppressContent = True
System.Web.HttpContext.Current.ApplicationInstance.CompleteRequest()
End Sub

Related

Gridview dropping milliseconds from database timestamp

I have a Gridview that displays record from a database, including a timestamp.
In the RowDataBound event, I'm trying to check the timestamp to see if I need to disable a button but the milliseconds are dropped and so my comparisons aren't working correctly.
How do I preserve the milliseconds from the database all the way to the RowDataBound event?
My code:
Dim dt As New DataTable
Using cn = New SqlConnection(ConfigurationManager.AppSettings("MyConStr"))
cn.Open()
Dim cmd As SqlCommand = New SqlCommand("spGetRecords", cn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#FirstParm", FirstParm)
cmd.Parameters.AddWithValue("#SecondParm", SecondParm)
Using reader = cmd.ExecuteReader()
dt.Load(reader)
End Using
End Using
gvMyGrid.DataSource = dt
gvMyGrid.DataBind()
And my RowDataBound:
Protected Sub gvMyGrid_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles gvMyGrid.RowDataBound
If gvNotes.DataSource.Rows.Count > 0 Then
Dim someTime As DateTime = "03/18/2019 08:28:09.090"
If e.Row.Cells.Item(timestampIndex).Text = someTime 'This is never true because the cell text drops the milliseconds
'Disable button here...
End If
End If
End Sub

Response.Redirect(Request.Url.AbsoluteURI) Clearing all user entries

I have a page where a user fills in some text boxes which get saved to a SQL database using a Submit button. The page also contains a button that allows them to upload attachments. If the user uploads an attachment BEFORE clicking the submit button to save the other data, the values in the text boxes are cleared once the upload routine executes the Response.Redirect(Request.Url.AbsoluteUri). I have tried saving the values I want to restore into the Session, but I don't seem to be able to restore them. The debugger shows they are there, but once the Response.Redirect is executed, the next lines are never executed. I'm brand new to ASP.NET, so I may just be missing something obvious. Here is the code for the upload procedure:
Protected Sub Upload(sender As Object, e As EventArgs) Handles btnUpload.Click
Session("Phone") = txtPhone.Text
Session("Name") = txtName.Text
Session("Email") = txtEmail.Text
Session("StartDate") = txtStartDate.Text
Session("EndDate") = txtEndDate.Text
Session("Subject") = txtSubject.Text
Session("Description") = txtDescription.Value
Dim filename As String = Path.GetFileName(FileUpload1.PostedFile.FileName)
Dim contentType As String = FileUpload1.PostedFile.ContentType
Using fs As Stream = FileUpload1.PostedFile.InputStream
Using br As New BinaryReader(fs)
Dim bytes As Byte() = br.ReadBytes(fs.Length)
Dim constr As String = ConfigurationManager.ConnectionStrings("EngineeringRequestsConnectionString").ConnectionString
Using con As New SqlConnection(constr)
Dim query As String = "insert into Attachments values (#id, #Name, #ContentType, #Data)"
Using cmd As New SqlCommand(query)
cmd.Connection = con
cmd.Parameters.Add("#id", SqlDbType.Int).Value = nextId
cmd.Parameters.Add("#Name", SqlDbType.VarChar).Value = filename
cmd.Parameters.Add("#ContentType", SqlDbType.NVarChar).Value = contentType
cmd.Parameters.Add("#Data", SqlDbType.VarBinary).Value = bytes
con.Open()
cmd.ExecuteNonQuery()
con.Close()
End Using
End Using
End Using
End Using
hasUpload = True
Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri)
BindGrid()
End Sub
The BindGrid() procedure attempts to restore the values from Session but never gets executed.
If hasUpload Then
txtPhone.Text = CType(Session("Phone"), String)
txtName.Text = CType(Session("Name"), String)
txtStartDate.Text = CType(Session("StartDate"), String)
End If
This is my first post on SO. I apologize if in advance if it is not clear enough.
If you are new to ASP.NET webforms it's probably worth checking out the Page Lifecycle as this dictates the order in which events are fired when a page is loaded. The issue is that you are effectively taking the user from page A to page B but expecting them to see results on page A.
In your method
Protected Sub Upload(sender As Object, e As EventArgs) Handles btnUpload.Click
.. skip ..
Response.Redirect(HttpContext.Current.Request.Url.AbsoluteUri)
BindGrid()
When you call Response.Redirect() the browser will redirect to a new page (e.g. from A -> B), this will start the page lifecycle over again, anything that happens after Response.Redirect() won't be rendered. I think what is confusing you is that you are redirecting from (A -> A), however this will still cause the page to be reloaded.
One option is to call BindGrid() and reload the data from session in one of the page load events, or remove the call to Response.Redirect() all together and instead leave the page as-is.

Deleting multiple Rows in GridView using Checkbox, Delete button outside of the Grid, without going to database

How do I delete multiple Rows in a GridView using a Checkbox, Delete button outside of the Grid, without going to database?
My purpose is to be able to delete multiple rows by SelectedCheckbox in the Gridview. After that, I use data show in gridview to make changes in database.
Dim dt As DataTable = ViewState("gridview")
Dim row As GridViewRow
For Each row In gridview.Rows
Dim SelectedRow As CheckBox = CType(row.FindControl("cbSelect"), CheckBox)
If SelectedRow.Checked Then
'gridview.rows(row.index).visible = false is not my case because those indexes are still there
End If
Next
I couldn't usegridview.rows(row.index).remove()/delete()
My 2nd option is store gridview to datatable by viewstate(). When i try to remove index in the FOR EACH loop, it showed error because after the 1st remove the datatable reindex.
Thanks for your help.
I got my 2nd option done by deleting row from bottom up.
I really appreciate If anyone could help me with my 1st option.
Dim dt As DataTable = ViewState("gridview")
For i As Integer = gridview.Rows.Count - 1 To 0 Step -1
Dim row As GridViewRow = gridview.Rows(i)
Dim SelectedRow As CheckBox = CType(row.FindControl("cbSelect"), CheckBox)
If SelectedRow.Checked Then
dt.Rows(i).Delete()
End If
Next
gridview.DataSource = dt
gridview.DataBind()
Yes, yes, delete each row from the bottom up. I believe it will be like this.
Private Sub Button_Delete_Checked_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button_Delete_Checked.Click
For Each row As DataGridViewRow In DataGridView1.Rows
If row.Cells("ToDelete").Value Then
MessageBox.Show(row.Cells("SomeText").Value & " will be deleted.")
End If
Next
End Sub
As for the Insert Into, this methodology should work fine.
Dim Con As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Music_Sales_Database.mdb;")
Dim Com As OleDbCommand
Dim SaleCode As Integer
Dim MusicID As String
Dim SubTotalPrice As Decimal
Dim Copies1 As Integer
Dim STR1 As String
SaleCode = 1
Com = New OleDbCommand
Com.Connection = Con
Connection.open()
For x As Integer = 0 To SalesDataGridView.Rows.Count - 1
MusicID = SalesDataGridView.Rows(x).Cells(0).Value
SubTotalPrice = SalesDataGridView.Rows(x).Cells(5).Value
Copies1 = SalesDataGridView.Rows(x).Cells(3).Value
STR1 = "INSERT INTO Sales(Sales_ID, Sales_Date, Copies, Music_ID, Staff_ID, Total_Price) VALUES (#Sales_ID, #Sales_Date, #Copies, #Music_ID, #Staff_ID, #Total_Price)"
Dim Comm As New OleDbCommand(STR1, Con)
Comm.Parameters.AddWithValue("#Sales_ID", SaleCode)
Comm.Parameters.AddWithValue("#Sales_Date", txtDateAndTime)
Comm.Parameters.AddWithValue("#Copies", Copies1)
Comm.Parameters.AddWithValue("#Music_ID", MusicID)
Comm.Parameters.AddWithValue("#Staff_ID", txtStaff_ID)
Comm.Parameters.AddWithValue("#Total_Price", SubTotalPrice)
Command.ExecuteNonQuery()
Comm.Dispose()
Next
Connection.Close()
Obviously, you need to modify this to suit your specific needs.

Export DataTable to Excel from VB.Net

The following code is my current attempt at opening some data in excel from a website button in VB.Net. I would like the data to show up barebones, but the formatting from the table on the website always follows. The paging and colors make the data near impossible to read and can only see the first page of data. Any quick fixes? I've tried a lot of things I've found on here but to no avail.
Private Sub DownloadExcel()
Response.Clear()
'Dim dt As DataTable = TryCast(ViewState("GridData"), DataTable)
Grid_Bad_Meters.AllowPaging = False
Grid_Bad_Meters.AllowSorting = False
'Grid_Bad_Meters.DataSource = dt
'Grid_Bad_Meters.DataBind()
Dim sfile As String = "Communication_Failures" & Now.Ticks
Response.AddHeader("content-disposition", "attachment;filename=" & sfile & ".xls")
Response.Charset = ""
' If you want the option to open the Excel file without saving then
' comment out the line below
' Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/vnd.ms-excel"
Dim stringWrite As New System.IO.StringWriter()
Dim htmlWrite As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(stringWrite)
Grid_Bad_Meters.RenderControl(htmlWrite)
Response.Write(stringWrite.ToString())
Response.End()
'Grid_Bad_Meters.AllowPaging = True
'Grid_Bad_Meters.AllowSorting = True
'GridView1.DataSource = dt
'GridView1.DataBind()
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Try
Dim ScriptManager As ScriptManager = ScriptManager.GetCurrent(Me.Page)
ScriptManager.RegisterPostBackControl(Me.btnExportToExcel)
Catch ex As Exception
End Try
End Sub
Protected Sub btnExportToExcel_Click(sender As Object, e As EventArgs) Handles btnExportToExcel.Click
Try
Dim sw As New System.IO.StringWriter()
Dim hw As New System.Web.UI.HtmlTextWriter(sw)
Dim style As String = "<style>.textmode{mso-number-format:\#;}</style>"
Response.Clear()
Response.Buffer = True
Response.AddHeader("content-disposition", "attachment;filename=SignExport.xls")
Response.Charset = ""
Response.ContentType = "application/vnd.ms-excel"
For i As Integer = 0 To Me.Grid_Bad_Meters.Rows.Count - 1
Dim row As GridViewRow = Grid_Bad_Meters.Rows(i)
row.Attributes.Add("class", "textmode")
Next
'lblRptHeader.RenderControl(hw)
hw.WriteBreak()
'lblReportDateRange.RenderControl(hw)
Grid_Bad_Meters.RenderControl(hw)
Response.Write(style)
Response.Output.Write(sw.ToString())
Response.Flush()
Response.End()
Catch ex As Exception
End Try
End Sub
You could use one of the two approaches mentioned below. Of course, there are other ways of meeting your requirement like exporting to csv file as mentioned in a comment or using a .Net library meant for Excel exporting like epplus.
OpenXML Approach
If you are looking for a way to export to Excel without using the html approach, then you can use OpenXML approach that is explained very clearly with a working example at this URL: Export to Excel using OpenXML. This will eliminate all the CSS styles that can get associated with exporting using html approach and you seem to be using this html approach according to the code in your original post. However, if you want to use the html approach, then the code below should work and eliminate all CSS styles that can come in the way when viewing the excel file. I have actually tried this posted code on my machine before putting it here.
Html Approach
You can create a new instance of GridView in your export method rather than use an existing instance, and data bind it to same data as the existing gridview on your page before rendering it to excel. Before you data bind it in the export method you need to make sure that no styles are set and specifically the grid line are set to none as in code below.
You can see an actual video of how this works at this URL : Grid Export without any CSS Styles. This was how the code behaved on my laptop when I ran it.
You can use sample code below, but make sure the data source is set to data that includes all records across all pages of original gridview. I have used SqlDataSource1 as data source but you can replace it by an appropriate method in your situation.
Protected Sub btnExport_Click(sender As Object, e As EventArgs)
Dim GridView2 As New GridView()
GridView2.AllowPaging = False
GridView2.AllowSorting = False
GridView2.Style.Clear()
GridView2.CellPadding = 0
GridView2.CellSpacing = 0
GridView2.GridLines = GridLines.None
GridView2.BorderStyle = BorderStyle.None
GridView2.BorderWidth = Unit.Pixel(0)
GridView2.AlternatingRowStyle.BorderStyle = BorderStyle.None
GridView2.DataSource = SqlDataSource1
GridView2.DataBind()
' Clear the response
Response.Clear()
' Set the type and filename
Response.AddHeader("content-disposition", "attachment;filename=griddata.xls")
Response.Charset = ""
Response.ContentType = "application/vnd.xls"
' Add the HTML from the GridView to a StringWriter so we can write it out later
Dim sw As New System.IO.StringWriter()
Dim hw As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(sw)
GridView2.RenderControl(hw)
' Write out the data
Response.Write(sw.ToString())
Response.[End]()
End Sub
Public Overrides Property EnableEventValidation() As Boolean
Get
Return False
End Get
'Do nothing
Set
End Set
End Property
Public Overrides Sub VerifyRenderingInServerForm(control As Control)
'Allows for printing
End Sub

Exporting datatable to excel file with save dialog

I have a datatable being created with various inputs. Sometimes the resulting table is 35000+ rows. Currently, the datatable gets displayed onto a gridview. It loads fine after a couple minutes. Then, theres an option to export the gridview to an excel file. Everytime we have a large table to export, the conversion fails.
My goal is to bypass the gridview step and take the formatted table and put it directly into an excel file. Could also be a csv file if thats faster to write/load, as long as the data table is similar to the gridview output.
I tried the following code here Export DataTable to Excel File. I did my best to convert it to vb, here...
Protected Sub btnExportData_Click(sender As Object, e As EventArgs) Handles btnExportData.Click
Dim dt As DataTable
dt = CreateDataSource()
Dim filename As String = "attachment; filename=DistComplain.xls"
Response.ClearContent()
Response.AddHeader("content-disposition", filename)
Response.ContentType = "application/vnd.ms-excel"
Dim tab As String = ""
For Each dc As DataColumn In dt.Columns
Response.Write((tab + dc.ColumnName))
tab = "" & vbTab
Next
Response.Write("" & vbLf)
Dim i As Integer
For Each dr As DataRow In dt.Rows
tab = ""
i = 0
Do While (i < dt.Columns.Count)
Response.Write((tab + dr(i).ToString))
tab = "" & vbTab
i = (i + 1)
Loop
Response.Write("" & vbLf)
Next
Response.End()
End Sub
CreateDataSource() is the table that gets created in memory. Then theres other buttons that call it to fill the gridview. Right now it successfully complies and runs, and then it successfully creates the file. Although, when the file tries to open I get this error...
This happens when I try both xls and csv files. Something is not getting translated right. Any solutions?
(Written with help from Google) Create an export using the StringWriter class:
Public Shared Sub ExportDataSetToExcel(ds As DataSet, filename As String)
Dim response As HttpResponse = HttpContext.Current.Response
'Clean response object
response.Clear()
response.Charset = ""
'Set response header
response.ContentType = "application/vnd.ms-excel"
response.AddHeader("Content-Disposition", "attachment;filename=""" & filename & """")
'Create StringWriter and use to create CSV
Using sw As New StringWriter()
Using htw As New HtmlTextWriter(sw)
'Instantiate DataGrid
Dim dg As New DataGrid()
dg.DataSource = ds.Tables(0)
dg.DataBind()
dg.RenderControl(htw)
response.Write(sw.ToString())
response.[End]()
End Using
End Using
End Sub
You just need to pass the function the DataSet and the File Name. If you do not want to edit your CreateDataSource() function, you can merge it into a DataSet first like so:
Dim dt As DataTable = CreateDataSource()
Dim ds As New DataSet
ds.Merge(dt)
Your question is about why you're getting the message about being unable to open the file, correct?
According to Microsoft, this occurs when you have the "Ignore other applications that use Dynamic Data Exchange (DDE)" setting turned on. (See here). The link includes instructions to change the setting.

Resources