Narzędzia do generowania PDF z wykresami?

0

Witam serdecznie. Jest to kontynuacja tego tematu: https://4programmers.net/Forum/Webmastering/363185-asp_netcore_pobranie_zawartosci_strony_po_wykonaniu_skryptu_js?p=1864615#id1864615

Szukałem w google i nie zauważyłem żadnych darmowych narzędzi, które pozwalają generować wykresy po stronie serwera. Co gorsza, potem muszę te wykresy umieścić w pliku pdf i zwrócić w żądaniu.

Moje pytania:

  1. Czy znacie jakieś darmowe (lub opcjonalnie płatne) za pomocą których będę w stanie wygenerować wykres zbliżony do tego (chart.js) tyle, że po stronie serwera?
    screenshot-20220906104547.png
  2. Czy znacie analogicznie jakieś narzędzia do generowania plików pdf po stronie serwera?
2

Darmowych nie ma. To prawda, ale zainteresuj się Puppeteer Sharp. Działa na zasadzie uruchomienia wirtualnej przeglądarki i pobrania kodu html ze strony, więc powinno w twoim przypadku zadziałać z tym co masz obecnie na stronie. Tutaj przykład użycia z Razorem https://www.kambu.pl/blog/how-to-generate-pdf-from-html-in-net-core-applications/

0

Witam ponownie wszystkich. Wziąłem się za to i mam problem z właściwie uruchomieniem tego. Nie potrafię zrozumieć tego, czy mój serwer musi mieć zainstalowanego chroma (bo chyba pakiet próbuje znaleźć chrome.exe). Podczas uruchamiania tego kodu otrzymuję następujące błędy:

[Authorize]
        [Route("GetReport")]
        public async Task<IActionResult> GetReport(string DateFrom, string DateTo, string Machines)
        {
            var bfOptions = new BrowserFetcherOptions()
            {
                
            };
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                bfOptions.Path = Path.GetTempPath();
            }
            using var browserFetcher = new BrowserFetcher(bfOptions);
            _logger.LogInformation("START ====================");
            await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision);
            var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true
            });
            using (var page = await browser.NewPageAsync())
            {
                await page.GoToAsync(Url.Action("Index", "ReportGenerator", new
                {
                    DateFrom = DateFrom,
                    DateTo = DateTo,
                    Machines = Machines
                }));
                var watchDog = page.WaitForFunctionAsync("generatingCompleted == true");
                await watchDog;
            }
            _logger.LogInformation("END ====================");
            return Ok(JsonConvert.SerializeObject(new { success = true }));
        }

Błąd łapię na tym poleceniu:
await browserFetcher.DownloadAsync(BrowserFetcher.DefaultChromiumRevision);

Błędy:

App.Controllers.ReportGeneratorController: Information: START ====================
„iisexpress.exe” (CoreCLR: clrhost): załadowano „C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.21\System.IO.Compression.ZipFile.dll”. 
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w PuppeteerSharp.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
Zgłoszony wyjątek: „System.IO.FileNotFoundException” w System.Private.CoreLib.dll
„iisexpress.exe” (CoreCLR: clrhost): załadowano „C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.21\System.Diagnostics.StackTrace.dll”. 
„iisexpress.exe” (CoreCLR: clrhost): załadowano „C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.21\System.Reflection.Metadata.dll”. 
„iisexpress.exe” (CoreCLR: clrhost): załadowano „C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.21\System.IO.MemoryMappedFiles.dll”. 
„iisexpress.exe” (CoreCLR: clrhost): załadowano „C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.21\System.IO.Compression.dll”. 
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.

System.IO.FileNotFoundException: Failed to launch browser! path to executable does not exist
File name: 'C:\Users\user\Desktop\ZLECENIA\App\App\.local-chromium\Win64-970485\chrome-win\chrome.exe'
   at PuppeteerSharp.Launcher.GetOrFetchBrowserExecutableAsync(LaunchOptions options) in C:\projects\puppeteer-sharp\lib\PuppeteerSharp\Launcher.cs:line 184
   at PuppeteerSharp.Launcher.LaunchAsync(LaunchOptions options) in C:\projects\puppeteer-sharp\lib\PuppeteerSharp\Launcher.cs:line 50
   at App.Controllers.ReportGeneratorController.GetReport(String DateFrom, String DateTo, String Machines) in C:\Users\user\Desktop\ZLECENIA\App\App\Controllers\ReportGeneratorController.cs:line 146
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.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()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   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 Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Nie wyobrażam sobie takiej sytuacji, że na serwerze instaluję chroma, na pewno coś źle rozumuję :D

0

@markone_dev: Czyli dobrze mi się wydawało. Z tego co wyczytałem z dokumentacji muszę podać ścieżkę do AppData w BrowserFetcherOptions.Path. Też mi nic to nie daje. Teraz pracuję na laptopie i dostać się do plików chrome'a nie będzie problemem. Ale co dalej? np. kiedy będę chciał wypuścić apkę do Azure Web Service?

1
gswidwa1 napisał(a):

@markone_dev: Czyli dobrze mi się wydawało. Z tego co wyczytałem z dokumentacji muszę podać ścieżkę do AppData w BrowserFetcherOptions.Path. Też mi nic to nie daje. Teraz pracuję na laptopie i dostać się do plików chrome'a nie będzie problemem. Ale co dalej? np. kiedy będę chciał wypuścić apkę do Azure Web Service?

To jedyną i najlepszą imo opcją jest zbudowanie obrazu Docker/Podman i deploy kontenera do AppService, jeżeli ma działać ciągle (24/7) albo Container Instances lub Azure Functions, jeżeli ma być odpalany cyklicznie lub on-demand. Dzięki temu że wszystko masz w jednym kontenerze (aplikacja, pupeteer oraz chromium) łatwo go przenosić pomiędzy środowiskami/usługami bez konieczności mozolnej konfiguracji.

0

@markone_dev: patrzyłem na te biblioteki i mają problem z obsługą JavaScriptu. A to właśnie chart.js robi za mnie cała robotę i wykres jest odzwierciedleniem tego czego oczekuje klient. Problem taki, że on zaczyna działać po zdarzeniu document.ready i standardowe backendowe generatory zwracają mi pusty Canvas. Gdyby nie te wykresy to najprostsza backendowa biblioteka by starczyła. Ale weryfikuję jeszcze to wszystko i natknąłem się na to: https://stackoverflow.com/questions/24919302/how-to-make-rotativa-wait-for-javascript-jquery-execution
Według tego co piszą można ustawić czas oczekiwania na zakończenie skryptów, co byłoby idealne w moim przypadku. Co sądzicie o tym? Korzystaliście kiedyś z Rotativa ?

1

No to jeżeli chcesz generować dokładnie te same wykresy w PDF co na frontendzie za pomocą background joba na serwerze, to w twoim przypadku nie ma innej opcji jak użyć Pupeteer do uruchomienia przeglądarki w tle i pobrania danych ze strony. Bo musisz jakoś wywołać ten kod w JS z poziomu joba.

Co do zabawy JSem to nie pomogę, ale ustawianie delay w kodzie na sztywną wartość aby zaczekać aż jakiś proces się wykona to nigdy nie było dobre rozwiązanie. Co jeżeli z jakiegoś powodu proces będzie trwał dłużej niż ustawione n sekund? A webie to nie wyjątek tylko częsta sytuacja.

Zrób obraz Docker/Podman z wymaganymi zależnościami jak napisałem wyżej i będzie git.

1 użytkowników online, w tym zalogowanych: 0, gości: 1