I'm trying to download a PDF file from blazor sent by my WebAPI as byte[] (byteArray).
I know that my PDF is working because when I send it as filestreamResult and retrieve it directly from my WebAPI (using swagger for example), I get the correct PDF file.
My WebAPI:
var response = await GetResponse(new Query(request.AnalysisDate), ct);
string html = await _templateService.PerformanceReport(response);
MemoryStream stream = _pdfService.GeneratePdf(html);
return await stream.ToArray(); // as byte[]
Blazor Side:
var bytes = await _http.GetByteArrayAsync(endpoint);
await JsRuntime.InvokeVoidAsync("BlazorDownloadFile", "file.pdf", "application/pdf", bytes);
JS:
function BlazorDownloadFile(filename, contentType, content) {
// Create the URL
const file = new File([content], filename, { type: contentType });
const exportUrl = URL.createObjectURL(file);
// Create the <a> element and click on it
const a = document.createElement("a");
document.body.appendChild(a);
a.href = exportUrl;
a.download = filename;
a.target = "_self";
a.click();
// We don't need to keep the object url, let's release the memory
// On Safari it seems you need to comment this line... (please let me know if you know why)
URL.revokeObjectURL(exportUrl);
}
I Correctly download a PDF file named file.pdf but it looks corrupted.
Is this the correct approach with .NET 6?
Should I send a FileStreamResult from the Web API (I Like the fact that I can get the file directly from swagger)?
I can't really find a good example for blazor on how to do this. Any help would be appreciated.
I am using another aproach. I send base64String. I convert byte array to base64 string then i pass the string to javascript function
Server side call:
js.InvokeVoidAsync("jsSaveAsFile",
filename,
Convert.ToBase64String(GetFileByteArrayFunction())
);
javascript function:
function jsSaveAsFile(filename, byteBase64) {
var link = document.createElement('a');
link.download = filename;
link.href = "data:application/octet-stream;base64," + byteBase64;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);}
js -> using Microsoft.JSInterop.
You should send your file like this:
app.MapGet("/download", () =>
{
//...
Results.File("myfile.pdf");
});
there are some overloads of this method you can pick up the one that fit your requirements.
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[]:
I'm returning a FileContentResult from my c# .NET API, in my angular 7 application I receive it as a blob with HttpClient and then use the following to open a new tab and try to display it.
this.myService.GetPdfBlob().subscribe(data => {
var fileURL = window.URL.createObjectURL(data);
window.open(fileURL, '_blank');
})
After doing this it opens up a new tab, but I see a huge json object's text where it starts with FileContents property and that value is a massive string that I imagine is the bytes of the pdf document. It looks kind of like this:
{"FileContents":"JVBERi0xLjUNJeLjz9MNCjI1IDAgb2JqDTw8L0xpbmVhcml6ZWQgMS9MIDg1NzU1L08gMjcvRSA3MzgzMy9OIDQvVCA4NTQxNS9IIFsgNDc5IDIyMl0+Pg1lbmRvYmoNICAgICAgICAgICAgICAgICAgDQozOCAwIG9iag08PC9EZWNvZGVQYXJtczw8L0NvbHVtb...it keeps going..", "FileDownloadName":"MyBigFile.pdf"}
How do I make it display the actual pdf document and not this big JSON object of my FileContentResult? I eventually need to display multiple file types, but I'm just starting with pdf.
I changed up my C# code slightly to return an object that has a bytes array byte[] called Bytes and a filename. I still had the problem until I did this in my angular code:
this.http.get(myUrl).subscribe(response => {
var byteCharacters = atob(response.Bytes);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], {type: "application/pdf"});
var fileURL = URL.createObjectURL(blob);
window.open(fileURL, '_blank');
})
The last 2 days, I have been trying to create an image upload system for my website. When I try to save an uploaded image in the "wwwroot" of my Api, everything goes as planned except that I get an empty image in my folder.
At the backend, I receive the filename I send in the frontend but the bytes of the image itself are not there. For some reason the the data of the stream I put in the post call is missing but I do receive the filename in the formfile.
Edit:
To clear things up about my application, I'm working with an Asp.Net Mvc as frontend and Asp.Net Api as backend. I know this isn't how you are supposed to use Asp.Net but this is a school project and I have to do it like this. Normally i would work with Angular or something else but that is not an option for me right now.
So, I'm sending data from the Asp.Net Mvc (frontend) to the Asp.Net Api (backend) and I'm trying to do it by sending it as form data. That means there is no real form that is being submitted.
This is the guide I tried to use:
https://ilclubdellesei.blog/2018/02/14/how-to-upload-images-to-an-asp-net-core-rest-service-with-xamarin-forms/
Backend
ImageController:
[HttpPost("upload")]
public async Task<IActionResult> UploadImage([FromForm(Name = "file")] IFormFile file)
{
if (file.Length == 0)
return BadRequest("Empty file");
string imageName = file.FileName;
using (var fs = new FileStream("wwwroot/" + imageName, FileMode.Create, FileAccess.Write))
{
await file.CopyToAsync(fs);
}
return Ok();
}
Frontend
Method that uploads 1 image as a MemoryStream to the server
private async Task<string> Upload(Stream image, string name, string contentType)
{
_httpClient = _clientFactory.CreateClient("ProjectApi");
HttpContent fileStreamContent = new StreamContent(image);
fileStreamContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") { Name = "file", FileName = name };
fileStreamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
using (var formData = new MultipartFormDataContent())
{
formData.Add(fileStreamContent);
HttpResponseMessage response = await _httpClient.PostAsync("api/images/upload", formData);
var input = await response.Content.ReadAsStringAsync();
return input;
}
}
The content doesn't seem to be empty:
The filename has been successfully send to the Api but the bytes of the image have not been send:
Structure after uploading some images without checking the size of the formfile (They are empty):
I am not 100% sure but I suppose the reason why you get empty file is that you did not set what type data your api endpoint will consume and maybe the form encryption type & method attributes. My suggestion is that you should update your code to below. ,
[Consumes("multipart/form-data")]
private async Task<string> Upload(Stream image, string name, string contentType)
And in case you forget to add form attributes to your html section, please set attributes as follows <form method="post" enctype="multipart/form-data">. Hope this solves your problem.
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.
I'm trying to download a blob from private Azure Blob storage container and display it in an image tag.
On the question below you can see how I'm returning the blob from the Web API in Stream format.
Getting 403 error when trying to retrieve an Azure blob on Web API request
From the HTTP response, I am able to retrieve the content type of the blob by including it on the headers section of the request. To use it to generate the data URI to be used on the image tag. I understand I need to convert the Stream into a base64 string to be able to include it on the src attribute of an image tag. I'm currently struggling to convert the result from the HTTP request into a base64 string.
I have created this js fiddle which contains the data (image) received from the HTTP request along with my attempt to convert the data into a base64 string:
'http://jsfiddle.net/chesco9/6a7ohgho/'
EDIT
Thank you Tom for your help. I was able to implement your solution and it worked out. I had been stuck on this problem for a few days now.
public async Task<AzureBlobModel> DownloadBlob(Guid blobId)
{
try
{
//get picture record
Picture file = await _media.GetPictureAsync(blobId);
// get string format blob name
var blobName = file.PictureId.ToString() + file.Extension;
if (!String.IsNullOrEmpty(blobName))
{
var blob = _container.GetBlockBlobReference(blobName);
// Strip off any folder structure so the file name is just the file name
var lastPos = blob.Name.LastIndexOf('/');
var fileName = blob.Name.Substring(lastPos + 1, blob.Name.Length - lastPos - 1);
var fileLength = blob.Properties.Length;
var stream = await blob.OpenReadAsync();
MemoryStream ms = new MemoryStream();
stream.CopyTo(ms);
var result = new AzureBlobModel()
{
FileName = fileName,
FileSize = blob.Properties.Length,
Stream = stream,
ContentType = blob.Properties.ContentType,
StreamBase64 = Convert.ToBase64String(ms.ToArray())
};
return result;
}
}
catch(Exception ex)
{
await _log.CreateLogEntryAsync("exception thrown: " + ex.ToString());
}
await _log.CreateLogEntryAsync("returning null");
// Otherwise
return null;
}
I'm currently struggling to convert the result from the HTTP request into a base64 string.
Base on my understanding, now you can download the blob from the Azure storage.
According to your mentioned link, the WebApi return the AzureBlobModel.
We can convert the stream to base64 string easily with C# code backend.You can add following code in your code. If it is prossible, return this value in the AzureBlobModel.
MemoryStream ms = new MemoryStream();
stream.CopyTo(ms);
string strBase64 = Convert.ToBase64String(ms.ToArray());