Berichte in C# generieren; wie Crystal Reports (.NET 10)

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

die HTML-zu-PDF-Berichterstellung in C# .NET mit IronPDF ersetzt den proprietären .rpt-Designer von Crystal Reports durch standardmäßige HTML-, CSS- und Razor-Vorlagen und ermöglicht es .NET-Entwicklern, datengesteuerte Geschäftsberichte mit ihren bereits vorhandenen Webentwicklungskenntnissen zu erstellen. Dazu gehört die volle Unterstützung für dynamische Tabellen, JavaScript-gestützte Diagramme, bedingte Formatierung, Multidokumenten-Stapelverarbeitung und plattformübergreifende Bereitstellung in jeder Umgebung, in der .NET läuft.

TL;DR: Quickstart Guide

Dieses Tutorial behandelt den Ersatz von Crystal Reports durch HTML-to-PDF-Berichterstellung in C# .NET, von grundlegenden Vorlagen bis hin zur Stapelverarbeitung und zeitgesteuerten Erstellung.

  • Für wen ist es geeignet: .NET-Entwickler, die Crystal Reports ersetzen oder neue Berichtssysteme von Grund auf aufbauen.
  • Was Sie erstellen werden: Drei vollständige Berichtsimplementierungen (Verkaufsrechnung, Mitarbeiterverzeichnis, Inventarbericht), plus Chart.js-Visualisierungen, Kopf- und Fußzeilen mit Markenzeichen, Erstellung von Inhaltsverzeichnissen, Zusammenführung von Teilberichten und parallele Stapelverarbeitung.
  • Wo es läuft: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+, und .NET Standard 2.0. Keine reinen Windows-COM-Abhängigkeiten.
  • Wann sollte dieser Ansatz verwendet werden: Wenn die fehlende Unterstützung von .NET Core, die Windows-Bindung oder die komplexe Lizenzierung von Crystal Reports zu einem Engpass werden.
  • Warum es technisch wichtig ist: HTML/CSS wird plattformübergreifend identisch gerendert, lässt sich mit CI/CD integrieren und führt JavaScript für Diagramme aus - und das alles ohne proprietären Designer oder Gebühren pro Dokument.

Um den Codebeispielen zu folgen, installieren Sie IronPDF über NuGet (Install-Package IronPdf). Erstellen Sie Ihren ersten Bericht mit nur wenigen Codezeilen:

Nuget IconLegen Sie jetzt mit NuGet los, um PDFs zu erstellen:

  1. Installieren Sie IronPDF mit dem NuGet-Paketmanager.

    PM > Install-Package IronPdf

  2. Kopieren Sie diesen Codeausschnitt und führen Sie ihn aus.

    // 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. Bereitstellen zum Testen in Ihrer Live-Umgebung

    Beginnen Sie noch heute mit der Nutzung von IronPDF in Ihrem Projekt – mit einer kostenlosen Testversion.
    arrow pointer

Nachdem Sie IronPDF gekauft oder sich für eine 30-tägige Testversion angemeldet haben, fügen Sie Ihren Lizenzschlüssel am Anfang Ihrer Anwendung hinzu.

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

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

Nutzen Sie IronPDF heute kostenlos in Ihrem Projekt.

Erster Schritt:
green arrow pointer
NuGet Mit NuGet installieren

PM >  Install-Package IronPdf

Schauen Sie sich IronPDF auf NuGet für eine schnelle Installation an. Mit über 10 Millionen Downloads transformiert es die PDF-Entwicklung mit C#. Sie können auch das DLL oder den Windows Installer herunterladen.

Inhaltsverzeichnis

C# Report Generator: HTML-Vorlagen in PDF

Die HTML-zu-PDF-Generierung basiert auf einer linearen architektonischen Pipeline. Anstelle eines proprietären Dateiformats verwendet die Anwendung Standarddatenmodelle, um Razor-Ansichten oder HTML-Vorlagen zu füllen. Der resultierende HTML-String wird dann an eine Rendering-Engine wie IronPDF übergeben, die die visuelle Ausgabe als PDF-Dokument erfasst. Dieser Ansatz entkoppelt das Berichtsdesign von der Hosting-Umgebung, so dass der gleiche Code auf jeder Plattform ausgeführt werden kann, die .NET unterstützt.

Dieser Arbeitsablauf spiegelt die Standard-Webentwicklung wider. Front-End-Entwickler erstellen das Layout mit CSS und können es sofort in jedem Browser anzeigen. Die Backend-Entwickler binden die Daten dann mit C# ein. Diese Trennung ermöglicht es den Teams, ihre bestehenden Versionskontroll-, Codeüberprüfungs- und kontinuierlichen Bereitstellungsprozesse für Berichte genauso zu nutzen wie für den Rest der Anwendung.

HTML ermöglicht Funktionen, die in Crystal Reports nicht verfügbar sind: interaktive Diagramme, reaktionsfähige Tabellen und gemeinsame Stile für ein einheitliches Branding.

Warum Crystal Reports in .NET-Anwendungen ersetzen

Die Abkehr von Crystal Reports ist nicht das Ergebnis eines einzelnen katastrophalen Problems oder einer plötzlichen Aufgabe durch SAP. Vielmehr handelt es sich um eine Anhäufung von Reibungspunkten, die es insgesamt immer schwieriger machen, die Plattform für neue Projekte zu rechtfertigen und die Wartung bestehender Lösungen zu erschweren. Die Identifizierung dieser Schmerzpunkte verdeutlicht, warum viele Teams nach Alternativen suchen und welche Kriterien bei der Bewertung von Ersatzoptionen am wichtigsten sind.

Keine Unterstützung von .NET 8 oder .NET Core

Crystal Reports unterstützt weder .NET Core noch .NET 5-10. SAP hat in Foren erklärt, dass sie nicht planen, diese Unterstützung hinzuzufügen. Das SDK verwendet COM-Komponenten, die mit dem plattformübergreifenden .NET nicht kompatibel sind. Die Unterstützung von modernem .NET würde eine komplette Neufassung erfordern, die SAP abgelehnt hat.

Daher können Teams, die neue Anwendungen mit aktuellen .NET-Versionen erstellen, Crystal Reports nicht verwenden. Organisationen, die auf .NET 8 oder .NET 10 standardisiert sind, können sie nicht integrieren. Bei bestehenden Anwendungen muss bei einem Upgrade auf eine moderne .NET-Laufzeitumgebung zunächst das Berichtssystem ersetzt werden.

Komplexe Lizenzierung und versteckte Kosten

Bei der Lizenzierung von Crystal Reports wird zwischen Designerlizenzen, Laufzeitlizenzen, Serverimplementierungen und eingebetteter Nutzung unterschieden. Für Desktop-, Web- und Terminaldienste gelten unterschiedliche Regeln. Die Einhaltung von Vorschriften in einer Einrichtung kann in einer anderen zusätzliche Lizenzen erfordern. Wenn nach der Bereitstellung Lücken auftreten, entstehen unerwartete Kosten. Viele Unternehmen entscheiden, dass die Ungewissheit schlimmer ist als die Migration auf eine Lösung mit klarerer Lizenzierung.

Windows-Only Platform Lock-In

Crystal Reports läuft nur unter Windows mit dem älteren .NET Framework. Sie können diese Anwendungen nicht in Linux-Containern, Azure App Service on Linux, AWS Lambda oder Google Cloud Run bereitstellen. Da Unternehmen containerisierte, plattformunabhängige und serverlose Systeme verwenden, werden diese Einschränkungen immer wichtiger.

Entwicklungsteams, die Microservices entwickeln, stehen vor zusätzlichen Herausforderungen. Wenn neun Dienste in leichtgewichtigen Linux-Containern laufen, man aber Windows für Crystal Reports benötigt, ist die Bereitstellung komplizierter. Sie benötigen Windows-Container-Images, Windows-kompatibles Hosting und separate Bereitstellungseinstellungen. Der Berichterstattungsdienst wird zur Ausnahme und blockiert die Standardisierung.

Einrichten eines C# Berichtsgenerators in .NET 10

Die ersten Schritte mit IronPDF sind einfach. Installieren Sie die Bibliothek über NuGet wie jede andere .NET-Abhängigkeit. Es muss keine zusätzliche Software heruntergeladen werden, und es ist auch kein separater Runtime-Installer für Produktionsserver erforderlich.

Wählen Sie einen Template-Ansatz: Razor, HTML, oder Hybrid

IronPDF unterstützt drei verschiedene Ansätze zur Erstellung von Berichtsvorlagen. Jeder Ansatz bietet je nach Teamzusammensetzung, Projektanforderungen und langfristigen Wartungserwägungen spezifische Vorteile.

Razor Views bieten die beste Entwicklungserfahrung für Teams, die bereits im .NET-Ökosystem arbeiten. Stark typisierte Modelle mit vollständiger IntelliSense-Unterstützung in Visual Studio und VS Code, Kompilierzeitprüfung und die volle Leistungsfähigkeit von C# für Schleifen, Konditionale, Nullbehandlung und Stringformatierung sind verfügbar. Die Syntax von Razor ist denjenigen vertraut, die bereits ASP.NET Core-Anwendungen erstellt haben, sodass die Lernkurve, die mit Template-Engines aus anderen Ökosystemen verbunden ist, entfällt. Die Vorlagen werden im Projekt zusammen mit anderen Quelldateien gespeichert, nehmen an Refactoring-Operationen teil und werden als Teil des normalen Build-Prozesses kompiliert.

Plain HTML with String Interpolation eignet sich gut für einfachere Berichte oder Teams, die es vorziehen, die Vorlagen vollständig vom .NET-Code zu trennen. HTML-Vorlagen können als eingebettete Ressourcen gespeichert werden, die in die Assembly kompiliert werden, als externe Dateien, die neben der Anwendung bereitgestellt werden, oder sogar zur Laufzeit aus einer Datenbank oder einem Content-Management-System abgerufen werden. Die grundlegende Datenbindung verwendet string.Replace() für einzelne Werte oder eine leichtgewichtige Vorlagenbibliothek wie Scriban oder Fluid für fortgeschrittenere Szenarien. Dieser Ansatz maximiert die Übertragbarkeit und ermöglicht es Designern, Vorlagen ohne installierte .NET-Tools zu bearbeiten und nur einen Texteditor und einen Webbrowser für die Vorschau zu verwenden.

Hybridansätze kombinieren beide Techniken für Szenarien, die Flexibilität erfordern. Zum Beispiel könnte eine Razor-Ansicht gerendert werden, um die Haupt-HTML-Struktur zu generieren, und dann mit zusätzlichen String-Ersetzungen für dynamische Elemente, die nicht sauber in das Ansichtsmodell passen, nachbearbeitet werden. Alternativ kann eine HTML-Vorlage geladen werden, die von einem Nicht-Entwickler entworfen wurde, und es können Razor-Teilansichten verwendet werden, um nur die komplexen, datengesteuerten Abschnitte zu rendern, bevor alles kombiniert wird. Die HTML-zu-PDF-Konvertierung ist unabhängig von der HTML-Quelle, so dass Sie je nach den Anforderungen des jeweiligen Berichts verschiedene Ansätze wählen können.

Angesichts dieser Optionen konzentriert sich dieses Tutorial in erster Linie auf Razor-Ansichten, da diese die beste Balance zwischen Typsicherheit, Wartbarkeit und Funktionsreichtum für typische Geschäftsberichtsszenarien bieten. Die Fähigkeiten lassen sich direkt übertragen, wenn zukünftige Anforderungen die Arbeit mit einfachen HTML-Vorlagen beinhalten, da beide Methoden HTML-Strings erzeugen.

Erstellung eines datengesteuerten PDF-Berichts in C

In diesem Abschnitt wird die vollständige Erstellung eines Verkaufsrechnungsberichts von Anfang bis Ende demonstriert. Das Beispiel deckt das grundlegende Muster ab, das für alle Berichte verwendet wird: Definieren eines Modells, das die Daten strukturiert, Erstellen eines Razor-Templates, das die Daten in formatiertes HTML umwandelt, Rendern dieses Templates in einen HTML-String und Konvertieren des HTML in ein PDF-Dokument, das angezeigt, per E-Mail versendet oder archiviert werden kann.

Erstellen Sie die HTML/CSS-Berichtsvorlage

Der erste Schritt besteht darin, das Datenmodell zu definieren. Eine echte Rechnung erfordert Kundeninformationen, Einzelposten mit Beschreibungen und Preisen, berechnete Gesamtsummen, die Behandlung von Steuern und Branding-Elemente des Unternehmens. Die Modellklassen sollten so strukturiert sein, dass sie diese Gruppierungen widerspiegeln:

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

Die berechneten Eigenschaften für Subtotal, TaxAmount und GrandTotal sind in dem Modell enthalten. Diese Berechnungen gehören in das Modell und nicht in die Vorlage, damit sich die Razor-Ansichten auf die Präsentation konzentrieren, während das Modell die Geschäftslogik behandelt. Diese Trennung macht Unit-Tests einfach, so dass Berechnungen überprüft werden können, ohne dass HTML gerendert werden muss.

Erstellen Sie nun die Razor-Ansicht, die dieses Modell in eine professionell formatierte Rechnung umwandelt. Speichern Sie diese als InvoiceTemplate.cshtml in Ihrem Ordner 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

Das in dieser Vorlage eingebettete CSS übernimmt die gesamte visuelle Gestaltung wie Farben, Schriftarten, Abstände und Tabellenformatierung. IronPDF unterstützt auch moderne CSS-Funktionen wie Flexbox, Grid-Layouts und CSS-Variablen. Die gerenderte PDF-Datei stimmt exakt mit der Druckvorschau von Chrome überein, was die Fehlersuche vereinfacht: Wenn etwas in der PDF-Datei falsch aussieht, öffnen Sie die HTML-Datei in einem Browser und verwenden Sie die Entwicklerwerkzeuge, um die Stile zu überprüfen und anzupassen.

Daten an die Vorlage binden

Nach der Erstellung des Modells und der Vorlage muss die PDF-Datei über den ChromePdfRenderer von IronPDF gerendert werden. Der wichtigste Schritt ist die Konvertierung der Razor-Ansicht in eine HTML-Zeichenkette und die anschließende Übergabe dieser Zeichenkette an den Renderer:

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

Für einfachere Szenarien, in denen Sie nicht das vollständige ASP.NET Core MVC-Setup benötigen, wie z. B. in einer Konsolenanwendung oder einem Hintergrunddienst, können Sie einfach HTML-Strings mit Interpolation und StringBuilder für die dynamischen Teile verwenden.

Musterausgabe

Kopfzeilen, Fußzeilen und Seitenzahlen hinzufügen

Professionelle Berichte enthalten in der Regel einheitliche Kopf- und Fußzeilen auf allen Seiten, die das Firmenlogo, den Titel des Dokuments, das Erstellungsdatum und die Seitenzahl enthalten. IronPDF bietet zwei Ansätze für die Implementierung dieser Elemente: textbasierte Kopfzeilen für einfache Inhalte, die nur eine minimale Formatierung erfordern, und HTML-Kopfzeilen für die vollständige Kontrolle über das Styling mit Logos und benutzerdefinierten Layouts.

Textbasierte Kopfzeilen eignen sich gut für grundlegende Informationen und werden schneller gerendert, da sie kein zusätzliches HTML-Parsing erfordern:

: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

Zu den verfügbaren Platzhaltern gehören {page} für die aktuelle Seitenzahl, {total-pages} für die Gesamtseitenzahl des Dokuments, {date} und {time} für den Zeitstempel der Generierung, {url} für die Quell-URL, wenn von einer Webseite gerendert wird, und {html-title} und {pdf-title} für die Dokumententitel. Diese Platzhalter werden beim Rendern automatisch ersetzt.

Für Kopfzeilen mit Logos, benutzerdefinierten Schriftarten oder komplexen mehrspaltigen Layouts sollten Sie HTML-Kopfzeilen verwenden, die eine vollständige CSS-Gestaltung unterstützen:

: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

Musterausgabe

Erstellen von dynamischen Tabellen und sich wiederholenden Abschnitten

Berichte müssen oft Datensammlungen anzeigen, die sich über mehrere Seiten erstrecken. Die Schleifenkonstrukte von Razor erledigen dies auf natürliche Weise, indem sie über Sammlungen iterieren und Tabellenzeilen oder Kartenelemente für jedes Element erzeugen.

Hier ist ein vollständiges Beispiel für ein Mitarbeiterverzeichnis, das eine gruppierte Datendarstellung mit Abteilungsbereichen zeigt:

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

Die CSS-Eigenschaft page-break-inside: avoid für die Abteilungsklasse weist den PDF-Renderer an, Abteilungsabschnitte nach Möglichkeit auf einer einzigen Seite zusammenzuhalten. Wenn der Inhalt einer Abteilung zu einem Seitenumbruch in der Mitte des Abschnitts führen würde, verschiebt der Renderer stattdessen den gesamten Abschnitt auf die nächste Seite. Der Selektor .department:not(:first-child) mit page-break-before: always zwingt jede Abteilung nach der ersten, auf einer neuen Seite zu beginnen, wodurch eine saubere Trennung der Abschnitte im gesamten Verzeichnis entsteht.

Musterausgabe

Erweiterte C# Berichtserstellung mit IronPDF

Geschäftsberichte erfordern häufig Funktionen, die über statische Tabellen und Text hinausgehen. Diagramme visualisieren Trends, die in tabellarischer Form nur mühsam zu verstehen wären. Die bedingte Formatierung lenkt die Aufmerksamkeit auf Elemente, die eine Aktion erfordern. In Teilberichten werden Daten aus verschiedenen Quellen zu zusammenhängenden Dokumenten kombiniert. Dieser Abschnitt befasst sich mit der Implementierung jeder dieser Funktionen unter Verwendung der Chromium-Rendering-Engine von IronPDF.

Hinzufügen von Diagrammen und Grafiken zu PDF-Berichten

Da JavaScript während des Renderings ausgeführt wird, können Sie jede clientseitige Diagrammbibliothek verwenden, um Visualisierungen direkt in Ihren Berichten zu erstellen. Das Diagramm wird als Teil der Seite gerastert und erscheint in der endgültigen PDF-Datei genauso wie auf dem Bildschirm. Chart.js bietet ein hervorragendes Gleichgewicht zwischen Einfachheit, Leistungsfähigkeit und Dokumentation für die meisten Berichtsanforderungen.

Binden Sie Chart.js von einem CDN ein und konfigurieren Sie Ihr Diagramm mit Daten, die von Ihrem C#-Modell serialisiert wurden:

@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

Konfigurieren Sie beim Rendern von Seiten mit JavaScript-generierten Inhalten den Renderer so, dass er wartet, bis die Ausführung der Skripte abgeschlossen ist, bevor er die Seite erfasst:

: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

Musterausgabe

Bedingte Formatierung und Geschäftslogik anwenden

Inventarberichte profitieren von visuellen Indikatoren, die die Aufmerksamkeit sofort auf Punkte lenken, die Maßnahmen erfordern. Anstatt die Benutzer zu zwingen, Hunderte von Zeilen auf der Suche nach Problemen zu durchforsten, macht die bedingte Formatierung Ausnahmen visuell deutlich. Verwenden Sie die Inline-Ausdrücke von Razor, um CSS-Klassen auf der Grundlage von Datenwerten anzuwenden:


@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

Musterausgabe

Erstellen von Unterberichten und Abschnittswechseln

Um unabhängig voneinander erstellte Berichte in einem Dokument zu kombinieren, verwenden Sie die Merge-Funktion von 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

Musterausgabe

Erstellen eines Inhaltsverzeichnisses

IronPDF kann automatisch ein Inhaltsverzeichnis auf der Grundlage von Überschriftenelementen in Ihrem HTML generieren:

: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

Migrieren von Crystal Reports zu IronPDF

Die Migration eines etablierten Berichtssystems erfordert eine sorgfältige Planung, um Unterbrechungen zu minimieren und gleichzeitig die Gelegenheit zur Modernisierung und Vereinfachung zu nutzen. Sie werden schneller vorankommen, wenn Sie verstehen, wie Crystal Reports-Konzepte auf den HTML-basierten Ansatz abgebildet werden, anstatt zu versuchen, jede Funktion buchstäblich zu replizieren oder jede Eigenheit der Originalberichte zu bewahren.

Konzepte von Crystal Reports auf IronPDF übertragen

Das Verständnis des konzeptionellen Mappings hilft Ihnen bei der systematischen Übersetzung bestehender Berichte:

Crystal Reports IronPDF-Äquivalent
Abschnitte des Berichts HTML-Divs mit CSS-Seitenumbruch-Eigenschaften
Parameter-Felder Modelleigenschaften, die an Razor-Ansichten übergeben werden
Formel-Felder C# berechnete Eigenschaften in Modellklassen
Laufende Summen LINQ-Aggregationen
Teilberichte Teilansichten oder zusammengeführte PDF-Dokumente
Gruppierung/Sortierung LINQ-Operationen vor der Übergabe von Daten an eine Vorlage
Kreuztabellen-Berichte HTML-Tabellen mit verschachtelten Schleifen
Bedingte Formatierung Razor @if-Blöcke mit CSS-Klassen

Beste Strategie für die Konvertierung von .rpt-Vorlagen

Versuchen Sie nicht, .rpt-Dateien programmatisch zu parsen. Behandeln Sie stattdessen die vorhandenen PDF-Ausgaben als visuelle Spezifikationen und bauen Sie die Logik mithilfe einer systematischen Vier-Schritte-Strategie neu auf:

  1. Inventarisierung: Katalogisieren Sie alle .rpt-Dateien mit ihrem Zweck, ihren Datenquellen und ihrer Nutzungshäufigkeit. Entfernen Sie veraltete Berichte, um den Umfang der Migration zu verringern.

  2. Priorisieren: Migrieren Sie zuerst die Berichte mit hoher Frequenz. Konzentrieren Sie sich auf Berichte mit einfachen Layouts oder anhaltenden Wartungsproblemen.

  3. Referenz: Exportieren Sie vorhandene Crystal Reports als PDFs. Verwenden Sie diese als visuelle Spezifikationen für die Entwickler, um sie anzupassen.

  4. Validieren: Testen Sie mit Produktionsdatenmengen. Vorlagen, die bei 10 Zeilen sofort gerendert werden, können bei 10.000 Zeilen langsam sein.

Batch-Berichterstellung und Zeitplanung in .NET

Produktionssysteme müssen oft viele Berichte gleichzeitig erstellen oder Berichtsaufträge nach Zeitplan ausführen. Das thread-sichere Design von IronPDF unterstützt beide Szenarien effizient.

Mehrere Berichte parallel generieren

Für die Stapelverarbeitung verwenden Sie Parallel.ForEachAsync oder asynchrone Muster mit 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

Musterausgabe

Das Beispiel für die Stapelverarbeitung erzeugt mehrere Rechnungen parallel. Hier ist eine der generierten Batch-Rechnungen:

Berichterstellung mit ASP.NET Core Hintergrunddiensten integrieren

Die zeitgesteuerte Berichterstellung fügt sich nahtlos in die Infrastruktur der gehosteten Dienste von ASP.NET Core ein:

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

Das komplette Testprojekt herunterladen

Alle Codebeispiele aus diesem Tutorial sind in einem lauffähigen .NET 10-Testprojekt verfügbar. Der Download enthält den kompletten Quellcode, Datenmodelle, HTML-Vorlagen und einen Test-Runner, der alle oben gezeigten Beispiel-PDFs erzeugt.

Nächste Schritte

Die Beispiele in diesem Leitfaden zeigen, dass IronPDF die gesamte Bandbreite der Anforderungen an Geschäftsberichte abdeckt: einfache Rechnungen mit Einzelposten und Summen, komplexe Mitarbeiterverzeichnisse mit gruppierten Daten und Fotos, Inventurberichte Berichte mit bedingter Formatierung und Diagrammen, Stapelverarbeitung von Hunderten von Dokumenten parallel und zeitgesteuerte Erstellung durch Hintergrunddienste.

Wenn Sie Alternativen für eine bestehende Crystal Reports-Implementierung evaluieren, beginnen Sie mit einem einzigen hochwertigen Bericht. Erstellen Sie die Übersetzung mit den hier gezeigten HTML-to-PDF-Mustern, vergleichen Sie die Entwicklungserfahrung und die Qualität der Ausgabe und erweitern Sie sie dann. Viele Teams stellen fest, dass ihr erster konvertierter Bericht einige Stunden dauert, da sie Muster und Basisvorlagen erstellen, während nachfolgende Berichte nur wenige Minuten in Anspruch nehmen, da sie Razor-Vorlagen und Styling wiederverwenden. Der pixel-perfect rendering guide beschreibt, wie die Crystal Reports-Ausgabe mit CSS exakt angepasst werden kann.

Bereit, loszulegen? Laden Sie IronPDF herunter und probieren Sie es mit einer kostenlosen Testversion aus. Dieselbe Bibliothek eignet sich für alles, vom Rendering einzelner Berichte bis zur Batch-Generierung großer Mengen in .NET-Umgebungen. Wenn Sie Fragen zur Migration von Berichten haben oder eine Anleitung zur Architektur benötigen, wenden Sie sich an unser technisches Support-Team.

Häufig gestellte Fragen

Was ist IronPDF?

IronPDF ist eine C#-Bibliothek, die es Entwicklern ermöglicht, PDF-Dokumente programmgesteuert zu erstellen, zu bearbeiten und zu generieren, und die eine moderne Alternative zu herkömmlichen Berichtswerkzeugen wie Crystal Reports darstellt.

Wie kann IronPDF als Alternative zu Crystal Reports dienen?

IronPDF bietet einen flexiblen und modernen Ansatz für die Erstellung von Berichten, indem es Entwicklern die Verwendung von HTML/CSS-Vorlagen ermöglicht, die im Gegensatz zu der eher starren Struktur von Crystal Reports leicht gestaltet und geändert werden können.

Kann ich mit IronPDF Rechnungen erstellen?

Ja, mit IronPDF können Sie detaillierte und individuelle Rechnungen mithilfe von HTML/CSS-Vorlagen erstellen, die die Gestaltung professionell aussehender Dokumente erleichtern.

Ist es möglich, mit IronPDF Mitarbeiterverzeichnisse zu erstellen?

Unbedingt. Mit IronPDF können Sie umfassende Mitarbeiterverzeichnisse erstellen, indem Sie dynamische Daten und HTML/CSS für eine klare und übersichtliche Darstellung nutzen.

Wie kann IronPDF bei Bestandsberichten helfen?

IronPDF kann die Erstellung von Bestandsberichten durch die Verwendung von HTML/CSS-Vorlagen rationalisieren, die Daten dynamisch auffüllen können, um aktuelle und visuell ansprechende Berichte zu erstellen.

Was sind die Vorteile der Verwendung von HTML/CSS-Vorlagen in IronPDF?

Die Verwendung von HTML/CSS-Vorlagen in IronPDF bietet Flexibilität beim Design, einfache Aktualisierungen und Kompatibilität mit Webtechnologien, was die Pflege und Verbesserung von Berichtslayouts erleichtert.

Unterstützt IronPDF .NET 10?

Ja, IronPDF ist mit .NET 10 kompatibel, so dass Entwickler die neuesten .NET-Funktionen und -Verbesserungen für ihre Berichtserstellung nutzen können.

Wie verbessert IronPDF die Geschwindigkeit der Berichtserstellung?

IronPDF ist auf Leistung optimiert und ermöglicht die schnelle Erstellung von Berichten durch die effiziente Verarbeitung von HTML/CSS und deren Umwandlung in hochwertige PDF-Dokumente.

Curtis Chau
Technischer Autor

Curtis Chau hat einen Bachelor-Abschluss in Informatik von der Carleton University und ist spezialisiert auf Frontend-Entwicklung mit Expertise in Node.js, TypeScript, JavaScript und React. Leidenschaftlich widmet er sich der Erstellung intuitiver und ästhetisch ansprechender Benutzerschnittstellen und arbeitet gerne mit modernen Frameworks sowie der Erstellung gut strukturierter, optisch ansprechender ...

Weiterlesen
Bereit anzufangen?
Nuget Downloads 17,386,124 | Version: 2026.2 gerade veröffentlicht