3

Rotativa 1.6.4 keeps issuing System.NullReferenceException in Rotativa.dll

Specifically it occurs on BuildPdf(context)

I have tried Thread, Task and other background type jobs. Why? The PDF generation is extremely slow at times and buggy using ActionAsPdf (see Here) so I want to spin it off to a new thread/task or background job, and only log errors, so I can get the user back on their way asap.

The BuildPdf takes the current controller context, which always is going in null I've tried making a new context, using httpcontext, making a new instance of controller all with same results. Once it gets into the Rotativa.dll the context is compeltely gone, no matter if I pass it in from before I being the Thread/Task, or create a new one right before I call BuildPdf();

How do I get a valid controller context from either before I begin the thread/task job, or once inside the thread/task job into Rotativa?

At this point I'm more than ready to spend real money on a real PDF solution, but so far it seems they all are PITA and are only mediocre at best even if you have the most simplistic child needs of PDF.

Community
  • 1
  • 1
SouthPlatte
  • 297
  • 2
  • 7
  • 17

3 Answers3

0

Did you try to create a SynchronizationContext instead?

Seems like the library is non-thread-safe.

  • The library itself is said to be thread-safe according to the author, but I'm finding it's likely not. How would I make a SynchronizationContext work? I am vaguely familiar with know it exists. – SouthPlatte Sep 30 '15 at 21:47
  • Well, the documentation is admittedly scarce on this. As you can only get non-null SynchronizationContext.Current() from the main thread, you should declare a delegate function that will function as a proxy for `SynchronizationContext.Current()` getter. Then create a SynchronizationContext in the main thread, and implement the delegate to return the `Current()` getter's value. Finally, call the delegate from the worker thread to get the context that you can pass on to the library. – Victor - Reinstate Monica Sep 30 '15 at 22:29
0

I was dealing with the same issue. I've tried to Mock the ControllerContext, create FakeControllerContexts with MvcContrib (as it can be seen in Rotativa unit test libraries), etc... But all approaches gave me mess, circular references and they didn't functioned well in threads.

In order to call functions from model I ended up with use of RazorEngine (to get html filled by model data) + iTextSharp (to convert html to pdf) instead of Rotativa. This creates an elegant templating possibilities inside my projects (emails, pdf,...).

using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.html.simpleparser;

...
public class PDFHelper { 
        public static byte[] GetPDFGromHTMLString(string pHTML) {
            byte[] bPDF = null;

            MemoryStream ms = new MemoryStream();
            TextReader txtReader = new StringReader(pHTML);

            // 1: create object of a itextsharp document class
            Document doc = new Document(PageSize.A4, 25, 25, 25, 25);

            // 2: we create a itextsharp pdfwriter that listens to the document and directs a XML-stream to a file
            PdfWriter oPdfWriter = PdfWriter.GetInstance(doc, ms);

            // 3: we create a worker parse the document
            HTMLWorker htmlWorker = new HTMLWorker(doc);

            // 4: we open document and start the worker on the document
            doc.Open();
            htmlWorker.StartDocument();

            // 5: parse the html into the document
            htmlWorker.Parse(txtReader);

            // 6: close the document and the worker
            htmlWorker.EndDocument();
            htmlWorker.Close();
            doc.Close();

            bPDF = ms.ToArray();

            return bPDF;
        }

and in model I use it as:

using RazorEngine;
using RazorEngine.Templating;
...
string razorText = System.IO.File.ReadAllText(HostingEnvironment.MapPath(@"~/Views/MyReport.cshtml"));
string body = Razor.Parse(razorText, model);
byte[] pdfBinary = MyFramework.PDFHelper.GetPDFGromHTMLString(body);
/**other stuff */

If you don't want to deal with RequestContext in RazorEngine, you shouldn't use Html and Url helpers (change @Html.DisplayFor(model => ..) to @Model.MyProperty)

0

Try this one!!

Task.Factory.StartNew(() =>                  
 {
     Do Something here
 },
 CancellationToken.None,
 TaskCreationOptions.PreferFairness,
 TaskScheduler.FromCurrentSynchronizationContext() 
);
Santosh P
  • 125
  • 1
  • 3