I am developing a web application which uses a .NET MVC back end and HTML with JavaScript and AJAX on the front end.
One page has a feature where the page makes an AJAX call to the .NET controller which then returns a JSON object. Based on the contents of this object, the browser downloads a PDF file. This is implemented so that the same .NET controller has another method which returns a stream, and the JavaScript code then makes a full page request (not an AJAX call in this case) to that method, which causes the browser to offer to view or save the PDF file.
However, this seems like extra work, particularly since the PDF file is already available when the first AJAX call returns. I could just as easily dump its contents into the returned JSON object, encoded in Base64 if necessary. But my question is, how can I then trigger the download in the browser in this case?
Within your ajax callback, create an <a> tag with the href attribute set to the base64 string and then click on the tag.
.then((response) => {
var pdf = response.data;
if(!pdf) { return; }
var a = document.createElement("a");
a.href = `data:application/pdf;base64,${pdf}`;
a.download = "filename.pdf";
a.click();
})
Although I would recommend twitching your controller to make it return the pdf file directly with Content-Type set to application/pdf and replace the ajax request with a simple JavaScript statement like window.location="/api/pdf/username"
Related
I've searched for this answer in the past but have never come across an answer for this problem. I'm hoping the SO community can finally put this question to rest. I need to know how to embed an image whose source bytes are retrieved via a POST request in JavaScript/ JQuery. POST is needed because in order to generate the image the server needs to be sent a base64 encoded string of instructions from the client. Sometimes those instructions are too lengthy to be sent via GET. Also, I'm working with some legacy code right now, so I'm trying to avoid changing major functions of the server code. It's still sth that is possible to do, just not preferred as editing server code won't always be an option in future situations.
I know you can embed base64 in an img tag. I know you can also do it by rendering the data on a canvas. But both of those methods require you either have an encoded base64 string...
<img src='data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0.....AwXEQA7'>
A standard URL...
<img src="www.examplesite.com/theimage.png">
Or a URL that uses GET parameters.
<img src="www.examplesite.com/getImage.php?id=9001">
What about a URL that uses POST parameters?
<script>
var url = 'www.examplesite.com/getImage.php';
var postData = {'id': 9001};
var outDiv = document.getElementById("outputDiv");
$.post(url,postData,function(data){
// What do we do here to display the returned image?
// The image will have the Content-Type: 'image/png'.
});
</script>
From the post data, create the image on the server, then return a code to the client to access the image e.g. imagabd1. Then have a getImage.php script which returns an image via a get with a unique identifier to the image created. Add an image element to the DOM and add the get request as the src. Example:
1) Post sends base64 data.
2) Server creates image and stores on server
3) Server returns a code, e.g. imagabd1
4) Create an image element on the client and add http://server/getImage?id=imagabd1 as the source. The server should know what image to return given this get request.
Make sure the script is returning the Base64, should be as simple as:
outDiv.html('<img src="' + data '">');
Or change the SRC attr of an image:
element.attr('src', data)
I try to write an extension caching some large media files used on my website so you can locally cache those files when the extension is installed:
I pass the URLs via chrome.runtime.sendMessage to the extension (works)
fetch the media file via XMLHttpRequest in the background page (works)
store the file using FileSystem API (works)
get a File object and convert it to a URL using URL.createObjectURL (works)
return the URL to the webpage (error)
Unfortunately the URL can not be used on the webpage. I get the following error:
Not allowed to load local resource: blob:chrome-extension%3A//hlcoamoijhlmhjjxxxbl/e66a4ebc-1787-47e9-aaaa-f4236b710bda
What is the best way to pass a large file object from an extension to the webpage?
You're almost there.
After creating the blob:-URL on the background page and passing it to the content script, don't forward it to the web page. Instead, retrieve the blob using XMLHttpRequest, create a new blob:-URL, then send it to the web page.
// assuming that you've got a valid blob:chrome-extension-URL...
var blobchromeextensionurlhere = 'blob:chrome-extension....';
var x = new XMLHttpRequest();
x.open('GET', blobchromeextensionurlhere);
x.responseType = 'blob';
x.onload = function() {
var url = URL.createObjectURL(x.response);
// Example: blob:http%3A//example.com/17e9d36c-f5cd-48e6-b6b9-589890de1d23
// Now pass url to the page, e.g. using postMessage
};
x.send();
If your current setup does not use content scripts, but e.g. the webRequest API to redirect request to the cached result, then another option is to use data-URIs (a File or Blob can be converted to a data-URI using <FileReader>.readAsDataURL. Data-URIs cannot be read using XMLHttpRequest, but this will be possible in future versions of Chrome (http://crbug.com/308768).
Two possibilities I can think of.
1) Employ externally_connectable.
This method is described in the docs here.
The essence of it: you can declare that such and such webpage can pass messages to your extension, and then chrome.runtime.connect and chrome.runtime.sendMessage will be exposed to the webpage.
You can then probably make the webpage open a port to your extension and use it for data. Note that only the webpage can initiate the connection.
2) Use window.PostMessage.
The method is mentioned in the docs (note the obsolete mention of window.webkitPostMessage) and described in more detail here.
You can, as far as I can tell from documentation of the method (from various places), pass any object with it, including blobs.
In our application we need to implement following scenario:
A request is send from client
Server handles the request and generates file
Server returns file in response
Client browser displays file download popup dialog and allows user to download the file
Our application is ajax based application, so it would be very easy and convenient for us to send ajax request (like using jquery.ajax() function).
But after googilng, it turned out that file downloading is possible only when using non-ajax POST request (like described in this popular SO thread). So we needed to implement uglier and more complex solution that required building HTML structure of form with nested hidden fields.
Could someone explain in simple words why is that ajax requests cannot be used to download file? What's the mechanics behind that?
It's not about AJAX. You can download a file with AJAX, of course. However the file will be kept in memory, i.e. you cannot save file to disk. This is because JavaScript cannot interact with disk. That would be a serious security issue and it is blocked in all major browsers.
This can be done using the new HTML5 feature called Blob. There is a library FileSaver.js that can be utilized as a wrapper on top of that feature.
That's the same question I'd asked myself two days ago. There was a project with client written using ExtJS and server side realisation was on ASP.Net. I have to translate server side to Java. There was a function to download an XML file, that server generates after Ajax request from the client. We all know, that it's impossible to download file after Ajax request, just to store it in memory. But ... in the original application browser shows usual dialog with options open, save and cancel downloading. ASP.Net somehow changed the standard behaviour... It takes me two day to to prove again - there is no way to download file by request usual way ... the only exception is ASP.Net... Here is ASP.Net code
public static void WriteFileToResponse(byte[] fileData, string fileName)
{
var response = HttpContext.Current.Response;
var returnFilename = Path.GetFileName(fileName);
var headerValue = String.Format("attachment; filename={0}",
HttpUtility.UrlPathEncode(
String.IsNullOrEmpty(returnFilename)
? "attachment" : returnFilename));
response.AddHeader("content-disposition", headerValue);
response.ContentType = "application/octet-stream";
response.AddHeader("Pragma", "public");
var utf8 = Encoding.UTF8;
response.Charset = utf8.HeaderName;
response.ContentEncoding = utf8;
response.Flush();
response.BinaryWrite(fileData);
response.Flush();
response.Close();
}
This method was called from WebMethod, that, in turn, was called from ExtJS.Ajax.request. That's the magic. What's to me, I've ended with servlet and hidden iframe...
you can do this by using hidden iframe in your download page
just set the src of the hidden ifame in your ajax success responce and your task is done...
$.ajax({
type: 'GET',
url: './page.php',
data: $("#myform").serialize(),
success: function (data) {
$("#middle").attr('src','url');
},
});
I am creating a pixel image in javascript, by creating the image tag and injecting it into the DOM.
The tag looks something like this:
<img style="height:0;visibility:hidden;display:none;" src="http://xyz.com/pixel.aspx">
As you can see, this tag will actually call an ASP.Net page, which will do some processing and write back a pixel like this:
byte[] _imgbytes = Convert.FromBase64String("R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==");
Response.ContentType = "image/gif";
Response.AppendHeader("Content-Length", _imgbytes.Length.ToString());
Response.Cache.SetLastModified(DateTime.Now);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.BinaryWrite(_imgbytes);
What I need is a way to either pass a small piece of data (a GUID value generated in the javascript before the pixel request) to the server or pass some data (a different GUID generated in the pixel aspx code) back to the browser in a way in which it can be picked up by javascript.
Caveats:
Cannot use query string, because the pixel needs to be cachable, and if query string is different the pixel will not be pulled from the browser cache (filename will be different) on subsequent requests.
Cannot use cookies, pixel request is on different root domain than the application, and need this to work in the case where third party cookies are disabled.
Cannot use AJAX, because don't want to worry about cross-domain issues
Use http://xyz.com/pixel.aspx/your-guid-here. Then it's just a matter of configuring your webserver and/or application so it doesn't result in a 404 error but executes your aspx code.
I resolved this problem myself.
Instead of an image, I went with a remote script request. So my injected tag looks like this:
<script async src="http://xyz.com/tracking.aspx"></script>
Then, in my aspx, I have the following:
string script = "(function(){Complete('" + guidValue + "');})();";
Response.ContentType = "application/javascript";
Response.Cache.SetLastModified(DateTime.Now);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Write(script);
When the response returns to the browser, the Complete method (which resides in a javascript file already loaded by the page) executes with my guid parameter. This enables my page to have full access to the guid.
Also, since the request URL never changes, the script is properly cached by the browser.
Voila.
I am accessing an existing WCF web service (which returns a PDF as a byte stream) using jquery's ajax methods.
When the call to the service completes, I end up with a javascript variable containing a PDF (the variable has the binary data in, starting "%PDF-1.4...").
I'd like to display this PDF in a new browser window, but I'm having difficulty achieving this.
My research so far shows that I might be able to achieve what I want using a data: uri, so my code that's called when the ajax call completes is as follows:
function GotPDF(data)
{
// Here, data contains "%PDF-1.4 ..." etc.
var datauri = 'data:application/pdf;base64,' + Base64.encode(data);
var win = window.open("", "Your PDF", "width=1024,height=768,resizable=yes,scrollbars=yes,toolbar=no,location=no,directories=no,status=no,menubar=no,copyhistory=no");
win.document.location.href = datauri;
}
This causes a new browser window to open, but the contents are blank.
Interestingly, if I point my browser (IE9) at an existing file on my local disk by using a file: uri, such as file://c:/tmp/example.pdf, then I get the same result, i.e. a blank window.
Is there any way I can display this PDF data?
Code you wrote does not display anything, simply open a blank window (location.href is an hash for browsing history, not the content of the page).
To display a PDF you have, at least, following options:
× Embed the PDF viewer inside an object tag. It may not be as straightforward as you may imagine, take a look to this post for sample code. In short it should be something like this:
<object data="your_url_to_pdf" type="application/pdf">
<div>No PDF viewer available</div>
</object>
That's basic code but I suggest to follow what I say in the linked post if you need higher cross-browser compatibility (it also contains a few examples about how you might try to detect support for a PDF viewer).
× Download the file to local computer (simply add the full URL of your web service method that produces the file, do not forget to add the proper Content-Disposition in the header).
× Open the file into a new browser window. Create a normal a tag as you point to a PDF file on-line that you want to display in a new window. Change the href to javascript:functionName and in that function produce the URI you'll use to call the web service method.
Whatever you'll do, do not forget to set the proper MIME type in your response moreover you method shouldn't return a byte stream (even if encoded) but a valid response for your web browser.
If you are using <embed> or <object> tag to display a streamed PDF file (or other file types) from a server as in:
<object data="SomeServlet?do=get_doc&id=6" type="application/pdf" width="800" height="400">
make sure the server sends the proper http content-disposition value, which in this case would be inline.