In this article, we’re going to look at how to retrieve the file from a web page that uses JavaScript for its interactions (in my case, what inspired this was a React.js application).
In a previous post, we discussed how to create a zip file on demand on the server so that it can be served via Web API. The code for the previous post and this one for can be found in my github repository.
So today, we’re going to look at how to retrieve the file from a web page that uses JavaScript for its interactions (in my case, what inspired this was a React.js application).
First, we’re going to cover why we need this workaround and secondly, what solution I ended up using by doing some research on StackOverflow, as well as MDN to figure out how and why it works.
To test this out, we’re going to create an empty HMTL page with a button on it. Next, we’re going to hook that button up with an event for when it’s clicked. The HTML will look something like this:
<button type="button" id="testButton" onclick="getArchiveAjax()">Get Archives</button>
Next up, I added a script
tag for Axios since I find it very easy to use especially for this scenario, though since the issue is browser related, you could use any framework/library you wish that allows you to implement this workaround.
So to add Axios without too much fuss for this case, I added the following script
tag in the head of the page:
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
So now that the infrastructure is in place, we can go on and start implementing the getArchiveAjax
function. We will start simple just so that we can also learn what the issue is.
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive").then(x=> {
console.log(x)
});
}
When we run this function by clicking the button, we will see that the response that comes back, contains the bytes of the file in Base64 format. In theory, we could write it to disk like that, but JavaScript in the browser doesn’t let us do that due to security concerns because we could end up with all sorts of malware from websites.
Because of these security concerns, browsers only let you download files from a user interaction, or a simulated used interaction.
For this, we need to create a link on the page so that either the user can click it, or, in our case, the script can click the link. So we’re going to look into how to create a URL out of a byte array by using the static URL functionality form JavaScript.
As we can see from the documentation, the URL.createObjectURL()
function requires as an input a blob. Luckily for us, we can instruct Axios to request the file as a blob using the following configuration:
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive",{
responseType: "blob"
}).then(x=> {
console.log(x);
});
}
With this now, we will receive the data as a Blob and as such, we could create the URL from it using the URL.createObjectURL()
function which stores the file in memory and provides an unique URL towards that file.
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive",{
responseType: "blob"
}).then(x=> {
const url = URL.createObjectURL(x.data);
console.log(url);
URL.revokeObjectURL(url);
});
}
From the documentation, we also learned that it is a good practice to revoke the URL so that we release the used up memory.
Next up, we need to create the hyperlink for the file so that it can be clicked, first, we create the anchor element dynamically, provide it the URL, attach it to the DOM, click it, and then we remove the element so that we clean up after ourselves. The code looks as follows:
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive",{
responseType: "blob"
}).then(x=> {
const url = URL.createObjectURL(x.data);
const link = document.createElement('a');
link.href = url;
document.body.append(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
}
When we run this, we will see that the save file dialog shows up (I’m using Firefox), but something is odd about the file name.
This is easy to file by adding an additional attribute to the generated hyperlink:
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive",{
responseType: "blob"
}).then(x=> {
const url = URL.createObjectURL(x.data);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', "TestFileNameClient.zip");
document.body.append(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
}
Now when we get the file dialog, we will see it with the name we gave it:
But we can do one better, because the server might actually send file name associated with that zip file, which we can extract from the response header with a little string manipulation which gets us to our final form of the getArchiveAjax
function:
function getArchiveAjax(){
axios.get("/ServerSideZip/getArchive",{
responseType: "blob"
}).then(x=> {
const filenameHeader = x.headers["content-disposition"].split(';')[1];
const filename = filenameHeader.substring(filenameHeader.indexOf("=")+1);
const url = URL.createObjectURL(x.data);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.append(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
}
This is the way I ended up using the functionality for downloading a file via a Web API call and saving it on disk.
I hope you enjoyed the post and that it helped you.
Thank you and see you next time.