From 149995e1d0de1d8d017dc2db7ce8d70a2d50c4e9 Mon Sep 17 00:00:00 2001 From: axunonb Date: Sun, 29 Sep 2024 21:51:45 +0200 Subject: [PATCH] Refactor HtmlToPdfConverter and update ReportSheetCache (#192) * Refactor HtmlToPdfConverter and update ReportSheetCache * Move HtmlToPdfConverter to TournamentManager namespace * Add detailed logging and error handling in HtmlToPdfConverter * Add BrowserKind to set the kind of browser to use * Add ability to create PDF from html content or a file * Introduce DisplayMatchDate flag in ReportSheetCache * Modify IsOutdated to check DisplayMatchDate flag * Update Match constructor to include IServiceProvider * Update ReportSheet action to use _serviceProvider for ReportSheetCache * Updated ReportSheet.cshtml to conditionally display match date * Add html {-webkit-print-color-adjust: exact; } CSS to ReportSheet.cshtml (enables colored output for PDF) * Moved PuppeteerSharp package reference to TournamentManager.csproj * Added localization for English and German --- League/Caching/ReportSheetCache.cs | 19 +++++- League/Controllers/Match.cs | 20 ++++-- League/League.csproj | 1 - League/Views/Match/ReportSheet.cshtml | 17 +++-- .../HtmlToPdfConverter/BrowserKind.cs | 21 ++++++ .../HtmlToPdfConverter}/HtmlToPdfConverter.cs | 68 +++++++++++++++---- .../TournamentManager.csproj | 1 + 7 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 TournamentManager/TournamentManager/HtmlToPdfConverter/BrowserKind.cs rename {League/Caching => TournamentManager/TournamentManager/HtmlToPdfConverter}/HtmlToPdfConverter.cs (70%) diff --git a/League/Caching/ReportSheetCache.cs b/League/Caching/ReportSheetCache.cs index b5cb8cc..754ead4 100644 --- a/League/Caching/ReportSheetCache.cs +++ b/League/Caching/ReportSheetCache.cs @@ -20,6 +20,8 @@ public class ReportSheetCache private readonly string _pathToBrowser; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; + // Can eventually be removed when the date is never displayed on report sheets + public static readonly bool DisplayMatchDate = false; /// /// Folder name for the report sheet cache @@ -55,6 +57,11 @@ public ReportSheetCache(ITenantContext tenantContext, IConfiguration configurati /// public bool UsePuppeteer { get; set; } + /// + /// The kind of browser to use for generating the PDF. + /// + public TournamentManager.HtmlToPdfConverter.BrowserKind BrowserKind { get; set; } + private void EnsureCacheFolder() { var cacheFolder = Path.Combine(_webHostEnvironment.WebRootPath, ReportSheetCacheFolder); @@ -81,8 +88,8 @@ public async Task GetOrCreatePdf(MatchReportSheetRow data, string html, { _logger.LogDebug("Create new match report for tenant '{Tenant}', match '{MatchId}'", _tenantContext.Identifier, data.Id); - using var converter = new HtmlToPdfConverter(_pathToBrowser, CreateTempPathFolder(), _loggerFactory) - { UsePuppeteer = UsePuppeteer }; + using var converter = new TournamentManager.HtmlToPdfConverter.HtmlToPdfConverter(_pathToBrowser, CreateTempPathFolder(), _loggerFactory) + { UsePuppeteer = UsePuppeteer, BrowserKind = BrowserKind}; var pdfData = await converter.GeneratePdfData(html, cancellationToken); @@ -104,7 +111,13 @@ public async Task GetOrCreatePdf(MatchReportSheetRow data, string html, private static bool IsOutdated(string cacheFile, DateTime dataModifiedOn) { var fi = new FileInfo(cacheFile); - return !fi.Exists || fi.LastWriteTimeUtc < dataModifiedOn; // Database dates are in UTC + + if (DisplayMatchDate) // Can eventually be removed when the date is never displayed on report sheets + { + return !fi.Exists || fi.LastWriteTimeUtc < dataModifiedOn; // Database dates are in UTC + } + + return !fi.Exists; } private string GetPathToCacheFile(long matchId) diff --git a/League/Controllers/Match.cs b/League/Controllers/Match.cs index f617566..7685ffb 100644 --- a/League/Controllers/Match.cs +++ b/League/Controllers/Match.cs @@ -14,6 +14,7 @@ using TournamentManager.DAL.HelperClasses; using TournamentManager.DAL.TypedViewClasses; using TournamentManager.ExtensionMethods; +using TournamentManager.HtmlToPdfConverter; using TournamentManager.ModelValidators; using TournamentManager.MultiTenancy; @@ -30,6 +31,7 @@ public class Match : AbstractController private readonly IStringLocalizer _localizer; private readonly IAuthorizationService _authorizationService; private readonly Axuno.Tools.DateAndTime.TimeZoneConverter _timeZoneConverter; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly Axuno.BackgroundTask.IBackgroundQueue _queue; private readonly SendEmailTask _sendMailTask; @@ -52,6 +54,7 @@ public Match(ITenantContext tenantContext, IStringLocalizer localizer, _appDb = tenantContext.DbContext.AppDb; _localizer = localizer; _authorizationService = authorizationService; + _serviceProvider = serviceProvider; _logger = logger; // Get required services from the service provider to stay below the 7 parameter limit of SonarCloud @@ -706,17 +709,17 @@ private async Task GetEnterResultViewModel(MatchEntity mat /// if the match has not already been played. /// /// - /// /// /// A match report sheet suitable for a printout, if the match has not already been played. [HttpGet("[action]/{id:long}")] - public async Task ReportSheet(long id, IServiceProvider services, CancellationToken cancellationToken) + public async Task ReportSheet(long id, CancellationToken cancellationToken) { if (!ModelState.IsValid) return BadRequest(ModelState); - var cache = services.GetRequiredService(); - + MatchReportSheetRow? model = null; + var cache = _serviceProvider.GetRequiredService(); cache.UsePuppeteer = false; + cache.BrowserKind = BrowserKind.Chromium; try { @@ -733,8 +736,12 @@ public async Task ReportSheet(long id, IServiceProvider services, $"~/Views/{nameof(Match)}/{ViewNames.Match.ReportSheet}.cshtml", model); var stream = await cache.GetOrCreatePdf(model, html, cancellationToken); - _logger.LogInformation("PDF file returned for tenant '{Tenant}' and match id '{MatchId}'", _tenantContext.Identifier, id); - return new FileStreamResult(stream, "application/pdf"); + + if (stream != Stream.Null) // Returning Stream.Null would create an empty page in the web browser + { + _logger.LogInformation("PDF file returned for tenant '{Tenant}' and match id '{MatchId}'", _tenantContext.Identifier, id); + return new FileStreamResult(stream, "application/pdf"); + } } catch (Exception e) { @@ -743,6 +750,7 @@ public async Task ReportSheet(long id, IServiceProvider services, // Not able to render report sheet as PDF: return HTML Response.Clear(); + _logger.LogError("HTML content instead of PDF returned for tenant '{Tenant}' and match id '{MatchId}'", _tenantContext.Identifier, id); return View(ViewNames.Match.ReportSheet, model); } diff --git a/League/League.csproj b/League/League.csproj index d0d0b5f..14be8c7 100644 --- a/League/League.csproj +++ b/League/League.csproj @@ -85,7 +85,6 @@ Localizations for English and German are included. The library is in operation o true runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/League/Views/Match/ReportSheet.cshtml b/League/Views/Match/ReportSheet.cshtml index 34faf8c..b39ec62 100644 --- a/League/Views/Match/ReportSheet.cshtml +++ b/League/Views/Match/ReportSheet.cshtml @@ -6,6 +6,8 @@ @inject Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer localizer @model TournamentManager.DAL.TypedViewClasses.MatchReportSheetRow @{ + // Can eventually be removed when the date is never displayed on report sheets + var displayMatchDate = League.Caching.ReportSheetCache.DisplayMatchDate; Layout = null; var numberOfSets = Model.BestOf ? Model.NumOfSets * 2 - 1 : Model.NumOfSets; @@ -123,6 +125,9 @@