In my Javascript client, I'm using Fetch API to call the server to retrieve a server-generated file. I'm using the following client-side code:
var _url = "";
var initParms = {
method: "GET",
mode: 'cors'
}
fetch(_url, initParms)
.then(response => {
if(response.ok){
alert(response.headers.get("content-disposition"));
return response.blob();
}
throw new Error("Network response was not OK.");
})
.then(blob => {
var url = new URL.createObjectURL(blob);
})
This actually works just fine. However, the server generates a filename
for the file and includes it in the response as part of the content-disposition
header.
I need to save this file to the user's machine using the filename
generated by the server. In Postman, I can actually see that the content-disposition
header of the response is set to: Content-Disposition: attachment;filename=myfilename.txt
.
I made an attempt to read the content-disposition
from the response (see the alert in my JS code), but I always get null
(even though the same response shows the content-disposition
in Postman).
Am I doing something wrong? Is there a way to retrieve the filename
using the fetch response? Is there a better way to get the filename
from the server along with the file?
P.S. This is my server-side code for returning the file:
Controller Action
public IHttpActionResult GetFile(){
return new FileResult("myfilename.txt","Hello World!");
}
FileResult Class
public class FileResult : IHttpActionResult
{
private string _fileText = "";
private string _fileName = "";
private string _contentType = "";
public FileResult(string name, string text)
{
_fileText = text;
_fileName = name;
_contentType = "text/plain";
}
public Task<HttpResponseMessage> ExecuteActionAsync(CancellationToken token)
{
Stream _stream = null;
if (_contentType == "text/plain")
{
var bytes = Encoding.Unicode.GetBytes(_fileText);
_stream = new MemoryStream(bytes);
}
return Task.Run(() =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(_stream),
};
response.Content.Headers.ContentType =
new MediaTypeHeaderValue(_contentType);
response.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment")
{
FileName = _fileName
};
return response;
}, token);
Edit
My question was specifically about the fetch not the ajax api. Also, in my code, I showed that I was already reading the header from the response exactly like the accepted answer demonstrated on the suggested answer. However, as stated in my post, this solution was not working with fetch.
In the first line we use the global fetch() function to send a GET request to our API. The argument of fetch() is the URL with the server-side resource. We then chain the promise with the then() method, which captures the HTTP response in the response argument and call its json() method.
The fetch() method in JavaScript is used to request to the server and load the information on the webpages. The request can be of any APIs that return the data of the format JSON or XML. This method returns a promise.
Content-Disposition is an optional header and allows the sender to indicate a default archival disposition; a filename. The optional "filename" parameter provides for this. This header field definition is based almost verbatim on Experimental RFC 1806 by R. Troost and S.
To download a file using Javascript fetch, return the result as a blob, and create a download link to the blob object. fetch("URL") .then((res) => { return res.blob(); })
Using jQuery, you used the cleaner syntax with jQuery.ajax (). Now, JavaScript has its own built-in way to make API requests. This is the Fetch API, a new standard to make server requests with promises, but includes many other features. In this tutorial, you will create both GET and POST requests using the Fetch API.
The Response object that is returned from 'fetch', has a few methods that let you retrieve the data returned from the request We use the .text () method to get a string of the text from a file. The process is identical if we wanted to retrieve a .csv file and do something with the data that's in the file.
The fetch () method returns a promise. If the promise returned is resolve, the function within the then () method is executed. That function contains the code for handling the data received from the API. Below the then () method, include the catch () method: The API you call using fetch () may be down or other errors may occur.
So, shortly after posting this question, I ran across this issue on Github. It apparently has to do with using CORS.
The suggested work around was adding Access-Control-Expose-Headers:Content-Disposition
to the response header on the server.
This worked!
You can extract the filename from the content-disposition header like this:
let filename = '';
fetch(`/url`, { headers: { Authorization: `Bearer ${token}` }}).then((res) => {
const header = res.headers.get('Content-Disposition');
const parts = header!.split(';');
filename = parts[1].split('=')[1];
return res.blob();
}).then((blob) => {
// Use `filename` here e.g. with file-saver:
// saveAs(blob, filename);
});
Decided to post this, as the accepted answer (although helpful to many people) does not actually answer the original question as to how:
"to get the filename from a file downloaded using javascript fetch api?".
One can read the filename (as shown below), and download the file using a similar approach to this (the recommended downloadjs
library by this does not get updated anymore; hence, I wouldn't suggest using it). The below also takes into account scenarios where the filename
includes unicode characters (i.e.,-, !, (, )
, etc.) and hence, comes (utf-8
encoded) in the form of, for instance, filename*=utf-8''Na%C3%AFve%20file.txt
(see here for more details). In such cases, the decodeURIComponent()
function is used to decode the filename
. Working example is given below:
const url ='http://127.0.0.1:8000/'
fetch(url)
.then(res => {
const disposition = res.headers.get('Content-Disposition');
filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
if (filename.toLowerCase().startsWith("utf-8''"))
filename = decodeURIComponent(filename.replace("utf-8''", ''));
else
filename = filename.replace(/['"]/g, '');
return res.blob();
})
.then(blob => {
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a); // append the element to the dom
a.click();
a.remove(); // afterwards, remove the element
});
If you are doing a cross-origin request, make sure to set the Access-Control-Expose-Headers
response header on server side, in order to expose the Content-Disposition
header; otherwise, the filename
won't be accessible on client side trhough JavaScript (see furhter documentation here). For instance:
headers = {'Access-Control-Expose-Headers': 'Content-Disposition'}
return FileResponse("Naïve file.txt", filename="Naïve file.txt", headers=headers)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With