Generate Reports in C# Like Crystal Reports (.NET 10)

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

Geração de relatórios HTML-para-PDF em C# .NET com IronPDF substitui o designer proprietário do Crystal Reports .rpt por HTML, CSS e modelos Razor padrão, permitindo que desenvolvedores .NET criem relatórios empresariais orientados por dados usando as habilidades de desenvolvimento web que já possuem. Isso inclui suporte completo para tabelas dinâmicas, gráficos com tecnologia JavaScript , formatação condicional, processamento em lote de vários documentos e implantação multiplataforma em qualquer ambiente que execute .NET.

Resumo: Guia de Início Rápido

Este tutorial aborda a substituição do Crystal Reports pela geração de relatórios HTML para PDF em C# .NET, desde modelos básicos até processamento em lote e geração agendada.

  • Para quem é indicado: Desenvolvedores .NET que estão substituindo o Crystal Reports ou criando novos sistemas de relatórios do zero.
  • O que você vai desenvolver: Três implementações completas de relatórios (nota fiscal de vendas, diretório de funcionários e relatório de estoque), além de visualizações em Chart.js, cabeçalhos/rodapés personalizados, geração de sumário, fusão de sub-relatórios e processamento em lote paralelo.
  • Onde funciona: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+ e .NET Standard 2.0. Sem dependências COM exclusivas do Windows.
  • Quando usar essa abordagem: Quando a falta de suporte do Crystal Reports ao .NET Core , a dependência do Windows ou o licenciamento complexo se tornam um gargalo.
  • Por que isso é importante tecnicamente: HTML/CSS é renderizado de forma idêntica em todas as plataformas, integra-se com CI/CD e executa JavaScript para gráficos, tudo isso sem um designer proprietário ou taxas por documento.

Para acompanhar os exemplos de código, instale o IronPDF via NuGet (Install-Package IronPdf). Gere seu primeiro relatório com apenas algumas linhas de código:

  1. Instale IronPDF com o Gerenciador de Pacotes NuGet

    PM > Install-Package IronPdf
  2. Copie e execute este trecho 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. Implante para testar em seu ambiente de produção.

    Comece a usar IronPDF em seu projeto hoje com uma avaliação gratuita

    arrow pointer

Após adquirir ou se inscrever para um período de avaliação de 30 dias do IronPDF, adicione sua chave de licença no início do seu aplicativo.

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

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

!{--010011000100100101000010010100100100000101010010010110010101111101010011010101000100000101010010010101000101111101010001010010010010010010100000101001100010111110100001001001100010011110100001101001011--}

NuGet Instalar com NuGet

PM >  Install-Package IronPdf

Confira o IronPDF no NuGet para uma instalação rápida. Com mais de 10 milhões de downloads, ele está transformando o desenvolvimento de PDFs com C#. Você também pode baixar o arquivo DLL ou o instalador para Windows .

Índice

C# Report Generator: HTML Templates to PDF

A geração de PDF a partir de HTML baseia-se em um pipeline arquitetônico linear. Em vez de um formato de arquivo proprietário, o aplicativo usa modelos de dados padrão para preencher visualizações Razor ou modelos HTML. A string HTML resultante é então passada para um mecanismo de renderização como o IronPDF, que captura a saída visual como um documento PDF. Essa abordagem desacopla o design do relatório do ambiente de hospedagem, permitindo que o mesmo código seja executado em qualquer plataforma que suporte .NET.

Este fluxo de trabalho espelha o desenvolvimento web padrão. Os desenvolvedores front-end criam o layout usando CSS e o visualizam imediatamente em qualquer navegador. Os desenvolvedores de backend então vinculam os dados usando C#. Essa separação permite que as equipes usem seus processos existentes de controle de versão, revisão de código e implantação contínua para relatórios, da mesma forma que fazem para o restante do aplicativo.

O HTML permite funcionalidades indisponíveis no Crystal Reports: gráficos interativos, tabelas responsivas e estilos compartilhados para uma identidade visual consistente.

Por que substituir o Crystal Reports em aplicações .NET?

A migração do Crystal Reports não é resultado de um único problema catastrófico ou de um abandono repentino por parte da SAP. Em vez disso, é o acúmulo de pontos de atrito que, coletivamente, tornam a plataforma cada vez mais difícil de justificar para novos projetos e mais desafiadora de manter em soluções existentes. Identificar esses pontos problemáticos esclarece por que muitas equipes estão buscando alternativas e quais critérios são mais importantes ao avaliar opções de substituição.

Sem suporte for .NET 8 ou .NET Core.

O Crystal Reports não é compatível com .NET Core ou .NET 5-10. A SAP declarou em fóruns que não planeja adicionar suporte. O SDK utiliza componentes COM, que são incompatíveis com o .NET multiplataforma. O suporte para o .NET moderno exigiria uma reescrita completa, o que a SAP se recusou a fazer.

Consequentemente, as equipes que desenvolvem novos aplicativos em versões atuais do .NET não podem usar o Crystal Reports. Organizações que utilizam o .NET 8 ou o .NET 10 como padrão não conseguem integrá-lo. Para aplicações existentes, a atualização para um ambiente de execução .NET moderno requer primeiro a substituição do sistema de relatórios.

Licenciamento complexo e custos ocultos

O licenciamento do Crystal Reports distingue entre licenças de designer, licenças de tempo de execução, implantações em servidor e uso incorporado. As regras variam para serviços de desktop, web e terminal. A conformidade em uma configuração pode exigir licenças adicionais em outra. Se surgirem lacunas após a implementação, custos inesperados podem ocorrer. Muitas organizações concluem que a incerteza é pior do que migrar para uma solução com licenciamento mais claro.

Dependência da plataforma Windows

O Crystal Reports só funciona no Windows com a versão legada do .NET Framework. Não é possível implantar esses aplicativos em contêineres Linux, no Azure App Service no Linux, no AWS Lambda ou no Google Cloud Run. À medida que as organizações utilizam sistemas conteinerizados, independentes de plataforma e sem servidor, essas restrições tornam-se mais importantes.

As equipes de desenvolvimento que criam microsserviços enfrentam desafios adicionais. Se nove serviços forem executados em contêineres Linux leves, mas um deles precisar do Windows para o Crystal Reports, a implantação será mais complicada. Você precisa de imagens de contêiner do Windows, hospedagem compatível com Windows e configurações de implantação separadas. O serviço de relatórios torna-se uma exceção, bloqueando a padronização.

Set Up a C# Report Generator in .NET 10

Começar a usar o IronPDF é simples. Instale a biblioteca através do NuGet como qualquer outra dependência do .NET . Não é necessário baixar nenhum software adicional nem instalar um ambiente de execução separado para servidores de produção.

Escolha uma abordagem de modelo: Razor, HTML ou híbrida.

O IronPDF oferece suporte a três abordagens distintas para a criação de modelos de relatório. Cada abordagem oferece vantagens específicas dependendo da composição da equipe, dos requisitos do projeto e das considerações de manutenção a longo prazo.

As Razor Views oferecem a experiência de desenvolvimento mais completa para equipes que já trabalham no ecossistema .NET . Estão disponíveis modelos fortemente tipados com suporte completo ao IntelliSense no Visual Studio e no VS Code, verificação em tempo de compilação e todo o poder dos loops for, condicionais, tratamento de valores nulos e formatação de strings do C#. A sintaxe do Razor é familiar para quem já criou aplicações ASP.NET Core , eliminando a curva de aprendizado associada a mecanismos de templates de outros ecossistemas. Os modelos residem no projeto juntamente com outros arquivos de origem, participam de operações de refatoração e são compilados como parte do processo normal de construção.

HTML simples com interpolação de strings funciona bem para relatórios mais simples ou para equipes que preferem manter os modelos completamente separados do código .NET . Os templates HTML podem ser armazenados como recursos incorporados compilados na montagem, arquivos externos implantados junto com o aplicativo, ou até mesmo recuperados de um banco de dados ou sistema de gerenciamento de conteúdo em tempo de execução. A vinculação de dados básica utiliza string.Replace() para valores únicos ou uma biblioteca de template leve como Scriban ou Fluid para cenários mais avançados. Essa abordagem maximiza a portabilidade, permitindo que os designers editem modelos sem a necessidade de instalar nenhuma ferramenta .NET , utilizando apenas um editor de texto e um navegador da web para visualização.

As abordagens híbridas combinam ambas as técnicas para cenários que exigem flexibilidade. Por exemplo, uma view Razor pode ser renderizada para gerar a estrutura HTML principal e, em seguida, pós-processada com substituições de strings adicionais para elementos dinâmicos que não se encaixam perfeitamente no modelo de visualização. Alternativamente, um modelo HTML criado por alguém sem conhecimento de programação pode ser carregado, e as visualizações parciais do Razor podem ser usadas para renderizar apenas as seções complexas, orientadas a dados, antes de combinar tudo. A conversão de HTML para PDF é independente da fonte HTML, permitindo que você combine abordagens com base nas necessidades de cada relatório.

Dadas essas opções, este tutorial se concentra principalmente nas views Razor , pois elas oferecem o melhor equilíbrio entre segurança de tipos, facilidade de manutenção e riqueza de recursos para cenários típicos de relatórios empresariais. As habilidades são diretamente transferíveis caso as necessidades futuras incluam o trabalho com modelos HTML simples, já que ambos os métodos produzem strings HTML.

Build a Data-Driven PDF Report in C

Esta seção demonstra a criação completa de um relatório de fatura de vendas, do início ao fim. O exemplo abrange o padrão essencial usado em todos os relatórios: definir um modelo que estrutura os dados, criar um modelo Razor que transforma os dados em HTML formatado, renderizar esse modelo em uma string HTML e converter o HTML em um documento PDF pronto para visualização, envio por e-mail ou arquivamento.

Criar o modelo de relatório HTML/CSS

O primeiro passo é definir o modelo de dados. Uma fatura real requer informações do cliente, itens discriminados com descrições e preços, totais calculados, tratamento de impostos e elementos de identidade visual da empresa. As classes de modelos devem ser estruturadas de forma a refletir esses agrupamentos:

// 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

As propriedades calculadas para Subtotal, Valor do Imposto e Total Geral estão incluídas no modelo. Esses cálculos pertencem ao modelo, e não ao template, mantendo as views do Razor focadas na apresentação, enquanto o modelo lida com a lógica de negócios. Essa separação simplifica os testes unitários, permitindo que os cálculos sejam verificados sem a necessidade de renderizar HTML.

Agora, crie a visão Razor que transforma este modelo em uma fatura formatada profissionalmente. Salve isso como InvoiceTemplate.cshtml na sua pasta 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

O CSS incorporado neste modelo controla todo o estilo visual, como cores, fontes, espaçamento e formatação de tabelas. O IronPDF também oferece suporte a recursos modernos de CSS, como flexbox, layouts em grade e variáveis ​​CSS. O PDF gerado corresponde exatamente à pré-visualização de impressão do Chrome, o que facilita a depuração: se algo parecer errado no PDF, abra o HTML em um navegador e use as ferramentas de desenvolvedor para inspecionar e ajustar os estilos.

Vincular dados ao modelo

Com o modelo e o template definidos, a renderização do PDF requer a conexão entre eles através do ChromePdfRenderer do IronPDF. O passo fundamental é converter a visualização Razor em uma string HTML e, em seguida, passar essa string para o 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 cenários mais simples, quando você não precisa da configuração completa do ASP.NET Core MVC, como em um aplicativo de console ou serviço em segundo plano, você pode usar apenas strings HTML com interpolação e StringBuilder para as partes dinâmicas.

Exemplo de saída

Adicionar cabeçalhos, rodapés e números de página

Os relatórios profissionais normalmente incluem cabeçalhos e rodapés consistentes em todas as páginas, exibindo a marca da empresa, os títulos dos documentos, as datas de geração e os números das páginas. O IronPDF oferece duas abordagens para implementar esses elementos: cabeçalhos baseados em texto para conteúdo simples que requer formatação mínima e cabeçalhos HTML para controle total de estilo com logotipos e layouts personalizados.

Cabeçalhos baseados em texto funcionam bem para informações básicas e são renderizados mais rapidamente, pois não exigem análise HTML adicional:

: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

Os campos de mesclagem disponíveis incluem {page} para o número da página atual, {total-pages} para a contagem total de páginas do documento, {date} e {time} para timestamps de geração, {url} para a URL de origem se estiver renderizando a partir de uma página web, e {html-title} e {pdf-title} para os títulos dos documentos. Esses marcadores são substituídos automaticamente durante a renderização.

Para cabeçalhos com logotipos, fontes personalizadas ou layouts complexos de várias colunas, use cabeçalhos HTML que suportem estilização CSS completa:

: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

Exemplo de saída

Criar tabelas dinâmicas e seções repetidas

Muitas vezes, os relatórios precisam exibir conjuntos de dados que se estendem por várias páginas. Os mecanismos de repetição do Razor lidam com isso naturalmente, iterando sobre coleções e gerando linhas de tabela ou elementos de cartão para cada item.

Aqui está um exemplo completo de Diretório de Funcionários demonstrando a apresentação de dados agrupados com seções departamentais:

// 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

A propriedade CSS page-break-inside: avoid na classe do departamento informa ao renderizador de PDF para manter as seções do departamento juntas em uma única página quando possível. Se o conteúdo de um departamento causar uma quebra de página no meio da seção, o renderizador move toda a seção para a próxima página. O seletor .department:not(:first-child) com page-break-before: always força cada departamento após o primeiro a começar em uma nova página, criando uma separação limpa de seções em todo o diretório.

Exemplo de saída

Advanced C# Report Generation With IronPDF

Os relatórios empresariais frequentemente exigem recursos que vão além de tabelas estáticas e texto. Os gráficos visualizam tendências que seriam tediosas de compreender em formato de tabela. A formatação condicional chama a atenção para os itens que exigem ação. Os sub-relatórios combinam dados de múltiplas fontes em documentos coesos. Esta seção aborda a implementação de cada um desses recursos usando o mecanismo de renderização Chromium do IronPDF.

Adicionar gráficos e tabelas a relatórios em PDF

Como o JavaScript é executado durante a renderização, você pode usar qualquer biblioteca de gráficos do lado do cliente para gerar visualizações diretamente em seus relatórios. O gráfico é rasterizado como parte da página e aparece no PDF final exatamente como apareceria na tela. O Chart.js oferece um excelente equilíbrio entre simplicidade, capacidade e documentação para a maioria das necessidades de geração de relatórios.

Inclua o Chart.js a partir de uma CDN e configure seu gráfico com dados serializados do seu modelo 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

Ao renderizar páginas que incluem conteúdo gerado por JavaScript, configure o renderizador para aguardar a conclusão da execução dos scripts antes de capturar a 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

Exemplo de saída

Aplicar formatação condicional e lógica de negócios

Os relatórios de inventário se beneficiam de indicadores visuais que chamam a atenção imediatamente para os itens que exigem ação. Em vez de obrigar os usuários a examinar centenas de linhas em busca de problemas, a formatação condicional torna as exceções visualmente óbvias. Utilize expressões embutidas do Razor para aplicar classes CSS com base em valores de dados:


@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

Exemplo de saída

Criar sub-relatórios e quebras de seção

Para combinar relatórios gerados independentemente em um único documento, utilize a funcionalidade de mesclagem do 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

Exemplo de saída

Gerar um índice

O IronPDF pode gerar automaticamente um sumário com base nos elementos de cabeçalho do seu 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

Migre do Crystal Reports para o IronPDF

Migrar um sistema de relatórios já estabelecido exige um planejamento cuidadoso para minimizar interrupções, aproveitando ao mesmo tempo a oportunidade de modernizar e simplificar. Você avançará mais rapidamente se entender como os conceitos do Crystal Reports se relacionam com a abordagem baseada em HTML, em vez de tentar replicar literalmente cada recurso ou preservar cada peculiaridade dos relatórios originais.

Mapear conceitos do Crystal Reports para o IronPDF

Compreender o mapeamento conceitual ajuda você a traduzir sistematicamente os relatórios existentes:

Crystal Reports Equivalente ao IronPDF
Seções do relatório Divs HTML com propriedades CSS de quebra de página
Campos de parâmetros Propriedades do modelo passadas para visualizações Razor
Campos de fórmula Propriedades computadas em classes de modelo C#
Totais acumulados Agregações LINQ
Sub-relatórios Visualizações parciais ou documentos PDF mesclados
Agrupamento/classificação Operações LINQ antes de passar dados para o modelo
relatórios de tabulação cruzada Tabelas HTML usando loops aninhados
Formatação condicional Blocos Razor @if com classes CSS

Melhor estratégia para converter modelos .rpt

Não tente analisar arquivos .rpt programaticamente. Em vez disso, trate as saídas em PDF existentes como especificações visuais e reconstrua a lógica usando uma estratégia sistemática de quatro etapas:

  1. Inventário: Catalogar todos os arquivos .rpt com sua finalidade, fontes de dados e frequência de uso. Remova relatórios obsoletos para reduzir o escopo da migração.

  2. Priorize: Migre primeiro os relatórios de alta frequência. Dê preferência aos relatórios com layouts simples ou que apresentem problemas de manutenção persistentes.

  3. Referência: Exportar relatórios Crystal Reports existentes como PDFs. Use essas especificações visuais para que os desenvolvedores possam reproduzi-las.

  4. Validar: Testar com volumes de dados de produção. Modelos que são renderizados instantaneamente com 10 linhas podem ficar lentos com 10.000 linhas.

Geração e agendamento de relatórios em lote no .NET

Os sistemas de produção frequentemente precisam gerar muitos relatórios simultaneamente ou executar tarefas de geração de relatórios de acordo com agendamentos. O design thread-safe do IronPDF suporta ambos os cenários de forma eficiente.

Gere vários relatórios em paralelo.

Para processamento em lote, use Parallel.ForEachAsync ou padrões assíncronos com 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

Exemplo de saída

O exemplo de processamento em lote gera várias faturas em paralelo. Aqui está uma das faturas em lote geradas:

Integre a geração de relatórios com os serviços em segundo plano do ASP.NET Core.

A geração de relatórios agendados se integra naturalmente à infraestrutura de serviços hospedados do 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

Baixe o projeto de teste completo.

Todos os exemplos de código deste tutorial estão disponíveis em um projeto de teste .NET 10 pronto para uso. O download inclui o código-fonte completo, os modelos de dados, os modelos HTML e um executor de testes que gera todos os PDFs de exemplo mostrados acima.

Próximos passos

Os exemplos ao longo deste guia demonstram que o IronPDF atende a todas as necessidades de relatórios empresariais: faturas simples com itens e totais, diretórios de funcionários complexos com dados agrupados e fotos, relatórios de estoque com formatação condicional e gráficos, processamento em lote de centenas de documentos em paralelo e geração agendada por meio de serviços em segundo plano.

Se você estiver avaliando alternativas para uma implementação existente do Crystal Reports, comece com um único relatório de alto valor. Recrie o projeto usando os padrões de HTML para PDF mostrados aqui, compare a experiência de desenvolvimento e a qualidade da saída e, em seguida, expanda a partir daí. Muitas equipes descobrem que seu primeiro relatório convertido leva algumas horas, enquanto estabelecem padrões e modelos básicos, ao passo que os relatórios subsequentes levam apenas minutos para serem montados, pois reutilizam modelos e estilos do Razor . Para garantir a precisão do layout, o guia de renderização pixel-perfect aborda como reproduzir com exatidão a saída do Crystal Reports usando CSS.

Pronto para começar a construir? Baixe o IronPDF e experimente com um período de teste gratuito. A mesma biblioteca lida com tudo, desde a renderização de relatórios individuais até a geração de lotes de alto volume em ambientes .NET . Se você tiver dúvidas sobre a migração de relatórios ou precisar de orientação sobre arquitetura, entre em contato com nossa equipe de suporte de engenharia .

Perguntas frequentes

O que é o IronPDF?

IronPDF é uma biblioteca C# que permite aos desenvolvedores criar, editar e gerar documentos PDF programaticamente, oferecendo uma alternativa moderna às ferramentas de relatório tradicionais, como o Crystal Reports.

Como o IronPDF funciona como uma alternativa ao Crystal Reports?

O IronPDF oferece uma abordagem flexível e moderna para a geração de relatórios, permitindo que os desenvolvedores usem modelos HTML/CSS, que podem ser facilmente estilizados e modificados, em contraste com a estrutura mais rígida do Crystal Reports.

Posso criar faturas usando o IronPDF?

Sim, você pode criar faturas detalhadas e personalizadas usando modelos HTML/CSS com o IronPDF, facilitando a criação de documentos com aparência profissional.

É possível gerar listas de funcionários com o IronPDF?

Com certeza. O IronPDF permite gerar diretórios de funcionários completos, aproveitando dados dinâmicos e HTML/CSS para uma apresentação clara e organizada.

Como o IronPDF pode ajudar com relatórios de inventário?

O IronPDF pode simplificar a criação de relatórios de inventário usando modelos HTML/CSS, que podem preencher dados dinamicamente para fornecer relatórios atualizados e visualmente atraentes.

Quais são as vantagens de usar modelos HTML/CSS no IronPDF?

O uso de modelos HTML/CSS no IronPDF oferece flexibilidade de design, facilidade de atualização e compatibilidade com tecnologias web, tornando mais fácil a manutenção e o aprimoramento dos layouts de relatórios.

O IronPDF é compatível com o .NET 10?

Sim, o IronPDF é compatível com o .NET 10, garantindo que os desenvolvedores possam aproveitar os recursos e melhorias mais recentes do .NET para suas necessidades de geração de relatórios.

Como o IronPDF melhora a velocidade de geração de relatórios?

O IronPDF é otimizado para desempenho, permitindo gerar relatórios rapidamente, processando HTML/CSS de forma eficiente e renderizando-os em documentos PDF de alta qualidade.

Curtis Chau
Redator Técnico

Curtis Chau é bacharel em Ciência da Computação (Universidade Carleton) e se especializa em desenvolvimento front-end, com experiência em Node.js, TypeScript, JavaScript e React. Apaixonado por criar interfaces de usuário intuitivas e esteticamente agradáveis, Curtis gosta de trabalhar com frameworks modernos e criar manuais ...

Leia mais
Pronto para começar?
Nuget Downloads 18,318,263 | Versão: 2026.4 acaba de ser lançado
Still Scrolling Icon

Ainda está rolando a tela?

Quer provas rápidas? PM > Install-Package IronPdf
executar um exemplo Veja seu HTML se transformar em um PDF.