14

I was using this Rotativa 1.6.4 code example to generate a PDF from a page in my .NET MVC 5 app.

public ActionResult PrintIndex()
{
    var a = new ActionAsPdf("Index", new { name = "Giorgio" }) { FileName = "Test.pdf" };
    a.Cookies = Request.Cookies.AllKeys.ToDictionary(k => k, k => Request.Cookies[k].Value);
    a.FormsAuthenticationCookieName = System.Web.Security.FormsAuthentication.FormsCookieName;
    a.CustomSwitches = "--load-error-handling ignore";
    return a;
}

public ActionResult Index(string name)
{
    ViewBag.Message = string.Format("Hello {0} to ASP.NET MVC!", name);

    return View();
}

It was not printing the Index page, but instead was printing my login page.

Once I fixed the authentication issue, PDF generation was extremely slow even with CustomSwitches. (Several minutes)

The above code might actually work for you - it got around the authentication issue using the Cookies property, but it was way too slow for me.

How do I print a secure page as well as do it quickly?

Jess
  • 23,901
  • 21
  • 124
  • 145

5 Answers5

20

I struggled with this for probably 8 hours and I am posting my own solution partly as a self reference, but also because there was no good answer in stack overflow.

Download the Rotativa Source

It's open source on github. I tried lots of other solutions where people said to use UrlAsPdf and other solutions from github issues, but none of this worked for me. Another advantage besides reading the code... Build the pdb file, toss it into your solution and debug into it. It will reveal a lot! One thing I found is that Rotativa uses wkhtmltopdf.exe under the covers. This uses web kit to render the html. Also the command usually makes an http request to a url. Why? We are already on the server! That means we would have to re-authenticate and explains why we can sometimes get the Login page. Copying cookies will help, but why make an http request to yourself when you can do it in-line?

Breakthrough

I found an extension method in the source GetHtmlFromView which generates the view html without making a separate http request! YES! Who calls GetHtmlFromView? Why ViewAsPdf of course. So this lead me to try the below code, which works and is fast!

Code to put into an ASP.NET MVC Controller Action:

// ViewAsPdf calls Rotativa.Extensions.ControllerContextExtensions.GetHtmlFromView
// Which generates the HTML inline instead of making a separate http request which CallDriver (wkhtmltopdf.exe) does.
var a = new ViewAsPdf();
a.ViewName = "Index";
a.Model = _service.GetMyViewModel(id);
var pdfBytes = a.BuildPdf(ControllerContext);

// Optionally save the PDF to server in a proper IIS location.
var fileName = string.Format("my_file_{0}.pdf", id);
var path = Server.MapPath("~/App_Data/" + fileName);
System.IO.File.WriteAllBytes(path, pdfBytes);

// return ActionResult
MemoryStream ms = new MemoryStream(pdfBytes);
return new FileStreamResult(ms, "application/pdf");
Jess
  • 23,901
  • 21
  • 124
  • 145
  • One more note. I tried jsPDF using an HTML page as source, but it did not do a good job of rendering gif, certain fonts, tables, etc. – Jess Jan 28 '16 at 17:08
  • 1
    When using this, it is the line "var pdfBytes = a.BuildPdf(ControllerContext);" that is slow for me – Kevin Feb 05 '16 at 14:26
  • @Kevin my suggestion is to download the source from github. Build the pdb. Put the pdb into your solution and then debug into the code leaving strategic breakpoints until you find the slow code. Are you using Rotativa from nuget version 1.6.4? (Not the Rotativa.Mvc nuget package) – Jess Feb 05 '16 at 15:21
  • 1
    I have already fixed it by replacing the exe in my solution with a recent version (see my answer below) Thanks for the sugfgestion though! – Kevin Feb 05 '16 at 15:25
  • @Kevin also does your page use lots of javascript / CSS? Is the page itself fast or slow? You may also want to review the documentation for wkhtmltopdf which is quite good and see if any additional custom switches would help you. (Set the CustomSwitches property on the action. See code in the question as example) – Jess Feb 05 '16 at 15:25
  • 1
    Thanks Jess! it worked for me too. I tried Kevin's solution, it didnt work so I changed ActionAsPdf to ViewAsPdf code in your answer and it is fast again. Thank you! – Ram Gandhapuneni May 26 '16 at 07:56
  • @Jess a.Model = _service.GetMyViewModel(id);, is showing it has erro,the Action is (pdfAction) , and the print only is (pdfPrint) it means secound will print first, the first is partial view only table. now for solving the error do i need to include any package or lib in controller – SAR Dec 13 '16 at 05:08
  • 2
    @SAR, replace `a.Model = _service.GetMyViewModel(id);` with your code to populate the model, view model, or view bag that you want to pass into your view. – Jess Dec 13 '16 at 12:46
  • @Jess, var pdfBytes = a.BuildPdf(ControllerContext); here it wil generate the error {the model item passed into the dictionary is of type 'System.Web.Mvc.PartialViewResult', but this dictionary requires a model item of type 'PagedList.IPagedList`} , the action which i am requesting in PDFACTION it's working fine if i run it self – SAR Dec 17 '16 at 07:30
  • @SAR Please set the model that you need on this line `a.Model = _service.GetMyViewModel(id);` Here you will set the Model to your 'PagedList.IPagedList`. – Jess Dec 19 '16 at 12:52
5

I hope this code solve first question

public ActionResult DownloadViewPDF() {
 Dictionary<string, string> cookieCollection = new Dictionary<string, string>();
   foreach (var key in Request.Cookies.AllKeys)
     {
       cookieCollection.Add(key, Request.Cookies.Get(key).Value);
     }
     return new ActionAsPdf("Index")
       {
          FileName = "Name.pdf",
          Cookies = cookieCollection
        };
     }
  • I had the same issue and it was required to pass cookies. My app requires sign in. So, thanks for help. – Marek Bar Oct 25 '17 at 20:14
3

I was having the same problem, but the answer of Jess did not work for me.

When I updated the wkhtmltopdf.exe file in my project, downloaded from here, it worked fast again.

Kevin
  • 1,516
  • 1
  • 16
  • 31
1

ViewAsPdf solved my problem. ActionAsPdf generating error or generating login page.

        Dictionary<string, string> cookieCollection = new Dictionary<string, string>();
        foreach (var key in Request.Cookies.AllKeys)
        {
            cookieCollection.Add(key, Request.Cookies.Get(key).Value);
        }
        var abc = new ViewAsPdf("invoice", _customers)
        {
            FileName = "Name.pdf",
            Cookies = cookieCollection,
            FormsAuthenticationCookieName = FormsAuthentication.FormsCookieName
        };
        var byteArray = abc.BuildPdf(ControllerContext);
        var fileStream = new FileStream(Server.MapPath(subPath) + "/abc.pdf", FileMode.Create, FileAccess.Write);
        fileStream.Write(byteArray, 0, byteArray.Length);
        fileStream.Close();
Anish Manchappillil
  • 697
  • 2
  • 10
  • 19
0

In my case using UrlAsPdf() along with just adding the cookie that I needed, had resolved the issue. Following is the sample code that I have used.

    var report = new UrlAsPdf(url);

    Dictionary<string, string> cookieCollection = new Dictionary<string, string>();
    foreach (var key in Request.Cookies.AllKeys)
    {
       if (Crypto.Hash("_user").Equals(key))
       {
          cookieCollection.Add(key, Request.Cookies.Get(key).Value);
          break;
       }
    }
    report.Cookies = cookieCollection;
    report.FormsAuthenticationCookieName = FormsAuthentication.FormsCookieName;
  • You have already posted this answer [here](https://stackoverflow.com/questions/32854225/rotativa-actionaspdf-very-slow/64741729#64741729) please do not copy and paste answers, instead if the question appears to be asking the same thing, mark it as a duplicate. – DCCoder Nov 08 '20 at 19:21