Azure C# 函数应用程序,使用 HTML 到 PDF 库 (DinkToPDF)。仅运行一次。错误:Qt:无法初始化 OLE(错误 80010106)

问题描述 投票:0回答:2

我已遵循本教程:https://www.freecodecamp.org/news/convert-html-to-pdf-with-azure-functions-and-wkhtmltopdf/

解决方案是 Azure C# Function App,它将 HTML 转换为 PDF 文档,并将该 PDF 保存在 Blob 存储中。这个特定的函数应用程序接受以下内容,我在通过 Postman 在本地运行时通过请求正文传递这些内容:

{
  "PDFFileName": "test.pdf",
  "HtmlContent": "<html>\n        <head><meta http-equiv=Content-Type content=\"text/html; charset=UTF-8\">\n        </head>\n        <body>\n            <div>test</div>\n        </body>    \n    </html>"
}

本教程使用此库:https://github.com/rdvojmoc/DinkToPdf

发生的事情有点难以调试。我在本地运行函数应用程序,它运行良好,并且 PDF 保存在我的 blob 存储中,没有任何问题,但运行时我确实在控制台中看到以下错误。

Qt: Could not initialize OLE (error 80010106)

然后,当我再次运行此命令时(甚至删除 pdf,以确保那里没有冲突),应用程序只是挂起,直到它返回 503 或我取消请求。

追踪代码,正是这一行崩溃了(来自下面的代码):

var PDFByteArray = BuildPdf(Request.HtmlContent, new MarginSettings(2, 2, 2, 2));

直接在门户中发布和运行时(测试+代码并在逻辑应用程序流程中运行),我观察到相同的行为。

我的应用程序服务计划是基本 (B1: 1)。任何解决此问题的帮助将不胜感激!

完整代码如下:

using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using DinkToPdf;
using IPdfConverter = DinkToPdf.Contracts.IConverter;

[assembly: FunctionsStartup(typeof(pdfCreation.Startup))]

namespace pdfCreation
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton(typeof(IPdfConverter), new SynchronizedConverter(new PdfTools()));            
        }
    }

    public class Html2Pdf
    {
        // Read more about converter on: https://github.com/rdvojmoc/DinkToPdf
        // For our purposes we are going to use SynchronizedConverter
        IPdfConverter pdfConverter = new SynchronizedConverter(new PdfTools());

        // A function to convert html content to pdf based on the configuration passed as arguments
        // Arguments:
        // HtmlContent: the html content to be converted
        // Margins: the margis around the content
        // DPI: The dpi is very important when you want to print the pdf.
        // Returns a byte array of the pdf which can be stored as a file
        private byte[] BuildPdf(string HtmlContent, MarginSettings Margins, int? DPI = 180)
        {
            // Call the Convert method of SynchronizedConverter "pdfConverter"
            return pdfConverter.Convert(new HtmlToPdfDocument()
            {
                // Set the html content
                Objects =
                {
                    new ObjectSettings
                    {
                        HtmlContent = HtmlContent
                    }
                },
                // Set the configurations
                GlobalSettings = new GlobalSettings
                {
                    PaperSize = PaperKind.A4,
                    DPI = DPI,
                    Margins = Margins
                }
            });
        }

        private string ConnectionString(ILogger log, ExecutionContext context)
        {
            var config = new ConfigurationBuilder()
                .SetBasePath(context.FunctionAppDirectory)
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

            var connString = config.GetConnectionString("MyConnectionString");

            if(connString == null){log.LogInformation("Connection String is null");}

            return connString;
        }

        // The first arugment tells that the functions can be triggerd by a POST HTTP request. 
        // The second argument is mainly used for logging information, warnings or errors
        [FunctionName("Html2Pdf")]
        [STAThread]
        public async Task<string> Run([HttpTrigger(AuthorizationLevel.Function, "POST")] Html2PdfRequest Request, ILogger Log, ExecutionContext context)
        {
            Log.LogInformation("C# HTTP trigger function started for HTML2PDF.");
            Log.LogInformation(Request.HtmlContent);
            // PDFByteArray is a byte array of pdf generated from the HtmlContent 
            var PDFByteArray = BuildPdf(Request.HtmlContent, new MarginSettings(2, 2, 2, 2));

            // The connection string of the Storage Account to which our PDF file will be uploaded
            var StorageConnectionString = ConnectionString(Log, context);

            // Generate an instance of CloudStorageAccount by parsing the connection string
            var StorageAccount = CloudStorageAccount.Parse(StorageConnectionString);

            // Create an instance of CloudBlobClient to connect to our storage account
            CloudBlobClient BlobClient = StorageAccount.CreateCloudBlobClient();

            // Get the instance of CloudBlobContainer which points to a container name "pdf"
            // Replace your own container name
            CloudBlobContainer BlobContainer = BlobClient.GetContainerReference("pdf");
            
            // Get the instance of the CloudBlockBlob to which the PDFByteArray will be uploaded
            CloudBlockBlob Blob = BlobContainer.GetBlockBlobReference(Request.PDFFileName);
            
            Log.LogInformation("Attempting to upload " + Request.PDFFileName);
            // Upload the pdf blob
            await Blob.UploadFromByteArrayAsync(PDFByteArray, 0, PDFByteArray.Length);

            return "Created PDF Packing Slip.";
        }
    }
}

namespace pdfCreation
{
    public class Html2PdfRequest
    {
        // The HTML content that needs to be converted.
        public string HtmlContent { get; set; }
      
        // The name of the PDF file to be generated
        public string PDFFileName { get; set; }
    }
}

.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="DinkToPdf" Version="1.0.8" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.16" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.16" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" />
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0-preview1-25914-04" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
    <None Update="libwkhtmltox.dll">
     <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
    <None Update="libwkhtmltox.dylib">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="libwkhtmltox.so">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="wwwroot\**\*">
     <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
     <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
  </ItemGroup>
</Project>
c# azure pdf dinktopdf
2个回答
1
投票

好的,所以我似乎已经解决了这个问题。我仍然收到“Qt:无法初始化 OLE(错误 80010106)”错误消息,但我可以连续使用 API 并继续生成 PDF,这样问题就解决了。

如果有人遇到此问题并遇到问题,那么我的解决方案的代码如下:

.csproj 文件:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="DinkToPdf" Version="1.0.8" />
    <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.13" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.13" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" />
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0-preview1-25914-04" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
    <None Update="libwkhtmltox.dll">
     <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </None>
    <None Update="libwkhtmltox.dylib">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="libwkhtmltox.so">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="wwwroot\**\*">
     <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
     <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
  </ItemGroup>
</Project>

功能应用程序文件:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using DinkToPdf;
using IPdfConverter = DinkToPdf.Contracts.IConverter;

[assembly: FunctionsStartup(typeof(pdfCreation.Startup))]

namespace pdfCreation
{
    public class Startup : FunctionsStartup
    {
        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            FunctionsHostBuilderContext context = builder.GetContext();
            builder.ConfigurationBuilder.AddEnvironmentVariables();
        }

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton(typeof(IPdfConverter), new SynchronizedConverter(new PdfTools()));
        }
    }

    public class Html2Pdf
    {
        // Read more about converter on: https://github.com/rdvojmoc/DinkToPdf
        // For our purposes we are going to use SynchronizedConverter
        //IPdfConverter pdfConverter = new SynchronizedConverter(new PdfTools());

        //Note we are using SynchronisedConverter / IPDFConverter via dependency injection
        private readonly IPdfConverter pdfConverter;
        public Html2Pdf(IPdfConverter pdfConverter)
        {
            this.pdfConverter = pdfConverter;
        }

        // A function to convert html content to pdf based on the configuration passed as arguments
        // Arguments:
        // HtmlContent: the html content to be converted
        // Margins: the margis around the content
        // DPI: The dpi is very important when you want to print the pdf.
        // Returns a byte array of the pdf which can be stored as a file
        private byte[] BuildPdf(string HtmlContent, MarginSettings Margins, int? DPI = 180)
        {
            // Call the Convert method of IPdfConverter / SynchronisedConverter "pdfConverter"
            return pdfConverter.Convert(new HtmlToPdfDocument()
            {
                // Set the html content
                Objects =
                {
                    new ObjectSettings
                    {
                        HtmlContent = HtmlContent,
                        WebSettings = { DefaultEncoding = "UTF-8", LoadImages = true }
                    }
                },
                // Set the configurations
                GlobalSettings = new GlobalSettings
                {
                    PaperSize = PaperKind.A4,
                    DPI = DPI,
                    Margins = Margins
                }
            });
        }

        private string ConnectionString(ILogger log, ExecutionContext context)
        {
            var config = new ConfigurationBuilder()
                .SetBasePath(context.FunctionAppDirectory)
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

            var connString = config.GetConnectionString("ConnectionString");            

            if(connString == null){log.LogInformation("Connection String is null");}

            return connString;
        }

        // The first arugment tells that the functions can be triggerd by a POST HTTP request. 
        // The second argument is mainly used for logging information, warnings or errors
        [FunctionName("Html2Pdf")]
        [STAThread]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "POST")] Html2PdfRequest Request, ILogger Log, ExecutionContext Context)
        {
            try{
                Log.LogInformation("C# HTTP trigger function started for HTML2PDF.");
                Log.LogInformation(Request.HtmlContent);

                // PDFByteArray is a byte array of pdf generated from the HtmlContent 
                var PDFByteArray = BuildPdf(Request.HtmlContent, new MarginSettings(2, 2, 2, 2));

                // The connection string of the Storage Account to which our PDF file will be uploaded
                var StorageConnectionString = ConnectionString(Log, Context);

                // Generate an instance of CloudStorageAccount by parsing the connection string
                var StorageAccount = CloudStorageAccount.Parse(StorageConnectionString);

                // Create an instance of CloudBlobClient to connect to our storage account
                CloudBlobClient BlobClient = StorageAccount.CreateCloudBlobClient();

                // Get the instance of CloudBlobContainer which points to a container name "pdf"
                // Replace your own container name
                CloudBlobContainer BlobContainer = BlobClient.GetContainerReference("pdf");
                
                // Get the instance of the CloudBlockBlob to which the PDFByteArray will be uploaded
                CloudBlockBlob Blob = BlobContainer.GetBlockBlobReference(Request.PDFFileName);
                
                Log.LogInformation("Attempting to upload " + Request.PDFFileName);
                // Upload the pdf blob
                await Blob.UploadFromByteArrayAsync(PDFByteArray, 0, PDFByteArray.Length);

                return (ActionResult)new OkObjectResult(Request.PDFFileName + " was successfully created.");
            }
            catch(Exception e)
            {
                Log.LogInformation("Error occurred: " + e);
                return (ActionResult)new OkObjectResult("Error" + e.Message);
            }
            
        }
    }
}

namespace pdfCreation
{
    public class Html2PdfRequest
    {
        // The HTML content that needs to be converted.
        public string HtmlContent { get; set; }
      
        // The name of the PDF file to be generated
        public string PDFFileName { get; set; }
    }
}

我应该强调这里的变化:

  1. 这些版本需要更改为3.1.13
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.13" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.13" />

更新了下面这部分代码:

public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            FunctionsHostBuilderContext context = builder.GetContext();
            builder.ConfigurationBuilder.AddEnvironmentVariables();
        }

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton(typeof(IPdfConverter), new SynchronizedConverter(new PdfTools()));
        }
    }
private readonly IPdfConverter pdfConverter;
public Html2Pdf(IPdfConverter pdfConverter)
{
    this.pdfConverter = pdfConverter;
}

另外,不要忘记在函数应用程序内的配置/应用程序设置下添加连接字符串。

希望对某人有帮助!


0
投票

正如 distantcam 在 github issues #119

中的回答

这是由于线程SynchronizedConverter未处于STA模式。 我测试了它,它解决了出现的错误

Qt: Could not initialize OLE (error 80010106)

您可以按如下方式创建 STA 同步转换器:

public class STASynchronizedConverter : BasicConverter
{
    Thread conversionThread;

    BlockingCollection<Task> conversions = new BlockingCollection<Task>();

    bool kill = false;

private readonly object startLock = new object();

public STASynchronizedConverter(ITools tools) : base(tools)
{
}

public override byte[] Convert(IDocument document)
{
    return Invoke(() => base.Convert(document));
}

public TResult Invoke<TResult>(Func<TResult> @delegate)
{
    StartThread();

    Task<TResult> task = new Task<TResult>(@delegate);

    lock (task)
    {
        //add task to blocking collection
        conversions.Add(task);

        //wait for task to be processed by conversion thread
        Monitor.Wait(task);
    }

    //throw exception that happened during conversion
    if (task.Exception != null)
    {
        throw task.Exception;
    }

    return task.Result;
}

private void StartThread()
{
    lock (startLock)
    {
        if (conversionThread == null)
        {
            conversionThread = new Thread(Run)
            {
                IsBackground = true,
                Name = "wkhtmltopdf worker thread"
            };

            // This is to fix issue https://github.com/rdvojmoc/DinkToPdf/issues/119
            conversionThread.SetApartmentState(ApartmentState.STA);

            kill = false;

            conversionThread.Start();
        }
    }
}

private void StopThread()
{
    lock (startLock)
    {
        if (conversionThread != null)
        {
            kill = true;

            while (conversionThread.ThreadState == ThreadState.Stopped)
            { }

            conversionThread = null;
        }
    }
}

private void Run()
{
    while (!kill)
    {
        //get next conversion taks from blocking collection
        Task task = conversions.Take();

        lock (task)
        {
            //run taks on thread that called RunSynchronously method
            task.RunSynchronously();

            //notify caller thread that task is completed
            Monitor.Pulse(task);
        }
    }
}
}

并在 DI 中使用此转换器:

builder.Services.AddSingleton(typeof(IConverter), new STaSynchronizedConverter(new PdfTools()));
© www.soinside.com 2019 - 2024. All rights reserved.