I would like the browser to show that the xls file has been saved. How to do it? In the example code below: it saves my Report in the correct path, but the browser does not show that the file has been saved. When I cliced button "Download report" return me empty page and not download report.
public ActionResult FilePathResult(List<PrintOutModel> printOutModelList )
{
Prints.Print.PrintReport.Printer print = new Prints.Print.PrintReport.Printer();
var printReport = print.CreateRender(printOutModelList);
byte[] reportResult = printReport;
string path = AppDomain.CurrentDomain.BaseDirectory +"Report9802.xls";
var fileStream = new FileStream(path,
FileMode.Create,
FileAccess.ReadWrite
);
fileStream.Write(reportResult, 0, reportResult.Length);
return new FileStreamResult(fileStream, "application/xls");
}
Any help or sugestion is welcome
Because you are talking about browser, I am assuming your API implementation is all good and the question is front-end focussed.
Here is an example code for Angular 7. In order to download a file, your API "must" be a POST call. That is how browsers can request for a file download. You should basically be calling the postBlob method on a service, which obviously has the HttpClient injected as a member named http in this example. Because this code reads the content disposition sent by the server and sets the dynamic link, it creates an effect of user clicking a hyperlink to an XLS file and invokes the browser's default download behaviour (prompt a "save as", open automatically, etc).
Hope this helps !!
public postBlob(url: string, param: any): Observable<BlobFile> {
return this.http.post<Blob>(url, param, { observe: 'response', responseType: 'blob' as 'json' }).pipe(map(r => {
return this.onBlobResponse(r);
}));
}
private onBlobResponse(r: HttpResponse<Blob>) {
const contentDisposition = r.headers.get('Content-Disposition');
const filename = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
this.saveFile(filename, r.body);
return <BlobFile>{
name: filename,
blob: r.body
};
}
private saveFile(filename: string, blob: Blob) {
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename);
} else {
const a = document.createElement('a');
document.body.appendChild(a);
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = filename;
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 0);
}
}
Related
Using DynamicPDF's Cloud API, instead of generating a pdf back to the local file system, I would like it to directly open in another browser tab to be available for printing immediately. How do I accomplish that?
The method I am using (.NET Core 6 / Blazor) is below:
public async Task CallDynPDFCloudAPI()
{
var basePath = #"JSONFiles\";
var apiKey = "foo";
var cloudPath = "bar.dlex";
Pdf pdf = new Pdf();
pdf.ApiKey = apiKey;
LayoutDataResource layoutDataResource = new LayoutDataResource(basePath + "FooBar.json");
pdf.AddDlex(cloudPath, layoutDataResource);
PdfResponse pdfResponse = pdf.Process();
if (pdfResponse.IsSuccessful)
{
File.WriteAllBytes(basePath + "Manifest_" + manifestBranch + ".pdf", pdfResponse.Content);
}
else
{
Console.WriteLine(pdfResponse.ErrorJson);
}
}
Reread article on https://learn.microsoft.com/en-us/aspnet/core/blazor/file-downloads?view=aspnetcore-6.0
#page "/file-download-1"
#using System.IO
#inject IJSRuntime JS
<h1> File Download Example</h1>
<button #onclick = "DownloadFileFromStream" >
Download File From Stream
</button>
#code {
private Stream CallDynPDFCloudAPI()
{
var basePath = #"JSONFiles\";
var apiKey = "foo";
var cloudPath = "bar.dlex";
Pdf pdf = new Pdf();
pdf.ApiKey = apiKey;
LayoutDataResource layoutDataResource = new LayoutDataResource(basePath + "FooBar.json");
pdf.AddDlex(cloudPath, layoutDataResource);
PdfResponse pdfResponse = pdf.Process();
if (pdfResponse.IsSuccessful)
{
return new MemoryStream(pdfResponse.Content);
}
else
{
throw new Exception("");
}
}
private async Task DownloadFileFromStream()
{
var fileStream = CallDynPDFCloudAPI();
var fileName = "file.pdf";
using var streamRef = new DotNetStreamReference(stream: fileStream);
await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
}
}
You won't be able to access the PDF content for this request from another browser tab. I'd recommend opening the new tab before making the call and then streaming it there. If you're using an 'a href' link, you can accomplish this by setting the 'target="_blank"' property of the 'a href'. If this is a form submission, you can set the 'target="_blank"' property of the 'form'.
The other option would be to store the PDF somewhere temporarily (as a file, in a DB or in BLOB storage) then stream it to the other tab once it's opened.
Ive impletented #Mihal's answer, with modified code suggested by #DynamicPDF to achieve the result I was looking for. My two goals were:
Not clutter the client device with excessive dowloaded files
Not needing to save the file in Server or DB
Javascript:
<script>
window.downloadFileFromStream = async (fileName,
contentStreamReference) => {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
//--Opens PDF file in new Tab
fetch(url)
.then(response => response.blob())
.then(data => window.open(URL.createObjectURL(data), '_blank'))
//--Downloads file to Browser (uncomment if desired)
//const anchorElement = document.createElement('a');
//anchorElement.href = url;
//anchorElement.download = fileName ?? 'Manifest';
//anchorElement.click();
//anchorElement.remove();
//URL.revokeObjectURL(url);
}
</script>
*NOTE! My application is purely internal-facing to our organization. Our Windows client machines and browsers are managed by Group Policy. I have not yet tested this on Mac / Safari clients yet.
In my project I use CKEditor WYSWYG package to make HTML content for my website.
There is possible to insert image and send it directly from the package to the server.
Since 2 days I try to figure out how is it possible to catch the sent image from the Angular front-end to the Web API, but still no success.
I use .Net6 and Angular 12 with CKEditor 5.
public async Task<ActionResult<string>> AddPostPhoto(IFormFile photo)
{
try
{
System.Console.WriteLine(Request.ContentType);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos/PostPhotos", "fileName.jpg");
var memoryStream = new MemoryStream();
await Request.Body.CopyToAsync(memoryStream);
System.Console.WriteLine(Request.HttpContext.Request.ContentLength);
System.Console.WriteLine(Request.Form.Keys);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
Finally I could find a working solution.
my-upload-adapter.ts
//ckeditorExComponent class Ends here and MyUploadAdapter class begins here in the same ckeditorEx.ts
export class MyUploadAdapter {
xhr: any;
loader: any;
serverUrl: string;
baseApiUrl: string;
constructor(loader: any, serverUrl: string, baseApiUrl: string) {
// The file loader instance to use during the upload.
this.loader = loader;
this.serverUrl = serverUrl;
this.baseApiUrl = baseApiUrl;
}
// Starts the upload process.
upload() {
return this.loader.file
.then((file: any) => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
// Note that your request may look different. It is up to you and your editor
// integration to choose the right communication channel. This example uses
// a POST request with JSON as a data structure but your configuration
// could be different.
//Replace below url with your API url
xhr.open('POST', this.baseApiUrl + 'Tutorial/add-post-photo', true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve: any, reject: any, file: any) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
// This example assumes the XHR server's "response" object will come with
// an "error" which has its own "message" that can be passed to reject()
// in the upload promise.
//
// Your integration may handle upload errors in a different way so make sure
// it is done properly. The reject() function must be called when the upload fails.
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
// If the upload is successful, resolve the upload promise with an object containing
// at least the "default" URL, pointing to the image on the server.
// This URL will be used to display the image in the content. Learn more in the
// UploadAdapter#upload documentation.
resolve({
default: this.serverUrl + response.url
});
});
// Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
// properties which are used e.g. to display the upload progress bar in the editor
// user interface.
if (xhr.upload) {
xhr.upload.addEventListener('progress', (evt: any) => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file: any) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
// Important note: This is the right place to implement security mechanisms
// like authentication and CSRF protection. For instance, you can use
// XMLHttpRequest.setRequestHeader() to set the request headers containing
// the CSRF token generated earlier by your application.
// Send the request.
this.xhr.send(data);
}
}
In the Angular component
onReady($event: any) {
$event.plugins.get('FileRepository').createUploadAdapter = (loader: any) => {
return new MyUploadAdapter(loader, this.serverUrl, this.apiUrl);
};
}
The C# Web API controller
[HttpPost("add-post-photo")]
public async Task<ActionResult<string>> AddPostPhoto(IFormFile upload)
{
try
{
FileInfo fileInfo = new FileInfo(upload.FileName);
System.Console.WriteLine(upload.FileName);
var folderDirectory = $"\\Photos\\PostPhotos";
var path = Path.Combine("Photos\\PostPhotos", upload.FileName);
var memoryStream = new MemoryStream();
await upload.OpenReadStream().CopyToAsync(memoryStream);
if (!Directory.Exists(folderDirectory))
{
Directory.CreateDirectory(folderDirectory);
}
await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
return Ok(new { Url = path });
}
catch(Exception exception)
{
return BadRequest(exception.Message);
}
}
It is important to have the parameter upload, otherwise the find the back-end endpoint
Controller code:
[HttpGet]
public FileStreamResult GETPDF(string guid)
{
var stream = XeroHelper.GetXeroPdf(guid).Result;
stream.Position = 0;
var cd = new ContentDisposition
{
FileName = $"{guid}.pdf",
Inline = true
};
Response.AppendHeader("Content-Disposition", cd.ToString());
return File(stream, "application/pdf");
}
As you can see the method's name is GETPDF. You can also see that I am configuring the name of the file name in the ContentDisposition header. If you see below, you will see that the method name is used as the title in the toolbar, rather than the file name.
The file name does get perpetuated. When I click "Download" the filename is the default value that is used in the file picker (note i changed the name to hide the sensitive guid):
If anyone has any ideas how to rename the title of that toolbar, it would be greatly appreciated.
As an aside, this is NOT a duplicate of: C# MVC: Chrome using the action name to set inline PDF title as no answer was accepted and the only one with upvotes has been implemented in my method above and still does not work.
Edit- For clarification, I do not want to open the PDF in a new tab. I want to display it in a viewer in my page. This behavior is already happening with the code I provided, it is just the Title that is wrong and coming from my controller method name. Using the controller code, I am then showing it in the view like so:
<h1>Quote</h1>
<object data="#Url.Action("GETPDF", new { guid = #Model.QuoteGuid })" type="application/pdf" width="800" height="650"></object>
try something like this:
[HttpGet]
public FileResult GETPDF(string guid)
{
var stream = XeroHelper.GetXeroPdf(guid).Result;
using (MemoryStream ms = new MemoryStream())
{
stream.CopyTo(ms);
// Download
//return File(ms.ToArray(), "application/pdf", $"{guid}.pdf");
// Open **(use window.open in JS)**
return File(ms.ToArray(), "application/pdf")
}
}
UPDATE: based on mention of viewer.
To embed in a page you can try the <embed> tag or <object> tag
here is an example
Recommended way to embed PDF in HTML?
ie:
<embed src="https://drive.google.com/viewerng/
viewer?embedded=true&url=[YOUR ACTION]" width="500" height="375">
Might need to try the File method with the 3rd parameter to see which works.
If the title is set in the filename, maybe this will display as the title.
(not sure what a download will do though, maybe set a download link with athe pdf name)
UPDATE 2:
Another idea:
How are you calling the url?
Are you specifying: GETPDF?guid=XXXX
Maybe try: GETPDF/XXXX (you may need to adjust the routing for this or call the parameter "id" if this is the default)
You could do this simply by adding your filename as part of URL:
<object data="#Url.Action("GETPDF/MyFileName", new { guid = #Model.QuoteGuid })" type="application/pdf" width="800" height="650"></object>`
You should ignore MyFileName in rout config. Chrome and Firefox are using PDFjs internally. PDFjs try to extract display name from URL.
According to the PDFjs code, it uses the following function to extract display name from URL:
function pdfViewSetTitleUsingUrl(url) {
this.url = url;
var title = pdfjsLib.getFilenameFromUrl(url) || url;
try {
title = decodeURIComponent(title);
} catch (e) {
// decodeURIComponent may throw URIError,
// fall back to using the unprocessed url in that case
}
this.setTitle(title);
}
function getFilenameFromUrl(url) {
const anchor = url.indexOf("#");
const query = url.indexOf("?");
const end = Math.min(
anchor > 0 ? anchor : url.length,
query > 0 ? query : url.length
);
return url.substring(url.lastIndexOf("/", end) + 1, end);
}
As you can see this code uses the last position of "/" to find the file name.
The following code is from PDFjs, I don't know why PDFjs doesn't use this instead of getFilenameFromUrl. This code use query string to detect file name and it uses as a fallback to find the file name.
function getPDFFileNameFromURL(url, defaultFilename = "document.pdf") {
if (typeof url !== "string") {
return defaultFilename;
}
if (isDataSchema(url)) {
console.warn(
"getPDFFileNameFromURL: " +
'ignoring "data:" URL for performance reasons.'
);
return defaultFilename;
}
const reURI = /^(?:(?:[^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
// SCHEME HOST 1.PATH 2.QUERY 3.REF
// Pattern to get last matching NAME.pdf
const reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
const splitURI = reURI.exec(url);
let suggestedFilename =
reFilename.exec(splitURI[1]) ||
reFilename.exec(splitURI[2]) ||
reFilename.exec(splitURI[3]);
if (suggestedFilename) {
suggestedFilename = suggestedFilename[0];
if (suggestedFilename.includes("%")) {
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
try {
suggestedFilename = reFilename.exec(
decodeURIComponent(suggestedFilename)
)[0];
} catch (ex) {
// Possible (extremely rare) errors:
// URIError "Malformed URI", e.g. for "%AA.pdf"
// TypeError "null has no properties", e.g. for "%2F.pdf"
}
}
}
return suggestedFilename || defaultFilename;
}
Previously I was sending file as Byte array from ASP.net core 2.0 and in Angular 4 application I am calling below function to download the file
function (response) { // Here response is byte array
var url= window.URL.createObjectURL(res);
var link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", this.zipLocation + ".zip");
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
But now I want to send the file path from the server like below
https://websiteaddress/file/path/to/download.ext
So in Angular 5, I can directly attach link to href attribute of anchor tag and will make automatic click on that. So I don't need to Convert byte array to url
Here the issue is I don't know how to create that downloadable file path using ASP.net core and send it to frontend
And also I want to know, which approach is better, whether sending Byte array or Sending the direct link? Is there any performance issue with any of the two?
If you are using api response as file data
add responseType: 'arraybuffer' in request header.
Try something like this:
HTML:
<a (click)="downLoad()">Click To Download</a>
TS:
downLoad(){
this.fileService.getFileFromServer(fileId.toString()).subscribe(respData => {
this.downLoadFile(respData, this.type);
}, error => {
});
}
/**
* Method is use to download file.
* #param data - Array Buffer data
* #param type - type of the document.
*/
downLoadFile(data: any, type: string) {
var blob = new Blob([data], { type: type.toString() });
var url = window.URL.createObjectURL(blob);
var pwa = window.open(url);
if (!pwa || pwa.closed || typeof pwa.closed == 'undefined') {
console.log('Please disable your Pop-up blocker and try again');
}
}
file-service.ts:
getFileFromServer(id){
return this.http.get(url, {responseType: 'arraybuffer',headers:headers});
}
your question make confuse about angular frontend and backend
frontend you can use mvc
<a asp-controller="Controller"
asp-action="Download"
asp-route-id="#Model.FileName">Download #Model.FileName</a>
or using angular
Download
<a [href]="ControllerRoute+'/Download?name='+fileName" download>Download {{fileName}}</a>
Ok maybe your problem is your action (in controller) doesnt server a file
you need return a HttpResponse with a MediaType, this is just a example, dont forget best practices on your code
[HttpGet]
public HttpResponseMessage GetDownloadableFIle(string name)
{
try
{
var result = new HttpResponseMessage(HttpStatusCode.OK);
var filePath = $"{MyRootPath}/{name}";
var bytes = File.ReadAllBytes(filePath );
result.Content = new ByteArrayContent(bytes);
var mediaType = "application/octet-stream";
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mediaType);
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.InternalServerError, ex.ToString()));
}
}
So I have a pdf that I generated using PDF Sharp and saved within my App_Data folder. When the user clicks a button on my controller, I want the user be able to download that file.
I've managed to get it to work (sort of) in the sense that the user can download the file, however when they open up this pdf file the document is blank and appears to be nearly twice the size of the original file saved in my App_Data folder. I'm not sure what's going wrong, but can only guess it's something to do with how I'm reading the file and streaming that data.
Anyway this is the web api controller function:
[HttpGet]
public HttpResponseMessage DownloadPdf(int itemId)
{
var item = _myService.GetItem(itemId);
var result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new StreamContent(File.ReadAllBytes(HostingEnvironment.MapPath(item.ReportFilePath)));
result.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(HostingEnvironment.MapPath(item.ReportFilePath));
return result;
}
And this is the relevant function on my angular controller (note the businessResource call just routes the data to my web api function via an $http call):
$scope.downloadPdf = function() {
businessResource.downloadPdf($scope.itemId).then(function (response) {
var headers = response.headers;
var filename;
var disposition = headers('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
var matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) {
filename = matches[1].replace(/['"]/g, '');
}
}
// Get the blob url creator
var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (urlCreator) {
var link = document.createElement('a');
if ('download' in link) {
// Prepare a blob URL
var blob = new Blob([response.data], { type: headers('content-type') });
var url = urlCreator.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute("download", filename);
// Simulate clicking the download link
var event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
link.dispatchEvent(event);
}
}
}, function (response) {
notificationsService.error("Error", "Could not generate report");
});
}
So just to clarify: The call is working in the sense I'm getting a pdf file downloaded, however this file seems to contain no content when I open it in the browser/pdf reader and is nearly twice the size of the original file I'm storing in the App_Data folder.
I don't know where the data is getting corrupted.. any ideas?
Figured it out:
The $http.get that was making the call to the web api needed to be changed from:
return $http.get('<end point url>);
to:
return $http.get('<end point url>', { responseType: 'arraybuffer' });
ie. the responseType needs to be set to 'arraybuffer'. Hope this helps someone!