How do I send a PDF file I am downloading from a server to the browser's download area instead of opening it in a browser window?
I am working with C# in Blazor. This is my current code, which I need to modify, but don't know how to (ofc I googled before asking here):
async void DownloadDocument(string apiURL, Guid ID)
{
JSRuntime.InvokeAsync<string>("open", $"{apiURL}/GetPDF/{ID}", "_blank");
}
The server returns a FileStreamResult here and the browser shows the file in a new tab. I want it to send it to its downloads folder instead.
You are invoking a JS Function called "open" with two params. (https://developer.mozilla.org/en-US/docs/Web/API/Window/open)
Try creating your own JS download function inside of a script file / tag and invoking it.
The crucial part to save a file in the downloads folder is to set the download attribute of the a tag.
It could look something like this.
inside wwwroot/index.html:
<script>
window.downloadFile = (fileName, pdfData) => {
const linkSource = `data:application/pdf;base64,${pdfData}`;
const downloadLink = document.createElement("a");
downloadLink.href = linkSource;
downloadLink.download = fileName;
downloadLink.click();
downloadLink.remove();
}
</script>
and in your blazor component:
async void DownloadDocument(string apiURL, Guid ID)
{
// call your api to download the file you want to download
var response = await Http.GetAsync($"{apiURL}/GetPDF/{ID}"));
// convert to base64
var pdfExportBytes = await response.Content.ReadAsByteArrayAsync();
var pdfExportB64 = Convert.ToBase64String(pdfExportBytes);
// invoke js download
await JSRuntime.InvokeVoidAsync("downloadFile", "FileName", pdfExportB64);
}
The file will still be opened if configured so in the users browser, but it will also be stored in the download folder.
Related
I am trying to download a List of Items as excel from Blazor server side project . I used CsvHelper to generate the byte[] from the list and using the below code to download the byte[] content as excel file .
The code from Blazor Component is like below [ Click event of button to download excel]
#inject IJSRuntime JsRuntime
private async Task ExportToExcel()
{
jobStarted = true;
var fileBytes= ExportHelper.ConvertContactDataToCsv(); // This will return byte[] of contacts
if (await JsRuntime.InvokeAsync<bool>("confirm", $"Do you want to Export?"))
{
var fileName = $"Contacts";
await JsRuntime.InvokeAsync<object>("saveAsFile", fileName, Convert.ToBase64String(fileBytes));
}
jobStarted = false;
}
The Javascript code is like below
window.saveAsFile = function (fileName, byteBase64) {
var link = this.document.createElement('a');
link.download = fileName;
link.href = "data:application/vnd.ms-excel;base64," + byteBase64;
this.document.body.appendChild(link);
link.click();
this.document.body.removeChild(link);
}
Execution of this generates the file without any issues , but while opening the file using Microsoft Excel the warning message is like
The file format and extension of 'Contacts.xls' don't match. The file
could be corrupted or unsafe. Unless you trust its source, don't open
it. Do you want to open it anyway?
And if i ignore the warning and proceed with opening all contents in excel looks good
Now i tried to generate file with extension xlsx and different mime type inside JS code The changes are like
var fileName = $"Contacts.xlsx";
and inside JS tried these 2 combinations separately
link.href = "data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64," + byteBase64;
link.href = "data:application/octet-stream;base64," + byteBase64;
The code executed without any issues and files getting generated but while opening file the warning is like
Excel cannot open the file 'Contacts.xlsx' because the file format
for the file extension is not valid.’ Verify that the file has not
been corrupted and that the file extension matches the format of the
file
I am not able to open the file at all.
So what is the right MIME type to be used to ensure downloaded excel file can be opened in Excel without warnings
Since you are using CsvHelper (via ConvertContactDataToCsv) your output file is a Comma Separated Value text file, not a binary Excel .xls file.
Don't use the .xls file extension, instead use e.g. .csv;
so Contacts.csv instead of Contacts.xls.
For the mime type, you might use text/csv.
But foremost, it is the wrong file extension that makes Excel error on that file.
What is the best and fastest way to download a local file with javascript function:
I have a button once clicked should launch the download process, I am trying to use javacript to handle this but it is not working !!!!
here is the code: the function receives the file name as parameter and the path is static:
function downloadFile(filename) {
var filePath = "C:\\LangsDirectory\\Test\\" + filename;
var downloadLink = document.createElement("a");
downloadLink.href = uri;
downloadLink.download = filePath;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
The download attribute does not work on cross-origin URLs and all file: scheme URLs are considered cross-origin.
The only way I can see to do this would be to use a file <input> to have the user select the file, then read it with JavaScript, generate a Blob from it, and then set up a download for that.
I wouldn't bother. The file is already local. The user can move or copy it to wherever they like using their normal file manager.
I want to rename a blob file downloaded from a server. I looked for other solutions but they mainly deal with the created anchor link and setting name to anchor. I tried this approach:
getRenamed(title): void {
this.docTitle = title;
this.requestService.download(title).subscribe((response) => {
let thefile = new Blob([response], {
type:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
let url = window.URL.createObjectURL(thefile);
window.open(url);
});
}
But the problem with this approach is that it creates a random filename. And I want to give it a specific filename.
I have made a user interface to fetch data from a MySQL table and visualize it. It is running on a bokeh server. My users connect remotely to the server using their browser (firefox). This works perfectly fine: I simply import the table into a pandas dataframe.
My users also need to download the table as excel. This means I cannot use the export_csv example which is pure javascript.
I have no experience with JavaScript. All I want is to transfer a file from the directory where my main.py is to the client side.
The technique I have tried so far is to join a normal on_click callback to a button, export the information I need to 'output.xls', then change a parameter from a dummy glyph which in turn runs a Javascript code. I got the idea from Bokeh widgets call CustomJS and Python callback for single event? . Note I haven't set the alpha to 0, so that I can see if the circle is really growing upon clicking the download button.
At the bottom of my message you can find my code. You can see I have tried with both XMLHttpRequest and with Fetch directly. In the former case, nothing happens. In the latter case I obtain a file named "mydata.xlsx" as expected, however it contains only this raw text: <html><title>404: Not Found</title><body>404: Not Found</body></html>.
Code:
p = figure(title='mydata')
#download button
download_b = Button(label="Download", button_type="success")
download_b.on_click(download)
#dummy idea from https://stackoverflow.com/questions/44212250/bokeh-widgets-call-customjs-and-python-callback-for-single-event
dummy = p.circle([1], [1],name='dummy')
JScode_xhr = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);
var xhr = new XMLHttpRequest();
xhr.open('GET', '/output.xlsx', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
alert('seems to work...');
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a');
link.href = URL.createObjectURL(blob);
window.open(link.href, '_blank');
link.download = filename;
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'));
URL.revokeObjectURL(url);
}
}
else {
alert('Ain't working!');
}
};
"""
JScode_fetch = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);
fetch('/output.xlsx').then(response => response.blob())
.then(blob => {
alert(filename);
//addresses IE
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
window.open(link.href, '_blank');
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'))
URL.revokeObjectURL(url);
}
return response.text();
});
"""
dummy.glyph.js_on_change('size', CustomJS(args=dict(p=p),
code=JScode_fetch))
plot_tab = Panel(child=row(download_b,p),
title="Plot",
closable=True,
name=str(self.test))
def download():
writer = pd.ExcelWriter('output.xlsx')
data.to_excel(writer,'data')
infos.to_excel(writer,'info')
dummy = p.select(name='dummy')[0]
dummy.glyph.size = dummy.glyph.size +1
Trying out Eugene Pakhomov's answer, I found what was the issue.
The javascript code I named JScode_fetch is almost correct, however I get a 404 because it is not pointing correctly to the right path.
I made my application in the directory format: I changed my .py file to main.py, placed it into a folder called app, and changed this one line of code in JScode_fetch:
fetch('/app/static/output.xlsx', {cache: "no-store"}).then(response => response.blob())
[...]
You can see the problem was that it was trying to access localhost:5006/output.xlsx, instead of localhost:5006/app/output.xlsx. As it is in directory format, the right link is now localhost:5006/app/static/output.xlsx to count for the static directory.
I also changed a few lines in the download function:
def download():
dirpath = os.path.join(os.path.dirname(__file__),'static')
writer = pd.ExcelWriter(os.path.join(dirpath,'output.xlsx'))
writer = pd.ExcelWriter('output.xlsx')
data.to_excel(writer,'data')
infos.to_excel(writer,'info')
dummy = p.select(name='dummy')[0]
dummy.glyph.size = dummy.glyph.size +1
Now it is working flawlessly!
edit: I have added , {cache: "no-store"} within the fetch() function. Otherwise the browser thinks the file is the same if you have to download a different dataframe excel while using the same output.xlsx filename. More info here.
bokeh serve creates just a few predefined handlers to serve some static files and a WebSocket connection - by default, it doesn't have anything to serve files from the root of the project.
Instead of using the one-file format, you can try using the directory format, save your files to static directory and download them from /static/.
One downside of this approach is that you still have to write that convoluted code to just make your backend create the file before a user downloads it.
The best solution would be to go one step further and embed Bokeh Server as a library into your main application. Since you don't have any non-Bokeh code, the simplest way would be to go with Tornado (an example).
bokeh.server.server.Server accepts extra_patterns argument - you can add a handler there to dynamically create Excel files and serve them from, say, /data/. After all that, the only thing that you need in your front-end is a single link to the Excel file.
I have about 10 .csv and 10 .xlsx files of sale items that are each zipped and and deployed daily at a URL specified by an API get request I make depending on which file the user wants to download.
So basically, when I call the API, I get back a string of the destination URL, and it's hot, so if you navigate to that url, the file downloads for the user, however this is how I'm doing it, and I'm wondering if there is a better way, or an Angular way?
Here is the response I get the URL from the API get request when the user changes a radio button for the file they want:
{
"fileUrl": "https://example.com.csv.zip"
}
Then I'm using #ngrx store to set that destination URL in state. I won't go into that here...
Here is the method that is called when the user clicks the download button:
downloadCSV() {
const url = this.state.destinationUrl;
this.http.get(url).subscribe(res => {
// Don't like modifying the DOM just to download a file :(
const link = document.createElement('a');
link.download = this.state.destinationUrl;
link.href = this.state.destinationUrl;
document.body.appendChild(link);
// Don't like forcing an event :(
link.click();
}, (error) => {
console.log('error fetching file download');
});
}
The problem is, this seems hacky, and I am looking for a better solution. Is there a better way of doing this, keeping in ind that I want to avoid popup blockers? I have seen file-saver used in Angular but I don't think I can create a Blob out of the .zip file location and us FileSaver.saveAs(blob, 'example.zip'). Any suggestions would be greatly appreciated.
download() {
const url = 'mydomain.com';
if (url) {
const link = document.createElement('a');
link.href = url;
document.body.appendChild(link);
link.click();
}
}