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.
Related
So I know that this is common problem, page freezes (any buttons visually are clickable but doesn't perform any action) because it doesn't close some request after file download.
System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
response.ClearContent();
response.Clear();
response.ContentType = "text/txt";
response.AddHeader("Content-Disposition", "attachment; filename=" + TreeView1.SelectedNode.Text + ";");
response.TransmitFile(TreeView1.SelectedNode.Value);
response.Flush();
response.Close();
HttpContext.Current.ApplicationInstance.CompleteRequest();
This code is in TreeView SelectedNodeChanged event, and this is a problem.
For example if will put that transmit code in some button, I can fix freezing of page by adding OnClientClick="javascript:setFormSubmitToFalse()" to the button and this small JavaScript fixes page freezing after download.
<script type="text/javascript">
//Fix page freeze after download dialog
function setFormSubmitToFalse() {
setTimeout(function () { _spFormOnSubmitCalled = false; }, 3000);
return true;
}
</script>
But I don't know how to fix this if I'm clicking on TreeView node. TreeView doesn't have OnClientClick, I also tried to run that JS function from code behind right before and after transmit code Page.ClientScript.RegisterStartupScript(this.GetType(), "CallMyFunction", "setFormSubmitToFalse()", true);
but it doesn't help, page is still freezing after file download.
Any ideas how can I fix this? Thanks.
This is webpart (almost asp.net page) on sharepoint site.
Edit:
I'll try to explain better my situation and what I'm trying to achive.
So my main goal: There is a shared folder with documents in local network, users that will be using this app should not have access to this shared folder, only entity that is running App pool has access to it.
I am making some SQL request that gives me path of some subfolder. I am building treeview of that subfolder and when user clicks on some file in treeview SelectedNodeChanged event shots and I perform transmitting file as app pool identity
SPSecurity.RunWithElevatedPrivileges(delegate()
{
/* transmit file code */
});
Everything is working fine but if I understand right, something on CLIENT side probably freezes page after TransmitFile shoots and prevents from sending any other post request to server, that's why my little JS solution worked when it was performed OnClientClick.
This is original how author of that fix explained that, but I don't know how to implement this into treeview https://stackoverflow.com/a/17186011/5805492
So solution was pretty simple but a little bit tricky.
Nodes doesn't have onClientClick event, but we can add JS click event to any page element, right? So here is the trick, when creating TreeView just add click to actual text of the node with help of Span:
...
TreeNode fileNode = new TreeNode
{
//before
//Text = file.Name,
//after
Text = "<span onclick=\"javascript:setFormSubmitToFalse();\">"+file.Name+"</span>",
Value = file.FullName,
};
...
And in transmitting file parse text of the node to have normal name of the file:
response.AddHeader("Content-Disposition", "attachment; filename=" +
TreeView1.SelectedNode.Text.Replace("<span onclick=\"javascript:setFormSubmitToFalse();\">", string.Empty).Replace("</span>", string.Empty) + ";");
Now everything is working and page is not freezing anymore after downloading.
I'm not sure I really understand your situation, but your app is likely being frozen due to the fact that the UI thread is hung up on code that you are trying to run synchronously.
In order to prevent that from happening, you need to run the logic asynchronously.
public async void OnSelectedNodeChanged(object sender, EventArgs e)
{
await Task.Run(() => {
System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
response.ClearContent();
response.Clear();
response.ContentType = "text/txt";
response.AddHeader("Content-Disposition", "attachment; filename=" + TreeView1.SelectedNode.Text + ";");
response.TransmitFile(TreeView1.SelectedNode.Value);
response.Flush();
response.Close();
HttpContext.Current.ApplicationInstance.CompleteRequest();
}).ConfigureAwait(false);
}
This is an async void method, which means the code that fires this event will not wait for it to finish, nor will it know when it has finished.
This will prevent the thread from locking your UI, but if the user has the ability to "rapid fire" these events by clicking all around your UI, it may not be desirable to write the code this way.
Regardless, you will likely need some form of async logic to accomplish what you are wanting.
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
}
}
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.
I've created a page that allows users to download a file when they click a button... the button's onclick event is linked to the following chunk of code:
this.Page.Response.Clear();
this.Page.Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Zip;
this.Page.Response.AppendHeader("Content-Disposition", "attachment; filename=\"" + System.IO.Path.GetFileName(filename) + "\"");
this.Page.Response.TransmitFile(filename);
this.Page.Response.Flush();
this.Page.Response.End();
The download works fine, but now when I try to interact with the page, (for instance, hit the download button again), nothing posts back.
Am I responding to the download request incorrectly (should I be using a different/new response object), or is there something else I need to do to make the page active after the download?
Edit:
So I've tried encorporating the two posters suggestions for creating a httphandler, and calling a Response.Redirect to the handler from the button's click event.
void submitButton_Click(object sender, EventArgs e)
{
label.Text = "Boo!";
this.Page.Response.Redirect("~/ViewAttachment.ashx?id=foo", false);
}
If I step through this on the debugger, it continues after the redirect call, but the page just returns to a state where the button doesn't work, and the labels have their default values. Am I now doing the redirect wrong?
If using another handler an option?
Here's a slimmed down version of what I've used before (suggestions welcome this was a quick write). The HttpHandler (AttachmentFile is just a class with the blob data and some attributes collected when the file was uploaded):
public class AttachmentHandler : IHttpHandler
{
public const string QueryKeyID = "ID";
public void ProcessRequest(HttpContext context)
{
var r = context.Response;
var attachmentID = context.Request.QueryString[QueryKeyID];
Attachment a = DataContext.GetById<AttachmentFile>(attachmentID);
r.ContentType = a.ContentType;
r.AppendHeader("Content-Type", a.ContentType);
r.AppendHeader("content-disposition", string.Format("attachment; filename=\"{0}{1}\"", a.AttachmentName, GetExtension(a.FileName)));
r.BufferOutput = false; //Stream the content to the client, no need to cache entire streams in memory...
r.BinaryWrite(a.BlobData);
r.End();
}
private static string GetExtension(string fileName)
{
if(fileName.IsNullOrEmpty()) return string.Empty;
var i = fileName.LastIndexOf(".");
return i > 0 ? fileName.Substring(i) : string.Empty;
}
}
In the web.config:
<system.web>
<httpHandlers>
<add verb="*" path="ViewAttachment.ashx" type="MyNamespace.AttachmentHandler, MyWebDllName"/>
</httpHandlers>
</system.web>
Then all you do is render a link in the form of ~/ViewAttachment.ashx?ID=5 on the page, clicking the button will download the file, but not mess with your page's lifecycle at all.
Now there are more considerations, like security and such....I trimmed them out for this example. I'm also using this style of link: ~/Attachment/{id} via webforms routing...if either of these interest you I'll update to include them. Or you could hate this approach all-together...just letting you know it's an option.
You are mostly there.
All you need to do in your button click event handler is Response.Redirect to another ASPX page. Put that code your wrote on the other page.
Then everything will work just as you expect. The browser will actually stay on the original page and not go to the new page because you have set the content disposition to attachment - which is great.
By using Response.Clear() you've killed the headers. While there may appear to be valid html markup in the browser there really isn't. The headers are gone, so the controls that appear to be still rendered aren't valid. If you click "back" you should be able to repost information. What you probably want to do is make this call in a different window using PostBackUrl in your button click and setting the target of your form to a different window (then using SetTimeout to reset it to _self).
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..