ASP.Net Web API - Display HTML and download file - asp.net

In our AngularJS / ASP.Net WebAPI application, we need to generate a PDF file when clicking a button. The button re-directs the user to the following URL on a new browser page:
xyz.com/api/getpdf?Token=f3Ttkwf5XyvvZwcOZpEz
getpdf is a API that returns the PDF file or an HTML error code if an error occurs.
The problem is that the PDF can take up to 30 seconds to generate. How can I display some HTML on the page (informing user to wait) before I send the file? The HTML should also be returned in the same API call. I've read that it is possible to have multiple responses on one request. But how do I do this?
This is the simplified code in the controller to return the PDF:
[HttpGet]
public HttpResponseMessage GetPDF(string Token)
{
HttpResponseMessage resp = null;
try
{
// Generate PDF
Byte[] lPDF = GetPDF(Token);
// Return PDF in response
resp = Request.CreateResponse(HttpStatusCode.OK);
resp.Content = new ByteArrayContent(lPDF);
resp.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("inline");
resp.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");
resp.Content.Headers.ContentDisposition.FileName = "PDF-File.pdf";
resp.Content.Headers.ContentLength = lPDF.Length;
return resp;
}
catch (Exception ex)
{
Toolkit.LogError("GetPDF", ex);
return Request.CreateResponse(HttpStatusCode.InternalServerError);
}
}

You can create an overlay on the previous page.
For example you have a button who call your controller's method, on click on this button display your overlay with the message 'Loading'.
When the document will be generated, the page will be redirected and the overlay will disappear.
Example with JQuery
$("#generate").click(function(){
$(".overlay").show();
setTimeout(function(){window.location.href="http://stackoverflow.com"},2000);
});
.overlay{
display:none;
position:fixed;
top:0;
left:0;
background-color:rgba(0,0,0,0.5);
width:100%;
height:100%;
color:white;
text-align:center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="overlay">Loading...</div>
<button id="generate">Generate</button>
Here the setTimeout method is just used for simulate a time of response.

If I understand you correctly you may generate pdf-file in two steps.
First step is to ask web server to generate pdf-file. (POST-request)
Second step is to ask web server if pdf-file is already generated. (GET-request or SignalR)

Related

How to render area of ASP .Net Core razor page (for pdf)

I am writing an ASP.Net Core Web App with Razor pages and need to render part of a page to a PDF. I'm pretty sure the actual PDF creation should be simple using one of the pdf libraries available like jsreport. I found several samples for rendering a page to a string which is great except I only want the report area.
What I'm struggling with is how to render just a portion of my page for the PDF. This image shows basically how my page is structured. The header, nav and footer come from the _Layout for my app. The range partial is shared by all the report pages and the report content is the actual page.
Is there a way to render just a section of a page?
If the content area was also a partial could that be rendered separately?
I have almost no experience with UI development so please pardon my naivety :)
Here is my solution to render the report area of my page.
The simple answer is, post the html content of the page section I want to render back to the server, render it to a PDF and return that as a file for downloading.
To do this I first needed to get the html content for the section of the page containing the report. I did this by setting the id of the div for my reports to “report-content” and then using some script to capture that html.
$(function () {
$("#btnSubmit").click(function () {
$("input[name='ReportContent']").val($("#report-content").html());
});
});
The form to submit the request has a hidden “ReportContent” element where the html is stored.
#using (Html.BeginForm("Export", "Home", FormMethod.Post))
{
<input type="hidden" name="ReportContent" />
<input type="submit" id="btnSubmit" value="Download PDF" />
}
On the server side I created a method using JsReport API to render the PDF from the html.
protected async Task<IActionResult> RenderPDFAsync(string content)
{
var rs = new LocalReporting().UseBinary(JsReportBinary.GetBinary()).AsUtility().Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.ChromePdf,
Engine = Engine.None,
Content = content
}
});
return new FileStreamResult(report.Content, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("application/pdf"));
}
Finally, I added the handler for the post that renders the PDF and returns the file result to each report page.
public async Task<IActionResult> OnPostAsync()
{
return await RenderPDFAsync(Request.Form["ReportContent"].ToString());
}
My code has a bit more validation than shown here and I have a common PageModel derived base class for all of my report pages so the only thing required for new reports is adding the post hander and setting the id on the section to be included in the PDF. I also intend to move the rendering to a service so it can be shared rather than recreated every time a report is created.

ReportViewerForMvc event on report load

I am loading a report in an iframe using ReportViewerForMvc. Currently, I have a spinner so that the user will know the report is loading. However, the spinner stops spinning when the iframe is placed on the page...not when the content of the report is finished rendering. I have found people using isLoading with $find but I am pretty sure that is just for asp and I need my to be in .Net
What is the simplest way to have spinner continue to spin until the report is loaded in the iframe?
Currently, I have a shared view for all reports that I am hoping to add some javascript to:
#using ReportViewerForMvc;
<div id="reportViewer">#Html.ReportViewer(Model.ReportViewer as Microsoft.Reporting.WebForms.ReportViewer)</div>
iframe onload does not work to stop spinner here.You would need cookies and client side script to accomplish that.
The server code will set the value in the cookie .Once the report is rendered the value will be read on the client side(cshtml) and spinner can be stopped.
Read this article.Here you can replace the blocker with the spinner.
http://gruffcode.com/2010/10/28/detecting-the-file-download-dialog-in-the-browser/
//This should be called on the event when you are loading the report
//In your case you will route the url to controller or invoke the link
//for the report
$(document).ready(function () {
$('#create_pdf_form').submit(function () {
blockUIForDownload();
});
});
//This is where you will place the spinner
function blockUIForDownload() {
var token = new Date().getTime();
//use the current timestamp as the token value
$('#download_token_value_id').val(token);
$.blockUI();
fileDownloadCheckTimer = window.setInterval(function () {
var cookieValue = $.cookie('fileDownloadToken');
if (cookieValue == token)
finishDownload();
}, 1000);
}
//This will read the token generated from the server side controller or
//aspx.cs or ashx handler
function finishDownload() {
window.clearInterval(fileDownloadCheckTimer);
// $.removeCookie('fileDownloadToken'); //clears this cookie value
//$.cookie('fileDownloadToken', null);
//$.removeCookie("fileDownloadToken");
setCookie("fileDownloadToken", '2')
$.unblockUI();
}
//On the server side set the token , it could be controller or ashx handler
var response = HttpContext.Current.Response;
response.Clear();
response.AppendCookie(new HttpCookie("fileDownloadToken",
downloadTokenValue); //downloadTokenValue will have been provided in the
form submit via the hidden input field
response.Flush();
//Lastly don't forget to add these source js files.
<script src="~/Scripts/jquery-1.5.1.js"></script>
<script src="~/Scripts/jquery.blockUI.js"></script>
<script src="~/Scripts/jquery.cookie.js"></script>

Iframe shows ActionMethod name while loading PDF file in ASP MVC

i am using iframe tag in order to call ActionMethod that returns FileResult to display PDF file. The issue is after it loads PDF document instead of showing pdf file name, it shows ActionMethod name on the top of the PDF file name in Chrome.
Razor Code:
<iframe src="#Url.Action("GetAgreementToReview", "Employee")" style="zoom: 0.60; -ms-zoom: 1; width: 100%;" width="99.6%" height="420" frameborder="0" id="agreementPdf"></iframe>
CS Code:
public ActionResult GetAgreementToReview()
{
Response.AddHeader("Content-Disposition", "inline; filename=Master-Agreement.pdf");
return File("~/Content/Master-Agreement.pdf", "application/pdf");
}
Image: As you can see in the screenshot, it shows 'GetAgreementToReview' i.e. ActionMethod name instead of 'Master-Agreement.pdf'.
Does anyone know how to fix this issue?
Thanks.
Sanjeev
I had a similar problem and found a solution after exploring almost everything the web has to offer.
You must use a custom route for your action and you must send the filename from UI into the URL itself. (Other parameters won't be affected)
//add a route property
[Route("ControllerName/GetAgreementToReview/{fileName?}")]
public ActionResult GetAgreementToReview(string fileName, int exampleParameter)
{
byte[] fileData = null; //whatever data you have or a file loaded from disk
int a = exampleParameter; // should not be affected
string resultFileName = string.format("{0}.pdf", fileName); // you can modify this to your desire
Response.AppendHeader("Content-Disposition", "inline; filename=" + resultFileName);
return File(fileData, "application/pdf"); //or use another mime type
}
And on client-side, you can use whatever system to reach the URL.
It's a bit hacky, but it works.
If you have an Iframe on HTML to display the PDF (the issue I had), it could look like this:
<iframe src='/ControllerName/GetAgreementToReview/my file?exampleParameter=5' />
And this way you have a dynamic way of manipulate the filename shown by the IFRAME that only uses the last part of an URL for its name shown on the web page (quite annoying). The downloaded filename will have its name given from server-side Content-disposition.
Hope it helps !
please try this code :
return File("~/Content/Master-Agreement.pdf", "application/pdf", "filename.pdf");
Other Helper Link : Stream file using ASP.NET MVC FileContentResult in a browser with a name?

Return FilePathResult Between Views

Usually, in ASP.NET, MVC or otherwise, we make the client download a file by having the user click a link/button; I assume this link/button action goes to the correct controller then redirects back to the main page. However, how can I cause the download from some intermediate controller, during a series of redirects?
Basically, when the user clicks a button to create a PDF, the action goes to PdfController to create the PDF, then, since I'm assuming he/she wants to download the PDF anyway (if he/she doesn't, he/she can always click "No"), I want to have the browser download the PDF before the page gets rendered again. If I haven't lost you yet, how do I accomplish this?
Here's a sample of what I have so far. Button that starts the action:
<a class="btn btn-primary col-md-2 col-md-offset-1" href="#Url.Action("MakePdf", "Pdf")">Create PDF</a>
PdfController's MakePdf() method:
public ActionResult MakePdf()
{
string PdfUrl = Environment.GetEnvironmentVariable("HOMEDRIVE") + Environment.GetEnvironmentVariable("HOMEPATH") + "/Sites/Bems/PDF/UserPdfs/report" + Id + ".pdf";
// create the PDF at this PdfUrl
return RedirectToAction("ShowPdf", "Pdf", new { PdfUrl = PdfUrl });
}
PdfController's ShowPdf() method, redirected from the previous MakePdf() method:
public ActionResult ShowPdf(string PdfUrl)
{
if (System.IO.File.Exists(PdfUrl))
{
return File(PdfUrl, "application/pdf"); // Here is where I want to cause the download, but this isn't working
}
else
{
using (StreamWriter sw = System.IO.File.CreateText(PdfUrl))
{
sw.WriteLine("A PDF file should be here, but we could not find it.");
}
}
return RedirectToAction("Edit", "Editor"); // Goes back to the editing page
}
I'm trying to cause the download at the place in the code I specified, but I'm not sure how to cause it. Usually you return it somehow to the view, whereas here I'm calling an object, I think, but I'm really fuzzy on how that all works. Regardless, something I'm doing isn't working. Any ideas on what that is?
You can't return the ViewResult RedirectToAction("Edit", "Editor") and in the same response a FileResult File(PdfUrl, "application/pdf")
To accomplish your task you could follow this scenario:
click on button create pdf
call the RedirectToAction("Edit", "Editor");
in this case at the end of the view, add a javascript call to the action method returning the FileResult
once the page is rendered, the download will start automatically

Convert messageBox in asp.net in VB

I have the following function that creates a message box.
Function Question() As Boolean
If MsgBox("Are you sure?", MsgBoxStyle.YesNo, "Question") = MsgBoxResult.Yes Then
RunSubRoutine()
txtbox.focus
Return True
Else
Return False
End If
End Function
How would I change it so it works the same in ASP.Net because I need it to return a value?
You just can't prompt the user that way because ASP.NET is server-side language. You need to use javascript (or JQuery for more advanced popup windows) to do that like this:
<script>
function Question() {
if (confirm('Are you sure?')) {
document.getElementById("your_textbox_id").focus()
}
}
</script>
And if you want the confirmation to return back to the server:
There are a lot ways to do it depending on your requirements.
I cannot know which will fit best in your stiation but the eaisest way to refresh the page by adding a query string value to the main url. For example, let's say your URL is www.blabla.com/test.aspx then you can just redirect the client browser like this:
<script>
function Question() {
if (confirm('Are you sure?')) {
window.location.href = "test.aspx?confirm=yes";
}
}
</script>
and then catch the querystring in the test.aspx page by using:
If Request.QueryString("confirm") = "yes" then
//call your asp.net routine here
End If
Or you can POST the result returned from javascript to the server by making an AJAX request, which is more complicated but doesn't need to reload the page...
There is no such direct option to achieve this.
ASP.NET distroy the process after page loading. You can send request either using ajax or event handling.
There is no such thing as messagebox in asp.net. You can do this alternative.
if(confirm('Are you sure?')){
// do this
}
else{
// do something else
}

Resources