I have project written in Angular + TS + Net 6 Web api.
My goal is to have button. On button click, pdf should be created in backend and PDF downloaded to user.
PDF is long report which has some variables. I heard that PDF can be generated from HTML. To simplify, lets say it looks like this:
<html>
<head></head>
<body>
<div>My name is FirstParameter</div>
<div>My age is SecondParameter</div>
</body>
</html>
I believe FileStreamResult should be returned in backend. How to achieve it (prefer without 3rd party library)
.cs
[HttpGet]
[Route("DownloadPDF")]
public async Task<IActionResult> DownloadPDF(Guid userId)
{
var user = await _context.Users.FirstAsync(p=>p.id == userId);
string firstParameter = user.FirstName;
string secondParameter = user.SecondName;
// generating pdf as filestream
return File(fs, "application/pdf", "random.pdf");
}
.html
<a (click)="download('f58c8036-915a-4c39-bbb8-352e21eae49c')">Download pdf</a>
.ts
download(id) {
}
Related
I'm looking for a way to create a link that will create a screenshot of a Razor component and download it as a JPG, PNG or PDF through a Blazor Server application. Ideally, it will only contain the Razor component and all child components, but no parents, and the image will have the precise appearance of the current state displayed on the browser.
The only similar thing is capturing HTML canvases, but since I'm so new to Blazor, I'm not exactly sure how to apply that, and was wondering if there's a way of achieving something similar via C#. Any suggestions would be appreciated. Thanks :)
I don't think it will be easy to achieve that with C# alone, since the best approach to the problem is to convert the HTML data to canvas and export it as an image which is a hard process, but there are libraries available for that, in JS.
How to do it:
I'm gonna use this library since it seems to be the simplest: html2canvas
Here is the link to it's JS file: html2canvas.js
download it and place it in your wwwroot folder, then, include that in the _host.cshtml by adding this at the end of the body tag:
<script src="html2canvas.js"></script>
then add a JS function to call from C# so we can have the even handler written in C# by adding this after the previous code in _host.cshtml:
<script>
window.takeScreenshot = async function(id) {
var img = "";
await html2canvas(document.querySelector("#" + id)).then(canvas => img = canvas.toDataURL("image/png"));
var d = document.createElement("a");
d.href = img;
d.download = "image.png";
d.click();
return img;
}
</script>
This will automatically take a screenshot from the element and download it, also return its URL. Note that the component must be inside a div tag with an id, otherwise, you can't select it alone, example good child in parent:
Parent.razor
<div id="child"></div>
<Child />
</div>
To use this function, use the JsInterop class. Simply, inject (basically include) it in your razor component where you need this functionality by adding this at the top of the file:
#inject IJSRuntime JS
next, a function to do everything:
#inject IJSRuntime JS
#code {
public async System.Threading.Tasks.Task<string> TakeImage(string id)
{
return await JS.InvokeAsync<string>("takeScreenshot", id);
}
}
This function will return a data URL of an image taken from an element specified by the id parameter.
Sample usage with a button to take image:
#page "/screenshot"
#inject IJSRuntime JS
#code {
string image_url = "";
string child_id = "child";
public async System.Threading.Tasks.Task<string> TakeImage(string id)
{
return await JS.InvokeAsync<string>("takeScreenshot", id);
}
public async System.Threading.Tasks.Task ButtonHandler()
{
image_url = await TakeImage(child_id);
}
}
<button #onclick="ButtonHandler">Take image</button>
<h3>Actual component:</h3>
<div id=#child_id>
<ChildComponent />
</div>
<h3>Image:</h3>
<img src=#image_url />
<br />
<br />
<br />
<p>URL: #image_url</p>
Pressing the button will download the image, show it, show the raw URL, and save the URL to variable image_url.
You can shorten System.Threading.Tasks.Task to Task by adding #using System.Threading.Tasks to _Imports.razor file.
You can remove auto-download functionality by removing these 4 lines in the JS function:
var d = document.createElement("a");
d.href = img;
d.download = "image.png";
d.click();
If you want to take an image of the entire page automatically and download it without any user interaction:
modify the JS function and set the query selector to body and remove the id parameter:
<script>
window.takeScreenshot = async function() {
var img = "";
await html2canvas(document.querySelector("body")).then(canvas => img = canvas.toDataURL("image/png"));
var d = document.createElement("a");
d.href = img;
d.download = "image.png";
d.click();
return img;
}
</script
set the function to run when the document loads:
#inject IJSRuntime JS
#page "/component"
#code {
string image_url = "";
public async System.Threading.Tasks.Task<string> TakeImage()
{
return await JS.InvokeAsync<string>("takeScreenshot");
}
protected override async System.Threading.Tasks.Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender) //Ensure this is the page load, not any page rerender
{
image_url = await TakeImage();
}
}
}
Special URL to automatically download image:
To generate a URL, which when loaded will download the image as the previous part, but only when that special URL is loaded, not the normal page URL.
To do this, I'm gonna use query strings, so the special URL will be like this: http://localhost:5001/page?img=true
For that, we need to get the URI, using NavigationManager, which can be injected like the IJSRuntime. For parsing the URI, we can use QueryHelpers class.
The final code will look like this:
#inject IJSRuntime JS
#inject NavigationManager Nav
#page "/component"
#code {
string image_url = "";
public async System.Threading.Tasks.Task<string> TakeImage()
{
return await JS.InvokeAsync<string>("takeScreenshot");
}
protected override async System.Threading.Tasks.Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender) //Ensure this is the page load, not any page rerender
{
var uri = Nav.ToAbsoluteUri(Nav.Uri);
if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("img", out var isImg))
{
if (System.Convert.ToBoolean(isImg.ToString()))
{
image_url = await TakeImage();
}
}
}
}
}
Now you can add ?img=true to the component's URL and you will get a screenshot of it.
Note that if the body/parent of the div has a background, and you want it to be in the image, you need to add background: inherit; to the CSS rules of the div containing the child component.
I am trying to have a HTML file in my assets folder that is nothing but some header tags, dates and feature lists to serve as release notes for our website. I have an angular modal component that I want to read this file each time its route is called, rather than the alternative of having the HTML in the component itself which would require us to redeploy anytime we updated the release notes.
As mentioned I originally had this as part of my components HTML file but this was then being compiled into javascript each time and unable to be updated without a redeploy. Everything I have tried to search for doing something similar seems to be pointing me to just doing it that way.
ReleaseNotes.html
<!DOCTYPE html>
<html lang='en' xmlns='http://www.w3.org/1999/xhtml'>
<body>
<h1>Example header one</h1>
<h3>03/01/2019</h3>
<h4>Patch 1.03 Title</h4>
<ul>
<li>Feature that was added</li>
<li>Feature that was added</li>
<li>Feature that was added</li>
</ul>
<hr>
release-notes-modal.component.ts
export class ReleaseNotesModalComponent implements OnInit {
faTimesCircle = faTimesCircle;
contents: string;
constructor(public dialogRef: MatDialogRef<ReleaseNotesModalComponent>) {
//this.contents = System.IO.File.ReadAllText("ReleaseNotes.html");
}
ngOnInit() {
}
close() {
this.dialogRef.close();
}
}
There are a few ways you can accomplish this. This is how I've done this in the past.
In a Controller in the c# application, you would read the html file and return it:
[HttpGet]
[Route("releasenotes")]
public async Task<IActionResult> ReadReleaseNotes()
{
var viewPath = Path.GetFullPath(Path.Combine(HostingEnvironment.WebRootPath, $#"..\Views\Home\releasenotes.html"));
var viewContents = await System.IO.File.ReadAllTextAsync(viewPath).ConfigureAwait(false);
return Content(viewContents, "text/html");
}
Then in the angular application in a service you would call this method and retrieve this file as follows:
getReleaseNotes(): Observable<string> {
return this.http
.get([INSERT_BASE_URL_HERE] + '/releasenotes', { responseType: 'text' });
}
You can then utilize that in the ReleaseNotesModalComponent through something like this:
#Component({
template: '<span [innerHTML]="contents"></span>'
})
export class ReleaseNotesModalComponent implements OnInit {
faTimesCircle = faTimesCircle;
contents: string;
constructor(public dialogRef: MatDialogRef<ReleaseNotesModalComponent>, private service: ReleaseNotesService) {
service.getReleaseNotes(html => this.contents = html);
}
ngOnInit() {
}
close() {
this.dialogRef.close();
}
}
For the Angular side of things, I created a StackBlitz example.
I have invoice screen and in this screen there are number of order are available so when we create invoice there are one form we need to fill so I want solution is when I submit this invoice form or click this submit button pdf should be open in new tab. I want to clarify to you we are not save this pdf anywhere.
<div class="modal-footer custom-no-top-border">
<input type="submit" class="btn btn-primary" id="createdata" value="#T("Admin.Common.Create")" />
</div>
When I click on this button, the pdf should be opened in a new tab.
Here is pdf code
[HttpPost]
public virtual ActionResult PdfInvoice(int customerOrderselectedId)
{
var customerOrder = _customerOrderService.GetCustomerOrderById(customerOrderselectedId);
var customerOrders = new List<DD_CustomerOrder>();
customerOrders.Add(customerOrder);
byte[] bytes;
using (var stream = new MemoryStream())
{
_customerOrderPdfService.PrintInvoicePdf(stream, customerOrders);
bytes = stream.ToArray();
}
return File(bytes, MimeTypes.ApplicationPdf, string.Format("order_{0}.pdf", customerOrder.Id));
}
This code downloads the pdf when I click on the button.
Thank you !!
The most important thing is Controller.File() works with [HttpGet], hence you should do these steps:
1) Change HTTP method type from [HttpPost] to [HttpGet] and set return File() without specifying fileDownloadName parameter (using overload of Controller.File() which accepts 2 parameters).
[HttpGet]
public virtual ActionResult PdfInvoice(int customerOrderselectedId)
{
var customerOrder = _customerOrderService.GetCustomerOrderById(customerOrderselectedId);
var customerOrders = new List<DD_CustomerOrder>();
customerOrders.Add(customerOrder);
byte[] bytes;
using (var stream = new MemoryStream())
{
_customerOrderPdfService.PrintInvoicePdf(stream, customerOrders);
bytes = stream.ToArray();
}
// use 2 parameters
return File(bytes, MimeTypes.ApplicationPdf);
}
2) Handle click event of that button (preferred using <input type="button" .../>) and use _blank option, or use an anchor tag (<a>) with target='_blank' attribute:
$('#createdata').click(function (e) {
// if using type="submit", this is mandatory
e.preventDefault();
window.open('#Url.Action("PdfInvoice", "ControllerName", new { customerOrderselectedId = selectedId })', '_blank');
});
The reason why fileDownloadName parameter is not used here is that parameter sets Content-Disposition: attachment while file name is provided, otherwise if you're omit it or using null value, then Content-Disposition: inline will be set automatically.
Note that because you're using FileResult, you should not setting Content-Disposition using Response.AddHeader before return File() like this, because doing so will sent multiple Content-Disposition headers which causing browser to not display the file:
// this is wrong way, should not be used
Response.AddHeader("Content-Disposition", "inline; filename=order_XXX.pdf");
return File(bytes, MimeTypes.ApplicationPdf);
Related issues:
How To Open PDF File In New Tab In MVC Using C#
ASP.NET MVC: How can I get the browser to open and display a PDF instead of displaying a download prompt?
Stream file using ASP.NET MVC FileContentResult in a browser with a name?
In my application I have a folder structure where you can upload all kinds of files (jpg, png, pdf, txt etc.) You can download them directly as a file or you can view the file in the browser.
To display the file in the browser, I use the folowing controller action:
public virtual ActionResult FileDisplay(int fileId, bool isFile)
{
var viewmodel = _presenter.GetDocumentDisplayViewModel(fileId, isFile);
return base.File(viewmodel.Data, viewmodel.MediaType);
}
The file is displayed in the browser like this:
https://i.stack.imgur.com/TXmTy.jpg
The browser tab shows my controller name "FileDisplay" and (for this example) the dimensions of the image.
Question:
How can I display the filename in the brower tab title in stead of the controller name? This for all file types.
1. Content-Dispostion header
I have found several posts where they say to add the Content-Disposition header to the response with the filename:
Returning a file to View/Download in ASP.NET MVC
Make a file open in browser instead of downloading it
But this doesn't work.
2. Add filename to the File constructor
return base.File(viewmodel.Data, viewmodel.MediaType, viewmodel.FileName);
If I do this, the file is downloaded in stead of displayed in the browser.
3. PDF file title
I have found out that sometimes the browser tab title is correct! If I display a PDF file in the browser that has a file title (title in pdf properties, not the file name) the browser title is correct:
https://i.stack.imgur.com/orAZS.jpg
Can someone help me with this?
Workaround for now:
I have created a new action and view with a fullscreen iframe so I can set the title of this container view. The IFrame source is calling my File directly.
public virtual ActionResult FileDisplay(string fileName, int fileId, bool isFile)
{
var viewModel = new IFrameDocumentDisplayViewModel
{
FileName = fileName,
FileId = fileId,
IsFile = isFile
};
return PartialView("IFrameFileDisplayView", viewModel);
}
public virtual ActionResult GetFile(int fileId, bool isFile)
{
var viewmodel = _presenter.GetDocumentDisplayViewModel(fileId, isFile);
return base.File(viewmodel.Data, viewmodel.MediaType);
}
#model OnView.Models.ViewModels.Document.IFrameDocumentDisplayViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>#Model.FileName</title>
</head>
<body>
<iframe style="border: 0; position:absolute; top:0; left:0; right:0; bottom:0; width:100%; height:100%" src="#Html.Raw(Url.Action("GetFile", "Document", new { fileId = Model.FileId, isFile = Model.IsFile}))"></iframe>
</body>
</html>
So for now I can set the page title. But I am still curious how I can accomplish it without IFrame.
What the browser display as the tab title is the last part of the URL (the browser has no idea what is your controller name), so you could reformat your url like this :
localhost/OnView.Web/Document/FileDisplay/1685/True/MyTabName
Then the tab title would be MyTabName.
But changing a whole URL schema for such purpose also seems like a workaround...
Sidenote : I have the same problem, but my url is like this : FileDisplay/3?isFile=true and my tab title is 3 (in chrome. For IE, the tab title is the domain).
Try This:-
public virtual ActionResult FileDisplay(int fileId, bool isFile)
{
var viewmodel = _presenter.GetDocumentDisplayViewModel(fileId, isFile);
ViewBag.Title= viewmodel.FileName;
return base.File(viewmodel.Data, viewmodel.MediaType);
}
I am generating a Pdf using ItextSharp using html to pdf. I want to display a preview of the Pdf on the screen so the user can see it. The user will be able to play around with the html and get the desired resulting pdf. I am generating the pdf correctly but am having trouble displaying it on the screen.
Ajax call:
function Preview()
{
var HtmlInfo = document.getElementById('HtmlToBarCodes').value;
$.ajax({
url: '/BarCodeGenerator/PreviewPDF',
type: 'POST',
cache: false,
data: { Html: HtmlInfo },
success: function (data) {
Loading = false;
$("#pdfResult").html(
$('<iframe>', {
src: data,
width: '600px',
height: "800px"
}));
},
error: function (x) {
Loading = false;
}
});
}
Controller
[HttpPost]
[ValidateInput(false)]
public FilePathResult PreviewPDF(string Html)
{
FileStream fs = null;
string pdfFilePath = "";
try
{
//ItextSharp, Html -> Pdf
pdfFilePath = BLL.GenerateBarCodes.GenerateBarcodes(Html, 5);
Response.ClearHeaders();
Response.ClearContent();
Response.Buffer = true;
Response.AppendHeader("Content-Disposition", "inline; attachment; filename=Preview.pdf");
Response.ContentType = "application/pdf";
Response.WriteFile(pdfFilePath);
Response.Flush();
Response.End();
//fs = new FileStream(pdfFilePath, FileMode.Open, FileAccess.Read);
}
catch (Exception ex)
{
//ToDo: log error
}
return File(pdfFilePath, "application/pdf", "Preview.pdf");
}
I can confirm that the Pdf is generating and saving on my end in my C:Temp folder and I can view it as well, but when I click preview all i get is a blank. I am not sure how to load the pdf data from the ajax call or if it is even possible.
There are many solutions and workarounds.I will point out two solid solutions.
solution 1:
step 1: generate pdf in back end and save it in server location. That pdf could be able to access via URL.
step 2:pass that URL as a response for AJAX call.
step 3:get the ajax response and set to iframe and show iframe
there are lot of ways to preview pdf in this SO answer.
references :
Recommended way to embed PDF in HTML?
Display PDF using an AJAX call
You can show your pdf using bellow jquery code
var iframe = $('<iframe>');
iframe.attr('src','http://www.pdf995.com/samples/pdf.pdf');
$('#targetDiv').append(iframe);
solution 2:
use a tag. Please go through this article.
You don't necessarily need Ajax for this. Just an link is enough if you set the content-disposition to attachment in the server side code. This way the parent page will just stay open, if that was your major concern (why would you unnecessarily have chosen Ajax for this otherwise?). Besides, there is no way to handle this nicely acynchronously. PDF is not character data. It's binary data. You can't do stuff like $(element).load(). You want to use completely new request for this. For that pdf is perfectly suitable.
reference :
Download and open pdf file using Ajax
The iframe src attribute expects a URI, not the raw data that populates the iframe.
You have 2 options.
a) use the srcdoc attribute (if you only need to support recent browsers).
b) construct a data URI from the data and use it as src
Below is a demo outlining both approaches
var data = "<html><body>hello world</body></html>"
$(function() {
$("#target").append($("<iframe>", {
srcdoc: data
}));
var dataUri = "data:text/html;base64," + btoa(data);
$("#target").append($("<iframe>", {
src: dataUri
}));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js">
</script>
<div id=target></div>
Edit:
Of course, pdf is a binary format. You can download as a blob, make a data URI and win!
$(function() {
var oReq = new XMLHttpRequest();
oReq.open("GET", "/somedoc.pdf", true);
oReq.responseType = "blob";
oReq.onload = function (oEvent) {
var blob = oReq.response;
var url = URL.createObjectURL(blob);
$("#target").append($("<iframe>", { src: url }));
};
oReq.send();
})