Test Reportes con Razor y Puppeteer
All checks were successful
Build and Deploy / remote-build-and-deploy (push) Successful in 28m23s

This commit is contained in:
2025-06-19 14:47:43 -03:00
parent 8591945eb4
commit 975a1e6d26
11 changed files with 493 additions and 86 deletions

View File

@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using PuppeteerSharp.Media;
namespace GestionIntegral.Api.Services.Pdf
{
/// <summary>
/// Define las opciones de configuración para la generación de un PDF.
/// </summary>
public class PdfGenerationOptions
{
public PaperFormat? Format { get; set; } = PaperFormat.A4;
public MarginOptions? Margin { get; set; }
public string? HeaderTemplate { get; set; }
public string? FooterTemplate { get; set; }
public bool PrintBackground { get; set; } = true;
public bool Landscape { get; set; } = false;
}
/// <summary>
/// Servicio para generar documentos PDF a partir de plantillas Razor.
/// </summary>
public interface IPdfGeneratorService
{
/// <summary>
/// Genera un archivo PDF a partir de una plantilla Razor y un modelo de datos.
/// </summary>
/// <typeparam name="T">El tipo del modelo de datos.</typeparam>
/// <param name="templatePath">La ruta relativa de la plantilla Razor (ej: "Controllers/Reportes/Templates/MiReporte.cshtml").</param>
/// <param name="model">El objeto con los datos para rellenar la plantilla.</param>
/// <param name="options">Opciones de configuración para el PDF (márgenes, formato, etc.).</param>
/// <returns>Un array de bytes representando el archivo PDF generado.</returns>
Task<byte[]> GeneratePdfFromRazorTemplateAsync<T>(string templatePath, T model, PdfGenerationOptions? options = null);
}
}

View File

@@ -0,0 +1,98 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PuppeteerSharp;
using PuppeteerSharp.Media;
using RazorLight;
using System;
using System.Threading.Tasks;
namespace GestionIntegral.Api.Services.Pdf
{
public class PuppeteerPdfGenerator : IPdfGeneratorService
{
private readonly IRazorLightEngine _razorEngine;
private readonly ILogger<PuppeteerPdfGenerator> _logger;
public PuppeteerPdfGenerator(IHostEnvironment hostEnvironment, ILogger<PuppeteerPdfGenerator> logger)
{
_logger = logger;
var rootPath = hostEnvironment.ContentRootPath;
_razorEngine = new RazorLightEngineBuilder()
.UseFileSystemProject(rootPath)
.UseMemoryCachingProvider()
.Build();
_logger.LogInformation("Verificando caché de Chromium…");
new BrowserFetcher().DownloadAsync().Wait();
_logger.LogInformation("Chromium listo en caché.");
}
public async Task<byte[]> GeneratePdfFromRazorTemplateAsync<T>(string templatePath, T model, PdfGenerationOptions? options = null)
{
if (string.IsNullOrEmpty(templatePath))
throw new ArgumentNullException(nameof(templatePath), "La ruta de la plantilla no puede ser nula o vacía.");
if (model == null)
throw new ArgumentNullException(nameof(model), "El modelo de datos no puede ser nulo.");
options ??= new PdfGenerationOptions();
string htmlContent;
try
{
htmlContent = await _razorEngine.CompileRenderAsync(templatePath, model);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error al compilar la plantilla Razor: {TemplatePath}", templatePath);
throw new InvalidOperationException($"No se pudo renderizar la plantilla Razor '{templatePath}'.", ex);
}
IBrowser? browser = null;
try
{
var launchOptions = new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage" }
};
_logger.LogInformation("Lanzando Chromium headless…");
browser = await Puppeteer.LaunchAsync(launchOptions);
await using var page = await browser.NewPageAsync();
_logger.LogInformation("Estableciendo contenido HTML en la página.");
await page.SetContentAsync(htmlContent, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } });
_logger.LogInformation("Generando PDF…");
var pdfOptions = new PdfOptions
{
Format = options.Format,
HeaderTemplate = options.HeaderTemplate,
FooterTemplate = options.FooterTemplate,
PrintBackground = options.PrintBackground,
Landscape = options.Landscape,
MarginOptions = options.Margin ?? new MarginOptions()
};
var pdfBytes = await page.PdfDataAsync(pdfOptions);
_logger.LogInformation("PDF generado exitosamente ({Length} bytes).", pdfBytes.Length);
return pdfBytes;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error durante la generación del PDF con PuppeteerSharp.");
throw;
}
finally
{
if (browser is not null && !browser.IsClosed)
{
await browser.CloseAsync();
_logger.LogInformation("Navegador cerrado.");
}
}
}
}
}