In my Angular JS project, I've an <a>
anchor tag, which when clicked makes an HTTP GET
request to a WebAPI method that returns a file.
Now, I want the file to be downloaded to the user once the request is successful. How do I do that?
The anchor tag:
<a href="#" ng-click="getthefile()">Download img</a>
AngularJS:
$scope.getthefile = function () {
$http({
method: 'GET',
cache: false,
url: $scope.appPath + 'CourseRegConfirm/getfile',
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
}).success(function (data, status) {
console.log(data); // Displays text data if the file is a text file, binary if it's an image
// What should I write here to download the file I receive from the WebAPI method?
}).error(function (data, status) {
// ...
});
}
My WebAPI method:
[Authorize]
[Route("getfile")]
public HttpResponseMessage GetTestFile()
{
HttpResponseMessage result = null;
var localFilePath = HttpContext.Current.Server.MapPath("~/timetable.jpg");
if (!File.Exists(localFilePath))
{
result = Request.CreateResponse(HttpStatusCode.Gone);
}
else
{
// Serve the file to the client
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "SampleImg";
}
return result;
}
This question is related to
c#
javascript
html
angularjs
asp.net-web-api
You could implement a showfile function which takes in parameters of the data returned from the WEBApi, and a filename for the file you are trying to download. What I did was create a separate browser service identifies the user's browser and then handles the rendering of the file based on the browser. For instance if the target browser is chrome on an ipad, you have to use javascripts FileReader object.
FileService.showFile = function (data, fileName) {
var blob = new Blob([data], { type: 'application/pdf' });
if (BrowserService.isIE()) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
}
else if (BrowserService.isChromeIos()) {
loadFileBlobFileReader(window, blob, fileName);
}
else if (BrowserService.isIOS() || BrowserService.isAndroid()) {
var url = URL.createObjectURL(blob);
window.location.href = url;
window.document.title = fileName;
} else {
var url = URL.createObjectURL(blob);
loadReportBrowser(url, window,fileName);
}
}
function loadFileBrowser(url, window, fileName) {
var iframe = window.document.createElement('iframe');
iframe.src = url
iframe.width = '100%';
iframe.height = '100%';
iframe.style.border = 'none';
window.document.title = fileName;
window.document.body.appendChild(iframe)
window.document.body.style.margin = 0;
}
function loadFileBlobFileReader(window, blob,fileName) {
var reader = new FileReader();
reader.onload = function (e) {
var bdata = btoa(reader.result);
var datauri = 'data:application/pdf;base64,' + bdata;
window.location.href = datauri;
window.document.title = fileName;
}
reader.readAsBinaryString(blob);
}
In your component i.e angular js code:
function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
I have gone through array of solutions and this is what I found to have worked great for me.
In my case I needed to send a post request with some credentials. Small overhead was to add jquery inside the script. But was worth it.
var printPDF = function () {
//prevent double sending
var sendz = {};
sendz.action = "Print";
sendz.url = "api/Print";
jQuery('<form action="' + sendz.url + '" method="POST">' +
'<input type="hidden" name="action" value="Print" />'+
'<input type="hidden" name="userID" value="'+$scope.user.userID+'" />'+
'<input type="hidden" name="ApiKey" value="' + $scope.user.ApiKey+'" />'+
'</form>').appendTo('body').submit().remove();
}
For me the Web API was Rails and client side Angular used with Restangular and FileSaver.js
Web API
module Api
module V1
class DownloadsController < BaseController
def show
@download = Download.find(params[:id])
send_data @download.blob_data
end
end
end
end
HTML
<a ng-click="download('foo')">download presentation</a>
Angular controller
$scope.download = function(type) {
return Download.get(type);
};
Angular Service
'use strict';
app.service('Download', function Download(Restangular) {
this.get = function(id) {
return Restangular.one('api/v1/downloads', id).withHttpConfig({responseType: 'arraybuffer'}).get().then(function(data){
console.log(data)
var blob = new Blob([data], {
type: "application/pdf"
});
//saveAs provided by FileSaver.js
saveAs(blob, id + '.pdf');
})
}
});
C# WebApi PDF download all working with Angular JS Authentication
Web Api Controller
[HttpGet]
[Authorize]
[Route("OpenFile/{QRFileId}")]
public HttpResponseMessage OpenFile(int QRFileId)
{
QRFileRepository _repo = new QRFileRepository();
var QRFile = _repo.GetQRFileById(QRFileId);
if (QRFile == null)
return new HttpResponseMessage(HttpStatusCode.BadRequest);
string path = ConfigurationManager.AppSettings["QRFolder"] + + QRFile.QRId + @"\" + QRFile.FileName;
if (!File.Exists(path))
return new HttpResponseMessage(HttpStatusCode.BadRequest);
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
//response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
Byte[] bytes = File.ReadAllBytes(path);
//String file = Convert.ToBase64String(bytes);
response.Content = new ByteArrayContent(bytes);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentDisposition.FileName = QRFile.FileName;
return response;
}
Angular JS Service
this.getPDF = function (apiUrl) {
var headers = {};
headers.Authorization = 'Bearer ' + sessionStorage.tokenKey;
var deferred = $q.defer();
$http.get(
hostApiUrl + apiUrl,
{
responseType: 'arraybuffer',
headers: headers
})
.success(function (result, status, headers) {
deferred.resolve(result);;
})
.error(function (data, status) {
console.log("Request failed with status: " + status);
});
return deferred.promise;
}
this.getPDF2 = function (apiUrl) {
var promise = $http({
method: 'GET',
url: hostApiUrl + apiUrl,
headers: { 'Authorization': 'Bearer ' + sessionStorage.tokenKey },
responseType: 'arraybuffer'
});
promise.success(function (data) {
return data;
}).error(function (data, status) {
console.log("Request failed with status: " + status);
});
return promise;
}
Either one will do
Angular JS Controller calling the service
vm.open3 = function () {
var downloadedData = crudService.getPDF('ClientQRDetails/openfile/29');
downloadedData.then(function (result) {
var file = new Blob([result], { type: 'application/pdf;base64' });
var fileURL = window.URL.createObjectURL(file);
var seconds = new Date().getTime() / 1000;
var fileName = "cert" + parseInt(seconds) + ".pdf";
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = fileURL;
a.download = fileName;
a.click();
});
};
And last the HTML page
<a class="btn btn-primary" ng-click="vm.open3()">FILE Http with crud service (3 getPDF)</a>
This will be refactored just sharing the code now hope it helps someone as it took me a while to get this working.
Send your file as a base64 string.
var element = angular.element('<a/>');
element.attr({
href: 'data:attachment/csv;charset=utf-8,' + encodeURI(atob(response.payload)),
target: '_blank',
download: fname
})[0].click();
If attr method not working in Firefox You can also use javaScript setAttribute method
We also had to develop a solution which would even work with APIs requiring authentication (see this article)
Using AngularJS in a nutshell here is how we did it:
Step 1: Create a dedicated directive
// jQuery needed, uses Bootstrap classes, adjust the path of templateUrl
app.directive('pdfDownload', function() {
return {
restrict: 'E',
templateUrl: '/path/to/pdfDownload.tpl.html',
scope: true,
link: function(scope, element, attr) {
var anchor = element.children()[0];
// When the download starts, disable the link
scope.$on('download-start', function() {
$(anchor).attr('disabled', 'disabled');
});
// When the download finishes, attach the data to the link. Enable the link and change its appearance.
scope.$on('downloaded', function(event, data) {
$(anchor).attr({
href: 'data:application/pdf;base64,' + data,
download: attr.filename
})
.removeAttr('disabled')
.text('Save')
.removeClass('btn-primary')
.addClass('btn-success');
// Also overwrite the download pdf function to do nothing.
scope.downloadPdf = function() {
};
});
},
controller: ['$scope', '$attrs', '$http', function($scope, $attrs, $http) {
$scope.downloadPdf = function() {
$scope.$emit('download-start');
$http.get($attrs.url).then(function(response) {
$scope.$emit('downloaded', response.data);
});
};
}]
});
Step 2: Create a template
<a href="" class="btn btn-primary" ng-click="downloadPdf()">Download</a>
Step 3: Use it
<pdf-download url="/some/path/to/a.pdf" filename="my-awesome-pdf"></pdf-download>
This will render a blue button. When clicked, a PDF will be downloaded (Caution: the backend has to deliver the PDF in Base64 encoding!) and put into the href. The button turns green and switches the text to Save. The user can click again and will be presented with a standard download file dialog for the file my-awesome.pdf.
Source: Stackoverflow.com