Blank PDF being generated from ActiveReports/WebAPI - c#

On a project I am working on, I'm building a feature that lets users generate a report - in my case, it will go on an envelope - on-demand from information stored in our database. The problem I'm trying to solve, is that a blank PDF is being generated.
I've tried some sanity checks. First I set a breakpoint in Visual Studio and ensured that the models being passed to the report had fixed data; the reports were blank. Next, I tried including a static label that's not tied to any data, to determine if it's a report data-binding issue - the static label is not appearing in the generated report either.
More stymying, is that I've used similar code in the past without issue. I have no idea why a blank PDF file would be generated in this case.
I've read the 'Similar Questions' provided by StackOverflow, specifically this question from one year ago, but it had no answers, and thus nothing to learn from it. I've also tried the requisite Google searches, but found nothing relevant.
The only thing I cannot provide is the actual ActiveReport itself. I've checked this for Silly Programmer Errors™ like having everything hidden, or transparent labels, or similar silly things. Unfortunately, I've made no such errors.
Report Code:
public partial class EnvelopeReport : SectionReport
{
public EnvelopeReport()
{
InitializeComponent();
}
internal void RunReport(IEnumerable<PrintedAddress> model)
{
if (model != null)
{
DataSource = model;
}
Run();
}
private void OnReportStart(object sender, EventArgs e)
{
Document.Printer.PrinterName = string.Empty;
PageSettings.PaperKind = PaperKind.Number10Envelope;
PageSettings.Margins.Top = 0.25f;
PageSettings.Margins.Left = 0.5f;
PageSettings.Margins.Right = 0.5f;
PageSettings.Margins.Bottom = 0.25f;
}
}
Web API Controller Code:
[HttpGet]
public HttpResponseMessage EnvelopeReport(int addressId, string attentionTo, bool isConfidential)
{
Address address = AddressRepository.GetAddress(addressId, true);
List<PrintedAddress> models = new List<PrintedAddress>
{
new PrintedAddress(address, attentionTo, isConfidential)
};
var report = new EnvelopeReport();
report.RunReport(models);
var pdfExporter = new ActiveReportsPdfExporter();
var reportBytes = pdfExporter.ExportPdf(report);
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new ByteArrayContent(reportBytes, 0, reportBytes.Length);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = "Envelope Report.pdf"
};
return response;
}
PDF Exporter:
public class ActiveReportsPdfExporter
{
private readonly PdfExport _pdfExport;
public ActiveReportsPdfExporter()
{
_pdfExport = new PdfExport();
}
public byte[] ExportPdf(SectionReport report)
{
using (var stream = new MemoryStream())
{
_pdfExport.Export(report.Document, stream);
return stream.ToArray();
}
}
public Stream ExportPdfToStream(SectionReport report)
{
var stream = new MemoryStream();
_pdfExport.Export(report.Document, stream);
return stream;
}
}
Client Service (Angular):
(function () {
angular.module('app').factory('addressSvc', [
'$http', addressSvc
]);
function addressSvc($http) {
var service = {
printAddress: function(addressId, attentionTo, someFlag) {
var args = {
'addressId': thingId,
'attentionTo': attentionTo,
'isConfidential': isConfidential
};
return $http.get('/api/common/EnvelopeReport', { 'params': args });
}
};
return service;
}
})();
Client Controller (Angular):
(function() {
angular.module('app').controller('someCtrl', [
'$window', 'addressSvc', controller
]);
function controller($window, addressSvc) {
var vm = this;
vm.attentionTo = ''; // Bound to UI.
vm.isConfidential = ''; // Also bound to UI.
vm.address = {}; // Unimportant how we get this.
vm.printAddress = printAddress;
function printAddress() {
addressSvc.printAddress(vm.address.id, vm.attentionTo, vm.isConfidential)
.then(function(result) {
var file = new Blob([result], {type: 'application/pdf'});
var fileURL = URL.createObjectURL(file);
if(window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(file, 'Envelope.pdf');
} else {
$window.open(fileURL);
}
});
}
}
)();
Question: Why is this code generating an empty PDF? I've used the Report/API Controller structure successfully in the past to generate PDFs, but usually in the context of MVC, not Web API. Another potential point of failure is the client code - I've not previously passed reports between server and client this way.

So, it turns out my server-side code was completely sane. The Client code was off.
Instead of Blobbing the data returned from the server and all of that work, what I instead needed to do was build a URL...and call $window.open(url); This is because my server code as it stands will return the PDF file as-is.

Related

The endpoint for my Azure APIs (Translator Text, Computer Vision) is not accesible (error code 404)

I followed the Hololens Tutorials provided by Microsoft and encountered some problems regarding Azure services in 301 (https://learn.microsoft.com/en-us/windows/mixed-reality/mr-azure-301#chapter-4--setup-debug-canvas) and 302 (https://learn.microsoft.com/en-us/windows/mixed-reality/mr-azure-302). I think the problem has something to do with the endpoint, because the error code 404 appears when checking https://westeurope.api.cognitive.microsoft.com/.
I use the Hololens first generation, Unity 2017.4.27f1, HoloToolkit-Unity-Examples-2017.4.3.0 and Visual Studio Community 2017. I've tried some of the Hololens tutorials, which all worked fine with these versions. My code is the one provided by Microsoft for the Tutorials 301 and 302. I've changed the location in Azure from
'global' to 'Western Europe' by creating a new ressource group and have now https://westeurope.api.cognitive.microsoft.com/ as my endpoint, I also created new authentification keys for the APIs and updated them in my code.
Instead of myKey I inserted the first key of the respective API (Translator Text, Computer Vision). The code below is from Tutorial 302 and the class 'VisionManager.cs' (which seems to be the responsible one for the communication with the services of the Computer Vision API).
public static VisionManager instance;
// you must insert your service key here!
private string authorizationKey = "myKey";
private const string ocpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";
private string visionAnalysisEndpoint = "https://westcentralus.api.cognitive.microsoft.com/vision/v2.0"; // This is where you need to update your endpoint, if you set your location to something other than west-us.
internal byte[] imageBytes;
internal string imagePath;
/// <summary>
/// Call the Computer Vision Service to submit the image.
/// </summary>
public IEnumerator AnalyseLastImageCaptured()
{
WWWForm webForm = new WWWForm();
using (UnityWebRequest unityWebRequest = UnityWebRequest.Post(visionAnalysisEndpoint, webForm))
{
// gets a byte array out of the saved image
imageBytes = GetImageAsByteArray(imagePath);
unityWebRequest.SetRequestHeader("Content-Type", "application/octet-stream");
unityWebRequest.SetRequestHeader(ocpApimSubscriptionKeyHeader, authorizationKey);
// the download handler will help receiving the analysis from Azure
unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
// the upload handler will help uploading the byte array with the request
unityWebRequest.uploadHandler = new UploadHandlerRaw(imageBytes);
unityWebRequest.uploadHandler.contentType = "application/octet-stream";
yield return unityWebRequest.SendWebRequest();
long responseCode = unityWebRequest.responseCode;
try
{
string jsonResponse = null;
jsonResponse = unityWebRequest.downloadHandler.text;
// The response will be in Json format
// therefore it needs to be deserialized into the classes AnalysedObject and TagData
AnalysedObject analysedObject = new AnalysedObject();
analysedObject = JsonUtility.FromJson<AnalysedObject>(jsonResponse);
if (analysedObject.tags == null)
{
Debug.Log("analysedObject.tagData is null");
}
else
{
Dictionary<string, float> tagsDictionary = new Dictionary<string, float>();
foreach (TagData td in analysedObject.tags)
{
TagData tag = td as TagData;
tagsDictionary.Add(tag.name, tag.confidence);
}
ResultsLabel.instance.SetTagsToLastLabel(tagsDictionary);
}
}
catch (Exception exception)
{
Debug.Log("Json exception.Message: " + exception.Message);
}
yield return null;
}
}
[System.Serializable]
public class TagData
{
public string name;
public float confidence;
}
[System.Serializable]
public class AnalysedObject
{
public TagData[] tags;
public string requestId;
public object metadata;
}
// Use this for initialization
void Start () {
}
private void Awake()
{
// allows this instance to behave like a singleton
instance = this;
}
// Update is called once per frame
void Update () {
}
/// <summary>
/// Returns the contents of the specified file as a byte array.
/// </summary>
private static byte[] GetImageAsByteArray(string imageFilePath)
{
FileStream fileStream = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
BinaryReader binaryReader = new BinaryReader(fileStream);
return binaryReader.ReadBytes((int)fileStream.Length);
}
I expected the output to be like the examples in the tutorials, but instead I got the same GUI results without any updates. In 301 I could see the same screen, but the text field 'You just said' and the 'Translation' never updated when I spoke. In 302 I could airtap and make a screenshot and could see at first 'Analyzing' and later on 'I see:' but without any results of detected objects. So it seems to me, that the Azure APIs services can't be contacted and I don't get any analysis data in return (probably you notice, that I'm quite new to the whole topic, so maybe my assumption is wrong).

ICalendar (ICS) - only VEVENT components works good with outlook/googleCalendar

I've got some problems with generating Icalendar files (*.ICS)
Im using library Ical.NET and c# language.
Code is very simple, for example this is VEVENT:
public override void HandleComponent(Ical.Net.Calendar root, CalendarData data)
{
var icalEvent = new Ical.Net.Event();
icalEvent.Start = new CalDateTime(data.Poczatek);
icalEvent.End = new CalDateTime(data.Koniec);
icalEvent.Location = data.Lokalizacja;
icalEvent.Description = data.Opis;
icalEvent.Summary = data.Nazwa;
root.Events.Add(icalEvent);
}
VJOURNEY and VTODO have very similar code <-- firstly i'm creating component, and then add it to calendar object.
Then i have generated file from this code:
var serializer = new CalendarSerializer(calendar);
var icsContent = serializer.SerializeToString();
return icsContent;
and structure of ics files looks like:
BEGIN:VCALENDAR
PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN
VERSION:2.0
BEGIN:VJOURNAL
ATTENDEE;CN=a;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:a#wp.pl
ATTENDEE;CN=a2;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:a2#wp.pl
DESCRIPTION:trele morele
DTSTAMP:20180913T072413Z
DTSTART:20180912T130700
ORGANIZER;CN=Administrator:mailto:Administrator#wp.pl
SEQUENCE:1
SUMMARY:qwerty22
UID:97032fa5-f554-4f10-9c4d-4fdda38148c7
END:VJOURNAL
END:VCALENDAR
just like specification says: https://www.kanzaki.com/docs/ical/vjournal.html
Problem:
Both Outlook 2016, and GoogleCalender handle properly only with VEVENT components on ICalendar file. When i import VJOURNAL or VTODO in GoogleCalendar it responds that he doesn't se any event...
Am i doing something wrong?
I also paste code where i create VJOURNAL
public class CalendarJournalComponent : CalendarComponents
{
public override CalendarComponentType SupportedComponent => CalendarComponentType.Journal;
public override void HandleComponent(Ical.Net.Calendar root, CalendarData data)
{
var journal = new Journal();
journal.Start = new CalDateTime(data.Poczatek);
journal.Description = data.Opis;
journal.Summary = data.Nazwa;
if (data.Prowadzacy.Any())
{
var prowadzacy = data.Prowadzacy.FirstOrDefault();
journal.Organizer = new Organizer() { CommonName = prowadzacy.Value, Value = emailUri(prowadzacy.Key)};
}
journal.Attendees = new List<IAttendee>();
foreach(var uczesnik in data.Uczestnicy)
{
journal.Attendees.Add(new Attendee() { CommonName = uczesnik.Value, Rsvp = true, Value = emailUri(uczesnik.Key), Role = "REQ-PARTICIPANT" });
}
root.Journals.Add(journal);
}
private Func<string, Uri> emailUri = x => new Uri(String.Format("mailto:{0}", x));
}
and VTODO compoment:
public class CalendarTodoComponent : CalendarComponents
{
public override CalendarComponentType SupportedComponent => CalendarComponentType.ToDo;
public override void HandleComponent(Ical.Net.Calendar root, CalendarData data)
{
var todo = new Todo();
todo.Start = new CalDateTime(data.Poczatek);
todo.Description = data.Opis;
todo.Summary = data.Nazwa;
todo.Location = data.Lokalizacja;
root.Todos.Add(todo);
}
}
Your code is most likely fine. But you need to ask yourself what you really expect to do by trying to import those VJOURNAL and VTODO in Google Calendar ?
As of today, Google Calendar really only support events/meetings:
It does not support VJOURNAL. As a matter of fact, there are really few software which do support VJOURNAL. From its definition (https://www.rfc-editor.org/rfc/rfc5545#section-3.6.3) you will see that it could be seen as the ancestor of a blog entry.
It does not support VTODO either. The closest you could find would be in Gmail which has a notion of Tasks list.

Xamarin forms, trying to bind my Image URL from my database to XAML but an image does not appear

I try to get an image from my database (in this case localhost) but it doesn't seem to work. I'm binding the result (URL to the image) to my XAML without any luck. I get no error but an Image does not appear either. This is my code.
Maybe it has something to do with my URL?
When I used parse (as backend) they gave us a URL that started like this:
"http://files.parsetfss.com/....."
My URL just starts with "localhost". Do I need to adjust the backend code somehow?
My cs file where I retrieve the http and jsondata:
static public async Task<JObject> getImages ()
{
var httpClientRequest = new HttpClient ();
var result = await httpClientRequest.GetAsync ("http://www.localhost.com/image.php");
var resultString = await result.Content.ReadAsStringAsync ();
var jsonResult = JObject.Parse (resultString);
return jsonResult;
}
It looks like this on the localhost:
{
"results": [
{
"GetImage": {
"ID": 1,
"Name": "Aqua",
"URL": "http://localhost.com/Images/Aquavit.jpg"
}
}]
}
This is my contentpage where I try to recieve the image:
async void clickHere (object s, EventArgs ar)
{
var createImage = await phpApi.getImages ();
list = new List<pictures> ();
foreach (var currentItem in createImage["results"]) {
var prodPic = "";
if(currentItem["GetImage"] != null)
{
prodPic = (string)currentItem ["GetImage"]["URL"];
}
list.Add (new pictures () {
image = prodPic,
});
System.Diagnostics.Debug.WriteLine (currentItem ["GetImage"]["URL"]); //here I get the exact URL.
}
}
My class:
public class pictures
{
public string image { get; set; }
}
And its XAML:
<Image Source = "{Binding image}" />
Just in addition:
If you want to load more images and/or allow to display a placeholder while loading, I recomment the FFImageLoading library. It offers nice functionality like downloading, caching, showing placeholder and error images and most important: down sampling the image to the target size. This is always a pain on android.
You can bind strings that contain urls directly to the Source. The code would like like:
<ffimageloading:CachedImage
Source="http://thecatapi.com/?id=MTQ5MzcyNA">
</ffimageloading:CachedImage>
I think you should be using a UriImageSource instead of string. Since it's a url. Try this:
your class:
public class pictures
{
public UriImageSource image { get; set; }
}
contentpage:
prodPic = (string)currentItem ["GetImage"]["URL"];
list.Add (new pictures () {
image = new UriImageSource { Uri = new Uri(prodPic) },
});
Also I believe your list needs to be an ObservableCollection instead of a List.

How to move CSS inline with PreMailer.Net whilst using MvcMailer for sending HTML emails

Using MvcMailer, the problem is that our emails are being sent without our CSS as inline style attributes.
PreMailer.Net is a C# Library that can read in an HTML source string, and return a resultant HTML string with CSS in-lined.
How do we use them together? Using the scaffolding example in the MvcMailer step-by-step guide, we start out with this example method in our UserMailer Mailer class:
public virtual MvcMailMessage Welcome()
{
return Populate(x => {
x.ViewName = "Welcome";
x.To.Add("some-email#example.com");
x.Subject = "Welcome";
});
}
Simply install PreMailer.Net via NugGet
Update the Mailer class:
public virtual MvcMailMessage Welcome()
{
var message = Populate(x => {
x.ViewName = "Welcome";
x.To.Add("some-email#example.com");
x.Subject = "Welcome";
});
message.Body = PreMailer.Net.PreMailer.MoveCssInline(message.Body).Html;
return message;
}
Done!
If you have a text body with HTML as an alternate view (which I recommend) you'll need to do the following:
var message = Populate(m =>
{
m.Subject = subject;
m.ViewName = viewName;
m.To.Add(model.CustomerEmail);
m.From = new System.Net.Mail.MailAddress(model.FromEmail);
});
// get the BODY so we can process it
var body = EmailBody(message.ViewName);
var processedBody = PreMailer.Net.PreMailer.MoveCssInline(body, true).Html;
// start again with alternate view
message.AlternateViews.Clear();
// add BODY as alternate view
var htmlView = AlternateView.CreateAlternateViewFromString(processedBody, new ContentType("text/html"));
message.AlternateViews.Add(htmlView);
// add linked resources to the HTML view
PopulateLinkedResources(htmlView, message.LinkedResources);
Note: Even if you think you don't care about text it can help with spam filters.
I recommend reading the source for MailerBase to get a better idea what's going on cos all these Populate methods get confusing.
Note: This may not run as-is but you get the idea. I have code (not shown) that parses for any img tags and adds as auto attachments.
Important part is to clear the HTML alternate view. You must have a .text.cshtml file for the text view.
If you're using ActionMailer.Net(.Next), you can do this:
protected override void OnMailSending(MailSendingContext context)
{
if (context.Mail.IsBodyHtml)
{
var inlineResult = PreMailer.Net.PreMailer.MoveCssInline(context.Mail.Body);
context.Mail.Body = inlineResult.Html;
}
for (var i = 0; i < context.Mail.AlternateViews.Count; i++)
{
var alternateView = context.Mail.AlternateViews[i];
if (alternateView.ContentType.MediaType != AngleSharp.Network.MimeTypeNames.Html) continue;
using (alternateView) // make sure it is disposed
{
string content;
using (var reader = new StreamReader(alternateView.ContentStream))
{
content = reader.ReadToEnd();
}
var inlineResult = PreMailer.Net.PreMailer.MoveCssInline(content);
context.Mail.AlternateViews[i] = AlternateView.CreateAlternateViewFromString(inlineResult.Html, alternateView.ContentType);
}
}
base.OnMailSending(context);
}
If you don't like using AngleSharp.Network.MimeTypeNames, you can just use "text/html". AngleSharp comes as a dependency of ActionMailer.Net.

Show loading screen before dynamically created PDF

I have a view that is, instead of returning a View(), is returning a dynamically created PDF and then showing the PDF in a new tab. I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
var cultivationModel = new CultivationModel(viewModel, ConstituentRepository, CampaignRepository);
var cultivationData = cultivationModel.GetCultivationActivityData();
var reportParamModel = new List<ReportParamModel>
{new ReportParamModel {AgencyName = SelectedUserAgency.AgencyName, StartDate = viewModel.StartDate, EndDate = viewModel.EndDate}};
var reportToRun = "ActionDateCultivationReport";
if (viewModel.SortActionBy == SolicitorActionReportSortType.Constituent) {
reportToRun = "ConstituentCultivationReport";
} else if (viewModel.SortActionBy == SolicitorActionReportSortType.Solicitor) {
reportToRun = "SolicitorCultivationReport";
}
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
public FileContentResult FileContentPdf(string folder, string reportName, object dataSet,object reportParamModel,object appealMassDataSet, object appealPortfolioDataSet) {
var localReport = new LocalReport();
localReport.ReportPath = Server.MapPath("~/bin/Reports/" + folder + "/rpt" + reportName + ".rdlc");
var reportDataSource = new ReportDataSource(reportName + "DataSet", dataSet);
var reportParamsDataSource = new ReportDataSource("ReportParamModelDataSet", reportParamModel);
var reportParamsDataSourceMass = new ReportDataSource("FundraisingAppealMassSummaryDataSet", appealMassDataSet);
var reportParamsDataSourcePortfolio = new ReportDataSource("FundraisingAppealPortfolioSummaryDataSet", appealPortfolioDataSet);
#region Setting ReportViewControl
localReport.DataSources.Add(reportDataSource);
localReport.DataSources.Add(reportParamsDataSource);
localReport.DataSources.Add(reportParamsDataSourceMass);
localReport.DataSources.Add(reportParamsDataSourcePortfolio);
localReport.SubreportProcessing += (s, e) => { e.DataSources.Add(reportDataSource); };
string reportType = "pdf";
string mimeType;
string encoding;
string fileNameExtension;
//The DeviceInfo settings should be changed based on the reportType
//http://msdn2.microsoft.com/en-us/library/ms155397.aspx
string deviceInfo = "<DeviceInfo><OutputFormat>PDF</OutputFormat></DeviceInfo>";
Warning[] warnings;
string[] streams;
byte[] renderedBytes;
//Render the report
renderedBytes = localReport.Render(reportType, deviceInfo, out mimeType, out encoding, out fileNameExtension, out streams, out warnings);
#endregion
return File(renderedBytes, mimeType);
}
I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
Short Answer
No, not in a new tab.
The main problem with what you're trying to do is the lack of power you have when it comes to controlling the browser. Specifically, when you tell an anchor to open its hyperlink in a new tab (ie target="_blank"). There are hacky ways around this that generally are just going to frustrate your user because you're changing behavior that they might be dependent/relying on.
Workaround
You can get very close to your desired outcome by using this jQuery File Download plugin (view a demo). Basically, it manipulates an iframe to queue a download. This makes it possible to show a loading div while also keeping the user on the active page (not directing them to another tab). Then, the user can click the downloaded PDF which will most-likely open in a new tab (view compatible browsers here).
If you decide to use this plugin, here are the steps to applying it:
Download the plugin js source and include it in your Scripts.
Include the FileDownloadAttribute class provided in the plugin MVC Demo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class FileDownloadAttribute: ActionFilterAttribute
{
public FileDownloadAttribute(string cookieName = "fileDownload", string cookiePath = "/")
{
CookieName = cookieName;
CookiePath = cookiePath;
}
public string CookieName { get; set; }
public string CookiePath { get; set; }
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="filterContext"></param>
private void CheckAndHandleFileResult(ActionExecutedContext filterContext)
{
var httpContext = filterContext.HttpContext;
var response = httpContext.Response;
if (filterContext.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
response.AppendCookie(new HttpCookie(CookieName, "true") { Path = CookiePath });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (httpContext.Request.Cookies[CookieName] != null)
{
response.AppendCookie(new HttpCookie(CookieName, "true") { Expires = DateTime.Now.AddYears(-1), Path = CookiePath });
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
CheckAndHandleFileResult(filterContext);
base.OnActionExecuted(filterContext);
}
}
github source
Apply the FileDownload attribute to your ActionResult method:
[FileDownload]
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
...
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
Include the necessary markup in the View to which you'll be linking to the report:
<a class="report-download" href="/Route/To/SolicitorActionReport">Download PDF</a>
Attach an event handler to the report-download anchor:
$(document).on("click", "a.report-download", function () {
$.fileDownload($(this).prop('href'), {
preparingMessageHtml: "We are preparing your report, please wait...",
failMessageHtml: "There was a problem generating your report, please try again."
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});
You can view working demos at http://jqueryfiledownload.apphb.com/. There is also a demo that uses pre-styled jQuery UI modals to "prettify" the user experience.
You can also download the demo ASP.NET MVC solution from johnculviner / jquery.fileDownload github to see all of this working.
I think you have two choices:
Redirect to a "loading" page with fancy GIF spinners, then direct the request to the PDF (this would work if the PDF take a little server time to generate - the visitor would be looking at a loading page while waiting for next page to load)
or
Use an iFrame: load a page that has an iframe. This page can overlay a spinning GIF and loading message while the iFrame loads the PDF itself. Note: you could make the iframe 100% width and height

Categories