As you might already know you can’t directly use AngularJS/JQuery/JavaScript to download a file by sending a HTTP request. That’s because usually the file downloads are triggered when a user click on a link and link’s href is targeted to the file location.
But imagine that your server expects a HTTP request (of course an authenticated request), and he will be serving you the file as a octet-stream or as an Attachment in the response.
To understand the scenario properly, consider the following Web API action.
[HttpGet]
[Route("values/download")]
public HttpResponseMessage Download(string name)
{
try
{
string fileName = string.Empty;
if (name.Equals("pdf", StringComparison.InvariantCultureIgnoreCase))
{
fileName = "SamplePdf.pdf";
}
else if (name.Equals("zip", StringComparison.InvariantCultureIgnoreCase))
{
fileName = "SampleZip.zip";
}
if (!string.IsNullOrEmpty(fileName))
{
string filePath = HttpContext.Current.Server.MapPath("~/App_Data/") + fileName;
using (MemoryStream ms = new MemoryStream())
{
using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
byte[] bytes = new byte[file.Length];
file.Read(bytes, 0, (int)file.Length);
ms.Write(bytes, 0, (int)file.Length);
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.Content = new ByteArrayContent(bytes.ToArray());
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
httpResponseMessage.StatusCode = HttpStatusCode.OK;
return httpResponseMessage;
}
}
}
return this.Request.CreateResponse(HttpStatusCode.NotFound, "File not found.");
}
catch (Exception ex)
{
return this.Request.CreateResponse(HttpStatusCode.InternalServerError, ex);
}
}
The action is expecting a URL parameter and based on the parameter it will be serving you either a pdf file or a zip file. Now to download a file by calling this endpoint, I believe what the most people would think is, following would work (including me).
$scope.downloadFile = function (name) {
$http({
method: 'GET',
url: 'api/values/download',
params: { name: name },
}).success(function (data, status, headers) {
}).error(function (data) {
});
};
Now if you call the downloadFile method to send the HTTP request from client side (for instance on the ng-click event of a anchor tag), it will not download the file. The reason is because JavaScript can’t access file system to save the files.
So here is a workaround (in Stack Overflow by answered by Scott).
$scope.downloadFile = function (name) {
$http({
method: 'GET',
url: 'api/values/download',
params: { name: name },
responseType: 'arraybuffer'
}).success(function (data, status, headers) {
headers = headers();
var filename = headers['x-filename'];
var contentType = headers['content-type'];
var linkElement = document.createElement('a');
try {
var blob = new Blob([data], { type: contentType });
var url = window.URL.createObjectURL(blob);
linkElement.setAttribute('href', url);
linkElement.setAttribute("download", filename);
var clickEvent = new MouseEvent("click", {
"view": window,
"bubbles": true,
"cancelable": false
});
linkElement.dispatchEvent(clickEvent);
} catch (ex) {
console.log(ex);
}
}).error(function (data) {
console.log(data);
});
};
So this is the output.
Download |
Download |
I have uploaded the sample to my OneDrive, so you can download and play around.
Happy Coding.
Regards,
Jaliya
Hi, you have an example with angular 2 and Typescript?
ReplyDeleteHi Jose,
DeleteI am sorry, I don't at this moment. But hopefully I will be writing a post on Angular2 and TypeScript soon!
Jaliya
Saved me hours, man. thanks!
ReplyDeletethanks you so much it has saved my life
ReplyDeletethanks you so much it has saved my life
ReplyDeleteI tried it but it doesn't work on Safari for me..The console throws error "Failed to load resource: Frame load interrupted" and it doesn't download at all...
ReplyDeleteHow to make it works with excel file?
ReplyDeleteHey man try this, it just worked for me:
Deletevar blob = new Blob([data], {type : contentType + ';charset=UTF-8'});
linkElement.setAttribute("download", 'filename.xls');
In IOS this is not working . Can you provide any solution ??
ReplyDeleteDoes this work in internet explorer?
ReplyDeleteHi, is it possible to modify this awesome code will being upload directly using web api to another server?
ReplyDeleteThanks
This is not working on Safari browser in Mac.
ReplyDeleteDid you get any solution for it? I have 90% of Target audience using Safari..Would be helpful if you can share anything to make it work in Safari
DeleteDude you rule.....so many hours saved
ReplyDeleteThanks :)
ReplyDeleteThanks a lot for this simple yet Powerful code, helped a lot.
ReplyDeleteHowever it does not works in Safari, any help please?
Hi
ReplyDeleteThis does not work with IE9, IE9 does not understand blob object.Any workaround?
You can use this polyfill https://github.com/eligrey/Blob.js
DeleteIts saved my day thank you so much...!!
ReplyDeleteThis won't work in safari neither on ipads
ReplyDeleteAmazing! It works fine! Thank you so much!
ReplyDeleteIt works fine! Thank you!
ReplyDeleteHi Jaliya. Thanks for tutorial.
ReplyDeletelet filename = headers['filename'];
in there filename is undefined. I checked browser developer tool the filename is in headers but i cannot get it. Any help will be great.
Shoudln't it be,
Deletelet filename = headers['x-filename'];