Generate Reports in C# Like Crystal Reports (.NET 10)
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:
-
Instale IronPDF com o Gerenciador de Pacotes NuGet
PM > Install-Package IronPdf -
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"); -
Implante para testar em seu ambiente de produção.
Comece a usar IronPDF em seu projeto hoje com uma avaliação gratuita
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"
!{--010011000100100101000010010100100100000101010010010110010101111101010011010101000100000101010010010101000101111101010001010010010010010010100000101001100010111110100001001001100010011110100001101001011--}
Índice
- Resumindo: Guia de Início Rápido
- Modelos HTML para Arquitetura em PDF
- Por que substituir o Crystal Reports em aplicações .NET?
- Configurar um gerador de relatórios em C# no .NET 10
- Criar um relatório em PDF baseado em dados usando C#
- Criar o modelo de relatório HTML/CSS
- Vincular dados ao modelo
- Adicionar cabeçalhos, rodapés e números de página
- Criar tabelas dinâmicas e seções repetidas Geração avançada de relatórios em C# com IronPDF
- Adicionar gráficos e tabelas a relatórios em PDF
- Aplicar formatação condicional e lógica de negócios
- Criar sub-relatórios e quebras de seção
- Gerar um índice
- Migrar do Crystal Reports para o IronPDF
- Mapear conceitos do Crystal Reports para o IronPDF
- Melhor estratégia para converter modelos .rpt Geração e agendamento de relatórios em lote no .NET
- Gerar vários relatórios em paralelo
- Integrar a geração de relatórios com os serviços em segundo plano do ASP.NET Core
- Baixe o projeto de teste completo
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
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>
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
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
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
}
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
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>
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)
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>
}
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
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")
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:
-
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.
-
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.
-
Referência: Exportar relatórios Crystal Reports existentes como PDFs. Use essas especificações visuais para que os desenvolvedores possam reproduzi-las.
- 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
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
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.

