I have a generic handler that I call using this very basic js code:
var formData = new FormData();
formData.append("fileId", this.model.get("id"));
xhr = new XMLHttpRequest();
xhr.open('POST', '/genericHandlers/DownloadFile.ashx');
xhr.onload = function () {
if (xhr.status === 200) {
// Do something here?
}
}
xhr.send(formData);
My generic handler code seems to look ok to me. Basically I'm attempting to build the response header and I thought that when this handler returned it would begin the download of the file.
Handler code:
var fileId = context.Request.Form["fileId"];
// File stored in the db as a byte array
var file = (from f in dataContext.OneEVA_Docs_File_Storages
where Equals(f.ID, fileId)
select f).FirstOrDefault();
context.Response.Clear();
context.Response.AddHeader("Content-Type", file.ContentType);
context.Response.AddHeader("Content-Length", file.ContentLength.ToString());
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}; size={1}", file.Name, file.ContentLength));
context.Response.BinaryWrite(file.File_Image.ToArray());
context.Response.Flush();
context.Response.End();
The XHR request completes ok. Here is the response header I get back:
Cache-Control:private
Connection:Close
Content-Disposition:attachment; filename=Mikes File; size=1860113
Content-Length:1860113
Content-Type:image/jpeg
Date:Wed, 16 May 2012 14:08:01 GMT
Server:ASP.NET Development Server/10.0.0.0
X-AspNet-Version:4.0.30319
What am I missing? What am I doing wrong?
That won't start a download in the browser. It's difficult to achieve using a POST request. Can it not be a GET request if you are only passing an id? Then you just do
document.location.href = '/genericHandlers/DownloadFile.ashx?id=' + this.model.get("id");
Related
I'm zipping multiple files via a Web API endpoint which I then download to my browser. Until the very last step, I can see that the zip file has a filename and that its size is set but for some reason, when downloaded, it comes back as a 1Kb file.
I've saved the binary array that contains the zip file to my local drive before returning it back to the browser via the Web API and it is indeed correct. I can open the zip file and see its content, so I'm not sure what's causing it the problem.
Clearly not the zipping since I can save it locally and it is correct, so I can only assume it has must be something with the EndPoint, but what? I use a custom IHttpActionResult:
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage();
var filename = this.Document.GetFilename();
var mimeType = MimeMapping.GetMimeMapping(filename);
response.Content = new StreamContent(new MemoryStream(this.Document.ToData()));
response.Content.Headers.ContentLength = this.Document.Data.Length;
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = filename
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
return Task.FromResult(response);
}
and it looks fine to me and the odd thing is that if I download a document such as MS Word or other, it download just fine using custom IHttpActionResult and same endpoint.
To download the file via the Web API and JQuery, I found the following article:
DOWNLOAD FILE WITH JQUERY AND WEB API 2.0 IHTTPACTIONRESULT
Could it be that the saving of the blob occurs before the file is fully downloaded but then why does it work for other files?
If you want me to provide any other code, let me know but if anyone has any suggestions that would be great.
Thanks.
UPDATE-1:
Unlike previously stated, my single file download such as MS Word no longer work either and also generate a 1kb file instead of the relevant file. Something has changed but I'm not sure what yet. I will post an update when I find out.
I have fixed the problem by using the code provided directly provided from:
Handle file download from ajax post
from #JonathanAmend (All kudos to him!!)
success: function(blob, status, xhr) {
// check for a filename
var filename = "";
var disposition = xhr.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
// IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
window.navigator.msSaveBlob(blob, filename);
} else {
var URL = window.URL || window.webkitURL;
var downloadUrl = URL.createObjectURL(blob);
if (filename) {
// use HTML5 a[download] attribute to specify filename
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
} else {
window.location.href = downloadUrl;
}
setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
}
Some subtle differences:
I had my code in the completed promise rather than the success
I didn't have the following code in my ajax request
xhrFields: {
// to avoid binary data being mangled on charset conversion
responseType: 'blob'
},
There are not minor differences between his code and the one I found in DOWNLOAD FILE WITH JQUERY AND WEB API 2.0 IHTTPACTIONRESULT as provided in the original question but I don't have time right now to compare the 2. I'll try to do this over the weekend and update my answer but from what I've seen so far, it doesn't seem likely that the differences caused the problem.
I have a web service (.asmx file extension), which creates a Document (Aspose.Words) which I save to MemoryStream.
I would like the user to download the file on click - without having to save the actual file anywhere but to the users computer when he downloads.
I am struggling with this for quite a while...
even if i manage to download the file - the content is not correct.
This is my angular code:
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
Generate() {
this.http.get(
`${this.environment.Url}Generate`, {
headers: headers,
responseType: 'arraybuffer'
}
).subscribe((res: any) => {
this.downLoadFile(res, 'application/msword');
}
);
}
downLoadFile(data: any, type: string) {
let blob = new Blob([data], { type: type});
let url = window.URL.createObjectURL(blob);
let pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') {
alert( 'Please disable your Pop-up blocker and try again.');
}
}
And this is what i did in the web service:
public async Task<Stream> Generate() {
Document doc = _Generate(profileToCV);
MemoryStream dstStream = new MemoryStream();
doc.Save(dstStream, SaveFormat.Docx);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(dstStream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "foo";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/msword");
response.Content.Headers.ContentLength = dstStream.GetBuffer().Length;
return await response.Content.ReadAsStreamAsync();
}
I tried alot of different variations.
like changing the responseType returned to 'blob' or 'blob' as 'json',
or returning the HttpResponseMessage.
nothing worked as expected.
I can't seem to extract the content saved in what is returned to client properly in file.
so - Help! ... Thanks in advance!
Just create a link element and fire click event,
this.blob = new Blob([data], {type: 'application/pdf'}); // your file format
var downloadURL = window.URL.createObjectURL(data);
var link = document.createElement('a');
link.href = downloadURL;
link.download = "help.pdf";
link.click();
I have an app that needs to read a PDF file from the file system and then write it out to the user. The PDF is 183KB and seems to work perfectly. When I use the code at the bottom the browser gets a file 224KB and I get a message from Acrobat Reader saying the file is damaged and cannot be repaired.
Here is my code (I've also tried using File.ReadAllBytes(), but I get the same thing):
using (FileStream fs = File.OpenRead(path))
{
int length = (int)fs.Length;
byte[] buffer;
using (BinaryReader br = new BinaryReader(fs))
{
buffer = br.ReadBytes(length);
}
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", String.Format("attachment;filename={0}", Path.GetFileName(path)));
Response.ContentType = "application/" + Path.GetExtension(path).Substring(1);
Response.BinaryWrite(buffer);
}
Try adding
Response.End();
after the call to Response.BinaryWrite().
You may inadvertently be sending other content back after Response.BinaryWrite which may confuse the browser. Response.End will ensure that that the browser only gets what you really intend.
Response.BinaryWrite(bytes);
Response.Flush();
Response.Close();
Response.End();
This works for us. We create PDFs from SQL Reporting Services.
We've used this with a lot of success. WriteFile do to the download for you and a Flush / End at the end to send it all to the client.
//Use these headers to display a saves as / download
//Response.ContentType = "application/octet-stream";
//Response.AddHeader("Content-Disposition", String.Format("attachment; filename={0}.pdf", Path.GetFileName(Path)));
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", String.Format("inline; filename={0}.pdf", Path.GetFileName(Path)));
Response.WriteFile(path);
Response.Flush();
Response.End();
Since you're sending the file directly from your filesystem with no intermediate processing, why not use Response.TransmitFile instead?
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition",
"attachment; filename=\"" + Path.GetFileName(path) + "\"");
Response.TransmitFile(path);
Response.End();
(I suspect that your problem is caused by a missing Response.End, meaning that you're sending the rest of your page's content appended to the PDF data.)
Just for future reference, as stated in this blog post:
http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx
It is not recommended to call Response.Close() or Response.End() - instead use CompleteRequest().
Your code would look somewhat like this:
byte[] bytes = {};
bytes = GetBytesFromDB(); // I use a similar way to get pdf data from my DB
Response.Clear();
Response.ClearHeaders();
Response.Buffer = true;
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=" + anhangTitel);
Response.AppendHeader("Content-Length", bytes.Length.ToString());
this.Context.ApplicationInstance.CompleteRequest();
Please read this before using Response.TransmitFile: http://improve.dk/blog/2008/03/29/response-transmitfile-close-will-kill-your-application
Maybe you are missing a Response.close to close de Binary Stream
In my MVC application, I have enabled gzip compression for all responses. If you are reading this binary write from an ajax call with gzipped responses, you are getting the gzipped bytearray rather than original bytearray that you need to work with.
//c# controller is compressing the result after the response.binarywrite
[compress]
public ActionResult Print(int id)
{
...
var byteArray=someService.BuildPdf(id);
return return this.PDF(byteArray, "test.pdf");
}
//where PDF is a custom actionresult that eventually does this:
public class PDFResult : ActionResult
{
...
public override void ExecuteResult(ControllerContext context)
{
//Set the HTTP header to excel for download
HttpContext.Current.Response.Clear();
//HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("content-disposition", string.Concat("attachment; filename=", fileName));
HttpContext.Current.Response.AddHeader("Content-Length", pdfBytes.Length.ToString());
//Write the pdf file as a byte array to the page
HttpContext.Current.Response.BinaryWrite(byteArray);
HttpContext.Current.Response.End();
}
}
//javascript
function pdf(mySearchObject) {
return $http({
method: 'Post',
url: '/api/print/',
data: mySearchObject,
responseType: 'arraybuffer',
headers: {
'Accept': 'application/pdf',
}
}).then(function (response) {
var type = response.headers('Content-Type');
//if response.data is gzipped, this blob will be incorrect. you have to uncompress it first.
var blob = new Blob([response.data], { type: type });
var fileName = response.headers('content-disposition').split('=').pop();
if (window.navigator.msSaveOrOpenBlob) { // for IE and Edge
window.navigator.msSaveBlob(blob, fileName);
} else {
var anchor = angular.element('<a/>');
anchor.css({ display: 'none' }); // Make sure it's not visible
angular.element(document.body).append(anchor); // Attach to document
anchor.attr({
href: URL.createObjectURL(blob),
target: '_blank',
download: fileName
})[0].click();
anchor.remove();
}
});
}
" var blob = new Blob([response.data], { type: type }); "
This will give you that invalid/corrupt file that you are trying to open when you turn that byte array into a file in your javascript if you don't uncompress it first.
To fix this, you have a choice to either prevent gzipping this binary data so that you can properly turn it into the file that you are downloading, or you have to decompress that gzipped data in your javascript code before you turn it into a file.
In addition to Igor's Response.Close(), I would add a Response.Flush().
I also found it necessary to add the following:
Response.Encoding = Encoding.Default
If I didn't include this, my JPEG was corrupt and double the size in bytes.
But only if the handler was returning from an ASPX page. It seemed running from an ASHX this was not required.
Using the below code I am unable to show the open/save as file dialog:
public void ProcessRequest(HttpContext context)
{
string link = context.Request.QueryString["Link"];
string extension = Path.GetExtension(link);
string fileName = Path.GetFileName(link);
string fullPath =
String.Format("{0}\\{1}",
context.Server.MapPath("~/Content/Uploads/"),
fileName);
if (File.Exists(fullPath))
{
context.Response.ClearContent();
context.Response.ClearHeaders();
context.Response.AddHeader(
"Content-Length",
new FileInfo(fullPath).Length.ToString());
string contentType;
switch (extension)
{
default:
contentType = "application/octet-stream";
break;
}
context.Response.ContentType = contentType;
context.Response.AddHeader(
"Content-Disposition",
String.Format("attachment; filename={0}", fileName));
context.Response.WriteFile(fullPath, true);
context.Response.Flush();
}
}
I've tried to close the response, leave the response open, use TrasmitFile(), but I never get any dialog or any feed back whatsoever. I've tried debugging it as well, but no exceptions are being thrown. Tried in IE 7/8, and Chrome. Any help is appreciated.
Thanks!
Below is the Fiddler output:
HTTP/1.1 200 OK Cache-Control: private
Content-Length: 3813 Content-Type:
application/octet-stream Server:
Microsoft-IIS/7.5 Content-Disposition:
attachment;
filename=b1af9b34-28cc-4479-a056-8c55b41a5ece.txt
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET Date: Thu, 23
Dec 2010 21:51:58 GMT
* Home
* Hotels
* Reviews
* Community
* Travel Guide
* Travel Insurance
* Contact us
* FIDDLER: RawDisplay truncated at 128 characters. Right-click to disable
truncation. *
Finally figured it out. There is actually no problem with the code I posted. As you can see in the Fiddler output, the contents of the text file were successfully written to the response stream and the headers used were also correct. The actual problem comes from how the actual http request was made. I used a
$.get(urlToGenericHandler);
request using jQuery. The reason why specifically I am not able to download a file using AJAX or a callback model is beyond the scope of this answer. See supported jQuery datatypes here
Anyways, I changed the call from using AJAX to using a basic post-back.
Thanks to all that helped.
Try changing
contentType = "application/octet-stream";
to
contentType = "application/download";
Update:
Try swapping the position of the header and content type
context.Response.AddHeader(
"Content-Disposition",
String.Format("attachment; filename={0}", fileName));
context.Response.ContentType = contentType;
context.Response.AddHeader(
"Content-Length",
new FileInfo(fullPath).Length.ToString());
I have an app that needs to read a PDF file from the file system and then write it out to the user. The PDF is 183KB and seems to work perfectly. When I use the code at the bottom the browser gets a file 224KB and I get a message from Acrobat Reader saying the file is damaged and cannot be repaired.
Here is my code (I've also tried using File.ReadAllBytes(), but I get the same thing):
using (FileStream fs = File.OpenRead(path))
{
int length = (int)fs.Length;
byte[] buffer;
using (BinaryReader br = new BinaryReader(fs))
{
buffer = br.ReadBytes(length);
}
Response.Clear();
Response.Buffer = true;
Response.AddHeader("content-disposition", String.Format("attachment;filename={0}", Path.GetFileName(path)));
Response.ContentType = "application/" + Path.GetExtension(path).Substring(1);
Response.BinaryWrite(buffer);
}
Try adding
Response.End();
after the call to Response.BinaryWrite().
You may inadvertently be sending other content back after Response.BinaryWrite which may confuse the browser. Response.End will ensure that that the browser only gets what you really intend.
Response.BinaryWrite(bytes);
Response.Flush();
Response.Close();
Response.End();
This works for us. We create PDFs from SQL Reporting Services.
We've used this with a lot of success. WriteFile do to the download for you and a Flush / End at the end to send it all to the client.
//Use these headers to display a saves as / download
//Response.ContentType = "application/octet-stream";
//Response.AddHeader("Content-Disposition", String.Format("attachment; filename={0}.pdf", Path.GetFileName(Path)));
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", String.Format("inline; filename={0}.pdf", Path.GetFileName(Path)));
Response.WriteFile(path);
Response.Flush();
Response.End();
Since you're sending the file directly from your filesystem with no intermediate processing, why not use Response.TransmitFile instead?
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition",
"attachment; filename=\"" + Path.GetFileName(path) + "\"");
Response.TransmitFile(path);
Response.End();
(I suspect that your problem is caused by a missing Response.End, meaning that you're sending the rest of your page's content appended to the PDF data.)
Just for future reference, as stated in this blog post:
http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx
It is not recommended to call Response.Close() or Response.End() - instead use CompleteRequest().
Your code would look somewhat like this:
byte[] bytes = {};
bytes = GetBytesFromDB(); // I use a similar way to get pdf data from my DB
Response.Clear();
Response.ClearHeaders();
Response.Buffer = true;
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=" + anhangTitel);
Response.AppendHeader("Content-Length", bytes.Length.ToString());
this.Context.ApplicationInstance.CompleteRequest();
Please read this before using Response.TransmitFile: http://improve.dk/blog/2008/03/29/response-transmitfile-close-will-kill-your-application
Maybe you are missing a Response.close to close de Binary Stream
In my MVC application, I have enabled gzip compression for all responses. If you are reading this binary write from an ajax call with gzipped responses, you are getting the gzipped bytearray rather than original bytearray that you need to work with.
//c# controller is compressing the result after the response.binarywrite
[compress]
public ActionResult Print(int id)
{
...
var byteArray=someService.BuildPdf(id);
return return this.PDF(byteArray, "test.pdf");
}
//where PDF is a custom actionresult that eventually does this:
public class PDFResult : ActionResult
{
...
public override void ExecuteResult(ControllerContext context)
{
//Set the HTTP header to excel for download
HttpContext.Current.Response.Clear();
//HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("content-disposition", string.Concat("attachment; filename=", fileName));
HttpContext.Current.Response.AddHeader("Content-Length", pdfBytes.Length.ToString());
//Write the pdf file as a byte array to the page
HttpContext.Current.Response.BinaryWrite(byteArray);
HttpContext.Current.Response.End();
}
}
//javascript
function pdf(mySearchObject) {
return $http({
method: 'Post',
url: '/api/print/',
data: mySearchObject,
responseType: 'arraybuffer',
headers: {
'Accept': 'application/pdf',
}
}).then(function (response) {
var type = response.headers('Content-Type');
//if response.data is gzipped, this blob will be incorrect. you have to uncompress it first.
var blob = new Blob([response.data], { type: type });
var fileName = response.headers('content-disposition').split('=').pop();
if (window.navigator.msSaveOrOpenBlob) { // for IE and Edge
window.navigator.msSaveBlob(blob, fileName);
} else {
var anchor = angular.element('<a/>');
anchor.css({ display: 'none' }); // Make sure it's not visible
angular.element(document.body).append(anchor); // Attach to document
anchor.attr({
href: URL.createObjectURL(blob),
target: '_blank',
download: fileName
})[0].click();
anchor.remove();
}
});
}
" var blob = new Blob([response.data], { type: type }); "
This will give you that invalid/corrupt file that you are trying to open when you turn that byte array into a file in your javascript if you don't uncompress it first.
To fix this, you have a choice to either prevent gzipping this binary data so that you can properly turn it into the file that you are downloading, or you have to decompress that gzipped data in your javascript code before you turn it into a file.
In addition to Igor's Response.Close(), I would add a Response.Flush().
I also found it necessary to add the following:
Response.Encoding = Encoding.Default
If I didn't include this, my JPEG was corrupt and double the size in bytes.
But only if the handler was returning from an ASPX page. It seemed running from an ASHX this was not required.