2

Processing of Converting the html to PDF takes a long time by using DinkToPdf library, when this line of code runs: ConverterToPdf.Convert(pdf)

How can I solve this problem?

public byte[] ConvertReportToPDFAsync<TViewModel>() where TViewModel : new()
{
   var globalSettings = new GlobalSettings
   {
      ColorMode = ColorMode.Color,
      Orientation = Orientation.Portrait,
      PaperSize = PaperKind.A4,
      Margins = new MarginSettings { Top = 10 },
      DocumentTitle = documentTitle,
   };

   var objectSettings = new ObjectSettings
   {
      PagesCount = true,
      HtmlContent =@"<html><body><div>Hello</div></body></html>",
      WebSettings = { DefaultEncoding = "utf -8", UserStyleSheet = Path.Combine(Directory.GetCurrentDirectory(), "assets", "PruefReportDataTableFormat.css") },
      HeaderSettings = { FontName = "Arial", FontSize = 9, Right = "Page [page] of [toPage]", Line = true },
      FooterSettings = { FontName = "Arial", FontSize = 9, Line = true, Center = "Report Footer" }
    };
    var pdf = new HtmlToPdfDocument()
    {
       GlobalSettings = globalSettings,
       Objects = { objectSettings }
    };

   byte[] file = ConverterToPdf.Convert(pdf); // TOO LONG PROCESS !!!!

   return file;

}

Startup class:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor(); 
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddMemoryCache(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddSessionStateTempDataProvider();

        services.AddSingleton(typeof(IConverter), new SynchronizedConverter(new PdfTools())); // DinkToPdf
    }
}

Updated:

After tracing, I figured out the problem is in WkHtmlToXBindings.wkhtmltopdf_convert method in namespace DinkToPdf

namespace DinkToPdf
{
   public sealed class PdfTools : ITools, IDisposable
   {
      // some codes
      public bool DoConversion(IntPtr converter)
      {
         return WkHtmlToXBindings.wkhtmltopdf_convert(converter); // ACTUALLY TOO LONG PROCESS OCCURS HERE !!!!
      }
   }
}

Update 2:

After deep tracing, I figured out that in this line of code, Monitor.Wait((object) task1);, occurred long process! (I cannot change this code, because this code is part of dinktopdf.dll file.)

namespace DinkToPdf
{
   public class SynchronizedConverter : BasicConverter
   {
      public TResult Invoke<TResult>(Func<TResult> @delegate)
      {
         this.StartThread();
         Task<TResult> task1 = new Task<TResult>(@delegate);
         Task<TResult> task2 = task1;
         bool lockTaken = false;
         try
         {
             Monitor.Enter((object) task2, ref lockTaken);
             this.conversions.Add((Task) task1);
             Monitor.Wait((object) task1);   //Bottleneck!! LONG PROCESS!!
         }
         finally
         {
            if (lockTaken)
               Monitor.Exit((object) task2);
         }
         if (task1.Exception != null)
            throw task1.Exception;
         return task1.Result;
      }
   }
}
x19
  • 8,277
  • 15
  • 68
  • 126
  • what's the `ConverterToPdf` in this line `byte[] file = ConverterToPdf.Convert(pdf);` ? Is it an instance of `IConverter` injected by DI ? I've tried your code. Assuming the `ConverterToPdf` is a standard instance injected by DI, it works well for me. If there's a demo that reproduces ? – itminus Feb 22 '19 at 05:43
  • Yes! It is an instance of IConverter injected by DI. public IConverter ConverterToPdf – x19 Feb 25 '19 at 06:59
  • Is there's a demo that produces the same issue? – itminus Feb 25 '19 at 08:20
  • There is no demo. – x19 Feb 25 '19 at 08:28
  • Just I thought but these are bindings to an unmanaged library doing the actual conversion. Could it be antivirus related? – Robbert Draaisma Feb 25 '19 at 20:26
  • It's not related to antivirus. – x19 Feb 26 '19 at 07:16
  • 1
    What do you exactly mean by "long time"? – Simon Mourier Feb 26 '19 at 09:26
  • for creating a simple pdf, it takes more than 3 minutes time. – x19 Feb 26 '19 at 09:32
  • SynchronizedConverter blocks and processes one operation at a time. This might be a source of bottlenecks. Can you try BasicConverter with a single request only? – Mohammad Feb 26 '19 at 12:05
  • byte[] file = null; using (var pdfTools = new PdfTools()) { var converter = new BasicConverter(pdfTools); file = converter.Convert(pdf); } That was not useful! – x19 Feb 26 '19 at 13:07
  • Why do you say you cannot change the code? it's open source (plus your code doesn't seem to match the github repo https://github.com/rdvojmoc/DinkToPdf/blob/b0546d21641eaef71f6fe0ddfd3998e8502baab7/src/DinkToPdf/SynchronizedConverter.cs ...) – Simon Mourier Feb 26 '19 at 20:50
  • Are you bound to DinkToPdf? – usselite Feb 27 '19 at 09:09
  • I use only the dll file, but when I debugging, Resharper can show the classes for DinkToPdf.dll during the debug. – x19 Feb 27 '19 at 10:28
  • Do you absolutely want to use this library ? I use another and it's very fast : [Rotativa](https://github.com/webgio/Rotativa.AspNetCore) – Loïc Sombart Feb 27 '19 at 14:02
  • @LoïcSombart Are you satisfied with Rotativa? Is it compatible with asp.net core 2.2 – x19 Feb 27 '19 at 15:42
  • @Jahan I use Rotativa with asp.net core 2.0. There are a few configurations lines and a file (.exe) to add in your project folder. Then it automatically transforms a view (.cshtml) into PDF. If you are interested or you have a configuration problem, I will put an answer. – Loïc Sombart Feb 27 '19 at 15:54
  • @LoïcSombart Thanks! I will Test it tomorrow. – x19 Feb 27 '19 at 16:05
  • If you are going to test Rotativa take a look at https://github.com/kblok/puppeteer-sharp, build upon the chrome-renderer. – usselite Feb 28 '19 at 07:37
  • @LoïcSombart: I got this error after installing. CS0246 C# The type or namespace name could not be found (are you missing a using directive or an assembly reference?) – x19 Feb 28 '19 at 08:19
  • @Jahan Have you installed Rotativa.AspNetCore version 1.1.1 by Giorgio Bozio ? – Loïc Sombart Feb 28 '19 at 08:22
  • @LoïcSombart: I solved it. But now I want to configure it. I will tell you the result. – x19 Feb 28 '19 at 08:52
  • @LoïcSombart: How can I set the path of cshtml file? for example I want to show contain of ViewComponent and it located in \\Views\MyController\Components\MyViewComponent\Default.cshtml – x19 Feb 28 '19 at 09:11
  • @Jahan, directly in the method of your controller : `return new ViewAsPdf(viewName)` – Loïc Sombart Feb 28 '19 at 09:33
  • @LoïcSombart: Suppose! I have a controller and in this controller there is a search button and select-option. It depends on, I get various result that call their ViewComponent. And now I want to save the result of ViewComponet as Pdf. – x19 Feb 28 '19 at 09:56
  • @Jahan Either you make a condition with several Return, or you can pass objects in your view with `return new ViewAsPdf(viewName, object)`. I have PdfController > method Site() > return new ViewAsPdf("Site") and my view is in Views/Pdf/Site.cshtml. I hope it will help you. – Loïc Sombart Feb 28 '19 at 10:01
  • I call a ViewComponent in my view of controller. like this:
    @await Component.InvokeAsync("PlakettenabrechnungReport", Model)
    – x19 Feb 28 '19 at 10:12
  • @Jahan what's the error with your code ? – Loïc Sombart Feb 28 '19 at 12:42

2 Answers2

1

It is a singleton you created on your Startup.cs so what you need to do firstly is to populate your root object with dependency injection.

private readonly IConverter _converter;

public YourControllerService(IConverter converter)
{            
    _converter = converter            
}

public void CreatePdf() {
    byte[] bytes = _converter.Convert(doc);
}

Secondly, make sure that you use web safe fonts to speed up the rendering. Using fonts from external urls inside your html will slow down the process. For instance, lets say this is your html template and your have 5 pages:

<html>
<head>
    <style>
        @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,700');
        body {
            font-family: 'Source Sans Pro', sans-serif;
        }
    <style>
<head>
</html>

if you change this with:

<html>
<head>
    <style>         
        body {
            font-family: 'Verdana'; 
        }
    <style>
<head>
</html>

you will see the speed difference. Also, according to my experience reducing the DPI also effects the rendering speed.

var doc = new HtmlToPdfDocument()
{
    GlobalSettings = {
        ColorMode = ColorMode.Color,
        Orientation = Orientation.Landscape,
        PaperSize = PaperKind.A4,
        DPI = 320,
        Out = path
    },
    Objects = {
        new ObjectSettings() {                               
            PagesCount = true,
            HtmlContent = html,
            WebSettings = { DefaultEncoding = "UTF-8", LoadImages = true },
            FooterSettings = { FontSize = 8, Right = "Page [page] of [toPage]", Line = false, Spacing = 2.812 }
        }
    }
};

byte[] bytes = _converter.Convert(doc);

Hope there help.

thus
  • 1,326
  • 1
  • 14
  • 23
0

I cannot see how you get ConverterToPdf, but you can try this: If you initialize converter with var converter = new SynchronizedConverter(new PdfTools());´, remove it, and just injectIConverter` in your service.

I tried and this approach is working, after all, we already register the converter, we don't need to create an instance using the new keyword.

Alexander
  • 151
  • 3
  • 5