Display PDF using Spring REST and Angularjs - angularjs

I spent a couple of days on this problem and read hundreds posts. But i still can't resolve it.
REST Controller:
#RequestMapping(value = "/goodInStocks/loadlist={id}", method = RequestMethod.GET)
public ResponseEntity<byte[]> loadList(#PathVariable("id") Integer id) {
byte[] bytes = pdfService.loadList(id);
String filename = "report.pdf";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/pdf"));
headers.setContentLength(bytes.length);
headers.setContentDispositionFormData("inline", filename);
if (bytes.length == 0) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND
}
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
Angular Service:
function getLoadList(id){
var deferred = $q.defer();
$http.get(REST_SERVICE_URI+'loadlist='+id, {responseType : 'arraybuffer'})
.then(
function (response) {
deferred.resolve(response);
},
function(errResponse){
console.error('Error while fetching load list');
deferred.reject(errResponse);
}
);
return deferred.promise;
}
Angular Controller:
function loadList(documentId){
GoodInStockService.getLoadList(documentId)
.then(
function(d){
var file = new Blob([d.data], {type: 'application/pdf'});
var url = window.URL || window.webkitURL;
var fileURL = url.createObjectURL(file);
window.open(fileURL);
},
function(errResponse){
console.error('Error while getting Load list');
}
)
}
Finally, I get new tab in browser with next error "Failed to load PDF document"
I tried to use different headers in rest controllers, create Blob from response, add 'produces="application/pdf' property in rest controller's method(by the way, in this way i got 406 error - why?)Also, i detect that arraybuffer(if i don't set length in header) and byte[] have different length, is it normal?

Try to write directly to response and flush/close.
RequestMapping(value = "/goodInStocks/loadlist={id}", method = RequestMethod.GET)
public void loadList(#PathVariable("id") Integer id, HttpServletResponse response) {
byte[] byteArray= pdfService.loadList(id);
response.setStatus(HttpStatus.SC_OK);
OutputStream os = response.getOutputStream();
os.write(byteArray);
os.flush();
os.close();
}

Related

Custom error message with content-type response

I'm trying to implement some error handling into my MCV AngularJS application, but came across this one issue that I'm not sure how to solve.
Structure
In my AngularJS service ticketService I have the following method:
this.downloadFile = function (fileId) {
return $http.get(baseUrl + "/Downloadfile/" + fileId, { responseType: "blob" });
}
And in my controller:
$scope.downloadFile = function (fileId) {
ticketService.downloadFile(fileId)
.then(function (response) {
// Handle correct request and response
}, function (err) {
// Handle error
notify({ message: "Something went wrong: " + err.data.Message, position: "center", duration: 10000 });
})
}
Here's what I return from the backend MVC Web API method:
var error = new HttpError("Failed to find file, bla bla bla.");
return Request.CreateResponse(HttpStatusCode.NotFound, error);
Problem
My issue is that since my responseType is set to be blob, my err object is the same response type. I would believe that it should be possible for my backend service to override this response type, and respond with an object that contains some Message.
From this response, I would've thought that I could get err.data.Message, but perhaps I misunderstood this scenario?
Thank you in advance.
public HttpResponseMessage Get(int id)
{
try
{
return Request.CreateResponse(HttpStatusCode.OK, new { Status =
"OK", Message = this._myContext.GetCustomer(id) });
}
catch(Exception e)
{
return Request.CreateResponse(HttpStatusCode.Conflict, new {
Status = "NO", Message = e.ToString() });
}
}
You can return any message like "Failed to find file, bla bla bla." in Message. Then you just need to check in ajax success method like data.Message,
The $http service uses the XHR API which is not capable of changing the responseType on the fly.
You can set the status message and use that:
public ActionResult Foo()
{
Response.StatusCode = 403;
Response.StatusDescription = "Some custom message";
return View(); // or Content(), Json(), etc
}
Then in AngularJS:
$scope.downloadFile = function (fileId) {
return ticketService.downloadFile(fileId)
.then(function (response) {
// Handle correct request and response
return response.data;
}).catch(function (response) {
// Handle error
console.log(response.status);
console.log(response.statusText);
throw response;
});
};
Alternative approaches are:
Use the Fetch API which has a more powerful and flexible feature set.
Use the FileReader API and JSONparse() method to convert the Blob to a JavaScript Object.

AngularJS save image file sent from Web API 2

I have been trying to follow different posts on downloading a file sent from my Web API. So far I can get the file to come, it will open the download window and it will save. However, I cannot open it so something must be wrong somewhere.
Here is my AngularJS so far.
return $http({
url: State.Endpoint + "/api/account/picture",
method: "GET",
responseType: 'arrayBuffer'
}).then(function (data) {
var octetStreamMime = 'application/octet-stream';
var success = false;
var file = new Blob([data.data], {
type: "image/jpeg"
});
var fileUrl = URL.createObjectURL(file);
var a = document.createElement('a');
a.href = fileUrl;
a.target = "_blank";
a.download = "myFile.jpg";
document.body.appendChild(a);
a.click();
});
That will make my successfully download the image for me. However, this doesn't let me open the file so either something is still wrong on client side or server side.
Server Side Code:
[Route("picture")]
[HttpGet]
public HttpResponseMessage GetPictureBlob()
{
HttpResponseMessage response = null;
var localFilePath = HttpContext.Current.Server.MapPath("~/Content/Images/demo.jpg");
if (!File.Exists(localFilePath))
{
response = Request.CreateResponse(HttpStatusCode.Gone);
}
else
{
var fStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read);
// Serve the file to the client
response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StreamContent(fStream)
};
response.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment")
{
FileName = Path.GetFileName(fStream.Name)
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
//response.Headers.Add("content-type", "application/octet-stream");
}
return response;
}
The provided value 'arrayBuffer' is not a valid enum value of type XMLHttpRequestResponseType.
Use arraybuffer all lowercase:
$http({
url: State.Endpoint + "/api/account/picture",
method: "GET",
//responseType: 'arrayBuffer'
//USE arraybuffer lowercase
responseType: 'arraybuffer'
//OR
//responseType: 'blob'
})
When the responseType is not valid, the XHR API defaults to decoding the response as UTF-8. This corrupts binary files such as JPEG images.
For more information, see MDN XHR Web API - responseType.
Creating a Download Button
Instead of creating a <a download></a> element with JavaScript DOM manipulation, consider using the AngularJS framework.
This is an example of a Download button that becomes active after the data is loaded from the server:
<a download="data_{{files[0].name}}" xd-href="data">
<button ng-disabled="!data">Download</button>
</a>
The xdHref Directive
app.module("myApp").directive("xdHref", function() {
return function linkFn (scope, elem, attrs) {
scope.$watch(attrs.xdHref, function(newVal) {
if (newVal) {
elem.attr("href", newVal);
}
});
};
});
The DEMO on PLNKR.
I've done the very same thing with this code, where:
data: Data received from server
format: data format, it must be one of https://developer.mozilla.org/en-US/docs/Web/API/Blob/type
name: your file's name
Code:
function downloadBlobFile(data, format, name) {
// format must be one of https://developer.mozilla.org/en-US/docs/Web/API/Blob/type
var file = new Blob([data], {type: 'application/' + format});
file.lastModified = new Date();
file.name = name + '.' + format.trim().toLowerCase();
// guarantee IE compatibility
if($window.navigator && $window.navigator.msSaveOrOpenBlob) {
$window.navigator.msSaveOrOpenBlob(file, file.name);
}
//other web browser
else {
/**
* Because this technology's specification has not stabilized, compatibility has been
* checked here: https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL#Browser_compatibility
*/
var fileURL = $window.URL.createObjectURL(file);
/* trick for downloading the file, borrowed from:
http://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link
*/
var a = angular.element("<a style='display: none;'/>").attr("href", fileURL).attr("download", file.name);
angular.element(document.body).append(a);
a[0].click();
$window.URL.revokeObjectURL(fileURL);
a.remove();
}
}
var a = document.createElement("a"); //Create <a>
a.href = "data:image/png;base64," + ImageBase64;
a.download = "Image.png"; //File name Here
a.click(); //Downloaded file
Simplest way worked for me

XMLHttpRequest cannot load http://localhost:57997/Home/Get

When I try to access WebApi from MVC, I get this error
XMLHttpRequest cannot load http://localhost:57997/Home/Get. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:64035' is therefore not allowed acces
Service.Js
app.service('MyService', function ($http) {
var ApiAddress = "http://localhost:57997/";
this.GetWebData = function () {
var Getdatas= $http({
url: ApiAddress + 'Home/Get',
type: 'GET',
dataType: 'json',
params: JSON.stringify(),
content:{'content-type' :'application/Json'}
})
return Getdatas;
}
});
Controller.Js
app.controller('WebCtrls', function ($scope,MyService) {
$scope.Hello = "Hello angular How r u...";
$scope.GetDb = function () {
alert('Hello..');
var SerData = MyService.GetWebData();
SerData.then(function (d) {
$scope.Emp = d.data;
})
}
})
WebApi
public JsonResult Get()
{
var x = prod.GetEmployees();
return new JsonResult { Data = x, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
Global.asax
in WebApi global file I wrote bellow code for cross page origin
protected void Application_BeginRequest()
{
string[] allowedOrigin = new string[] { "http://localhost:57997/" };
var origin = HttpContext.Current.Request.Headers["Origin"];
if (origin != null && allowedOrigin.Contains(origin))
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", origin);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET,POST");
//Need to add more later , will see when required
}
}
You can handle it by disabling web security option of chrome browser by following command from the folder location where chrome.exe is present. First close all instances of chrome. Then run following command
chrome.exe --disable-web-security
You can handle it at server side while filtering all request coming to server, add the header to response like this.
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");

Angular FileSaver with blob and WebAPI . PDF downloaded is blank. But API url returns PDF with content

This is my API for returning a PDF with multiple images . Now when I invoke this with url it perfectly downloads the PDF with images . For eg two pages with images .
[HttpGet]
public async Task<IHttpActionResult> Download(Guid customDocId)
{
byte[] responseContent = await Task.FromResult(FileNetApiClientFactory.Get(customDocId).DownloadDocument(customDocId, "pdf", true));
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(responseContent),
StatusCode = HttpStatusCode.OK,
};
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = string.Concat(customDocId.ToString(), ".pdf") };
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
return ResponseMessage(response);
}
Now from angular I am using blob and FileSaver for saving the PDF . So when I download it . Then it just returns two pages but with no content. But it shows page 1 and page2 but they are blank.
Here is my angular code :
//saveAs method is from FileSaver.js
vm.download = function () {
documentService.download($scope.customDocumentId).then(function (fileData) {
var blob = new Blob([fileData], { type: 'application/pdf' });
saveAs(blob, $scope.customDocumentId + ".pdf");
}).catch(function () {
});
}
And the service :
function _download(customDocumentId) {
return Restangular
.one('customdocument', customDocumentId).one('download')
.get(null, { responseType: 'arraybuffer' });
}
Does anyone has any idea why is it returning the blank pages when saved with FileSaver, while with direct download it is perfectly fine with all content.
I had to change certain things in Restangular . It was the responseType had to be arrayBuffer or 'blob'. I haven't tried with arrayBuffer explicitly . The blob responsetype worked for me . The configurations were missing from restangular. So I made a little change in my service and voila ! It was working.
So the updated Service looks like this now . DocumentServicesRestangular is nothing but a factory wrapper with changed baseurl through RestangularConfigurer.
function _download(customDocumentId) {
return DocumentServicesRestangular.one('customdocument', customDocumentId).one('download')
.withHttpConfig({ responseType: 'blob' }).get();
}
Try Like this,
$scope.download = function() {
var a = document.createElement("a");
document.body.appendChild(a);
var requestParams=$scope.downloadObj;
a.style = "display: none";
sServices.doAPIRequest(Url)
.then(function(generateData) {
var file = new Blob([$scope.base64ToArrayBuffer(generateData)], {
type : 'application/pdf'
});
var fileName="Data";
var fileURL = URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
});
};
$scope.base64ToArrayBuffer=function(data)
{
var binaryString = window.atob(data);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
};
I fixed this issue by changing the $http.get call in my service, so that the response type was set in the config argument:
return $http.get(apiTarget, { responseType: 'blob' });
If you are using FileSaver then this is the right syntax
var data = new Blob([response], { type: 'application/pdf;charset=utf-8' });
FileSaver.saveAs(data, filename);

ExtJs standard form submit with jsonData for file download

I'm trying to download a file from WebApi using ExtJs 4.2.3. After reading Extjs 4 downloading a file through ajax call, it looks like my best bet is to use the standard form.submit, however, my data is not passing as expected to the WebApi controller - using the below code, items comes through null.
Controller
public HttpResponseMessage Post(string exportType, List<PcAvailableComponent> items)
{
var dataToExport = _service.ExportItems(exportType, items);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new MemoryStream(dataToExport);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
Standard Submit
expForm.submit({
url: AppRootUrl + 'api/AdminDataExport?exportType=' + exportType,
//this doesn't work
jsonData: items,
waitMsg: 'Generating export...',
success: function (form, action) {
if (typeof expWindow !== "undefined") {expWindow.close();}
},
failure: function(form, action) {
Ext.Msg.alert('Failed', 'An error has occurred while generating the export: ' + JSON.parse(action.response.responseText)['ExceptionMessage']);
}
});
Ajax submit (works but can't get file back)
Ext.Ajax.request({
url: AppRootUrl + 'api/AdminDataExport',
params: {
exportType: 'PricingAndCosting'
},
jsonData: items,
method: 'POST',
success: function (response) {
expWindow.close();
console.log("success!");
}
});
Ended up abandoning WebApi for this controller, and just passing the JSON as a string, then deserializing it on the server side:
[HttpPost]
public ActionResult Index(string exportType, string items)
{
var dataToExport = _service.ExportItems(exportType, JsonConvert.DeserializeObject<List<PcAvailableComponent>>(items));
Response.AddHeader("Content-Disposition", "inline; filename=" + exportType + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".xlsx");
return File(dataToExport, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}

Resources