I have an Angular application with an ASP.NET Web API.
I want to download a file stored on my server. Currently, this is the code I have:
[HttpGet]
[Route("downloadFile")]
[JwtAuthentication] //Only a connected user can download the file
public async Task<HttpResponseMessage> DownloadFile(string path)
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var fileStream = File.OpenRead(path);
result.Content = new StreamContent(fileStream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentLength = fileStream.Length;
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = fileStream.Name,
Size = fileStream.Length
};
return result;
}
And in my Angular code:
// file-navigation.service.ts
downloadFile(file: FileElement) {
const data = { path: this.formatPath(true) + file.name };
return this.http.get(this.apiUrl + '/downloadFile', { params: data, responseType: 'blob' });
}
// file-navigation.component.ts
this.fileNavigationService.downloadFile(element).subscribe(result => {
this.generateDownload(element, result, false);
});
generateDownload(element: FileElement, blob: Blob, isArchive: boolean) {
const fileName = element != null ? element.name : 'Archive';
if (navigator.appVersion.toString().indexOf('.NET') > 0) {
window.navigator.msSaveBlob(blob, fileName + (isArchive ? '.zip' : ''));
} else {
const link = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'a'
);
(link as any).href = URL.createObjectURL(blob);
(link as any).download = fileName + (isArchive ? '.zip' : '');
document.body.appendChild(link);
link.click();
setTimeout(function () {
document.body.removeChild(link);
link.remove();
}, 100);
}
}
With this, I successfully download a file from the server.
However, the download bar in Chrome only appears when the download is done. So if the file is too big, the user won't get any indicator that his file is currently being downloaded.
Below is a screenshot of a 16Mb file being downloaded. The server is currently sending data but the download bar doesn't appear.
Then, once the download has completed, the file appears in the download bar at the bottom of the screen.
How can I send the file to the browser so that it shows this indicator to the user?
Thank you very much.
EDIT:
As #CodeCaster pointed out, redirecting to the URL could work, however, my URL is protected so that only connected users can download the file.
On Angular side, just use anchor tag and pass the API URL in href attribute.
<a href = {this.apiUrl + '/downloadFile' + '?' + 'your params'}>Download</a>
and also on server side before streaming data, make sure you have set the following response header.
res.setHeader('content-length',data.ContentLength) (optional)
res.setHeader('content-type', mimetype);
res.setHeader('content-disposition', 'attachment; filename');
Related
I've been having some problems when I try to convert a byte[] coming from C# into a pdf file using Angular.
When I perform the conversion the file is created but is corrupted and it can't be opened "We can't open this file", the file content is just basic text "Hello world".
Please let me know if you have any answer to this problem, the code I am using is below and I also try using "import { saveAs } from 'file-saver';" but it didn't work.
// THE WEB API CONTROLLER THAT GETS THE PDF BYTE ARRAY
[HttpPost("CreatePDF")]
public async Task<HttpResponseMessage> CreatePDF([FromBody] PDFContent pdfContent)
{
byte[] pdf = await _pdfGenerator.GeneratePDF(pdfContent.HtmlContent);
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(pdf);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}
// UI SERVICE LAYER THAT CALLS THE SERVER SIDE
createPdfReport(pdfContent: PDFContent): Observable<any> {
return this.restApi.create("CreatePDF",pdfContent).pipe(
tap(pdf =>
console.log('pdf = ' + pdf)
));
}
// THE COMPONENT THAT CALLS THE SERVICE LAYER AND WAITS FOR THE RESPONSE
this.historyService.createPdfReport(pdfContent).subscribe(data => {
var file = new Blob(data, { type: 'application/octet-stream' })
var fileURL = URL.createObjectURL(data);
const link = document.createElement('a');
link.setAttribute('target', '_blank');
link.setAttribute('href', fileURL);
link.setAttribute('download', `file.pdf`);
document.body.appendChild(link);
link.click();
link.remove();
});
}
This is the response I get in Angular after the controller returns the byte[]:
Helo, i already search on Google and here (StackOverflow) but no any solution i complete.
I Have a Excel file in my folder and i need to create a method on my controller to download this file.
And in my React Web Site i need to get this file to user computer.
I try to use ActionResult, FileStreamResult, HttpResponseMessage and other, read file from folder with File.ReadAllbytes, put the Header on response.
On the final i get this.
{ FileContents: "allcontentoffilehere....", Contenttype: "application/octet-stream", FileDownloadName: "filename.xls"}
And using this JavaScript do download:
var bytes = new Uint8Array(responseDownloadFile.data.FileContents);
var blob = new Blob([bytes], {
type: responseDownloadFile.data.ContentType
});
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = responseDownloadFile.data.FileDownloadName;
document.body.appendChild(link);
link.click();
But the file when download is corrupted.
Any on can help me?
Try to return HttpResponseMessage type on your API
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent([file bytes]);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/file type");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "filename.xlsx"
};
return result;
And on your Front end execute code:
window.location.href = "link to your api endpoint to download excel";
I've built a new Unity project that makes use of C# and System.Net to access Imgur's API. I'm able to pull images into my application from anywhere, and I can upload screenshots to Imgur through my new application's client ID. I'm able to get a decent amount of response data on a successful upload, but the URL in the response is always outdated by 1. If I just uploaded Screenshot D, the URL I get back will link to Screenshot C, and so on.
Here is what the response data looks like:
This is the result I've gotten across all attempts, which includes using w.UploadValuesAsync() and in trying both Anonymous and OAuth2 (no callback) versions of Imgur applications.
Here is the bulk of my code, which is sourced from here.
public void UploadThatScreenshot()
{
StartCoroutine(AppScreenshotUpload());
}
IEnumerator AppScreenshotUpload()
{
yield return new WaitForEndOfFrame();
ScreenCapture.CaptureScreenshot(Application.persistentDataPath + filename);
//Make sure that the file save properly
float startTime = Time.time;
while (false == File.Exists(Application.persistentDataPath + filename))
{
if (Time.time - startTime > 5.0f)
{
yield break;
}
yield return null;
}
//Read the saved file back into bytes
byte[] rawImage = File.ReadAllBytes(Application.persistentDataPath + filename);
//Before we try uploading it to Imgur we need a Server Certificate Validation Callback
ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
//Attempt to upload the image
using (var w = new WebClient())
{
string clientID = "a95bb1ad4f8bcb8";
w.Headers.Add("Authorization", "Client-ID " + clientID);
var values = new NameValueCollection
{
{ "image", Convert.ToBase64String(rawImage) },
{ "type", "base64" },
};
byte[] response = w.UploadValues("https://api.imgur.com/3/image.xml", values);
string returnResponse = XDocument.Load(new MemoryStream(response)).ToString();
Debug.Log(returnResponse);
}
}
Why is the URL I get back from w.UploadValues(...) always behind by one?
I'm trying to deliver a zip archive in response to an AJAX POST request made from Axios to WebAPI.
On the client side I have
import AjaxDownload from "../../data/AjaxDownload";
AjaxDownload.post(id, pageRecords, {
responseType: "blob"
}).then((response) => {
let blob = new Blob([response.data], { type: extractContentType(response) }),
url = window.URL.createObjectURL(blob);
window.open(url, "_self");
}).catch((error) => {
// ...
}).then(() => {
// ...
});
function extractContentType(response: AxiosResponse): string {
return response.headers["content-type"] || "";
}
// AjaxDownload:
import * as axios from "axios";
import { apiUrl } from "./Ajax";
const ajax = axios.default.create({
baseURL: new URL("download", apiUrl).toString(),
timeout: 3600000 // 1 hour
});
export default ajax;
That posts to the following WebAPI method - and the POST part of that client-side logic works exactly as expected.
[HttpPost]
[Route("download/{id:guid}")]
public async Task<HttpResponseMessage> Download(Guid id, [FromBody] IEnumerable<PageRecord> pageRecords)
{
var stream = await _repo.GetAsArchiveStream(id,
pageRecords,
true).ConfigureAwait(false);
stream.Position = 0;
var result = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(stream)};
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {FileName = $"{...}.zip"};
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // "application/zip" has same result
result.Content.Headers.ContentLength = stream.Length;
return result;
}
However, the browser displays the result.Content as a JSON object, without the zip archive. I assume that the it's displaying as JSON because the request mentions JSON, but why does it appear to ignore the binary content - particularly as the Content-Type header details the type of content?
And as you can see, the JavaScript is also expecting to read in the content as a blob.
I don't see how my code differs meaningfully from this answer - please explain if there is a crucial difference.
On the server-side, I've also tried returning...
return new FileStreamResult(stream, "application/zip");
The problem with this approach is that there's no way to set a filename. Firefox does download the zip albeit with a random name while Chrome doesn't appear to download anything at all.
There must be a way to do this, right? To POST a request to a WebAPI method which returns a zip archive, and the client-side then presents a Save dialog? What am I missing?
I managed to solve this simply by returning the zip from the controller action using...
return File(stream,
"application/zip",
"FILENAME.zip");
And in the client-side code I can fetch the filename from the headers using some JavaScript found in this SO answer.
let blob = new Blob([response.data], { type: extractContentType(response) }),
downloadUrl = window.URL.createObjectURL(blob),
filename = "",
disposition = response.headers["content-disposition"];
if (disposition && disposition.indexOf("attachment") !== -1) {
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/,
matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
var a = document.createElement("a");
// safari doesn't support this yet
if (typeof a.download === 'undefined') {
window.location.href = downloadUrl;
} else {
a.href = downloadUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
}
I have several export routes on an asp.net core application, but all of them are accessed by a GET request, eg: /api/{projectid}/{parameter}
These requests are generating xlsx files and sending them to the client. Now I have a similar request but I have to pass a long array to the method, so I would like to make it into a POST method and send the array (and other parameters) in the http body.
I get the correct response from the server (an array buffer starting with PK...) but I can't tell angular to save it as a file as I did with the similar GET requests. If I rewrite this back to start a GET request it works fine. What am I doing wrong?
Controller method:
[HttpPost("[action]")]
public IActionResult Export([FromBody] DistributionExportPostModel model)
{
var project = _ctx.Projects.FirstOrDefault(x=>x.Id == model.ProjectId);
byte[] xlsx = createXlsxFile(project, model.Selection, model.ComparisonBase);
var mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var fileName = $"TaskExport-"+project.Name+"-"+DateTime.Now.ToString("yyyyMMddHHmmss")+".xlsx";
return File(xlsx, mimeType, fileName);
}
Angular provider method:
export(projectid:string, selection:string[], comparisonBase:string):Promise<any[]> {
//let headers:Headers = new Headers();
//headers.append('Accept', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
//let params: URLSearchParams = new URLSearchParams();
let requestOptions = new RequestOptions();
requestOptions.responseType = ResponseContentType.ArrayBuffer;
return new Promise<any[]>((resolve) =>
this.http.post('/api/Distribution/Export', {
//'+projectid+'/'+comparisonBase+'/'+selection.join(','),
'ProjectId': projectid,
'Selection': selection,
'ComparisonBase': comparisonBase
}, requestOptions).subscribe((res) => {
window.open(res.url, '_blank');
resolve();
})
);
}
Below should work on Chrome.
var blob = new Blob(yourData, {type: "octet/stream"});
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = name;
a.click();
window.URL.revokeObjectURL(url);
For IE and Firefox, try this:
window.navigator.msSaveBlob(blob, filename);
You may need to add the anchor to the DOM before clicking it.