Générer des rapports en C# ; comme Crystal Reports (.NET 10)

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

La génération de rapports HTML vers PDF en C# .NET avec IronPDF remplace le concepteur .rpt propriétaire de Crystal Reports par des modèles HTML, CSS et Razor standard, ce qui permet aux développeurs .NET de créer des rapports professionnels axés sur les données en utilisant les compétences de développement web qu'ils possèdent déjà. La traduction doit rester professionnelle et préserver l'exactitude technique tout en expliquant les caractéristiques et les avantages de ces outils de développement, notamment la prise en charge complète des tableaux dynamiques, des graphiques alimentés par JavaScript, du formatage conditionnel, du traitement par lots de plusieurs documents et du déploiement multiplateforme dans tout environnement fonctionnant sous .NET.

TL;DR : Quickstart Guide

Ce tutoriel couvre le remplacement de Crystal Reports par la génération de rapports HTML-to-PDF en C# .NET, des modèles de base au traitement par lots et à la génération programmée.

  • À qui s'adresse ce document: Les développeurs .NET qui remplacent Crystal Reports ou qui créent de nouveaux systèmes de reporting à partir de zéro.
  • Ce que vous construirez: Trois implémentations complètes de rapports (facture de vente, répertoire des employés, rapport d'inventaire), plus des visualisations Chart.js, des en-têtes/pieds de page de marque, la génération de tables des matières, la fusion de sous-rapports et le traitement par lots en parallèle.
  • <Où ça marche: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+, et .NET Standard 2.0. Pas de dépendances COM pour Windows uniquement.
  • Quand utiliser cette approche : Lorsque l'absence de prise en charge de .NET Core par Crystal Reports, le verrouillage de Windows ou la complexité des licences deviennent un goulot d'étranglement.
  • Pourquoi c'est important sur le plan technique: HTML/CSS offre un rendu identique sur toutes les plateformes, s'intègre à CI/CD et exécute JavaScript pour les graphiques, le tout sans concepteur propriétaire ni frais par document.

Pour suivre les exemples de code, installez IronPDF via NuGet (Install-Package IronPdf). Générez votre premier rapport avec seulement quelques lignes de code :

Nuget IconCommencez dès maintenant à créer des PDF avec NuGet :

  1. Installez IronPDF avec le gestionnaire de packages NuGet

    PM > Install-Package IronPdf

  2. Copiez et exécutez cet extrait de code.

    // 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. Déployez pour tester sur votre environnement de production.

    Commencez à utiliser IronPDF dans votre projet dès aujourd'hui grâce à un essai gratuit.
    arrow pointer

Après avoir acheté ou vous être inscrit à une version d'essai de 30 jours d'IronPDF, ajoutez votre clé de licence au début de votre application.

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

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

Commencez à utiliser IronPDF dans votre projet aujourd'hui avec un essai gratuit.

Première étape :
green arrow pointer
NuGet Installer avec NuGet

PM >  Install-Package IronPdf

Consultez IronPDF sur NuGet pour une installation rapide. Avec plus de 10 millions de téléchargements, il transforme le développement PDF avec C#. Vous pouvez également télécharger le DLL ou l'installateur Windows.

Table des matières

C&num ; Générateur de rapports : Modèles HTML en PDF

La génération HTML-PDF repose sur un pipeline architectural linéaire. Au lieu d'un format de fichier propriétaire, l'application utilise des modèles de données standard pour alimenter les vues Razor ou les modèles HTML. La chaîne HTML résultante est ensuite transmise à un moteur de rendu tel qu'IronPDF, qui capture la sortie visuelle sous la forme d'un document PDF. Cette approche dissocie la conception du rapport de l'environnement d'hébergement, ce qui permet d'exécuter exactement le même code sur n'importe quelle plateforme compatible avec .NET.

Ce flux de travail reflète le développement web standard. Les développeurs frontaux créent la mise en page à l'aide de CSS et la prévoient immédiatement dans n'importe quel navigateur. Les développeurs de backend lient ensuite les données à l'aide de C#. Cette séparation permet aux équipes d'utiliser leurs processus existants de contrôle de version, d'examen du code et de déploiement continu pour les rapports, comme elles le font pour le reste de l'application.

Le langage HTML permet des fonctionnalités non disponibles dans Crystal Reports : graphiques interactifs, tableaux réactifs et styles partagés pour une image de marque cohérente.

Pourquoi remplacer Crystal Reports dans les applications .NET

La migration vers Crystal Reports n'est pas le résultat d'un seul problème catastrophique ou d'un abandon soudain par SAP. Il s'agit plutôt d'une accumulation de points de friction qui, collectivement, rendent la plateforme de plus en plus difficile à justifier pour les nouveaux projets et plus difficile à maintenir dans les solutions existantes. L'identification de ces points faibles permet de comprendre pourquoi de nombreuses équipes cherchent des alternatives et quels sont les critères les plus importants lors de l'évaluation des options de remplacement.

Pas de prise en charge de .NET 8 ou .NET Core

Crystal Reports ne prend pas en charge .NET Core ou .NET 5-10. SAP a déclaré sur des forums qu'elle ne prévoyait pas d'ajouter cette prise en charge. Le SDK utilise des composants COM, qui sont incompatibles avec la plateforme .NET. La prise en charge de la version moderne de .NET nécessiterait une réécriture complète, ce que SAP a refusé de faire.

Par conséquent, les équipes qui créent de nouvelles applications sur les versions .NET actuelles ne peuvent pas utiliser Crystal Reports. Les organisations normalisées sur .NET 8 ou .NET 10 ne sont pas en mesure de l'intégrer. Pour les applications existantes, la mise à niveau vers un runtime .NET moderne nécessite d'abord le remplacement du système de reporting.

Les licences complexes et les coûts cachés

Les licences Crystal Reports font la distinction entre les licences de conception, les licences d'exécution, les déploiements sur serveur et l'utilisation intégrée. Les règles varient selon qu'il s'agit de services de bureau, de services web ou de services de terminal. La conformité dans une configuration peut nécessiter des licences supplémentaires dans une autre. Si des lacunes apparaissent après le déploiement, des coûts inattendus sont à prévoir. De nombreuses organisations décident que l'incertitude est pire que la migration vers une solution dont les licences sont plus claires.

L'enfermement dans une plateforme Windows uniquement

Crystal Reports fonctionne uniquement sous Windows avec l'ancien .NET Framework. Vous ne pouvez pas déployer ces applications dans des conteneurs Linux, Azure App Service on Linux, AWS Lambda ou Google Cloud Run. À mesure que les organisations utilisent des systèmes conteneurisés, agnostiques en termes de plateforme et sans serveur, ces restrictions deviennent plus importantes.

Les équipes de développement qui créent des microservices sont confrontées à des défis supplémentaires. Si neuf services fonctionnent dans des conteneurs Linux légers, mais qu'un service a besoin de Windows pour Crystal Reports, le déploiement est plus compliqué. Vous avez besoin d'images de conteneurs Windows, d'un hébergement compatible avec Windows et de paramètres de déploiement distincts. Le service de reporting devient une exception, bloquant la normalisation.

Mise en place d'un générateur de rapports C# dans .NET 10

La prise en main d'IronPDF est simple. Installez la bibliothèque via NuGet comme n'importe quelle autre dépendance .NET. Il n'y a pas de logiciel supplémentaire à télécharger ni de programme d'installation distinct pour les serveurs de production.

Choisir une approche de modèle : Razor, HTML ou hybride

IronPDF prend en charge trois approches distinctes pour la construction de modèles de rapport. Chaque approche offre des avantages spécifiques en fonction de la composition de l'équipe, des exigences du projet et des considérations de maintenance à long terme.

les Razor Views offrent l'expérience de développement la plus riche aux équipes qui travaillent déjà dans l'écosystème .NET. Des modèles fortement typés avec une prise en charge IntelliSense complète dans Visual Studio et VS Code, une vérification au moment de la compilation et toute la puissance de C# pour les boucles, les conditionnelles, la gestion des null et le formatage des chaînes sont disponibles. La syntaxe de Razor est familière à ceux qui ont construit des applications ASP.NET Core, éliminant ainsi la courbe d'apprentissage associée aux moteurs de modèles d'autres écosystèmes. Les modèles résident dans le projet avec d'autres fichiers sources, participent aux opérations de refactorisation et se compilent dans le cadre du processus normal de construction.

Plain HTML with String Interpolation fonctionne bien pour les rapports plus simples ou les équipes qui préfèrent garder les modèles séparés du code .NET. Les modèles HTML peuvent être stockés en tant que ressources intégrées compilées dans l'assemblage, en tant que fichiers externes déployés avec l'application, ou même récupérés à partir d'une base de données ou d'un système de gestion de contenu au moment de l'exécution. La liaison de données de base utilise string.Replace() pour les valeurs uniques ou une bibliothèque de modélisation légère comme Scriban ou Fluid pour les scénarios plus avancés. Cette approche maximise la portabilité, permettant aux concepteurs d'éditer des modèles sans aucun outil .NET installé, en utilisant uniquement un éditeur de texte et un navigateur web pour la prévisualisation.

les approches hybrides combinent les deux techniques pour les scénarios nécessitant de la flexibilité. Par exemple, une vue Razor peut être rendue pour générer la structure HTML principale, puis post-traitée avec des remplacements de chaînes supplémentaires pour les éléments dynamiques qui ne s'intègrent pas proprement dans le modèle de la vue. Il est également possible de charger un modèle HTML conçu par un non-développeur et d'utiliser les vues partielles de Razor pour ne rendre que les sections complexes, basées sur des données, avant de tout combiner. La conversion HTML-PDF est indépendante de la source HTML, ce qui vous permet de combiner les approches en fonction des besoins de chaque rapport.

Compte tenu de ces options, ce tutoriel se concentre principalement sur les vues Razor parce qu'elles offrent le meilleur équilibre entre la sécurité des types, la maintenabilité et la richesse des fonctionnalités pour les scénarios typiques de création de rapports d'entreprise. Les compétences sont directement transférables si les besoins futurs incluent le travail avec des modèles HTML simples, car les deux méthodes produisent des chaînes HTML.

Construire un rapport PDF piloté par les données en C#;

Cette section présente la création complète d'un rapport de facture de vente, du début à la fin. L'exemple couvre le modèle essentiel utilisé pour tous les rapports : définir un modèle qui structure les données, créer un modèle Razor qui transforme les données en HTML formaté, rendre ce modèle en une chaîne HTML et convertir le HTML en un document PDF prêt à être visualisé, envoyé par courrier électronique ou archivé.

Créer le modèle de rapport HTML/CSS

La première étape consiste à définir le modèle de données. Une facture réelle nécessite des informations sur le client, des postes avec des descriptions et des prix, des totaux calculés, le traitement des taxes et des éléments de marque de l'entreprise. Les classes du modèle doivent être structurées de manière à refléter ces regroupements :

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

Les propriétés calculées pour Subtotal, TaxAmount et GrandTotal sont incluses dans le modèle. Ces calculs ont leur place dans le modèle plutôt que dans la maquette, ce qui permet aux vues Razor de se concentrer sur la présentation tandis que le modèle gère la logique commerciale. Cette séparation facilite les tests unitaires, car elle permet de vérifier les calculs sans rendre le moindre code HTML.

Créez maintenant la vue Razor qui transforme ce modèle en une facture au format professionnel. Enregistrez-la sous InvoiceTemplate.cshtml dans votre dossier 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

Le CSS intégré dans ce modèle gère tous les styles visuels tels que les couleurs, les polices, l'espacement et la mise en forme des tableaux. IronPDF prend également en charge les fonctionnalités CSS modernes telles que flexbox, les mises en page en grille et les variables CSS. Le PDF rendu correspond exactement à l'aperçu avant impression de Chrome, ce qui facilite le débogage : si quelque chose ne semble pas correct dans le PDF, ouvrez le HTML dans un navigateur et utilisez les outils de développement pour inspecter et ajuster les styles.

Lier les données au modèle

Une fois le modèle et le gabarit en place, le rendu du PDF nécessite de les connecter via le ChromePdfRenderer d'IronPdf. L'étape clé consiste à convertir la vue Razor en une chaîne HTML, puis à transmettre cette chaîne au moteur de rendu :

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

Pour les scénarios plus simples, lorsque vous n'avez pas besoin de la configuration complète d'ASP.NET Core MVC, comme dans une application console ou un service d'arrière-plan, vous pouvez simplement utiliser des chaînes HTML avec interpolation et StringBuilder pour les parties dynamiques.

Exemple de résultat

Ajouter des en-têtes, des pieds de page et des numéros de page

Les rapports professionnels comprennent généralement des en-têtes et des pieds de page cohérents sur toutes les pages, affichant la marque de l'entreprise, les titres des documents, les dates de génération et les numéros de page. IronPDF propose deux approches pour la mise en œuvre de ces éléments : les en-têtes textuels pour les contenus simples nécessitant une mise en forme minimale, et les en-têtes HTML pour un contrôle total du style avec des logos et des mises en page personnalisées.

Les en-têtes textuels conviennent bien pour les informations de base et s'affichent plus rapidement puisqu'ils ne nécessitent pas d'analyse HTML supplémentaire :

: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

Les champs de fusion disponibles comprennent {page} pour le numéro de page actuel, {total-pages} pour le nombre total de pages du document, {date} et {time} pour les horodatages de génération, {url} pour l'URL source en cas de rendu à partir d'une page web, et {html-title} et {pdf-title} pour les titres de documents. Ces espaces réservés sont automatiquement remplacés lors du rendu.

Pour les en-têtes comportant des logos, des polices personnalisées ou des mises en page complexes sur plusieurs colonnes, utilisez des en-têtes HTML qui prennent en charge le style CSS complet :

: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

Exemple de résultat

Créer des tableaux dynamiques et des sections répétitives

Les rapports doivent souvent afficher des collections de données qui s'étendent sur plusieurs pages. Les constructions en boucle de Razor gèrent cela naturellement en itérant sur les collections et en générant des lignes de tableau ou des éléments de carte pour chaque élément.

Voici un exemple complet d'annuaire des employés présentant des données groupées avec des sections départementales :

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

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

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

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

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

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

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

La propriété CSS page-break-inside : avoid sur la classe département indique au moteur de rendu PDF de garder les sections de département ensemble sur une seule page lorsque cela est possible. Si le contenu d'un département entraîne un saut de page en milieu de section, le moteur de rendu déplace l'ensemble de la section sur la page suivante. Le sélecteur .department:not(:first-child) avec page-break-before : always force chaque département après le premier à commencer sur une nouvelle page, créant ainsi une séparation nette des sections dans tout le répertoire.

Exemple de résultat

C# avancé ; génération de rapports avec IronPDF

Les rapports d'activité requièrent souvent des fonctionnalités allant au-delà des tableaux et textes statiques. Les graphiques permettent de visualiser des tendances qu'il serait fastidieux de comprendre sous forme de tableaux. La mise en forme conditionnelle attire l'attention sur les éléments nécessitant une action. Les sous-rapports combinent des données provenant de sources multiples dans des documents cohérents. Cette section couvre la mise en œuvre de chacune de ces fonctionnalités à l'aide du moteur de rendu Chromium d'IronPDF.

Ajouter des diagrammes et des graphiques aux rapports PDF

Étant donné que JavaScript s'exécute pendant le rendu, vous pouvez utiliser n'importe quelle bibliothèque graphique côté client pour générer des visualisations directement dans vos rapports. Le graphique est rastérisé en tant que partie de la page et apparaît dans le PDF final exactement comme il le ferait à l'écran. Chart.js offre un excellent équilibre entre simplicité, capacité et documentation pour la plupart des besoins de reporting.

Incluez Chart.js à partir d'un CDN et configurez votre graphique avec des données sérialisées à partir de votre modèle 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

Lors du rendu de pages contenant du contenu généré par JavaScript, configurez le moteur de rendu pour qu'il attende la fin de l'exécution des scripts avant de capturer la page :

: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

Exemple de résultat

Appliquer le formatage conditionnel et la logique commerciale

Les rapports d'inventaire bénéficient d'indicateurs visuels qui attirent immédiatement l'attention sur les éléments nécessitant une action. Plutôt que d'obliger les utilisateurs à parcourir des centaines de lignes à la recherche de problèmes, la mise en forme conditionnelle rend les exceptions visuellement évidentes. Utilisez les expressions en ligne de Razor pour appliquer des classes CSS basées sur des valeurs de données :


@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

Exemple de résultat

Créer des sous-rapports et des coupures de section

Pour combiner des rapports générés indépendamment en un seul document, utilisez la fonctionnalité de fusion d'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

Exemple de résultat

Générer une table des matières

IronPDF peut générer automatiquement une table des matières basée sur les éléments d'en-tête de votre 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

Migrer de Crystal Reports vers IronPDF

La migration d'un système de reporting établi nécessite une planification minutieuse afin de minimiser les perturbations tout en saisissant l'opportunité de moderniser et de simplifier. Vous progresserez plus rapidement en comprenant comment les concepts de Crystal Reports s'adaptent à l'approche HTML, plutôt que d'essayer de reproduire littéralement chaque fonctionnalité ou de préserver chaque particularité des rapports d'origine.

Mapper les concepts de Crystal Reports sur IronPDF

La compréhension de la cartographie conceptuelle vous aide à traduire systématiquement les rapports existants :

Crystal Reports Équivalent d'IronPDF
Sections du rapport Divs HTML avec propriétés CSS de saut de page
Champs de paramètres Propriétés du modèle transmises aux vues Razor
Champs de formule Propriétés calculées en C# dans les classes de modèle
Totaux courants Agrégations LINQ
Sous-rapports Vues partielles ou documents PDF fusionnés
Regroupement/tri Opérations LINQ avant de transmettre des données à un modèle
Rapports croisés Tableaux HTML utilisant des boucles imbriquées
mise en forme conditionnelle Blocs @if Razor avec classes CSS

La meilleure stratégie pour convertir les modèles .rpt

N'essayez pas d'analyser les fichiers .rpt de manière programmatique. Il s'agit plutôt de traiter les sorties PDF existantes comme des spécifications visuelles et de reconstruire la logique à l'aide d'une stratégie systématique en quatre étapes :

  1. Inventaire: Cataloguer tous les fichiers .rpt avec leur objectif, les sources de données et la fréquence d'utilisation. Supprimez les rapports obsolètes pour réduire l'ampleur de la migration.

  2. Prioriser: Migrer d'abord les rapports à haute fréquence. Ciblez les rapports dont la mise en page est simple ou qui présentent des problèmes de maintenance persistants.

  3. Référence: Exporter des rapports Crystal existants au format PDF. Utilisez-les comme spécifications visuelles pour que les développeurs s'y retrouvent.

  4. Valider: Testez avec des volumes de données de production. Les modèles qui s'affichent instantanément avec 10 lignes peuvent ralentir avec 10 000 lignes.

Génération de rapports par lots et planification en .NET

Les systèmes de production doivent souvent générer de nombreux rapports simultanément ou exécuter des tâches de rapport selon un calendrier. La conception à sécurité thread d'IronPDF prend en charge les deux scénarios de manière efficace.

Générer des rapports multiples en parallèle

Pour le traitement par lots, utilisez Parallel.ForEachAsync ou des modèles asynchrones avec 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

Exemple de résultat

L'exemple de traitement par lots génère plusieurs factures en parallèle. Voici l'une des factures par lot générées :

Intégrer la génération de rapports avec les services d'arrière-plan d'ASP.NET Core

La génération de rapports planifiés s'intègre naturellement dans l'infrastructure de services hébergés d'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

Télécharger le projet de test complet

Tous les exemples de code de ce tutoriel sont disponibles dans un projet de test .NET 10 prêt à être exécuté. Le téléchargement comprend le code source complet, les modèles de données, les modèles HTML et un programme de test qui génère tous les exemples de PDF présentés ci-dessus.

Prochaines étapes

Les exemples présentés tout au long de ce guide démontrent que IronPDF gère l'ensemble des besoins en matière de rapports d'entreprise : factures simples avec postes et totaux, répertoires complexes d'employés avec données groupées et photos, rapports d'inventaire avec formatage conditionnel et graphiques, traitement par lots de centaines de documents en parallèle, et génération programmée par le biais de services d'arrière-plan.

Si vous évaluez des alternatives pour une implémentation existante de Crystal Reports, commencez par un seul rapport de grande valeur. Reconstruisez-la en utilisant les modèles HTML-to-PDF présentés ici, comparez l'expérience de développement et la qualité du résultat, puis développez à partir de là. De nombreuses équipes constatent que leur premier rapport converti prend quelques heures, le temps d'établir des modèles de base, tandis que les rapports suivants ne prennent que quelques minutes à assembler, car ils réutilisent les modèles Razor et la mise en forme. Pour la précision de la mise en page, le pixel-perfect rendering guide explique comment faire correspondre exactement la sortie de Crystal Reports à l'aide de CSS.

Prêt à commencer la construction ? Téléchargez IronPDF et essayez-le avec une version d'essai gratuite. La même bibliothèque gère tout, du simple rendu de rapport à la génération de lots à haut volume dans les environnements .NET. Si vous avez des questions sur la migration des rapports ou si vous avez besoin de conseils en matière d'architecture, prenez contact avec notre équipe d'assistance technique.

Questions Fréquemment Posées

Qu'est-ce que IronPDF ?

IronPDF est une bibliothèque C# qui permet aux développeurs de créer, modifier et générer des documents PDF de manière programmatique, offrant ainsi une alternative moderne aux outils de reporting traditionnels tels que Crystal Reports.

En quoi IronPDF constitue-t-il une alternative à Crystal Reports ?

IronPDF offre une approche flexible et moderne de la génération de rapports en permettant aux développeurs d'utiliser des modèles HTML/CSS, qui peuvent être facilement stylisés et modifiés, par opposition à la structure plus rigide de Crystal Reports.

Puis-je créer des factures à l'aide d'IronPDF ?

Oui, vous pouvez créer des factures détaillées et personnalisées à l'aide de modèles HTML/CSS avec IronPDF, ce qui facilite la conception de documents d'aspect professionnel.

Est-il possible de générer des répertoires d'employés avec IronPdf ?

Absolument. IronPDF vous permet de générer des annuaires d'employés complets en tirant parti des données dynamiques et du HTML/CSS pour une présentation claire et organisée.

Comment IronPDF peut-il aider à la réalisation de rapports d'inventaire ?

IronPDF peut rationaliser la création de rapports d'inventaire en utilisant des modèles HTML/CSS, qui peuvent alimenter dynamiquement les données pour fournir des rapports actualisés et visuellement attrayants.

Quels sont les avantages de l'utilisation de modèles HTML/CSS dans IronPDF ?

L'utilisation de modèles HTML/CSS dans IronPDF offre une flexibilité dans la conception, une facilité de mise à jour et une compatibilité avec les technologies web, ce qui facilite la maintenance et l'amélioration des mises en page des rapports.

IronPDF prend-il en charge .NET 10 ?

Oui, IronPDF est compatible avec .NET 10, ce qui garantit que les développeurs peuvent profiter des dernières fonctionnalités et améliorations de .NET pour leurs besoins de génération de rapports.

Comment IronPDF améliore-t-il la vitesse de génération des rapports ?

IronPDF est optimisé pour la performance, ce qui lui permet de générer des rapports rapidement en traitant efficacement les HTML/CSS et en les rendant sous forme de documents PDF de haute qualité.

Curtis Chau
Rédacteur technique

Curtis Chau détient un baccalauréat en informatique (Université de Carleton) et se spécialise dans le développement front-end avec expertise en Node.js, TypeScript, JavaScript et React. Passionné par la création d'interfaces utilisateur intuitives et esthétiquement plaisantes, Curtis aime travailler avec des frameworks modernes ...

Lire la suite
Prêt à commencer?
Nuget Téléchargements 17,386,124 | Version : 2026.2 vient de sortir