Generar informes en C#; como Crystal Reports (.NET 10)

This article was translated from English: Does it need improvement?
Translated
View the article in English

la generación de informes HTML a PDF en C# .NET con IronPDF sustituye al diseñador propietario .rpt de Crystal Reports por plantillas estándar HTML, CSS y Razor, lo que permite a los desarrolladores .NET crear informes empresariales basados en datos utilizando las habilidades de desarrollo web que ya poseen. Esto incluye compatibilidad total con tablas dinámicas, gráficos basados en JavaScript, formato condicional, procesamiento por lotes de varios documentos e implementación multiplataforma en cualquier entorno que ejecute .NET.

TL;DR: Guía de inicio rápido

Este tutorial cubre la sustitución de Crystal Reports por la generación de informes HTML a PDF en C# .NET, desde plantillas básicas hasta el procesamiento por lotes y la generación programada.

  • A quién va dirigido: Desarrolladores .NET que sustituyan Crystal Reports o que creen nuevos sistemas de generación de informes desde cero.
  • Qué construirás: Tres implementaciones completas de informes (factura de ventas, directorio de empleados, informe de inventario), además de visualizaciones Chart.js, encabezados/pies de página de marca, generación de tabla de contenidos, fusión de subinformes y procesamiento paralelo por lotes.
  • Dónde funciona: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+ y .NET Standard 2.0. Sin dependencias COM exclusivas de Windows.
  • Cuándo utilizar este enfoque: Cuando la falta de compatibilidad de Crystal Reports con .NET Core, el bloqueo de Windows o las complejas licencias se convierten en un cuello de botella.
  • Por qué es importante desde el punto de vista técnico: HTML/CSS se renderiza de forma idéntica en todas las plataformas, se integra con CI/CD y ejecuta JavaScript para gráficos, todo ello sin un diseñador propietario ni tarifas por documento.

Para seguir los ejemplos de código, instale IronPDF a través de NuGet (Install-Package IronPdf). Genere su primer informe con sólo unas pocas líneas de código:

Nuget IconEmpieza a crear PDF con NuGet ahora:

  1. Instalar IronPDF con el gestor de paquetes NuGet

    PM > Install-Package IronPdf

  2. Copie y ejecute este fragmento de código.

    // Install-Package IronPdf
    var pdf = new IronPdf.ChromePdfRenderer()
        .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>")
        .SaveAs("sales-report.pdf");
  3. Despliegue para probar en su entorno real

    Empieza a utilizar IronPDF en tu proyecto hoy mismo con una prueba gratuita
    arrow pointer

Una vez que haya adquirido IronPDF o se haya suscrito a una versión de prueba de 30 días, añada su clave de licencia al inicio de su solicitud.

IronPdf.License.LicenseKey = "KEY";
IronPdf.License.LicenseKey = "KEY";
Imports IronPdf

IronPdf.License.LicenseKey = "KEY"
$vbLabelText   $csharpLabel

Comience a usar IronPDF en su proyecto hoy con una prueba gratuita.

Primer Paso:
green arrow pointer
NuGet Instalar con NuGet

PM >  Install-Package IronPdf

Echa un vistazo a IronPDF en NuGet para una instalación rápida. Con más de 10 millones de descargas, está transformando el desarrollo de PDF con C#. También puede descargar el DLL o el instalador de Windows.

Índice

C# Generador de informes: Plantillas HTML a PDF

La generación de HTML a PDF se basa en una arquitectura lineal. En lugar de un formato de archivo propietario, la aplicación utiliza modelos de datos estándar para rellenar vistas Razor o plantillas HTML. La cadena HTML resultante se pasa a un motor de renderizado como IronPDF, que captura el resultado visual como un documento PDF. Este enfoque desvincula el diseño del informe del entorno de alojamiento, lo que permite ejecutar exactamente el mismo código en cualquier plataforma compatible con .NET.

Este flujo de trabajo refleja el desarrollo web estándar. Los desarrolladores de front-end crean el diseño utilizando CSS y lo previsualizan inmediatamente en cualquier navegador. A continuación, los desarrolladores de backend vinculan los datos mediante C#. Esta separación permite a los equipos utilizar sus procesos actuales de control de versiones, revisión de código y despliegue continuo para los informes, al igual que hacen con el resto de la aplicación.

HTML permite funciones no disponibles en Crystal Reports: gráficos interactivos, tablas con capacidad de respuesta y estilos compartidos para una imagen de marca coherente.

Por qué sustituir Crystal Reports en aplicaciones .NET

La migración desde Crystal Reports no es el resultado de un único problema catastrófico o de un abandono repentino por parte de SAP. Se trata más bien de la acumulación de puntos de fricción que, en conjunto, hacen que la plataforma sea cada vez más difícil de justificar para nuevos proyectos y más complicada de mantener en las soluciones existentes. La identificación de estos puntos débiles aclara por qué muchos equipos buscan alternativas y qué criterios son los más importantes a la hora de evaluar las opciones de sustitución.

No compatible con .NET 8 ni .NET Core

Crystal Reports no es compatible con .NET Core ni con .NET 5-10. SAP ha declarado en foros que no tiene previsto añadir soporte. El SDK utiliza componentes COM, que son incompatibles con .NET multiplataforma. La compatibilidad con .NET requeriría una reescritura completa, algo que SAP se ha negado a hacer.

Como resultado, los equipos que crean nuevas aplicaciones en las versiones actuales de .NET no pueden utilizar Crystal Reports. Las organizaciones estandarizadas en .NET 8 o .NET 10 no pueden integrarla. Para las aplicaciones existentes, la actualización a un tiempo de ejecución .NET moderno requiere sustituir primero el sistema de informes.

Licencias complejas y costes ocultos

Las licencias de Crystal Reports distinguen entre licencias de diseñador, licencias de tiempo de ejecución, implementaciones de servidor y uso integrado. Las normas varían para los servicios de escritorio, web y terminal. La conformidad en una configuración puede necesitar licencias adicionales en otra. Si aparecen lagunas después de la implantación, surgen costes inesperados. Muchas organizaciones deciden que la incertidumbre es peor que migrar a una solución con licencias más claras.

Bloqueo de plataformas Windows

Crystal Reports sólo funciona en Windows con el legado .NET Framework. No es posible implementar estas aplicaciones en contenedores Linux, Azure App Service en Linux, AWS Lambda o Google Cloud Run. A medida que las organizaciones utilizan sistemas en contenedores, agnósticos de plataforma y sin servidor, estas restricciones se vuelven más importantes.

Los equipos de desarrollo que crean microservicios se enfrentan a retos adicionales. Si nueve servicios se ejecutan en contenedores Linux ligeros pero uno necesita Windows para Crystal Reports, la implantación es más complicada. Se necesitan imágenes de contenedor de Windows, alojamiento compatible con Windows y configuraciones de despliegue independientes. El servicio de informes se convierte en una excepción, bloqueando la estandarización.

Cómo crear un generador de informes C&num en .NET 10

Empezar a utilizar IronPDF es muy sencillo. Instale la biblioteca a través de NuGet como cualquier otra dependencia de .NET. No es necesario descargar software adicional ni instalar un programa de ejecución independiente para los servidores de producción.

Elige un enfoque de plantilla: Razor, HTML o híbrido

IronPDF admite tres enfoques distintos para crear plantillas de informes. Cada enfoque ofrece ventajas específicas en función de la composición del equipo, los requisitos del proyecto y las consideraciones de mantenimiento a largo plazo.

Razor Views ofrece la experiencia de desarrollo más rica para los equipos que ya trabajan en el ecosistema .NET. Se dispone de modelos fuertemente tipados con soporte completo de IntelliSense en Visual Studio y VS Code, comprobación en tiempo de compilación y toda la potencia de C# para bucles, condicionales, manejo de nulos y formato de cadenas. La sintaxis de Razor es familiar para quienes han creado aplicaciones ASP.NET Core, lo que elimina la curva de aprendizaje asociada a los motores de plantillas de otros ecosistemas. Las plantillas residen en el proyecto junto con otros archivos fuente, participan en operaciones de refactorización y se compilan como parte del proceso normal de compilación.

Plain HTML with String Interpolation funciona bien para informes más sencillos o equipos que prefieren mantener las plantillas totalmente separadas del código .NET. Las plantillas HTML pueden almacenarse como recursos incrustados compilados en el ensamblaje, archivos externos desplegados junto a la aplicación o incluso recuperarse de una base de datos o un sistema de gestión de contenidos en tiempo de ejecución. La vinculación básica de datos utiliza string.Replace() para valores individuales o una biblioteca de plantillas ligera como Scriban o Fluid para escenarios más avanzados. Este enfoque maximiza la portabilidad, permitiendo a los diseñadores editar las plantillas sin necesidad de tener instalada ninguna herramienta .NET, utilizando únicamente un editor de texto y un navegador web para la vista previa.

los enfoques híbridos combinan ambas técnicas para situaciones que requieren flexibilidad. Por ejemplo, una vista Razor puede renderizarse para generar la estructura HTML principal y, a continuación, posprocesarse con sustituciones de cadenas adicionales para elementos dinámicos que no encajan limpiamente en el modelo de vista. Como alternativa, se puede cargar una plantilla HTML diseñada por una persona que no sea desarrollador y utilizar vistas parciales de Razor para renderizar solo las secciones complejas basadas en datos antes de combinarlo todo. La conversión de HTML a PDF es independiente de la fuente HTML, lo que permite combinar enfoques en función de las necesidades de cada informe.

Dadas estas opciones, este tutorial se centra principalmente en las vistas Razor, ya que ofrecen el mejor equilibrio entre seguridad de tipos, facilidad de mantenimiento y riqueza de funciones para los escenarios típicos de elaboración de informes empresariales. Las habilidades se transfieren directamente si los requisitos futuros incluyen trabajar con plantillas HTML sin formato, ya que ambos métodos producen cadenas HTML.

Construya un informe PDF basado en datos en C#;

Esta sección muestra la creación completa de un informe de factura de venta, de principio a fin. El ejemplo cubre el patrón esencial utilizado para todos los informes: definir un modelo que estructure los datos, crear una plantilla Razor que transforme los datos en HTML formateado, renderizar esa plantilla en una cadena HTML y convertir el HTML en un documento PDF listo para visualizar, enviar por correo electrónico o archivar.

Crear la plantilla de informe HTML/CSS

El primer paso consiste en definir el modelo de datos. Una factura real requiere información sobre el cliente, partidas con descripciones y precios, totales calculados, gestión de impuestos y elementos de marca de la empresa. Las clases modelo deben estructurarse para reflejar estas agrupaciones:

// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
    public string InvoiceNumber { get; set; } = string.Empty;
    public DateTime InvoiceDate { get; set; }
    public DateTime DueDate { get; set; }

    public CompanyInfo Company { get; set; } = new();
    public CustomerInfo Customer { get; set; } = new();
    public List<LineItem> Items { get; set; } = new();

    // Computed totals - business logic stays in the model
    public decimal Subtotal => Items.Sum(x => x.Total);
    public decimal TaxRate { get; set; } = 0.08m;
    public decimal TaxAmount => Subtotal * TaxRate;
    public decimal GrandTotal => Subtotal + TaxAmount;
}

// Company details for invoice header
public class CompanyInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string LogoPath { get; set; } = string.Empty;
}

// Customer billing information
public class CustomerInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

// Individual invoice line item
public class LineItem
{
    public string Description { get; set; } = string.Empty;
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Total => Quantity * UnitPrice;
}
// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
    public string InvoiceNumber { get; set; } = string.Empty;
    public DateTime InvoiceDate { get; set; }
    public DateTime DueDate { get; set; }

    public CompanyInfo Company { get; set; } = new();
    public CustomerInfo Customer { get; set; } = new();
    public List<LineItem> Items { get; set; } = new();

    // Computed totals - business logic stays in the model
    public decimal Subtotal => Items.Sum(x => x.Total);
    public decimal TaxRate { get; set; } = 0.08m;
    public decimal TaxAmount => Subtotal * TaxRate;
    public decimal GrandTotal => Subtotal + TaxAmount;
}

// Company details for invoice header
public class CompanyInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string LogoPath { get; set; } = string.Empty;
}

// Customer billing information
public class CustomerInfo
{
    public string Name { get; set; } = string.Empty;
    public string Address { get; set; } = string.Empty;
    public string City { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

// Individual invoice line item
public class LineItem
{
    public string Description { get; set; } = string.Empty;
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Total => Quantity * UnitPrice;
}
Imports System
Imports System.Collections.Generic
Imports System.Linq

' Invoice data model with customer, company, and line item details
Public Class InvoiceModel
    Public Property InvoiceNumber As String = String.Empty
    Public Property InvoiceDate As DateTime
    Public Property DueDate As DateTime

    Public Property Company As New CompanyInfo()
    Public Property Customer As New CustomerInfo()
    Public Property Items As New List(Of LineItem)()

    ' Computed totals - business logic stays in the model
    Public ReadOnly Property Subtotal As Decimal
        Get
            Return Items.Sum(Function(x) x.Total)
        End Get
    End Property

    Public Property TaxRate As Decimal = 0.08D

    Public ReadOnly Property TaxAmount As Decimal
        Get
            Return Subtotal * TaxRate
        End Get
    End Property

    Public ReadOnly Property GrandTotal As Decimal
        Get
            Return Subtotal + TaxAmount
        End Get
    End Property
End Class

' Company details for invoice header
Public Class CompanyInfo
    Public Property Name As String = String.Empty
    Public Property Address As String = String.Empty
    Public Property City As String = String.Empty
    Public Property Phone As String = String.Empty
    Public Property Email As String = String.Empty
    Public Property LogoPath As String = String.Empty
End Class

' Customer billing information
Public Class CustomerInfo
    Public Property Name As String = String.Empty
    Public Property Address As String = String.Empty
    Public Property City As String = String.Empty
    Public Property Email As String = String.Empty
End Class

' Individual invoice line item
Public Class LineItem
    Public Property Description As String = String.Empty
    Public Property Quantity As Integer
    Public Property UnitPrice As Decimal

    Public ReadOnly Property Total As Decimal
        Get
            Return Quantity * UnitPrice
        End Get
    End Property
End Class
$vbLabelText   $csharpLabel

Las propiedades calculadas para Subtotal, TaxAmount y GrandTotal se incluyen en el modelo. Estos cálculos pertenecen al modelo y no a la plantilla, manteniendo las vistas Razor centradas en la presentación mientras que el modelo se encarga de la lógica empresarial. Esta separación facilita las pruebas unitarias, ya que permite verificar los cálculos sin renderizar ningún HTML.

Ahora crea la vista Razor que transforma este modelo en una factura con formato profesional. Guárdala como InvoiceTemplate.cshtml en tu carpeta Views:

@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        /* Reset and base styles */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
        .invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }

        /* Header with company info and invoice title */
        .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
        .company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
        .company-info p { color: #7f8c8d; font-size: 11px; }
        .invoice-title { text-align: right; }
        .invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
        .invoice-title p { font-size: 12px; color: #7f8c8d; }

        /* Address blocks */
        .addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
        .address-block { width: 45%; }
        .address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
        .address-block p { font-size: 12px; }

        /* Line items table */
        .items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
        .items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
        .items-table th:last-child, .items-table td:last-child { text-align: right; }
        .items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
        .items-table tr:nth-child(even) { background-color: #f9f9f9; }

        /* Totals section */
        .totals { float: right; width: 300px; }
        .totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
        .totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }

        /* Footer */
        .footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
    </style>
</head>
<body>
    <div class="invoice-container">

        <div class="header">
            <div class="company-info">
                <h1>@Model.Company.Name</h1>
                <p>@Model.Company.Address</p>
                <p>@Model.Company.City</p>
                <p>@Model.Company.Phone | @Model.Company.Email</p>
            </div>
            <div class="invoice-title">
                <h2>INVOICE</h2>
                <p>Invoice #: @Model.InvoiceNumber</p>
                <p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
                <p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
            </div>
        </div>

        <div class="addresses">
            <div class="address-block">
                <h3>Bill To</h3>
                <p>@Model.Customer.Name</p>
                <p>@Model.Customer.Address</p>
                <p>@Model.Customer.City</p>
                <p>@Model.Customer.Email</p>
            </div>
        </div>

        <table class="items-table">
            <thead>
                <tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
            </thead>
            <tbody>
                @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>
                }
            </tbody>
        </table>

        <div class="totals">
            <div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
            <div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
            <div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
        </div>

        <div class="footer">
            <p>Thank you for your business!</p>
            <p>Payment is due within 30 days. Please include invoice number with your payment.</p>
        </div>
    </div>
</body>
</html>
@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        /* Reset and base styles */
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
        .invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }

        /* Header with company info and invoice title */
        .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
        .company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
        .company-info p { color: #7f8c8d; font-size: 11px; }
        .invoice-title { text-align: right; }
        .invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
        .invoice-title p { font-size: 12px; color: #7f8c8d; }

        /* Address blocks */
        .addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
        .address-block { width: 45%; }
        .address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
        .address-block p { font-size: 12px; }

        /* Line items table */
        .items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
        .items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
        .items-table th:last-child, .items-table td:last-child { text-align: right; }
        .items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
        .items-table tr:nth-child(even) { background-color: #f9f9f9; }

        /* Totals section */
        .totals { float: right; width: 300px; }
        .totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
        .totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }

        /* Footer */
        .footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
    </style>
</head>
<body>
    <div class="invoice-container">

        <div class="header">
            <div class="company-info">
                <h1>@Model.Company.Name</h1>
                <p>@Model.Company.Address</p>
                <p>@Model.Company.City</p>
                <p>@Model.Company.Phone | @Model.Company.Email</p>
            </div>
            <div class="invoice-title">
                <h2>INVOICE</h2>
                <p>Invoice #: @Model.InvoiceNumber</p>
                <p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
                <p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
            </div>
        </div>

        <div class="addresses">
            <div class="address-block">
                <h3>Bill To</h3>
                <p>@Model.Customer.Name</p>
                <p>@Model.Customer.Address</p>
                <p>@Model.Customer.City</p>
                <p>@Model.Customer.Email</p>
            </div>
        </div>

        <table class="items-table">
            <thead>
                <tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
            </thead>
            <tbody>
                @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>
                }
            </tbody>
        </table>

        <div class="totals">
            <div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
            <div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
            <div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
        </div>

        <div class="footer">
            <p>Thank you for your business!</p>
            <p>Payment is due within 30 days. Please include invoice number with your payment.</p>
        </div>
    </div>
</body>
</html>
HTML

El CSS incrustado en esta plantilla se encarga de todo el estilo visual, como los colores, las fuentes, el espaciado y el formato de las tablas. IronPDF también es compatible con funciones CSS modernas como flexbox, diseños de cuadrícula y variables CSS. El PDF renderizado coincide exactamente con la vista previa de impresión de Chrome, lo que facilita la depuración: si algo no se ve bien en el PDF, abra el HTML en un navegador y utilice las herramientas de desarrollo para inspeccionar y ajustar los estilos.

Enlazar datos a la plantilla

Con el modelo y la plantilla en su lugar, la representación del PDF requiere conectarlos a través de ChromePdfRenderer de IronPDF. El paso clave es convertir la vista Razor en una cadena HTML y, a continuación, pasar esa cadena al renderizador:

using IronPdf;

// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public InvoiceReportService(
        IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    // Generate PDF from invoice model
    public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
    {
        // Render Razor view to HTML string
        string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);

        // Configure PDF renderer with margins and paper size
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

        // Convert HTML to PDF and return bytes
        var pdfDocument = renderer.RenderHtmlAsPdf(html);
        return pdfDocument.BinaryData;
    }

    // Helper method to render a Razor view to string
    private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using var stringWriter = new StringWriter();
        var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

        if (!viewResult.Success)
            throw new InvalidOperationException($"View '{viewName}' not found.");

        var viewDictionary = new ViewDataDictionary<TModel>(
            new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };

        var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
            new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
            stringWriter, new HtmlHelperOptions());

        await viewResult.View.RenderAsync(viewContext);
        return stringWriter.ToString();
    }
}
using IronPdf;

// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
    private readonly IRazorViewEngine _razorViewEngine;
    private readonly ITempDataProvider _tempDataProvider;
    private readonly IServiceProvider _serviceProvider;

    public InvoiceReportService(
        IRazorViewEngine razorViewEngine,
        ITempDataProvider tempDataProvider,
        IServiceProvider serviceProvider)
    {
        _razorViewEngine = razorViewEngine;
        _tempDataProvider = tempDataProvider;
        _serviceProvider = serviceProvider;
    }

    // Generate PDF from invoice model
    public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
    {
        // Render Razor view to HTML string
        string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);

        // Configure PDF renderer with margins and paper size
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

        // Convert HTML to PDF and return bytes
        var pdfDocument = renderer.RenderHtmlAsPdf(html);
        return pdfDocument.BinaryData;
    }

    // Helper method to render a Razor view to string
    private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
    {
        var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
        var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

        using var stringWriter = new StringWriter();
        var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

        if (!viewResult.Success)
            throw new InvalidOperationException($"View '{viewName}' not found.");

        var viewDictionary = new ViewDataDictionary<TModel>(
            new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };

        var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
            new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
            stringWriter, new HtmlHelperOptions());

        await viewResult.View.RenderAsync(viewContext);
        return stringWriter.ToString();
    }
}
Imports IronPdf
Imports Microsoft.AspNetCore.Mvc.Razor
Imports Microsoft.AspNetCore.Mvc.ViewFeatures
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.Mvc
Imports Microsoft.AspNetCore.Routing
Imports System.IO
Imports System.Threading.Tasks

' Service class for generating invoice PDFs from Razor views
Public Class InvoiceReportService
    Private ReadOnly _razorViewEngine As IRazorViewEngine
    Private ReadOnly _tempDataProvider As ITempDataProvider
    Private ReadOnly _serviceProvider As IServiceProvider

    Public Sub New(razorViewEngine As IRazorViewEngine, tempDataProvider As ITempDataProvider, serviceProvider As IServiceProvider)
        _razorViewEngine = razorViewEngine
        _tempDataProvider = tempDataProvider
        _serviceProvider = serviceProvider
    End Sub

    ' Generate PDF from invoice model
    Public Async Function GenerateInvoicePdfAsync(invoice As InvoiceModel) As Task(Of Byte())
        ' Render Razor view to HTML string
        Dim html As String = Await RenderViewToStringAsync("InvoiceTemplate", invoice)

        ' Configure PDF renderer with margins and paper size
        Dim renderer As New ChromePdfRenderer()
        renderer.RenderingOptions.MarginTop = 10
        renderer.RenderingOptions.MarginBottom = 10
        renderer.RenderingOptions.MarginLeft = 10
        renderer.RenderingOptions.MarginRight = 10
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter

        ' Convert HTML to PDF and return bytes
        Dim pdfDocument = renderer.RenderHtmlAsPdf(html)
        Return pdfDocument.BinaryData
    End Function

    ' Helper method to render a Razor view to string
    Private Async Function RenderViewToStringAsync(Of TModel)(viewName As String, model As TModel) As Task(Of String)
        Dim httpContext As New DefaultHttpContext With {.RequestServices = _serviceProvider}
        Dim actionContext As New ActionContext(httpContext, New RouteData(), New ActionDescriptor())

        Using stringWriter As New StringWriter()
            Dim viewResult = _razorViewEngine.FindView(actionContext, viewName, False)

            If Not viewResult.Success Then
                Throw New InvalidOperationException($"View '{viewName}' not found.")
            End If

            Dim viewDictionary As New ViewDataDictionary(Of TModel)(
                New EmptyModelMetadataProvider(), New ModelStateDictionary()) With {.Model = model}

            Dim viewContext As New ViewContext(actionContext, viewResult.View, viewDictionary,
                New TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                stringWriter, New HtmlHelperOptions())

            Await viewResult.View.RenderAsync(viewContext)
            Return stringWriter.ToString()
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Para situaciones más sencillas, cuando no se necesita la configuración completa de ASP.NET Core MVC, como en una aplicación de consola o un servicio en segundo plano, basta con utilizar cadenas HTML con interpolación y StringBuilder para las partes dinámicas.

Muestra de resultado

Añadir encabezados, pies de página y números de página

Los informes Professional suelen incluir encabezados y pies de página coherentes en todas las páginas, con la marca de la empresa, los títulos de los documentos, las fechas de generación y los números de página. IronPDF ofrece dos enfoques para implementar estos elementos: cabeceras basadas en texto para contenidos sencillos que requieren un formato mínimo, y cabeceras HTML para un control total del estilo con logotipos y diseños personalizados.

Las cabeceras basadas en texto funcionan bien para la información básica y se renderizan más rápido, ya que no requieren un análisis sintáctico adicional de HTML:

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/text-headers-footers.cs
using IronPdf;
using IronSoftware.Drawing;

// Configure text-based headers and footers
var renderer = new ChromePdfRenderer();

// Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1;

// Add centered header with divider line
renderer.RenderingOptions.TextHeader = new TextHeaderFooter
{
    CenterText = "CONFIDENTIAL - Internal Use Only",
    DrawDividerLine = true,
    Font = FontTypes.Arial,
    FontSize = 10
};

// Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = new TextHeaderFooter
{
    LeftText = "{date} {time}",
    RightText = "Page {page} of {total-pages}",
    DrawDividerLine = true,
    Font = FontTypes.Arial,
    FontSize = 9
};

// Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25;
renderer.RenderingOptions.MarginBottom = 20;
Imports IronPdf
Imports IronSoftware.Drawing

' Configure text-based headers and footers
Dim renderer As New ChromePdfRenderer()

' Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1

' Add centered header with divider line
renderer.RenderingOptions.TextHeader = New TextHeaderFooter With {
    .CenterText = "CONFIDENTIAL - Internal Use Only",
    .DrawDividerLine = True,
    .Font = FontTypes.Arial,
    .FontSize = 10
}

' Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = New TextHeaderFooter With {
    .LeftText = "{date} {time}",
    .RightText = "Page {page} of {total-pages}",
    .DrawDividerLine = True,
    .Font = FontTypes.Arial,
    .FontSize = 9
}

' Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25
renderer.RenderingOptions.MarginBottom = 20
$vbLabelText   $csharpLabel

Los campos de fusión disponibles incluyen {page} para el número de página actual, {total-pages} para el número total de páginas del documento, {date} y {time} para las marcas de tiempo de generación, {url} para la URL de origen si se renderiza desde una página web, y {html-title} y {pdf-title} para los títulos de los documentos. Estos marcadores de posición se sustituyen automáticamente durante la renderización.

Para cabeceras con logotipos, fuentes personalizadas o diseños complejos de varias columnas, utilice cabeceras HTML que admitan estilos CSS completos:

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/html-headers-footers.cs
using IronPdf;
using System;

var renderer = new ChromePdfRenderer();

// Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
    MaxHeight = 30,
    HtmlFragment = @"
        <div style='display: flex; justify-content: space-between; align-items: center;
                    width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
            <img src='logo.png' style='height: 25px;'>
            <span>Company Name Inc.</span>
            <span>Invoice Report</span>
        </div>",
    BaseUrl = new Uri(@"C:\assets\images\").AbsoluteUri
};

// Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    MaxHeight = 20,
    HtmlFragment = @"
        <div style='text-align: center; font-size: 9px; color: #999;
                    border-top: 1px solid #ddd; padding-top: 5px;'>
            Page {page} of {total-pages} | Generated on {date}
        </div>",
    DrawDividerLine = false
};
Imports IronPdf
Imports System

Dim renderer As New ChromePdfRenderer()

' Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
    .MaxHeight = 30,
    .HtmlFragment = "
        <div style='display: flex; justify-content: space-between; align-items: center;
                    width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
            <img src='logo.png' style='height: 25px;'>
            <span>Company Name Inc.</span>
            <span>Invoice Report</span>
        </div>",
    .BaseUrl = New Uri("C:\assets\images\").AbsoluteUri
}

' Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
    .MaxHeight = 20,
    .HtmlFragment = "
        <div style='text-align: center; font-size: 9px; color: #999;
                    border-top: 1px solid #ddd; padding-top: 5px;'>
            Page {page} of {total-pages} | Generated on {date}
        </div>",
    .DrawDividerLine = False
}
$vbLabelText   $csharpLabel

Muestra de resultado

Crear tablas dinámicas y secciones repetitivas

A menudo, los informes deben mostrar colecciones de datos que abarcan varias páginas. Las construcciones de bucle de Razor manejan esto de forma natural iterando sobre colecciones y generando filas de tabla o elementos de tarjeta para cada elemento.

Este es un ejemplo completo de Directorio de Empleados que muestra la presentación de datos agrupados con secciones departamentales:

// Employee directory data models
public class EmployeeDirectoryModel
{
    public List<Department> Departments { get; set; } = new();
    public DateTime GeneratedDate { get; set; } = DateTime.Now;
}

// Department grouping with manager info
public class Department
{
    public string Name { get; set; } = string.Empty;
    public string ManagerName { get; set; } = string.Empty;
    public List<Employee> Employees { get; set; } = new();
}

// Individual employee details
public class Employee
{
    public string Name { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string PhotoUrl { get; set; } = string.Empty;
    public DateTime HireDate { get; set; }
}
// Employee directory data models
public class EmployeeDirectoryModel
{
    public List<Department> Departments { get; set; } = new();
    public DateTime GeneratedDate { get; set; } = DateTime.Now;
}

// Department grouping with manager info
public class Department
{
    public string Name { get; set; } = string.Empty;
    public string ManagerName { get; set; } = string.Empty;
    public List<Employee> Employees { get; set; } = new();
}

// Individual employee details
public class Employee
{
    public string Name { get; set; } = string.Empty;
    public string Title { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public string Phone { get; set; } = string.Empty;
    public string PhotoUrl { get; set; } = string.Empty;
    public DateTime HireDate { get; set; }
}
' Employee directory data models
Public Class EmployeeDirectoryModel
    Public Property Departments As List(Of Department) = New List(Of Department)()
    Public Property GeneratedDate As DateTime = DateTime.Now
End Class

' Department grouping with manager info
Public Class Department
    Public Property Name As String = String.Empty
    Public Property ManagerName As String = String.Empty
    Public Property Employees As List(Of Employee) = New List(Of Employee)()
End Class

' Individual employee details
Public Class Employee
    Public Property Name As String = String.Empty
    Public Property Title As String = String.Empty
    Public Property Email As String = String.Empty
    Public Property Phone As String = String.Empty
    Public Property PhotoUrl As String = String.Empty
    Public Property HireDate As DateTime
End Class
$vbLabelText   $csharpLabel

La propiedad CSS page-break-inside: avoid de la clase department indica al renderizador de PDF que mantenga las secciones de departamento juntas en una sola página siempre que sea posible. Si el contenido de un departamento provocara un salto de página a mitad de sección, el renderizador traslada toda la sección a la página siguiente. El selector .department:not(:first-child) con page-break-before: always obliga a que cada departamento después del primero empiece en una nueva página, creando una separación de secciones limpia en todo el directorio.

Muestra de resultado

Generación avanzada de informes C# con IronPDF

Los informes empresariales suelen requerir capacidades que van más allá de las tablas estáticas y el texto. Los gráficos visualizan tendencias que serían tediosas de comprender en forma tabular. El formato condicional llama la atención sobre los elementos que requieren acción. Los subinformes combinan datos de múltiples fuentes en documentos coherentes. Esta sección cubre la implementación de cada una de estas características utilizando el motor de renderizado Chromium de IronPDF.

Añadir tablas y gráficos a informes PDF

Dado que JavaScript se ejecuta durante la renderización, puede utilizar cualquier biblioteca de gráficos del lado del cliente para generar visualizaciones directamente en sus informes. El gráfico se rasteriza como parte de la página y aparece en el PDF final exactamente igual que en la pantalla. Chart.js ofrece un excelente equilibrio entre simplicidad, capacidad y documentación para la mayoría de las necesidades de elaboración de informes.

Incluye Chart.js desde una CDN y configura tu gráfico con datos serializados desde tu modelo de C#:

@model SalesReportModel

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<canvas id="salesChart"></canvas>

<script>
    // Initialize bar chart with data from C# model
    const ctx = document.getElementById('salesChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: {
            // Serialize model data to JavaScript arrays
            labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
            datasets: [{
                label: 'Monthly Sales',
                data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
                backgroundColor: 'rgba(52, 152, 219, 0.7)'
            }]
        }
    });
</script>
@model SalesReportModel

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<canvas id="salesChart"></canvas>

<script>
    // Initialize bar chart with data from C# model
    const ctx = document.getElementById('salesChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: {
            // Serialize model data to JavaScript arrays
            labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
            datasets: [{
                label: 'Monthly Sales',
                data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
                backgroundColor: 'rgba(52, 152, 219, 0.7)'
            }]
        }
    });
</script>
HTML

Al renderizar páginas que incluyan contenido generado por JavaScript, configure el renderizador para que espere a que los scripts terminen de ejecutarse antes de capturar la página:

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/javascript-wait-rendering.cs
using IronPdf;

string html = "<h1>Report</h1>";

// Configure renderer to wait for JavaScript execution
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to complete
var pdf = renderer.RenderHtmlAsPdf(html);
Imports IronPdf

Dim html As String = "<h1>Report</h1>"

' Configure renderer to wait for JavaScript execution
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.WaitFor.JavaScript(500) ' Wait 500ms for JS to complete
Dim pdf = renderer.RenderHtmlAsPdf(html)
$vbLabelText   $csharpLabel

Muestra de resultado

Aplicar formato condicional y lógica empresarial

Los informes de inventario se benefician de indicadores visuales que llaman inmediatamente la atención sobre los elementos que requieren acción. En lugar de obligar a los usuarios a escanear cientos de filas en busca de problemas, el formato condicional hace que las excepciones sean visualmente obvias. Utiliza las expresiones en línea de Razor para aplicar clases CSS basadas en valores de datos:


@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
    // Apply CSS class based on stock level thresholds
    var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
                   item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";

    <tr class="@rowClass">
        <td>@item.SKU</td>
        <td>@item.ProductName</td>
        <td class="text-right">

            <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
                @item.Quantity
            </span>
        </td>
    </tr>
}

@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
    // Apply CSS class based on stock level thresholds
    var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
                   item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";

    <tr class="@rowClass">
        <td>@item.SKU</td>
        <td>@item.ProductName</td>
        <td class="text-right">

            <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
                @item.Quantity
            </span>
        </td>
    </tr>
}
HTML

Muestra de resultado

Crear subinformes y saltos de sección

Para combinar informes generados de forma independiente en un solo documento, utilice la función de fusión de IronPDF:

using IronPdf;

// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
    var renderer = new ChromePdfRenderer();

    // Render each report section separately
    var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
    var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));

    // Merge PDFs into one document
    var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
    return combined.BinaryData;
}
using IronPdf;

// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
    var renderer = new ChromePdfRenderer();

    // Render each report section separately
    var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
    var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));

    // Merge PDFs into one document
    var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
    return combined.BinaryData;
}
Imports IronPdf

' Combine multiple reports into a single PDF document
Public Function GenerateCombinedReport(sales As SalesReportModel, inventory As InventoryReportModel) As Byte()
    Dim renderer As New ChromePdfRenderer()

    ' Render each report section separately
    Dim salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales))
    Dim inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory))

    ' Merge PDFs into one document
    Dim combined = PdfDocument.Merge(salesPdf, inventoryPdf)
    Return combined.BinaryData
End Function
$vbLabelText   $csharpLabel

Muestra de resultado

Generar un índice

IronPDF puede generar automáticamente un índice basado en los elementos de encabezamiento de su HTML:

:path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/table-of-contents.cs
using IronPdf;

// Generate PDF with automatic table of contents
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;
var pdf = renderer.RenderHtmlFileAsPdf("report.html");
Imports IronPdf

' Generate PDF with automatic table of contents
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers
Dim pdf = renderer.RenderHtmlFileAsPdf("report.html")
$vbLabelText   $csharpLabel

Migrar de Crystal Reports a IronPDF

La migración de un sistema de informes establecido requiere una planificación cuidadosa para minimizar las interrupciones y aprovechar al mismo tiempo la oportunidad de modernizar y simplificar. Avanzará más rápido si comprende cómo se adaptan los conceptos de Crystal Reports al enfoque basado en HTML, en lugar de intentar replicar literalmente todas las funciones o conservar todas las peculiaridades de los informes originales.

Mapear conceptos de Crystal Reports a IronPDF

Comprender la cartografía conceptual le ayudará a traducir sistemáticamente los informes existentes:

Crystal Reports Equivalente de IronPDF
Secciones del informe Divs HTML con propiedades CSS de salto de página
Campos de parámetros Propiedades del modelo pasadas a las vistas Razor
Campos de fórmula Propiedades calculadas en C# en clases modelo
Totales Agregaciones LINQ
Subinformes Vistas parciales o documentos PDF fusionados
Agrupación/clasificación Operaciones LINQ antes de pasar datos a la plantilla
Informes cruzados Tablas HTML con bucles anidados
Formato condicional Bloques @if de Razor con clases CSS

La mejor estrategia para convertir plantillas .rpt

No intente analizar archivos .rpt mediante programación. En su lugar, trate los resultados PDF existentes como especificaciones visuales y reconstruya la lógica utilizando una estrategia sistemática de cuatro pasos:

  1. Inventario: Catalogar todos los archivos .rpt con su propósito, fuentes de datos y frecuencia de uso. Elimine los informes obsoletos para reducir el alcance de la migración.

  2. Priorizar: Migre primero los informes de alta frecuencia. Diríjase a los informes con diseños sencillos o problemas de mantenimiento persistentes.

  3. Referencia: Exportar informes Crystal Reports existentes como archivos PDF. Utilízalas como especificaciones visuales para que los desarrolladores las adapten.

  4. Validación: Realice pruebas con volúmenes de datos de producción. Las plantillas que se renderizan al instante con 10 filas pueden ralentizarse con 10.000 filas.

Generación y programación de informes por lotes en .NET

Los sistemas de producción a menudo necesitan generar muchos informes simultáneamente o ejecutar trabajos de informes según un calendario. El diseño a prueba de hilos de IronPDF soporta ambos escenarios de manera eficiente.

Generar varios informes en paralelo

Para el procesamiento por lotes, utilice Parallel.ForEachAsync o patrones asíncronos con Task.WhenAll:

using IronPdf;
using System.Collections.Concurrent;

// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
    var results = new ConcurrentBag<ReportResult>();

    // Process invoices concurrently with controlled parallelism
    await Parallel.ForEachAsync(invoices,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
        async (invoice, token) =>
        {
            // Each thread gets its own renderer instance
            var renderer = new ChromePdfRenderer();
            string html = BuildInvoiceHtml(invoice);
            var pdf = await renderer.RenderHtmlAsPdfAsync(html);

            // Save individual invoice PDF
            string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
            await pdf.SaveAsAsync(filename);

            results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
        });

    return results.ToList();
}
using IronPdf;
using System.Collections.Concurrent;

// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
    var results = new ConcurrentBag<ReportResult>();

    // Process invoices concurrently with controlled parallelism
    await Parallel.ForEachAsync(invoices,
        new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
        async (invoice, token) =>
        {
            // Each thread gets its own renderer instance
            var renderer = new ChromePdfRenderer();
            string html = BuildInvoiceHtml(invoice);
            var pdf = await renderer.RenderHtmlAsPdfAsync(html);

            // Save individual invoice PDF
            string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
            await pdf.SaveAsAsync(filename);

            results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
        });

    return results.ToList();
}
Imports IronPdf
Imports System.Collections.Concurrent
Imports System.Threading.Tasks

' Generate multiple invoices in parallel using async processing
Public Async Function GenerateInvoiceBatchAsync(invoices As List(Of InvoiceModel)) As Task(Of List(Of ReportResult))
    Dim results As New ConcurrentBag(Of ReportResult)()

    ' Process invoices concurrently with controlled parallelism
    Await Task.Run(Async Function()
                       Await Parallel.ForEachAsync(invoices,
                           New ParallelOptions With {.MaxDegreeOfParallelism = Environment.ProcessorCount},
                           Async Function(invoice, token)
                               ' Each thread gets its own renderer instance
                               Dim renderer As New ChromePdfRenderer()
                               Dim html As String = BuildInvoiceHtml(invoice)
                               Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html)

                               ' Save individual invoice PDF
                               Dim filename As String = $"Invoice_{invoice.InvoiceNumber}.pdf"
                               Await pdf.SaveAsAsync(filename)

                               results.Add(New ReportResult With {.InvoiceNumber = invoice.InvoiceNumber, .Success = True})
                           End Function)
                   End Function)

    Return results.ToList()
End Function
$vbLabelText   $csharpLabel

Muestra de resultado

El ejemplo de procesamiento por lotes genera varias facturas en paralelo. He aquí una de las facturas por lotes generadas:

Integre la generación de informes con los servicios de fondo de ASP.NET Core

La generación programada de informes encaja de forma natural en la infraestructura de servicios alojados de ASP.NET Core:

// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Calculate next run time (6 AM daily)
            var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
            await Task.Delay(nextRun - DateTime.Now, stoppingToken);

            // Create scoped service for report generation
            using var scope = _serviceProvider.CreateScope();
            var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();

            // Generate and distribute daily report
            var salesReport = await reportService.GenerateDailySalesSummaryAsync();
            // Email or save reports as needed
        }
    }
}
// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Calculate next run time (6 AM daily)
            var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
            await Task.Delay(nextRun - DateTime.Now, stoppingToken);

            // Create scoped service for report generation
            using var scope = _serviceProvider.CreateScope();
            var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();

            // Generate and distribute daily report
            var salesReport = await reportService.GenerateDailySalesSummaryAsync();
            // Email or save reports as needed
        }
    }
}
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.Extensions.DependencyInjection

' Background service for scheduled report generation
Public Class DailyReportService
    Inherits BackgroundService

    Private ReadOnly _serviceProvider As IServiceProvider

    Protected Overrides Async Function ExecuteAsync(stoppingToken As CancellationToken) As Task
        While Not stoppingToken.IsCancellationRequested
            ' Calculate next run time (6 AM daily)
            Dim nextRun = DateTime.Now.Date.AddDays(1).AddHours(6)
            Await Task.Delay(nextRun - DateTime.Now, stoppingToken)

            ' Create scoped service for report generation
            Using scope = _serviceProvider.CreateScope()
                Dim reportService = scope.ServiceProvider.GetRequiredService(Of IReportGenerationService)()

                ' Generate and distribute daily report
                Dim salesReport = Await reportService.GenerateDailySalesSummaryAsync()
                ' Email or save reports as needed
            End Using
        End While
    End Function
End Class
$vbLabelText   $csharpLabel

Descargar el proyecto de prueba completo

Todos los ejemplos de código de este tutorial están disponibles en un proyecto de prueba de .NET 10 listo para ejecutar. La descarga incluye el código fuente completo, modelos de datos, plantillas HTML y un ejecutor de pruebas que genera todos los PDF de muestra mostrados anteriormente.

Próximos pasos

Los ejemplos a lo largo de esta guía demuestran que IronPDF maneja toda la gama de necesidades de informes empresariales: simples facturas con partidas y totales, complejos directorios de empleados con datos agrupados y fotos, inventarios informes con formato condicional y gráficos, procesamiento por lotes de cientos de documentos en paralelo y generación programada mediante servicios en segundo plano.

Si está evaluando alternativas para una implementación existente de Crystal Reports, comience con un único informe de alto valor. Reconstrúyalo utilizando los patrones HTML-to-PDF que se muestran aquí, compare la experiencia de desarrollo y la calidad del resultado, y amplíe a partir de ahí. Muchos equipos descubren que su primer informe convertido les lleva unas horas, ya que establecen patrones y plantillas de base, mientras que los informes posteriores tardan solo unos minutos en ensamblarse, ya que reutilizan plantillas de Razor y estilos. En cuanto a la precisión del diseño, la guía pixel-perfect rendering guide explica cómo ajustar exactamente la salida de Crystal Reports con CSS.

¿Listo para empezar a construir? Descargue IronPDF y pruébelo gratuitamente. La misma biblioteca se encarga de todo, desde el renderizado de un único informe hasta la generación de lotes de gran volumen en entornos .NET. Si tiene alguna pregunta sobre la migración de informes o necesita orientación sobre arquitectura, póngase en contacto con nuestro equipo de asistencia técnica.

Preguntas Frecuentes

¿Qué es IronPDF?

IronPDF es una biblioteca de C# que permite a los desarrolladores crear, editar y generar documentos PDF mediante programación, ofreciendo una alternativa moderna a las herramientas tradicionales de generación de informes como Crystal Reports.

¿Cómo sirve IronPDF como alternativa a Crystal Reports?

IronPDF ofrece un enfoque flexible y moderno para la generación de informes, ya que permite a los desarrolladores utilizar plantillas HTML/CSS, a las que se puede dar estilo y modificar fácilmente, a diferencia de la estructura más rígida de Crystal Reports.

¿Puedo crear facturas con IronPDF?

Sí, puede crear facturas detalladas y personalizadas utilizando plantillas HTML/CSS con IronPDF, lo que facilita el diseño de documentos de aspecto profesional.

¿Es posible generar directorios de empleados con IronPDF?

Por supuesto. IronPDF le permite generar directorios completos de empleados aprovechando los datos dinámicos y HTML/CSS para una presentación clara y organizada.

¿Cómo puede IronPDF ayudar con los informes de inventario?

IronPDF puede agilizar la creación de informes de inventario utilizando plantillas HTML/CSS, que pueden rellenar datos dinámicamente para proporcionar informes actualizados y visualmente atractivos.

¿Cuáles son las ventajas de utilizar plantillas HTML/CSS en IronPDF?

El uso de plantillas HTML/CSS en IronPDF ofrece flexibilidad en el diseño, facilidad de actualización y compatibilidad con las tecnologías web, lo que facilita el mantenimiento y la mejora de los diseños de los informes.

¿Es IronPDF compatible con .NET 10?

Sí, IronPDF es compatible con .NET 10, lo que garantiza que los desarrolladores puedan aprovechar las últimas características y mejoras de .NET para sus necesidades de generación de informes.

¿Cómo mejora IronPDF la velocidad de generación de informes?

IronPDF está optimizado para el rendimiento, lo que le permite generar informes rápidamente procesando de forma eficiente HTML/CSS y convirtiéndolos en documentos PDF de alta calidad.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más
¿Listo para empezar?
Nuget Descargas 17,386,124 | Versión: 2026.2 recién lanzado