How to show error view after CSV export after directly writing to HttpContext.Current.Response? - asp.net-mvc

In an ASP.NET MVC web application I write directly to HttpContext.Current.Response to export to a CSV file.
This is done in an action in my controller. So I do something like this:
try
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.ClearHeaders();
HttpContext.Current.Response.Cookies.Clear();
HttpContext.Current.Response.Charset = System.Text.UTF8Encoding.UTF8.WebName;
HttpContext.Current.Response.ContentEncoding = System.Text.UTF8Encoding.UTF8;
HttpContext.Current.Response.AppendHeader("Pragma", "no-cache");
HttpContext.Current.Response.AppendHeader("Content-Disposition", fileName);
HttpContext.Current.Response.ContentType = "text/csv";
HttpContext.Current.Response.Write("a,b,c\n");
}
catch(Exception)
{
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.SuppressContent = true;
return RedirectToAction("Error");
//return PartialView("ErrorExportingData"); //Prefered!
}
This works without any problem, except that the page is not redirected (or the partial view is not displayed). I guess the problem is that a complete response was already created (and completed).
I flush the response and suppress the content before the redirect, so the exception stack trace does not end up in my CSV file. After this I somehow need to build a new response.
My question is: In this situation, How can I redirect to an error page, after an exception was thrown?
(If somebody wonders why I want to write directly to HttpContext.Current.Response? This is because it is the fastest way to write many records to a CSV file, using a SqlDataReader.)

Related

MVC - Excel download on button click Cannot redirect after HTTP headers have been sent

Firstly, I know there are multiple posts related to this error but nothing is helping so far.
I am getting an error "Cannot redirect after HTTP headers have been sent." in my MVC razor application when I make a call to SomeController.
How do I fix this?
[HttpPost]
public ActionResult SomeController(Object abc)
{
Helper.somemethod("","excel");
return View(abc);//I tried this
return RedirectToAction("SomeController"); //I tried this also
}
public static void somemethod(string settocken, string filenames, List<Sample> samples)
{
//Extra logic for excel that uses List<Sample> to generate excel.
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Cookies.Add(new HttpCookie("downloadToken", settocken));
HttpContext.Current.Response.ContentType = "Application/x-msexcel";
HttpContext.Current.Response.AddHeader("Content-Disposition", String.Format("attachment; filename={0}.xlsx", filenames));
using (MemoryStream ms = new MemoryStream())
{
try
{
book.Save(ms);
HttpContext.Current.Response.BinaryWrite(ms.ToArray());
}
catch
{
}
finally
{
}
HttpContext.Current.Response.End();
}
}
#CoolArchTek http communication loop will be ended after HttpContext.Current.Response.End() call, so you can't add anything after that, however as far as I know there is no possibility in http to do what you want, so I would recommend you render your view with javascript which will initiate file download by client's browser
I would suggest, have an iframe inside view where you want to redirect to and show the data. And set the excel file path(or the action which returns excel file) as source for that iframe.
With this, as soon as the page loads, download begins.
Hope it helps.

ASP.NET MVC: file response streaming?

When I return a FilePathResult from an MVC action method, which of the following happens (assuming, if it matters, that the file to which the result points is very large):
The file gets loaded in its entirety into the server's memory, and then sent to the client.
The file is somehow streamed to the client, in such a way that it is not at any point fully loaded into the server's memory.
Something else.
If the answer is 1, is it possible to have the file sent as in 2 instead by returning a different type of result?
UPDATE: FilePathResult uses response.TransmitFile which "Writes the specified file directly to an HTTP response output stream, without buffering it in memory.". Here's the source code for MVC.
You can stream data back using the FileStreamResult class:
return new FileStreamResult(stream, "application/pdf")
{
result.FileDownloadName = "somefile.pdf";
};
Or you could redirect to the file like this:
return Redirect("somefile.pdf");

Multi Post for Action with return File in ASP.NET MVC

Assume this code in One of my Actions
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("Index", new { info = info });
}
So every thing is OK yet but when I change return to:
InsertOneRawToDB();
byte[] myfile = GenerateAZipFile();
return File( myfile , "application/zip" , "MyFileName");
In this case I see a weird behavior: before return, One raw inserted to DB, and after return another raw inserted, it seems the InsertOneRawToDB called again.
Does any one know about this? what happening here?
PS: I use Visual Studio 2012 RTM + ASP.NET MVC4
PS: OK I Use IDM (Internet Download Manager) to download zip file and that cause MultiPost on this Action So How can I handle this?
Based on your conclusion that the problem is related to using a download manager... That's what download mangers do. They create multiple connections to the file.
One thing you could do is store a session variable that says "Already started downloading", and then only insert the record the first time.
however if the user legitimately downloaded the file multiple times then you would only get one record.
Another option would be to examine the Http headers and look for the "Range" header, which is what is used to download a file in multiple pieces (or resume a file). You would then have to take the Range parameters and only return the portion of the file requested.
Here's an example of how to do a Ranged download: http://tpeczek.com/2011/10/range-requests-in-aspnet-mvc.html
I'm actually quite surprised that this hasn't come up before... I guess most people don't do database actions in a download action or notice it.
You can redirect to new action and in this new action return file.
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("GetFile"};
}
[HttpGet]
public ActionResult GetFile
{
byte[] myfile = GenerateAZipFile();
return File(myfile, "application/zip", "filename.ext");
}

XSSFWorkbook#write() results in empty response

I'm trying to get my JSF to export a spreadsheet for download. I'm using Apache's POI library for the Excel document writing. I get the following error in an alert box when the code runs:
emptyResponse: An empty response was received from the server.
The method generating the spreadsheet and exporting to the OutputStream is below (I have renamed classes, methods etc for simplicity sake).
private void generateSpreadsheet(Object object) throws Exception {
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse();
String fileName = object.getProperty() + ".xlsx";
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName +"\"");
OutputStream os = response.getOutputStream();
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("Sheet 1");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("test");
wb.write(os);
os.flush();
os.close();
FacesContext.getCurrentInstance().responseComplete();
}
Any advice much appreciated, thanks.
If it makes a difference, I'm using AJAX (<f:ajax> tag) on the form submit that calls this method.
It makes definitely difference. You can not download files by Ajax. Ajax is executed by JavaScript code. But JavaScript has no facilities to force a Save As dialogue or to execute the platform default application associated with the file's mime type (thankfully; that would have been a major security/intrusion problem). Further, the JSF Ajax API expects a XML response in a specified structure conform the JSF specs. When you send a complete Excel file instead, the whole Ajax response would be ignored as ununderstandable garbage by the JSF Ajax API.
You need to send a normal synchronous request. Remove the <f:ajax> tag from the command link/button. The current page will remain the same anyway if the download is sent as an attachment.

Writing binary content directly to the client bypassing the Grails view layer

The following action is meant to write the binary content of bytes directly to the client completely bypassing the Grails view layer:
def actionName = {
byte[] bytes = ...
ServletOutputStream out = response.getOutputStream()
out.write(bytes)
out.flush()
out.close()
return false
}
I was under the impression that return false would make Grails completely skip the view layer. However, that appears not to be the case since the above code still makes Grails search for /WEB-INF/grails-app/views/controllerName/actionName.jsp (which fails with a 404, since no such file exists).
Question:
Given the code above, how do I completely bypass the view layer in Grails?
You should return null or nothing at all, which is interpreted as null. Here's some working code from an action that sends a dynamically generated PDF:
def pdf = {
byte[] content = ...
String filename = ...
response.contentType = 'application/octet-stream'
response.setHeader 'Content-disposition', "attachment; filename=\"$filename\""
response.outputStream << content
response.outputStream.flush()
}
It appears as Grails tries to render the view if response.contentType.startsWith('text/html'). This seems to be a known bug, see GRAILS-1223.
Here are two work arounds:
Use render(contentType: "text/html", text: htmlString) as suggested in GRAILS-1223. This will bypass the view layer.
Clear the content type with response.contentType = ''. This will also bypass the view layer. However, note that the content will be served to the end-user without Content-Type which can confuse some browsers.

Resources