Introducción

En el desarrollo de software, la conversión de documentos HTML a PDF es esencial para crear informes y facturas. Si buscas una solución eficiente para generar PDFs con plantillas HTML en .NET 8, Razor y iText son herramientas clave.

Este artículo te guiará en el uso de HtmlRenderer, una nueva clase en .NET 8, y iText, un paquete NuGet gratuito y de código abierto, para convertir documentos HTML en PDFs personalizados. Aprenderás a crear plantillas HTML dinámicas, insertar datos y convertirlas en PDFs de manera sencilla.

El ejemplo se basa en un proyecto Asp .NET Core WebApi, no Blazor, Razor Pages ni MVC. Esta implementación funciona en cualquier proyecto Asp .NET Core 8 en adelante.

¿Que es HtmlRenderer?

La clase HtmlRenderer es una herramienta fundamental en el desarrollo web con ASP.NET Core, especialmente al generar PDFs desde HTML con Razor y .NET 8. Esta clase está diseñada para ofrecer un mecanismo que permite la representación no interactiva de componentes como marcado HTML. En otras palabras, HtmlRenderer transforma los componentes de la aplicación en HTML estático, facilitando la generación de documentos PDF personalizados.

¿Que es iText7?

iText7 es una biblioteca de código abierto fundamental para .NET que simplifica la manipulación y producción de documentos PDF. Esta herramienta permite a los desarrolladores crear, editar y convertir PDFs de manera programática, ofreciendo un amplio conjunto de funciones para agregar contenido como texto, imágenes, tablas y enlaces. iText7 es compatible con estándares PDF, asegurando la calidad en la generación de documentos en aplicaciones .NET, Java y otros entornos.

Descubre cómo optimizar tu flujo de trabajo automatizando la generación de PDFs dinámicos con iText en C#, sin costosas licencias. ¡Comencemos a mejorar tu proceso de generación de PDFs desde HTML con Razor y .NET 8!

Paso 1: Creación del Proyecto WebApi con .NET 8

Primero, vas a crear un nuevo proyecto de Asp NET Core Web Api con .NET 8. SI no lo tenés, lo podés descargar acá.

Paso 2: Instalación de IText7

Segundo, debes instalar 2 paquetes NuGet en el proyecto. Si aún no lo has hecho, puedes agregarlo mediante NuGet con el siguiente comando:

 
dotnet add package itext7.pdfhtml
 
dotnet add package itext7.bouncy-castle-adapter

Paso 3: Crear clases Helpers RazorRenderer.cs y PdfGenerator.cs

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components;

public class RazorRenderer : IAsyncDisposable
{
    private readonly ServiceProvider _serviceProvider;
    private readonly ILoggerFactory _loggerFactory;
    private readonly HtmlRenderer _htmlRenderer;
    public RazorRenderer()
    {
        // Build all the dependencies for the HtmlRenderer
        var services = new ServiceCollection();
        services.AddLogging();
        _serviceProvider = services.BuildServiceProvider();
        _loggerFactory = _serviceProvider.GetRequiredService<ILoggerFactory>();
        _htmlRenderer = new HtmlRenderer(_serviceProvider, _loggerFactory);
    }

    // Dispose the services and DI container we created
    public async ValueTask DisposeAsync()
    {
        await _htmlRenderer.DisposeAsync();
        _loggerFactory.Dispose();
        await _serviceProvider.DisposeAsync();
    }

    // The other public methods are identical
    public Task<string> RenderComponentAsync<T>() where T : IComponent
        => RenderComponentAsync<T>(ParameterView.Empty);

    public Task<string> RenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent
        => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary));

    private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent
    {
        return _htmlRenderer.Dispatcher.InvokeAsync(async () =>
        {
            var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
            return output.ToHtmlString();
        });
    }
}
using iText.Html2pdf;
using iText.IO.Source;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;

using System.Text;
public class PdfGenerator
{
    public async Task<byte[]> GeneratePdfAsync(string html)
    {

        MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(html));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(byteArrayOutputStream);
        PdfDocument pdfDocument = new PdfDocument(writer);
        pdfDocument.SetDefaultPageSize(PageSize.A4);
        HtmlConverter.ConvertToPdf(stream, pdfDocument);
        pdfDocument.Close();

        return byteArrayOutputStream.ToArray();
    }
}

 

Paso 4: Preparar tu Plantilla HTML Dinámica con Razor y su clase Model

En lugar de tener un archivo HTML estático, prepara un componente de Razor para utilizar las capacidades de C# para agregar datos dinámicos a nuestro documento. Por ejemplo, podrías tener un componente Razor llamado "InvoiceTemplate.razor" como este:

El siguiente es el contenido de nuestro componente Razor. Este mismo contiene:

  • HTML
  • CSS
  • Linq
  • Foreach
  • DateTime.Now

Y podría tener aún más características tanto de C# como de Web para crear documentos más personalizados.

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Factura</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 20px;
        }

        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }

        th {
            background-color: #f2f2f2;
        }

        .total {
            font-weight: bold;
        }
    </style>
</head>
<body>
    <h1>Factura</h1>
    <p>Cliente: @Model.CustomerName</p>

    <table>
        <tr>
            <th>Descripción</th>
            <th>Cantidad</th>
            <th>Precio Unitario</th>
            <th>Total</th>
        </tr>
        @foreach (var item in Model.Items)
        {
            <tr>
                <td>@item.Description</td>
                <td>@item.Quantity</td>
                <td>@item.UnitPrice.ToString("C")</td>
                <td>@item.Total.ToString("C")</td>
            </tr>
        }
      
        <tr class="total">
            <td colspan="3">Total</td>
            <td>$@Model.Items.Sum(x => x.Total)</td>
        </tr>
    </table>
    <p>Fecha de emisión: @DateTime.Today.ToString("dd/MM/yyyy")</p>
</body>
</html>

@code {

    [Parameter]
    public InvoiceTemplateModel Model { get; set; }
}

Las siguientes clases las podemos poner en un nuevo archivo llamado InvoiceTemplateModel.cs que utilizaremos para pasar la información de la factura a nuestro Razor.

public class InvoiceTemplateModel
{
    public string CustomerName { get; set; }
    public IEnumerable<InvoiceItemModel> Items { get; set; }

}

public class InvoiceItemModel
{
    public string Description { get; set; }
    public int Quantity { get; set; }
    public double UnitPrice { get; set; }
    public double Total => Quantity * UnitPrice;
}

 

Paso 5: Escribir el Código para generar el PDF

Para simplificar, aquí les dejo el código de la clase Program.cs que incluye un endpoint MapGet (MinimalApi) con la lógica para guardar el archivo PDF y permitir su descarga desde Swagger. Esto facilitará el proceso de generación y gestión de PDFs desde HTML con Razor.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddScoped<RazorRenderer>();
builder.Services.AddScoped<PdfGenerator>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}


app.MapGet("GenerateInvoicePdf", async (RazorRenderer renderer, PdfGenerator pdfGenerator) =>
{

    // Generamos datos ficticios en un Modelo
    InvoiceTemplateModel model = new InvoiceTemplateModel
    {
        CustomerName = "John Doe",
        Items = [
                        new InvoiceItemModel
                        {
                            Description = "Item 1",
                            Quantity = 2,
                            UnitPrice = 10.0,

                        },
                        new InvoiceItemModel
                        {
                            Description = "Item 2",
                            Quantity = 1,
                            UnitPrice = 35.0,
                        },
                        new InvoiceItemModel
                        {
                            Description = "Item 3",
                            Quantity = 2,
                            UnitPrice = 10.0,
                        },
                        new InvoiceItemModel
                        {
                            Description = "Item 4",
                            Quantity = 3,
                            UnitPrice = 3.0,
                        },
        ]
    };

    // Renderizamos el componente de Razor a un String pasando los datos del Modelo
    var html = await renderer.RenderComponentAsync<InvoiceTemplate>(new Dictionary<string, object?>
            {
                { "Model", model }
            });

    // Ese mismo String, que contiene todo el HTML, lo convertimos en el documento PDF
    var pdfBytes = await pdfGenerator.GeneratePdfAsync(html);

    // Guardamos el archivo PDF de forma local
    System.IO.File.WriteAllBytes("invoice.pdf", pdfBytes);

    // La api descarga el PDF en el navegador
    return Results.File(pdfBytes, "application/pdf", "invoice.pdf");
});


app.Run();

Paso 6: Abrir Swagger y probar

Para finalizar, debemos iniciar nuestro proyecto, acceder a la página de Swagger y ejecutar nuestro endpoint para confirmar que podemos descargar y visualizar el PDF generado a partir de HTML con Razor y .NET 8. Esta prueba nos permitirá verificar el resultado final del proceso de generación de PDFs personalizados.

Conclusión

Con el uso combinado de una plantilla HTML dinámica y iText7, puedes crear PDFs personalizados y llenos de datos de manera eficiente. Esta técnica es valiosa para generar informes, facturas y otros documentos con información variable. Espero que esta guía te resulte útil al implementar esta funcionalidad en tu proyecto de generación de PDF desde HTML con Razor y .NET 8. ¡Aprovecha estas herramientas para mejorar tus procesos de producción de documentos PDF!

Si encuentras útil este trabajo y deseas mostrar tu apoyo, siempre puedes invitarme a un buen café ☕.

Buy Me A Coffee