Не получается генерировать pdf при помощи libwkhtmltox в docker контейнере

Не получается генерировать pdf при помощи libwkhtmltox в docker контейнере. На IIS Express все работает. При создании контейнера библиотеки копируются, в самом контейнере они есть, проверял. Лежат в папке app/PdfGenerationLib/64/libwkhtmltox.

Вот мой docker файл:

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 9090

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /src
COPY ["CoreAPI/CoreAPI.csproj", "CoreAPI/"]
COPY ["DAL/DAL.csproj", "DAL/"]
COPY ["Infrastructure/Infrastructure.csproj", "Infrastructure/"]
COPY ["BL.Services/BL.csproj", "BL.Services/"]
COPY ["DTO/DTL.csproj", "DTO/"]
RUN dotnet restore "CoreAPI/CoreAPI.csproj"
COPY . .
WORKDIR "/src/CoreAPI"
RUN dotnet build "CoreAPI.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "CoreAPI.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CoreAPI.dll"]

Вот файл docker-compose:

version: '3.4'

services:
  coreapi:
    image: image_timetable_api 
    container_name: container_timetable_api
    build:
      context: .
      dockerfile: CoreAPI/Dockerfile
    depends_on: 
      - "ms-sql-server"
      - "web-ui"
    ports:
      - "9090:9090"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:9090
  ms-sql-server: 
    image: mcr.microsoft.com/mssql/server:2019-latest
    container_name: container_timetable_db
    user: root
    ports:
      - 1433:1433
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=Skywoker669
    volumes:
      - ./data:/var/opt/mssql/data
  web-ui:
    image: image_timetable_webui
    build: 
      context: ./WebUI
      dockerfile: Dockerfile
    container_name: container_timetable_webui
    ports:
      - "8080:80"

В браузере получаю такую ошибку.

System.DllNotFoundException: Unable to load shared library '/app\PdfGenerationLib\64\libwkhtmltox' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: /app/PdfGenerationLib/64/libwkhtmltox: cannot open shared object file: No such file or directory
   at System.Runtime.Loader.AssemblyLoadContext.InternalLoadUnmanagedDllFromPath(String unmanagedDllPath)
   at System.Runtime.Loader.AssemblyLoadContext.LoadUnmanagedDllFromPath(String unmanagedDllPath)
   at BL.PdfGenerationLib.CustomAssemblyLoadContext.LoadUnmanagedDll(String unmanagedDllName) in /src/BL.Services/PdfGenerationLib/CustomAssemblyLoadContext.cs:line 14
   at BL.PdfGenerationLib.CustomAssemblyLoadContext.LoadUnmanagedLibrary(String absolutePath) in /src/BL.Services/PdfGenerationLib/CustomAssemblyLoadContext.cs:line 10
   at TimetableCore.BL.Services.PdfService.<>c__DisplayClass2_0.<SaveTimetableToPdfAsync>b__0() in /src/BL.Services/Services/PdfService.cs:line 51
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown ---
   at TimetableCore.BL.Services.PdfService.SaveTimetableToPdfAsync(PdfSaveModelDto pdfSaveModelDto) in /src/BL.Services/Services/PdfService.cs:line 78
   at CoreAPI.Controllers.PdfController.SaveTimetableToPdf(PdfSaveModelDto pdfSaveModelDto) in /src/CoreAPI/Controllers/PdfController.cs:line 25
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Сам метод который генерит pdf:

public async Task<Stream> SaveTimetableToPdfAsync(PdfSaveModelDto pdfSaveModelDto)
        {
            string style = @"<style>
                            .timetable{border-spacing:0px;border-collapse: collapse; text-align:center;}
                            .timetable th {  border: 1px solid black;  padding: 1px;  font-size: 10px;}
                            .timetable th:nth-child(1) {  border: 1px solid black;  padding: 1px;  max-width: 30px;}
                            .timetable th:nth-child(3) {  width: 150px;}
                            .timetable tr,td {  border: 1px solid black;  padding: 2px;}
                            .timetable tr {  height: 1.15rem;}
                            .td-style {  max-width: 6rem;  width: 6rem;  min-width: 6rem;  height: .5rem;  font-size: 8px;}
                            .rotated 
                                    {  
                                        display: block;  
                                        position: relative;  
                                        max-width: 50px;  
                                        -ms-transform: rotate(270deg); /* IE 9 */  
                                        -webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */  
                                        transform: rotate(270deg);
                                    }
                            .timetable-border {  border: 1px solid black;}
                            .timetable-border-right {  border-right: 1px solid black;}
                            .timetable-border-bottom {  border-bottom: 1px solid black;}
                            </style>";

            Task<Stream> task = new Task<Stream>(() =>
            {
                var context = new CustomAssemblyLoadContext();
                var coreApiPuth = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
                var libraryPuth = coreApiPuth + @"\PdfGenerationLib\64\libwkhtmltox";
                context.LoadUnmanagedLibrary(libraryPuth);

                var doc = new HtmlToPdfDocument()
                {
                    GlobalSettings = {
                        ColorMode = ColorMode.Color,
                        Orientation = Orientation.Landscape,
                        PaperSize = PaperKind.A4,
                        DPI = 300,
                    },
                    Objects = {
                        new ObjectSettings() {
                            PagesCount = true,
                            HtmlContent = pdfSaveModelDto.Html + style,
                            WebSettings = { DefaultEncoding = "utf-8" },

                        }
                    }
                };

                byte[] pdf = _convertor.Convert(doc);

                var pdfStream = new MemoryStream(pdf);

                return pdfStream;
            });

            task.Start();

            return await task;
        }

Ответы (1 шт):

Автор решения: Vivus

Всё исправил. Проблема была в зависимостях, в расположении и в точном названии библиотек.

  1. Перенес библиотеки в корневой каталог проекта.

Вот итоговый Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
RUN apt-get update
RUN apt-get install -y apt-utils
RUN apt-get install -y libgdiplus
RUN apt-get install -y libc6-dev
RUN ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll
WORKDIR /app
EXPOSE 9090

FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build
WORKDIR /src

COPY ["CoreAPI/CoreAPI.csproj", "CoreAPI/"]
COPY ["DAL/DAL.csproj", "DAL/"]
COPY ["Infrastructure/Infrastructure.csproj", "Infrastructure/"]
COPY ["BL.Services/BL.csproj", "BL.Services/"]
COPY ["DTO/DTL.csproj", "DTO/"]
RUN dotnet restore "CoreAPI/CoreAPI.csproj"
COPY . .
WORKDIR "/src/CoreAPI"
RUN dotnet build "CoreAPI.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "CoreAPI.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CoreAPI.dll"]

Метод сервиса, который генерит pdf

public async Task<Stream> SaveTimetableToPdfAsync(PdfSaveModelDto pdfSaveModelDto)
{
    string style = @"<style>
                    .timetable{border-spacing:0px;border-collapse: collapse; text-align:center;}
                    .timetable th {  border: 1px solid black;  padding: 1px;  font-size: 10px;}
                    .timetable th:nth-child(1) {  border: 1px solid black;  padding: 1px;  max-width: 30px;}
                    .timetable th:nth-child(3) {  width: 150px;}
                    .timetable tr,td {  border: 1px solid black;  padding: 2px;}
                    .timetable tr {  height: 1.15rem;}
                    .td-style {  max-width: 6rem;  width: 6rem;  min-width: 6rem;  height: .5rem;  font-size: 8px;}
                    .rotated 
                            {  
                                display: block;  
                                position: relative;  
                                max-width: 50px;  
                                -ms-transform: rotate(270deg); /* IE 9 */  
                                -webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */  
                                transform: rotate(270deg);
                            }
                    .timetable-border {  border: 1px solid black;}
                    .timetable-border-right {  border-right: 1px solid black;}
                    .timetable-border-bottom {  border-bottom: 1px solid black;}
                    </style>";

    Task<Stream> task = new Task<Stream>(() =>
    {
        var context = new CustomAssemblyLoadContext();
        var coreApiPuth = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);

        var libraryPuth = coreApiPuth ;

        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            libraryPuth += @"\libwkhtmltox.so";
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            libraryPuth += @"\libwkhtmltox.dylib";
        }
        else
        {
            libraryPuth += @"\libwkhtmltox.dll";
        }
                        
        context.LoadUnmanagedLibrary(libraryPuth);

        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Landscape,
                PaperSize = PaperKind.A4,
                DPI = 300,
            },
            Objects = {
                new ObjectSettings() {
                    PagesCount = true,
                    HtmlContent = pdfSaveModelDto.Html + style,
                    WebSettings = { DefaultEncoding = "utf-8" },

                }
            }
        };

        byte[] pdf = _convertor.Convert(doc);

        var pdfStream = new MemoryStream(pdf);

        return pdfStream;
    });

    task.Start();

    return await task;
}

Надеюсь кому-то поможет.

→ Ссылка