Не получается генерировать 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
→ Ссылка
Всё исправил. Проблема была в зависимостях, в расположении и в точном названии библиотек.
- Перенес библиотеки в корневой каталог проекта.
Вот итоговый 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;
}
Надеюсь кому-то поможет.