Export to Excel - ThreadAbortException - c#

I'm having a problem with the conversion to Excel code I'm finding. I am working on a website project in .NET 4.0, and I have created a class for this that does the following (based on
http://mattberseth.com/blog/2007/04/export_gridview_to_excel_1.html):
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("content-disposition",
string.Format("attachment; filename={0}", fileName)); HttpContext.Current.Response.ContentType = "application/ms-excel"; using (StringWriter sw = new StringWriter()) {
using (HtmlTextWriter htw = new HtmlTextWriter(sw)) {
//Create a table to contain the grid
//Add header row
//Add each data row
//Add Footer row
//Render the table into the htmlwriter
// render the htmlwriter into the response
HttpContext.Current.Response.Write(sw.ToString());
HttpContext.Current.Response.End();
}
}
I call this class from a usercontrol that contains a button that is added to a GridView displayed on the page. This works as expected - click the button, you are presented with a download option to either open or save the resulting excel spreadsheet containing the data from the GridView.
However, when I call this from a linkbutton inside a different GridView, I'd like to build a dynamic gridview to contain data and export that. When I do that, I get a ThreadAbortException from the Response.End call in the class.
Question 1: Why do I not get that ThreadAbortException when calling the same code from within a usercontrol? Do usercontrols get their own threads or some other kind of context?
Searching on the error I get when that ThreadAbortException occurs led me to attempt to replace it with ApplicationInstance.CompleteRequest(). When I do that I no longer get the ThreadAbortException, but this breaks the previously working usercontrol - instead of the resulting excel spreadsheet containing the data from the grid, it contains the HTML from the containing page, and at any rate it's easy enough to suppress that error with an empty catch. However, it doesn't fix the direct call with the dynamically generated GridView, that code renders a javascript error: "The message received from the server could not be parsed."
I would love to understand what exactly is going on here, but I'm at the point of needing results regardless of understanding. All the other approaches I've tried (datagrid instead of GridView, etc) run into the same problems, and are essentially the same when it comes down to "taking over"
the current response and using stringwriter and htmlwriter to render the data into a response with excel contentType. And since this demonstrably works in the context of a usercontrol, I am at my wit's end as to why it won't work when called directly...

The problem was actually completely unrelated to the excel export. The “…could not be parsed” error was the key. From these links I got the key, which was that the grid events cause only a partial postback event:
http://forums.asp.net/t/1392827.aspx
http://forums.aspfree.com/net-development-11/gridview-footer-template-button-in-updatepanel-not-posting-back-236087.html
This explains the ThreadAbortException and the “…could not be parsed” error. Adding this to the OnPreRender of the ImageButton was the solution:
protected void addTrigger_PreRender(object sender, EventArgs e)
{
if (sender is ImageButton)
{
ImageButton imgBtn = (ImageButton)sender;
ScriptManager ScriptMgr = (ScriptManager)this.FindControl("ScriptManager1");
ScriptMgr.RegisterPostBackControl(ImgBtn);
}
}

Try instead:
HttpApplication.CompleteRequest()
as per:
http://www.c6software.com/codesolutions/dotnet/threadabortexception.aspx
They discuss the additional html being flished

use this
Response.Clear()
Response.AddHeader("content-disposition", atchment;filename=fm_specification.xls")
Response.Charset = ""
Response.Cache.SetCacheability(HttpCacheability.NoCache)
Response.ContentType = "application/vnd.xls"
Dim stringWrite As System.IO.StringWriter = New System.IO.StringWriter
Dim htmlwrite As System.Web.UI.HtmlTextWriter = New HtmlTextWriter(stringWrite)
GridView1.RenderControl(htmlwrite)
Response.Write(stringWrite.ToString)
Response.End()
instead of gridview1 you can use div
dont forget to add this on your page
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As Control)
End Sub

The event on which the Export to excel code is called, must make a full postback. the issue is because it does only a partial postback.
I had the same error and it got solved when i did a full postback.
Hope this helps someone.

Related

Building a Word Document in C# is stopping me from making panels visible in ASP, what's going wrong?

So I'm working with this form which, at the end, generates a Word document for the user to download, fires off some emails, and then displays a success panel for the user. The problem I'm having is that the .Visible flags don't end up getting changed whenever I call the downloadWordFile() method. Is there something in that method that would be messing with my ability to change visibility of ASP panels? I've tried removing almost every part of this to see where the issue is popping up and I haven't been able to make heads or tails of it. Everything else works fine, it's just this method causing the issue. Thanks in advance for your help!
protected void btnSubmit_Click(object sender, EventArgs e)
{
pnlForm.Visible = false;
pnlSuccess.Visible = true;
email();
adminEmail();
downloadWordFile();
}
protected void downloadWordFile(){
Response.ContentType = "application/msword";
Response.ContentEncoding = System.Text.UnicodeEncoding.UTF8;
Response.Charset = "UTF-8";
Response.Write("<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'>");
Response.Write("<head>");
Response.Write("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"\"text/html; charset=UTF-8\"\">");
Response.Write("<meta name=ProgId content=Word.Document>");
Response.Write("</head>");
Response.Write("<body>");
Response.Write("<div class=Section2>");
Response.Write(buildForm());
Response.Write("</body>");
Response.Write("</html>");
Response.AppendHeader("Content-Disposition", "attachment; filename=" + "Generic.doc");
HttpContext.Current.Response.Flush();
}
EDIT 1: I ended up going a different route entirely. Rather than trying to switch panels, generate emails, and generate word files all in one go, I made the submit button switch the panels, and the success panel now shows a preview of the word document to be downloaded and provides the user a chance to make changes. There is a download button on the success panel that generates the emails and downloads the word document. This required minimal changes to the way the code was already set up and solved another problem I was having with the email generation.
A response can only be one thing. You can't respond with page content and a file. You are setting the response to a file so that is how the browser handles the response.
I would handle this situation by the calling a JavaScript function in the button's click event that downloads the file via an IFrame.
First you would need a "download file" page. In the OnLoad event of that page you can call email();, adminEmail(); and downloadWordFile();
The JavaScript click event handler would look something like this.
function DownloadFile() {
var downloadFrame = document.createElement("IFRAME");
if (downloadFrame != null) {
downloadFrame.setAttribute("src", 'DownloadFile.aspx');
downloadFrame.style.width = "0px";
downloadFrame.style.height = "0px";
document.body.appendChild(downloadFrame);
//Set the visibility of pnlForm and pnlSuccess
}
}
EDIT:
Per your comment, I've had to do the same thing. I solved it by posting the form as normal and assembling the form values into a query string. Then use ScriptManager.RegisterStartupScript to add a script that calls the DownloadFile() function when the page loads. The DownloadFile() function takes the query string as a parameter and the DownloadFile.aspx page uses it to get the form data.
So the click event handler would be updated like this.
function DownloadFile(queryString) {
var downloadFrame = document.createElement("IFRAME");
if (downloadFrame != null) {
downloadFrame.setAttribute("src", 'DownloadFile.aspx' + queryString);
downloadFrame.style.width = "0px";
downloadFrame.style.height = "0px";
document.body.appendChild(downloadFrame);
//Set the visibility of pnlForm and pnlSuccess
}
}

Create new page as control and show in a popup window

Essentially, what I want to do is this:
protected void Imgexport_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
//get the download page as an object (requires complex parameter)
GenericExcelDownloader Ged = new GenericExcelDownloader(gvEquipmentRuntime, "Projected Maintenance Report.xls");
//get the response stream from the download page
StringWriter sWriter = new StringWriter();
HtmlTextWriter hTextWriter = new HtmlTextWriter(sWriter);
Ged.RenderControl(hTextWriter);
//????
//Profit?
//WHAT DO?
}
The control I called contains code during page load which essentially ends like so: (there are other modifications made to the grid, which is added to the Div referenced as "Download" below)
Response.ClearContent();
Response.Buffer = true;
Response.AddHeader("content-disposition", "attachment; filename=" + fileName);
Response.Charset = "";
Response.ContentType = "application/excel";
StringWriter sWriter = new StringWriter();
HtmlTextWriter hTextWriter = new HtmlTextWriter(sWriter);
Download.RenderControl(hTextWriter);//this is a Div on the page which contains the visible portion of the page control
Response.Output.Write(sWriter.ToString());
Response.Flush();
Response.End();
The end goal is that on the click of the button, the div on the other page containing a modified version of the grid view passed in as an initial parameter in the click event, is downloaded on the client machine.
Where I'm stuck, is that I dont know how to get the ASPX Page in my code behind which I have as an object, into the front end. I dont even know for sure if my initial attempt is the correct starting place. Because of the complex parameter and its size I dont want to solely rely on a window.open with string parameters and/or a session variable...
UPDATE: WORKAROUND
I found a method which will accomplish my goal, although the original question hasn't actually been answered
By making use of Server.Execute() I was able to directly run the sub-page. It also gives me access to the Page.PreviousPage property, which is an object level reference to the calling page, which contains the complex object I needed. I then can back reference the page to get this object directly on the sub-page. The code looks something like this
protected void Imgexport_Click(object sender, System.Web.UI.ImageClickEventArgs e)
{
Server.Execute("GenericExcelDownloader.aspx?Report=Projected%20Maintenance%20Report%2Exls", true);
}
and on the other side
ReportName = Request.QueryString["Report"];
if (PreviousPage != null && PreviousPage is iReportPage)
ReportGrid = ((iReportPage)PreviousPage).ReportView;
This now allows the page to process directly, and have it's response handled so that it will download the grid the way I need it to.
KNOWN ISSUE:
DataSource in GridView is not stored in any persistent way across Postback so you have to save it somewhere (Viewstate or Session) or you have to request it again from your data-store (Es your db). Gridview Datasource is Null During Postback
I ended up tabling this entire experiment shortly after I got it working because of that. I wanted to avoid stashing all the data in memory, And avoid having to re-generate the grid during the post back. Apparently this is impossible, so I need to accomplish this in a slightly more direct manner.
I'm still quite interested in the answer to the original question though, I can imagine multiple uses for this.

How to export asp grid view inside a user control to excel?

I have a GridView in a WebUserControl in a web page thast uses a master page, and I need to export the grid data to excel and pdf.
I found this code:
Response.AppendHeader("content-disposition", "attachment;filename=ExportedHtml.xls");
Response.Charset = "";
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/vnd.ms-excel";
this.EnableViewState = false;
System.IO.StringWriter tw = new System.IO.StringWriter();
System.Web.UI.HtmlTextWriter hw = new System.Web.UI.HtmlTextWriter(tw);
((GridView)Hosts.FindControl("hostsGrid")).RenderControl(hw);
Response.Write(tw.ToString());
Response.End();
But I'm getting this error:
Control 'CPH_Body_Hosts_hostsGrid' of type 'GridView' must be placed inside
a form tag with runat=server.
Even though I have a form runat server on the Master page.
Well I found 2 solutions for this problem:
As King.code commented, it was already solved in GridView must be placed inside a form tag with runat=“server” even after the GridView is within a form tag
I also found this sample code that exports in many other formats Export GridView to doc/access/csv/Excel/pdf/xml/html/text/print

Export a GridView in an update panel to Excel and then refresh the grid - not working

I have a gridview in an update panel. I can export it to an excel using HTTP response and it exports the required data and open the file in a pop up which is all working fine.
But, once the data is exported I have to update the grid view indicating that these data were exported. I verified the correct data is set to the data source and databind is called. But the excel export is not triggering the screen to refresh.
If I trigger a refresh by changing a drop down or by something else, I can see the data changed. I tried UpdatePanelID.Update() - still in vain.
So how to trigger a gridview refresh after the excel export?
Thanks in advance.
My code for export:
var excelXml = GetExcelXml(dsInput, filename);
response.Clear();
response.AppendHeader("Content-Type", "application/vnd.ms-excel");
response.AppendHeader("Content-disposition", "attachment; filename=" + filename);
response.Write(excelXml);
response.Flush();
protected void btnExport_Click(object sender, EventArgs e)
{
try
{
if (list.Count() > 0)
{
ds.Tables.Add(dtForExport);
ExcelHelper.ToExcel(ds, filename); //To Excel method is described above.
LoadGridDetails();//Binds the new values
}
}
catch (Exception ex)
{
lblStatus.Text = "Error Exporting to Excel";
}
}//Screen is not refreshed after executing this line.
What you call a «Popup», it seems to me that you are talking about the browser download popup.
So, in this case, it wont work, because you already did an Response.Clear and you can't anymore do an refresh of your grid.
My suggestion is: you can open an real popup it means: window.popup, and this popup will do all the response job, or you can also put some java-script in your button "export" to do two things: do the post-back to export function and wait X milliseconds to call a click of some hidden button just to make a second post-back and do the refresh.
Hope it helps

Issues exporting datagridview to excel

I'm trying to do an export of a datagrid to excel. For some reason, known working methods aren't working. The export is done from a user control. My page (default.aspx) uses a master page and the page has a user control that actually has the datagrid I'm trying to export.
Here's my code on the ascx:
Response.ClearContent();
Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
Response.ContentType = "application/excel";
StringWriter sw = new StringWriter();
HtmlTextWriter htw = new HtmlTextWriter(sw);
_gvwResults.RenderControl(htw);
Response.Write(sw.ToString());
Response.End();
On my default.aspx (page the holds the ascx) is this code:
public override void VerifyRenderingInServerForm(Control control)
{
/* Confirms that an HtmlForm control is rendered for the specified ASP.NET
server control at run time. - required to export file */
}
Here's the error I receive:
Sys.Webforms.pagerequestmanagerparsererrorexception. The message received from the server could ot be parsed. Common causes for this error are when the response is modified by calls to Response.Write(), response filters, httpmodules or server trace is enabled.
Any ideas? This code should work but it's almost as if the response object is not being cleared. Ideas?
Turns out that the since the combobox was ajaxified, the export was not happening. The event was firing but, as the error message says, it was response.writing onto the existing page, thus throwing the error, which would not allow for a new document (xls in this case) to be rendered. After setting the combobox to do a postback on the page and un-ajaxifying it, the export started working..

Categories