# Generate Reports in C# Like Raporty Crystal Reports (.NET 10)
**Generowanie raportu HTML-do-PDF** w **C# .NET** z IronPDF zastępuje zastrzeżonego projektanta `.rpt` Raporty Crystal Reports za pomocą standardowych **szablonów HTML, CSS i Razor**, umożliwiając deweloperom .NET tworzenie biznesowych raportów opartych na danych, korzystając z umiejętności tworzenia stron internetowych, które już posiadają. Obejmuje to pełne wsparcie dla dynamicznych tabel, **wykresów opartych na JavaScript**, formatowania warunkowego, **przetwarzania wsadowego wielu dokumentów** oraz wdrażania między platformami w dowolnym środowisku uruchamiającym .NET.
*as-heading:2(TL;DR: Przewodnik Quickstart)*
Ten samouczek obejmuje zastępowanie Raporty Crystal Reports generowaniem raportów z *HTML do PDF* w C# .NET, od podstawowych szablonów po przetwarzanie wsadowe i generowanie zaplanowane.
- **Dla kogo jest to przeznaczone:** Programiści .NET zastępujący Raporty Crystal Reports lub budujący nowe systemy raportowania od podstaw.
- **Co zbudujesz:** Trzy kompletne implementacje raportów (faktura sprzedaży, katalog pracowników, raport inwentaryzacyjny), plus wizualizacje Chart.js, markowane nagłówki/stopki, generowanie spisu treści, scałanie podraportów i równoległe przetwarzanie wsadowe.
- **Gdzie działa:** .NET 10, .NET 8 LTS, .NET Framework 4.6.2+ oraz .NET Standard 2.0. Brak zależności od składników COM tylko dla Windows.
- **Kiedy używać tego podejścia:** Kiedy brak wsparcia Raporty Crystal Reports dla .NET Core, uzależnienie od Windows lub złożone licencjonowanie stają się wąskim gardłem.
- **Dlaczego to istotne technicznie:** HTML/CSS renderuje się identycznie na wszystkich platformach, integruje się z CI/CD i wykonuje JavaScript dla wykresów, wszystko bez zastrzeżonego projektanta czy opłat za dokument.
Aby kontynuować z przykładami kodu, zainstaluj `IronPdf` przez NuGet (`Install-Package IronPdf`). Wygeneruj swój pierwszy raport kilkoma linijkami kodu:
```cs
:title=Convert an HTML report template to PDF in one line!
// 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");
```
Po zakupie lub zapisaniu się na 30-dniowy okres próbny IronPDF, dodaj swój klucz licencyjny na początku aplikacji.
```cs
IronPdf.License.LicenseKey = "KEY";
```
!!!--LIBRARY_START_TRIAL_BLOCK--!!!
!!!--LIBRARY_NUGET_INSTALL_BLOCK--!!!
*as-heading:2(Spis treści)*
- **TL;DR: Przewodnik Szybkiego Startu**
- [Krótki Przegląd](#anchor-tldr-quickstart-guide)
- **Szablony HTML do PDF Architektura**
- [Generator Raportów C#: Szablony HTML do PDF](#anchor-c-report-generator-html-templates-to-pdf)
- **Dlaczego zastąpić Raporty Crystal Reports w aplikacjach .NET**
- [Brak wsparcia dla .NET 8 lub .NET Core](#anchor-no-net-8-or-net-core-support)
- [Złożone Licencjonowanie i Ukryte Koszty](#anchor-complex-licensing-and-hidden-costs)
- [Uzależnienie od Platformy Windows](#anchor-windows-only-platform-lock-in)
- **Ustawiając Generator Raportów C# w .NET 10**
- [Wybierz Podejście do Szablonu: Razor, HTML lub Hybrydowe](#anchor-choose-a-template-approach-razor-html-or-hybrid)
- **Budowanie Raportu PDF Opartego na Danych w C#**
- [Utwórz Szablon Raportu HTML/CSS](#anchor-create-the-htmlcss-report-template)
- [Powiąż Dane z Szablonem](#anchor-bind-data-to-the-template)
- [Dodaj Nagłówki, Stopki i Numery Stron](#anchor-add-headers-footers-and-page-numbers)
- [Utwórz Dynamiczne Tabele i Powtarzające Się Sekcje](#anchor-create-dynamic-tables-and-repeating-sections)
- **Zaawansowane Generowanie Raportów C# z IronPDF**
- [Dodaj Wykresy i Diagramy do Raportów PDF](#anchor-add-charts-and-graphs-to-pdf-reports)
- [Zastosuj Warunkowe Formatowanie i Logikę Biznesową](#anchor-apply-conditional-formatting-and-business-logic)
- [Utwórz Podraporty i Przerwy Sekcji](#anchor-create-sub-reports-and-section-breaks)
- [Generuj Spis Treści](#anchor-generate-a-table-of-contents)
- **Migracja z Raporty Crystal Reports do IronPDF**
- [Mapowanie Koncepcji Raporty Crystal Reports do IronPDF](#anchor-map-crystal-reports-concepts-to-ironpdf)
- [Najlepsza Strategia do Konwersji Szablonów .rpt](#anchor-best-strategy-for-converting-rpt-templates)
- **Generowanie i Harmonogramowanie Raportów Wsadowych w .NET**
- [Generuj Wiele Raportów Równolegle](#anchor-generate-multiple-reports-in-parallel)
- [Integruj Generowanie Raportów z ASP.NET Core Background Services](#anchor-integrate-report-generation-with-aspnet-core-background-services)
- **Pobierz Pełny Projekt Testowy**
- [Pobierz Projekt Testowy](#anchor-download-the-complete-test-project)
<a id="anchor-c-report-generator-html-templates-to-pdf"></a>
## C# Report Generator: HTML Templates to PDF
Generowanie z HTML do PDF opiera się na liniowym łańcuchu architektonicznym. Zamiast zastrzeżonego formatu plików, aplikacja używa standardowych modeli danych do wypełnienia widoków Razor lub szablonów HTML. Uzyskany ciąg HTML jest następnie przekazywany do silnika renderowania jak `IronPdf`, który rejestruje wizualny wynik jako dokument PDF. To podejście oddziela projektowanie raportu od środowiska hostingowego, pozwalając na uruchamianie dokładnie tego samego kodu na dowolnej platformie wspierającej .NET.
Ten przepływ pracy odzwierciedla standardowy rozwój webowy. Programiści frontendowi tworzą układ za pomocą CSS i natychmiast go podglądają w dowolnej przeglądarce. Następnie programiści backendowi wiążą dane z użyciem C#. To rozdzielenie pozwala zespołom na korzystanie z istniejącej kontroli wersji, przeglądu kodu i procesów ciągłej wdrożenia dla raportów tak samo jak dla reszty aplikacji.
HTML umożliwia funkcje niedostępne w Raporty Crystal Reports: interaktywne wykresy, responsywne tabele i wspólne style dla jednolitego brandingu.
## Dlaczego zastąpić Raporty Crystal Reports w aplikacjach .NET
Migracja z Raporty Crystal Reports nie jest wynikiem pojedynczego katastroficznego problemu czy nagłego porzucenia przez SAP. Zamiast tego, to kumulacja punktów tarcia, które razem sprawiają, że platforma staje się coraz trudniejsza do uzasadnienia dla nowych projektów i bardziej wymagająca do utrzymania w istniejących rozwiązaniach. Zidentyfikowanie tych punktów bólu wyjaśnia, dlaczego wiele zespołów poszukuje alternatyw i jakie kryteria są najważniejsze przy ocenie opcji zamienników.
## Brak wsparcia dla .NET 8 lub .NET Core
Raporty Crystal Reports nie wspiera .NET Core ani .NET 5-10. SAP stwierdziło na forach, że nie planują dodania wsparcia. SDK używa komponentów COM, które są niekompatybilne z międzyplatformowym .NET. Wsparcie dla nowoczesnego .NET wymagałoby całkowitego przepisania, czego SAP odmówiło.
W rezultacie zespoły budujące nowe aplikacje na bieżących wersjach .NET nie mogą używać Raporty Crystal Reports. Organizacje znormalizowane na .NET 8 lub .NET 10 nie są w stanie go zintegrować. W przypadku istniejących aplikacji, aktualizacja do nowoczesnego środowiska .NET wymaga najpierw zastąpienia systemu raportowania.
## Złożone Licencjonowanie i Ukryte Koszty
Licencjonowanie Raporty Crystal Reports odróżnia licencje projektanta, licencje runtime, wdrożenia serwera i użycie osadzone. Zasady różnią się dla usług desktop, web i terminal. Zgodność w jednej konfiguracji może wymagać dodatkowych licencji w innej. Jeśli pojawią się luki po wdrożeniu, nieoczekiwane koszty pojawiają się. Wiele organizacji decyduje, że niepewność jest gorsza niż migracja do rozwiązania z jaśniejszym licencjonowaniem.
## Uzależnienie od Platformy Windows
Raporty Crystal Reports działa tylko na Windows z dziedzictwem .NET Framework. Nie można wdrażać tych aplikacji do kontenerów Linux, Azure App Service na Linuxie, AWS Lambda ani Google Cloud Run. Gdy organizacje używają systemów kontenerowych, neutralnych platformowo i serverless, te ograniczenia stają się istotniejsze.
Zespoły programistyczne budujące mikroserwisowe systemy napotykają dodatkowe wyzwania. Jeśli dziewięć usług działa w lekkich kontenerach Linux, a jedna potrzebuje Windows dla Raporty Crystal Reports, wdrożenie jest bardziej skomplikowane. Potrzebujesz obrazów kontenerowych Windows, kompatybilnego z Windows hostingu i oddzielnych ustawień wdrożenia. Usługa raportowania staje się wyjątkiem, blokującym standaryzację.
## Set Up a C# Report Generator in .NET 10
Rozpoczęcie z IronPDF jest proste. Zainstaluj bibliotekę przez NuGet jak każdą inną zależność .NET. Nie ma potrzeby pobierać dodatkowego oprogramowania ani oddzielnego instalatora runtime do serwerów produkcyjnych.
### Wybierz Podejście do Szablonu: Razor, HTML lub Hybrydowe
IronPDF wspiera trzy różne podejścia do budowania szablonów raportów. Każde podejście oferuje specyficzne zalety w zależności od składu zespołu, wymagań projektu i długoterminowych rozważań dotyczących utrzymania.
**Widoki Razor** oferują najbogatsze środowisko projektowe dla zespołów już pracujących w ekosystemie .NET. Modele silnie typowane z pełnym wsparciem IntelliSense w Visual Studio i VS Code, sprawdzenie w czasie kompilacji oraz pełna moc C# dla pętli, konstrukcji warunkowych, obsługi null i formatowania ciągów znaków są dostępne. Składnia Razor jest znana tym, którzy budowali aplikacje ASP.NET Core, eliminując krzywą uczenia się związaną z silnikami szablonów z innych ekosystemów. Szablony znajdują się w projekcie obok innych plików źródłowych, biorą udział w operacjach refactoringu i kompilują się w ramach normalnego procesu budowy.
**Czysty HTML z interpolacją ciągów** dobrze sprawdza się przy prostszych raportach lub w zespołach, które wolą utrzymywać szablony całkowicie oddzielnie od kodu .NET. Szablony HTML mogą być przechowywane jako zasoby wkompilowane w assembly, zewnętrzne pliki implementowane obok aplikacji, a nawet odzyskane z bazy danych lub systemu zarządzania treścią w czasie wykonywania. Podstawowe wiązanie danych wykorzystuje `string.Replace()` dla pojedynczych wartości lub lekką bibliotekę szablonów jak Scriban lub Fluid dla bardziej zaawansowanych scenariuszy. To podejście maksymalizuje przenośność, pozwalając projektantom edytować szablony bez żadnych narzędzi .NET zainstalowanych, używając jedynie edytora tekstu i przeglądarki internetowej do podglądu.
**Podejścia hybrydowe** łączą obie techniki dla scenariuszy wymagających elastyczności. Na przykład, widok Razor może być użyty do generowania głównej struktury HTML, a następnie przetwarzany z dodatkowymi zamianami ciągów dla dynamicznych elementów, które nie pasują czysto do modelu widoku. Alternatywnie, zaprojektowany przez niedevelopera szablon HTML może być załadowany, a częściowe widoki Razor mogą być użyte do renderowania tylko złożonych, opartych na danych sekcji przed połączeniem całości. Konwersja z HTML do PDF jest niezależna od źródła HTML, co pozwala na mieszanie podejść w zależności od potrzeb każdego raportu.
Biorąc pod uwagę te opcje, ten samouczek skupia się głównie na widokach Razor, ponieważ oferują one najlepszy balans bezpieczeństwa typów, utrzymania i bogactwa funkcji dla typowych scenariuszy raportowania biznesowego. Umiejętności przenoszą się bezpośrednio, jeśli przyszłe wymagania obejmują pracę z czystymi szablonami HTML, ponieważ obie metody produkują łańcuchy HTML.
## Build a Data-Driven PDF Report in C#
Ta sekcja pokazuje pełne utworzenie raportu faktury sprzedażowej, od początku do końca. Przykład obejmuje kluczowy wzór używany do wszystkich raportów: definiowanie modelu strukturującego dane, tworzenie szablonu Razor, który przekształca dane w sformatowany HTML, renderowanie tego szablonu do łańcucha HTML i konwersję HTML do dokumentu PDF gotowego do podglądu, wysyłki e-mailem lub archiwizacji.
### Utwórz Szablon Raportu HTML/CSS
Pierwszym krokiem jest zdefiniowanie modelu danych. Prawdziwa faktura wymaga informacji o kliencie, pozycji z opisem i cenami, obliczonych sum, obsługi podatków i elementów brandingu firmy. Klasy modelowe powinny być zorganizowane, aby odzwierciedlały te grupy:
```csharp
// 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;
}
```
Obliczone właściwości dla `Subtotal`, `TaxAmount` i `GrandTotal` są uwzględnione w modelu. Te obliczenia należą do modelu, a nie do szablonu, dzięki czemu widoki Razor skupiają się na prezentacji, podczas gdy model obsługuje logikę biznesową. To rozdzielenie ułatwia testowanie jednostkowe, pozwalające zweryfikować obliczenia bez renderowania jakiegokolwiek HTML.
Teraz utwórz widok Razor, który przekształca ten model w profesjonalnie sformatowaną fakturę. Zapisz to jako `InvoiceTemplate.cshtml` w swoim folderze Views:
```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">
<!-- Company header and invoice details -->
<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>
<!-- Bill to address -->
<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>
<!-- Line items table -->
<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>
<!-- Totals -->
<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>
<!-- Footer message -->
<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>
```
Osadzony w tym szablonie CSS obsługuje całe stylowanie wizualne, takie jak kolory, czcionki, odstępy i formatowanie tabel. IronPDF obsługuje również nowoczesne funkcje CSS, takie jak flexbox, siatki gridowe i zmienne CSS. Generowany PDF dokładnie odpowiada podglądowi wydruku Chrome, co sprawia, że debugowanie jest proste: jeśli coś wygląda źle w PDF, otwórz HTML w przeglądarce i użyj narzędzi dewelopera do inspekcji i dostosowania stylów.
### Powiąż Dane z Szablonem
Z modelem i szablonem na miejscu, renderowanie PDF wymaga połączenia ich poprzez `ChromePdfRenderer` IronPDF. Kluczowym krokiem jest konwersja widoku Razor do łańcucha HTML, a następnie przekazanie tego łańcucha do renderera:
```csharp
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();
}
}
```
Dla prostszych scenariuszy, gdy nie potrzebujesz pełnej konfiguracji ASP.NET Core MVC, jak w aplikacji konsolowej czy serwisie w tle, możesz po prostu używać ciągów HTML z interpolacją i `StringBuilder` dla dynamicznych części.
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/01-basic-invoice.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-add-headers-footers-and-page-numbers"></a>
### Dodaj Nagłówki, Stopki i Numery Stron
Profesjonalne raporty zazwyczaj zawierają spójne nagłówki i stopki na wszystkich stronach, wyświetlające branding firmy, tytuły dokumentów, daty generacji i numery stron. IronPDF zapewnia dwa podejścia do implementacji tych elementów: nagłówki oparte na tekście dla prostych treści wymagających minimalnego formatowania oraz nagłówki HTML dla pełnej kontroli nad stylizacją z logo i niestandardowymi układami.
Nagłówki oparte na tekście dobrze działają dla podstawowych informacji i renderują się szybciej, ponieważ nie wymagają dodatkowego parsowania HTML:
```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;
```
Dostępne pola scalania obejmują `{page}` dla obecnego numeru strony, `{total-pages}` dla całkowitej liczby stron dokumentu, `{date}` i `{time}` dla znaczników czasowych generacji, `{url}` dla źródłowego URL jeśli renderowane z strony internetowej, oraz `{html-title}` i `{pdf-title}` dla tytułów dokumentów. Te zastępstwa są automatycznie wykonywane podczas renderowania.
Dla nagłówków z logo, niestandardowymi czcionkami lub złożonymi wielokolumnowymi układami użyj nagłówków HTML, które wspierają pełne stylizowanie CSS:
```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
};
```
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/03-text-headers-footers.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-create-dynamic-tables-and-repeating-sections"></a>
### Utwórz Dynamiczne Tabele i Powtarzające Się Sekcje
Raporty często muszą wyświetlać kolekcje danych obejmujących wiele stron. Pętle Razor zajmują się tym naturalnie, iterując po kolekcjach i generując wiersze tabel lub elementy kartowe dla każdego elementu.
Oto kompletny przykład Katalogu Pracowników pokazującego prezentację danych z grupowanymi działami:
```csharp
// 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; }
}
```
Właściwość CSS `page-break-inside: avoid` na klasie department mówi rendererowi PDF, aby utrzymał sekcje department razem na jednej stronie, jeśli to możliwe. Jeśli zawartość działu spowodowałaby przerwanie strony w środku sekcji, renderer przesuwa całą sekcję na następną stronę. Selektor `.department:not(:first-child)` z `page-break-before: always` wymusza, aby każdy department po pierwszym rozpoczynał się na nowej stronie, tworząc czystą separację sekcji w całym katalogu.
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/02-employee-directory.pdf" width="100%" height="400px">
</iframe>
## Advanced C# Report Generation With IronPDF
Raporty biznesowe często wymagają możliwości wykraczających poza statyczne tabele i tekst. Wykresy wizualizują trendy, które byłyby uciążliwe do zrozumienia w formie tabelarycznej. Formatowanie warunkowe przyciąga uwagę do pozycji wymagających działania. Podraporty łączą dane z wielu źródeł w spójne dokumenty. Ta sekcja obejmuje implementację każdej z tych funkcji z wykorzystaniem silnika renderowania Chromium IronPDF.
<a id="anchor-add-charts-and-graphs-to-pdf-reports"></a>
### Dodaj Wykresy i Diagramy do Raportów PDF
Ponieważ JavaScript wykonuje się podczas renderowania, możesz użyć dowolnej biblioteki wykresów po stronie klienta, aby generować wizualizacje bezpośrednio w swoich raportach. Wykres jest rastrowany jako część strony i pojawia się w końcowym PDF dokładnie tak, jak na ekranie. Chart.js oferuje doskonałą równowagę między prostotą, możliwościami i dokumentacją dla większości potrzeb raportowania.
Dołącz Chart.js z CDN i skonfiguruj swój wykres z danymi zserializowanymi z modelu C#:
```html
@model SalesReportModel
<!-- Include Chart.js library from CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Canvas element for chart rendering -->
<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>
```
Podczas renderowania stron zawierających treści generowane przez JavaScript, skonfiguruj renderer, aby czekał na zakończenie wykonania skryptów przed przechwyceniem strony:
```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);
```
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/03-sales-chart-report.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-apply-conditional-formatting-and-business-logic"></a>
### Zastosuj Warunkowe Formatowanie i Logikę Biznesową
Raporty inwentaryzacyjne korzystają z wskaźników wizualnych, które od razu przyciągają uwagę do pozycji wymagających działania. Zamiast zmuszać użytkowników do przeglądania setek wierszy w poszukiwaniu problemów, formatowanie warunkowe wyraźnie ukazuje wyjątki. Użyj wewnętrznych wyrażeń Razor, aby zastosować klasy CSS w oparciu o wartości danych:
```html
<!-- Iterate through inventory items sorted by quantity -->
@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">
<!-- Badge styling for critical stock items -->
<span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
@item.Quantity
</span>
</td>
</tr>
}
```
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/04-inventory-report.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-create-sub-reports-and-section-breaks"></a>
### Utwórz Podraporty i Przerwy Sekcji
Aby połączyć niezależnie wygenerowane raporty w jeden dokument, użyj funkcji scałania IronPDF:
```csharp
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;
}
```
#### Przykładowy Wynik
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/05-combined-report.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-generate-a-table-of-contents"></a>
### Generuj Spis Treści
IronPDF może automatycznie generować spis treści w oparciu o elementy nagłówków w twoim HTML:
```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");
```
## Migracja z Raporty Crystal Reports do IronPDF
Migracja ustalonego systemu raportowania wymaga starannego planowania, aby zminimalizować zakłócenia, przy jednoczesnym wykorzystaniu okazji do modernizacji i upraszczania. Szybciej poruszysz się, rozumiejąc, jak koncepcje Raporty Crystal Reports mapują się na podejście oparte na HTML, zamiast próbować dosłownie replikówać każdą funkcję lub zachować każdą osobliwość oryginalnych raportów.
<a id="anchor-map-crystal-reports-concepts-to-ironpdf"></a>
### Mapowanie Koncepcji Raporty Crystal Reports do IronPDF
Zrozumienie mapowania koncepcyjnego pomaga systematycznie tłumaczyć istniejące raporty:
<div class="content-table dotnet-core-pdf-table">
<table>
<thead>
<tr>
<th>Raporty Crystal Reports</th>
<th>Odpowiednik IronPDF</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sekcje raportu</td>
<td>HTML divs z właściwościami przerwy strony CSS</td>
</tr>
<tr>
<td>Pola parametrów</td>
<td>Właściwości modelu przekazywane do widoków Razor</td>
</tr>
<tr>
<td>Pola formuły</td>
<td>C# właściwości obliczane w klasach modeli</td>
</tr>
<tr>
<td>Suma bieżąca</td>
<td>Agregacje LINQ</td>
</tr>
<tr>
<td>Podraporty</td>
<td>Widoki częściowe lub scałane dokumenty PDF</td>
</tr>
<tr>
<td>Grupowanie/sortowanie</td>
<td>Operacje LINQ przed przekazaniem danych do szablonu</td>
</tr>
<tr>
<td>Raporty międzypola</td>
<td>Tabele HTML z użyciem zagnieżdżonych pętli</td>
</tr>
<tr>
<td>Formatowanie warunkowe</td>
<td>Bloki Razor @if z klasami CSS</td>
</tr>
</tbody>
</table>
</div>
<a id="anchor-best-strategy-for-converting-rpt-templates"></a>
### Najlepsza Strategia do Konwersji Szablonów .rpt
Nie próbuj programatycznie analizować plików .rpt. Zamiast tego traktuj istniejące wyjścia PDF jako specyfikacje wizualne i odbuduj logikę używając systemowej, czterostopniowej strategii:
1. **Inwentaryzacja:** Kataloguj wszystkie pliki .rpt z ich przeznaczeniem, źródłami danych i częstotliwością użytkowania. Usuń nieaktualne raporty, aby zmniejszyć zakres migracji.
2. **Priorytet:** Migruj najpierw raporty o wysokiej częstotliwości. Celuj w raporty z prostymi układami lub z ciągłymi problemami z utrzymaniem.
3. **Odniesienie:** Eksportuj istniejące Raporty Crystal Reports jako PDF. Użyj ich jako specyfikacji wizualnych, aby programiści mogli je dopasować.
4. **Walidacja:** Testuj z produkcyjnymi wolumenami danych. Szablony, które renderują się natychmiast z 10 wierszami, mogą zwolnić z 10 000 wierszy.
## Generowanie i Harmonogramowanie Raportów Wsadowych w .NET
Systemy produkcyjne często muszą generować wiele raportów jednocześnie lub uruchamiać zadania raportowe w harmonogramach. Project safe multi-threaded design supports both scenarios efficiently.
<a id="anchor-generate-multiple-reports-in-parallel"></a>
### Generuj Wiele Raportów Równolegle
Dla przetwarzania batch, użyj `Parallel.ForEachAsync` lub asynchronicznych wzorców z `Task.WhenAll`:
```csharp
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();
}
```
#### Przykładowy Wynik
Przykład przetwarzania wsadowego generuje wiele faktur równolegle. Oto jedna z wygenerowanych wsadowych faktur:
<iframe loading="lazy" src="/static-assets/pdf/tutorials/crystal-reports-alternative/06-batch-INV-2026-001.pdf" width="100%" height="400px">
</iframe>
<a id="anchor-integrate-report-generation-with-aspnet-core-background-services"></a>
### Integruj Generowanie Raportów z ASP.NET Core Background Services
Zaprogramowane generowanie raportów naturalnie pasuje do infrastruktury hostowanych usług ASP.NET Core:
```csharp
// 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
}
}
}
```
<a id="anchor-download-the-complete-test-project"></a>
## Pobierz Pełny Projekt Testowy
Wszystkie przykłady kodu z tego samouczka są dostępne w gotowym do uruchomienia projekcie testowym .NET 10. [Pobierz](/static-assets/pdf/tutorials/crystal-reports-alternative/CrystalReportsAlternativeTests.zip) zawiera kompletny kod źródłowy, modele danych, szablony HTML i uruchamiacza testów, który generuje wszystkie pokazane wyżej przykładowe PDF.
## Kolejne kroki
Przykłady w całym tym przewodniku pokazują, że [IronPDF](https://ironpdf.com/) obsługuje pełny zakres potrzeb raportowania biznesowego: proste [faktury](/tutorials/csharp-invoice-processing/) z pozycjami i sumami, złożone katalogi pracowników z grupowanymi danymi i zdjęciami, [raporty](/how-to/csharp-pdf-reports/) inwentarzowe z formatowaniem warunkowym i wykresami, [przetwarzanie wsadowe](/tutorials/batch-pdf-processing-csharp/) setek dokumentów równolegle i zaprogramowane generowanie za pomocą usług tła.
Jeśli oceniacie alternatywy dla istniejącej implementacji Raporty Crystal Reports, zacznijcie od jednego raportu o wysokiej wartości. Odbudujcie go używając pokazanych tutaj wzorców [HTML do PDF](/tutorials/html-to-pdf/), porównajcie doświadczenie z rozwojem i jakość wyjścia, a następnie rozwińcie. Wiele zespołów odkrywa, że ich pierwszy przekształcony raport zajmuje kilka godzin, ponieważ ustanawiają oni wzorce i szablony podstawowe, podczas gdy kolejne raporty zajmują tylko minuty do złożenia, ponieważ wykorzystują [szablony Razor](/how-to/cshtml-to-pdf-razor/) i stylizacje. Dla precyzji układu, [przewodnik rendowanie pikselowo dokładne](/how-to/pixel-perfect-html-to-pdf/) obejmuje, jak dopasować wyjście Raporty Crystal Reports dokładnie za pomocą CSS.
Czy jesteś gotowy do rozpoczęcia budowy? [Pobierz IronPDF](download-modal) i wyprobuj go z darmowa wersja probna. Ta sama biblioteka obsługuje wszystko od pojedyncze rendowanie raportów po [generację wsadową dużej objętości](/how-to/async/) w środowiskach .NET. Jeśli masz pytania dotyczące migracji raportów lub potrzebujesz wskazówek architektonicznych, [skontaktuj się z naszym zespołem wsparcia inżynierskiego](/troubleshooting/engineering-request-pdf/).
Generowanie raportu HTML-do-PDF w C# .NET z IronPDF zastępuje zastrzeżonego projektanta .rpt Raporty Crystal Reports za pomocą standardowych szablonów HTML, CSS i Razor, umożliwiając deweloperom .NET tworzenie biznesowych raportów opartych na danych, korzystając z umiejętności tworzenia stron internetowych, które już posiadają. Obejmuje to pełne wsparcie dla dynamicznych tabel, wykresów opartych na JavaScript, formatowania warunkowego, przetwarzania wsadowego wielu dokumentów oraz wdrażania między platformami w dowolnym środowisku uruchamiającym .NET.
TL;DR: Przewodnik Quickstart
Ten samouczek obejmuje zastępowanie Raporty Crystal Reports generowaniem raportów z HTML do PDF w C# .NET, od podstawowych szablonów po przetwarzanie wsadowe i generowanie zaplanowane.
Dla kogo jest to przeznaczone: Programiści .NET zastępujący Raporty Crystal Reports lub budujący nowe systemy raportowania od podstaw.
Co zbudujesz: Trzy kompletne implementacje raportów (faktura sprzedaży, katalog pracowników, raport inwentaryzacyjny), plus wizualizacje Chart.js, markowane nagłówki/stopki, generowanie spisu treści, scałanie podraportów i równoległe przetwarzanie wsadowe.
Gdzie działa: .NET 10, .NET 8 LTS, .NET Framework 4.6.2+ oraz .NET Standard 2.0. Brak zależności od składników COM tylko dla Windows.
Kiedy używać tego podejścia: Kiedy brak wsparcia Raporty Crystal Reports dla .NET Core, uzależnienie od Windows lub złożone licencjonowanie stają się wąskim gardłem.
Dlaczego to istotne technicznie: HTML/CSS renderuje się identycznie na wszystkich platformach, integruje się z CI/CD i wykonuje JavaScript dla wykresów, wszystko bez zastrzeżonego projektanta czy opłat za dokument.
Aby kontynuować z przykładami kodu, zainstaluj IronPdf przez NuGet (Install-Package IronPdf). Wygeneruj swój pierwszy raport kilkoma linijkami kodu:
1Install IronPDF with NuGet Package Manager
PM > Install-Package IronPdf
Install-Package IronPdf
2Skopiuj i uruchom ten fragment kodu.
// Install-Package IronPdfvar pdf = new IronPdf.ChromePdfRenderer() .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") .SaveAs("sales-report.pdf");
// 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");
' Install-Package IronPdfDim pdf = New IronPdf.ChromePdfRenderer() _ .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") _ .SaveAs("sales-report.pdf")
' Install-Package IronPdf
Dim pdf = New IronPdf.ChromePdfRenderer() _
.RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") _
.SaveAs("sales-report.pdf")
3Wdrożenie do testowania w środowisku produkcyjnym
Generowanie z HTML do PDF opiera się na liniowym łańcuchu architektonicznym. Zamiast zastrzeżonego formatu plików, aplikacja używa standardowych modeli danych do wypełnienia widoków Razor lub szablonów HTML. Uzyskany ciąg HTML jest następnie przekazywany do silnika renderowania jak IronPdf, który rejestruje wizualny wynik jako dokument PDF. To podejście oddziela projektowanie raportu od środowiska hostingowego, pozwalając na uruchamianie dokładnie tego samego kodu na dowolnej platformie wspierającej .NET.
Ten przepływ pracy odzwierciedla standardowy rozwój webowy. Programiści frontendowi tworzą układ za pomocą CSS i natychmiast go podglądają w dowolnej przeglądarce. Następnie programiści backendowi wiążą dane z użyciem C#. To rozdzielenie pozwala zespołom na korzystanie z istniejącej kontroli wersji, przeglądu kodu i procesów ciągłej wdrożenia dla raportów tak samo jak dla reszty aplikacji.
HTML umożliwia funkcje niedostępne w Raporty Crystal Reports: interaktywne wykresy, responsywne tabele i wspólne style dla jednolitego brandingu.
Dlaczego zastąpić Raporty Crystal Reports w aplikacjach .NET
Migracja z Raporty Crystal Reports nie jest wynikiem pojedynczego katastroficznego problemu czy nagłego porzucenia przez SAP. Zamiast tego, to kumulacja punktów tarcia, które razem sprawiają, że platforma staje się coraz trudniejsza do uzasadnienia dla nowych projektów i bardziej wymagająca do utrzymania w istniejących rozwiązaniach. Zidentyfikowanie tych punktów bólu wyjaśnia, dlaczego wiele zespołów poszukuje alternatyw i jakie kryteria są najważniejsze przy ocenie opcji zamienników.
Brak wsparcia dla .NET 8 lub .NET Core
Raporty Crystal Reports nie wspiera .NET Core ani .NET 5-10. SAP stwierdziło na forach, że nie planują dodania wsparcia. SDK używa komponentów COM, które są niekompatybilne z międzyplatformowym .NET. Wsparcie dla nowoczesnego .NET wymagałoby całkowitego przepisania, czego SAP odmówiło.
W rezultacie zespoły budujące nowe aplikacje na bieżących wersjach .NET nie mogą używać Raporty Crystal Reports. Organizacje znormalizowane na .NET 8 lub .NET 10 nie są w stanie go zintegrować. W przypadku istniejących aplikacji, aktualizacja do nowoczesnego środowiska .NET wymaga najpierw zastąpienia systemu raportowania.
Złożone Licencjonowanie i Ukryte Koszty
Licencjonowanie Raporty Crystal Reports odróżnia licencje projektanta, licencje runtime, wdrożenia serwera i użycie osadzone. Zasady różnią się dla usług desktop, web i terminal. Zgodność w jednej konfiguracji może wymagać dodatkowych licencji w innej. Jeśli pojawią się luki po wdrożeniu, nieoczekiwane koszty pojawiają się. Wiele organizacji decyduje, że niepewność jest gorsza niż migracja do rozwiązania z jaśniejszym licencjonowaniem.
Uzależnienie od Platformy Windows
Raporty Crystal Reports działa tylko na Windows z dziedzictwem .NET Framework. Nie można wdrażać tych aplikacji do kontenerów Linux, Azure App Service na Linuxie, AWS Lambda ani Google Cloud Run. Gdy organizacje używają systemów kontenerowych, neutralnych platformowo i serverless, te ograniczenia stają się istotniejsze.
Zespoły programistyczne budujące mikroserwisowe systemy napotykają dodatkowe wyzwania. Jeśli dziewięć usług działa w lekkich kontenerach Linux, a jedna potrzebuje Windows dla Raporty Crystal Reports, wdrożenie jest bardziej skomplikowane. Potrzebujesz obrazów kontenerowych Windows, kompatybilnego z Windows hostingu i oddzielnych ustawień wdrożenia. Usługa raportowania staje się wyjątkiem, blokującym standaryzację.
Set Up a C# Report Generator in .NET 10
Rozpoczęcie z IronPDF jest proste. Zainstaluj bibliotekę przez NuGet jak każdą inną zależność .NET. Nie ma potrzeby pobierać dodatkowego oprogramowania ani oddzielnego instalatora runtime do serwerów produkcyjnych.
Wybierz Podejście do Szablonu: Razor, HTML lub Hybrydowe
IronPDF wspiera trzy różne podejścia do budowania szablonów raportów. Każde podejście oferuje specyficzne zalety w zależności od składu zespołu, wymagań projektu i długoterminowych rozważań dotyczących utrzymania.
Widoki Razor oferują najbogatsze środowisko projektowe dla zespołów już pracujących w ekosystemie .NET. Modele silnie typowane z pełnym wsparciem IntelliSense w Visual Studio i VS Code, sprawdzenie w czasie kompilacji oraz pełna moc C# dla pętli, konstrukcji warunkowych, obsługi null i formatowania ciągów znaków są dostępne. Składnia Razor jest znana tym, którzy budowali aplikacje ASP.NET Core, eliminując krzywą uczenia się związaną z silnikami szablonów z innych ekosystemów. Szablony znajdują się w projekcie obok innych plików źródłowych, biorą udział w operacjach refactoringu i kompilują się w ramach normalnego procesu budowy.
Czysty HTML z interpolacją ciągów dobrze sprawdza się przy prostszych raportach lub w zespołach, które wolą utrzymywać szablony całkowicie oddzielnie od kodu .NET. Szablony HTML mogą być przechowywane jako zasoby wkompilowane w assembly, zewnętrzne pliki implementowane obok aplikacji, a nawet odzyskane z bazy danych lub systemu zarządzania treścią w czasie wykonywania. Podstawowe wiązanie danych wykorzystuje string.Replace() dla pojedynczych wartości lub lekką bibliotekę szablonów jak Scriban lub Fluid dla bardziej zaawansowanych scenariuszy. To podejście maksymalizuje przenośność, pozwalając projektantom edytować szablony bez żadnych narzędzi .NET zainstalowanych, używając jedynie edytora tekstu i przeglądarki internetowej do podglądu.
Podejścia hybrydowe łączą obie techniki dla scenariuszy wymagających elastyczności. Na przykład, widok Razor może być użyty do generowania głównej struktury HTML, a następnie przetwarzany z dodatkowymi zamianami ciągów dla dynamicznych elementów, które nie pasują czysto do modelu widoku. Alternatywnie, zaprojektowany przez niedevelopera szablon HTML może być załadowany, a częściowe widoki Razor mogą być użyte do renderowania tylko złożonych, opartych na danych sekcji przed połączeniem całości. Konwersja z HTML do PDF jest niezależna od źródła HTML, co pozwala na mieszanie podejść w zależności od potrzeb każdego raportu.
Biorąc pod uwagę te opcje, ten samouczek skupia się głównie na widokach Razor, ponieważ oferują one najlepszy balans bezpieczeństwa typów, utrzymania i bogactwa funkcji dla typowych scenariuszy raportowania biznesowego. Umiejętności przenoszą się bezpośrednio, jeśli przyszłe wymagania obejmują pracę z czystymi szablonami HTML, ponieważ obie metody produkują łańcuchy HTML.
Build a Data-Driven PDF Report in C#
Ta sekcja pokazuje pełne utworzenie raportu faktury sprzedażowej, od początku do końca. Przykład obejmuje kluczowy wzór używany do wszystkich raportów: definiowanie modelu strukturującego dane, tworzenie szablonu Razor, który przekształca dane w sformatowany HTML, renderowanie tego szablonu do łańcucha HTML i konwersję HTML do dokumentu PDF gotowego do podglądu, wysyłki e-mailem lub archiwizacji.
Utwórz Szablon Raportu HTML/CSS
Pierwszym krokiem jest zdefiniowanie modelu danych. Prawdziwa faktura wymaga informacji o kliencie, pozycji z opisem i cenami, obliczonych sum, obsługi podatków i elementów brandingu firmy. Klasy modelowe powinny być zorganizowane, aby odzwierciedlały te grupy:
// Invoice data model with customer, company, and line item detailspublic class InvoiceModel{ public stringInvoiceNumber { get; set; } = string.Empty; public DateTimeInvoiceDate { get; set; } public DateTimeDueDate { get; set; } public CompanyInfoCompany { get; set; } = new(); public CustomerInfoCustomer { get; set; } = new(); public List<LineItem> Items { get; set; } = new(); // Computed totals - business logic stays in the model public decimalSubtotal => Items.Sum(x => x.Total); public decimalTaxRate { get; set; } = 0.08m; public decimalTaxAmount => Subtotal * TaxRate; public decimalGrandTotal => Subtotal + TaxAmount;}// Company details for invoice headerpublic class CompanyInfo{ public stringName { get; set; } = string.Empty; public stringAddress { get; set; } = string.Empty; public stringCity { get; set; } = string.Empty; public stringPhone { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty; public stringLogoPath { get; set; } = string.Empty;}// Customer billing informationpublic class CustomerInfo{ public stringName { get; set; } = string.Empty; public stringAddress { get; set; } = string.Empty; public stringCity { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty;}// Individual invoice line itempublic class LineItem{ public stringDescription { get; set; } = string.Empty; public intQuantity { get; set; } public decimalUnitPrice { get; set; } public decimalTotal => 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;
}
ImportsSystemImportsSystem.Collections.GenericImportsSystem.Linq' Invoice data model with customer, company, and line item detailsPublic Class InvoiceModel Public Property InvoiceNumberAsString = String.Empty Public Property InvoiceDateAsDateTime Public Property DueDateAsDateTime Public Property CompanyAs New CompanyInfo() Public Property CustomerAs New CustomerInfo() Public Property ItemsAs New List(OfLineItem)() ' Computed totals - business logic stays in the model PublicReadOnlyPropertySubtotalAsDecimal Get ReturnItems.Sum(Function(x) x.Total)End Get End Property Public Property TaxRateAsDecimal = 0.08D PublicReadOnlyPropertyTaxAmountAsDecimal Get ReturnSubtotal * TaxRateEnd Get End Property PublicReadOnlyPropertyGrandTotalAsDecimal Get ReturnSubtotal + TaxAmountEnd Get End PropertyEnd Class' Company details for invoice headerPublic Class CompanyInfo Public Property NameAsString = String.Empty Public Property AddressAsString = String.Empty Public Property CityAsString = String.Empty Public Property PhoneAsString = String.Empty Public Property EmailAsString = String.Empty Public Property LogoPathAsString = String.EmptyEnd Class' Customer billing informationPublic Class CustomerInfo Public Property NameAsString = String.Empty Public Property AddressAsString = String.Empty Public Property CityAsString = String.Empty Public Property EmailAsString = String.EmptyEnd Class' Individual invoice line itemPublic Class LineItem Public Property DescriptionAsString = String.Empty Public Property QuantityAsInteger Public Property UnitPriceAsDecimal PublicReadOnlyPropertyTotalAsDecimal Get ReturnQuantity * UnitPriceEnd Get End PropertyEnd Class
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
Obliczone właściwości dla Subtotal, TaxAmount i GrandTotal są uwzględnione w modelu. Te obliczenia należą do modelu, a nie do szablonu, dzięki czemu widoki Razor skupiają się na prezentacji, podczas gdy model obsługuje logikę biznesową. To rozdzielenie ułatwia testowanie jednostkowe, pozwalające zweryfikować obliczenia bez renderowania jakiegokolwiek HTML.
Teraz utwórz widok Razor, który przekształca ten model w profesjonalnie sformatowaną fakturę. Zapisz to jako InvoiceTemplate.cshtml w swoim folderze Views:
Osadzony w tym szablonie CSS obsługuje całe stylowanie wizualne, takie jak kolory, czcionki, odstępy i formatowanie tabel. IronPDF obsługuje również nowoczesne funkcje CSS, takie jak flexbox, siatki gridowe i zmienne CSS. Generowany PDF dokładnie odpowiada podglądowi wydruku Chrome, co sprawia, że debugowanie jest proste: jeśli coś wygląda źle w PDF, otwórz HTML w przeglądarce i użyj narzędzi dewelopera do inspekcji i dostosowania stylów.
Powiąż Dane z Szablonem
Z modelem i szablonem na miejscu, renderowanie PDF wymaga połączenia ich poprzez ChromePdfRenderer IronPDF. Kluczowym krokiem jest konwersja widoku Razor do łańcucha HTML, a następnie przekazanie tego łańcucha do renderera:
using IronPdf;// Service class for generating invoice PDFs from Razor viewspublic class InvoiceReportService{ private readonly IRazorViewEngine _razorViewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; publicInvoiceReportService( 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 = awaitRenderViewToStringAsync("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();
}
}
ImportsIronPdfImportsMicrosoft.AspNetCore.Mvc.RazorImportsMicrosoft.AspNetCore.Mvc.ViewFeaturesImportsMicrosoft.Extensions.DependencyInjectionImportsMicrosoft.AspNetCore.HttpImportsMicrosoft.AspNetCore.MvcImportsMicrosoft.AspNetCore.RoutingImportsSystem.IOImportsSystem.Threading.Tasks' Service class for generating invoice PDFs from Razor viewsPublic Class InvoiceReportService PrivateReadOnly _razorViewEngine AsIRazorViewEngine PrivateReadOnly _tempDataProvider AsITempDataProvider PrivateReadOnly _serviceProvider AsIServiceProvider Public Sub New(razorViewEngine AsIRazorViewEngine, tempDataProvider AsITempDataProvider, serviceProvider AsIServiceProvider) _razorViewEngine = razorViewEngine _tempDataProvider = tempDataProvider _serviceProvider = serviceProvider End Sub ' Generate PDF from invoice model PublicAsync Function GenerateInvoicePdfAsync(invoice AsInvoiceModel) AsTask(OfByte()) ' Render Razor view to HTML string Dim html AsString = AwaitRenderViewToStringAsync("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 PrivateAsync Function RenderViewToStringAsync(OfTModel)(viewName AsString, model AsTModel) AsTask(OfString) Dim httpContext As New DefaultHttpContextWith {.RequestServices = _serviceProvider} Dim actionContext As New ActionContext(httpContext, New RouteData(), New ActionDescriptor())Using stringWriter As New StringWriter() Dim viewResult = _razorViewEngine.FindView(actionContext, viewName, False) IfNot viewResult.SuccessThenThrow New InvalidOperationException($"View '{viewName}' not found.") End If Dim viewDictionary As New ViewDataDictionary(OfTModel)( 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()EndUsing End FunctionEnd Class
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
Dla prostszych scenariuszy, gdy nie potrzebujesz pełnej konfiguracji ASP.NET Core MVC, jak w aplikacji konsolowej czy serwisie w tle, możesz po prostu używać ciągów HTML z interpolacją i StringBuilder dla dynamicznych części.
Przykładowy Wynik
Dodaj Nagłówki, Stopki i Numery Stron
Profesjonalne raporty zazwyczaj zawierają spójne nagłówki i stopki na wszystkich stronach, wyświetlające branding firmy, tytuły dokumentów, daty generacji i numery stron. IronPDF zapewnia dwa podejścia do implementacji tych elementów: nagłówki oparte na tekście dla prostych treści wymagających minimalnego formatowania oraz nagłówki HTML dla pełnej kontroli nad stylizacją z logo i niestandardowymi układami.
Nagłówki oparte na tekście dobrze działają dla podstawowych informacji i renderują się szybciej, ponieważ nie wymagają dodatkowego parsowania HTML:
using IronPdf;using IronSoftware.Drawing;// Configure text-based headers and footersvar renderer = new ChromePdfRenderer();// Set starting page numberrenderer.RenderingOptions.FirstPageNumber = 1;// Add centered header with divider linerenderer.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 rightrenderer.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/footerrenderer.RenderingOptions.MarginTop = 25;renderer.RenderingOptions.MarginBottom = 20;
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;
ImportsIronPdfImportsIronSoftware.Drawing' Configure text-based headers and footersDim renderer As New ChromePdfRenderer()' Set starting page numberrenderer.RenderingOptions.FirstPageNumber = 1' Add centered header with divider linerenderer.RenderingOptions.TextHeader = New TextHeaderFooterWith { .CenterText = "CONFIDENTIAL - Internal Use Only", .DrawDividerLine = True, .Font = FontTypes.Arial, .FontSize = 10}' Add footer with date on left, page numbers on rightrenderer.RenderingOptions.TextFooter = New TextHeaderFooterWith { .LeftText = "{date} {time}", .RightText = "Page {page} of {total-pages}", .DrawDividerLine = True, .Font = FontTypes.Arial, .FontSize = 9}' Set margins to accommodate header/footerrenderer.RenderingOptions.MarginTop = 25renderer.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
Dostępne pola scalania obejmują {page} dla obecnego numeru strony, {total-pages} dla całkowitej liczby stron dokumentu, {date} i {time} dla znaczników czasowych generacji, {url} dla źródłowego URL jeśli renderowane z strony internetowej, oraz {html-title} i {pdf-title} dla tytułów dokumentów. Te zastępstwa są automatycznie wykonywane podczas renderowania.
Dla nagłówków z logo, niestandardowymi czcionkami lub złożonymi wielokolumnowymi układami użyj nagłówków HTML, które wspierają pełne stylizowanie CSS:
using IronPdf;using System;var renderer = new ChromePdfRenderer();// Configure HTML header with logo and custom layoutrenderer.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 daterenderer.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};
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
};
ImportsIronPdfImportsSystemDim renderer As New ChromePdfRenderer()' Configure HTML header with logo and custom layoutrenderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooterWith { .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 daterenderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooterWith { .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
}
Przykładowy Wynik
Utwórz Dynamiczne Tabele i Powtarzające Się Sekcje
Raporty często muszą wyświetlać kolekcje danych obejmujących wiele stron. Pętle Razor zajmują się tym naturalnie, iterując po kolekcjach i generując wiersze tabel lub elementy kartowe dla każdego elementu.
Oto kompletny przykład Katalogu Pracowników pokazującego prezentację danych z grupowanymi działami:
// Employee directory data modelspublic class EmployeeDirectoryModel{ public List<Department> Departments { get; set; } = new(); public DateTimeGeneratedDate { get; set; } = DateTime.Now;}// Department grouping with manager infopublic class Department{ public stringName { get; set; } = string.Empty; public stringManagerName { get; set; } = string.Empty; public List<Employee> Employees { get; set; } = new();}// Individual employee detailspublic class Employee{ public stringName { get; set; } = string.Empty; public stringTitle { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty; public stringPhone { get; set; } = string.Empty; public stringPhotoUrl { get; set; } = string.Empty; public DateTimeHireDate { 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 modelsPublic Class EmployeeDirectoryModel Public Property DepartmentsAsList(OfDepartment) = New List(OfDepartment)() Public Property GeneratedDateAsDateTime = DateTime.NowEnd Class' Department grouping with manager infoPublic Class Department Public Property NameAsString = String.Empty Public Property ManagerNameAsString = String.Empty Public Property EmployeesAsList(OfEmployee) = New List(OfEmployee)()End Class' Individual employee detailsPublic Class Employee Public Property NameAsString = String.Empty Public Property TitleAsString = String.Empty Public Property EmailAsString = String.Empty Public Property PhoneAsString = String.Empty Public Property PhotoUrlAsString = String.Empty Public Property HireDateAsDateTimeEnd Class
' 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
Właściwość CSS page-break-inside: avoid na klasie department mówi rendererowi PDF, aby utrzymał sekcje department razem na jednej stronie, jeśli to możliwe. Jeśli zawartość działu spowodowałaby przerwanie strony w środku sekcji, renderer przesuwa całą sekcję na następną stronę. Selektor .department:not(:first-child) z page-break-before: always wymusza, aby każdy department po pierwszym rozpoczynał się na nowej stronie, tworząc czystą separację sekcji w całym katalogu.
Przykładowy Wynik
Advanced C# Report Generation With IronPDF
Raporty biznesowe często wymagają możliwości wykraczających poza statyczne tabele i tekst. Wykresy wizualizują trendy, które byłyby uciążliwe do zrozumienia w formie tabelarycznej. Formatowanie warunkowe przyciąga uwagę do pozycji wymagających działania. Podraporty łączą dane z wielu źródeł w spójne dokumenty. Ta sekcja obejmuje implementację każdej z tych funkcji z wykorzystaniem silnika renderowania Chromium IronPDF.
Dodaj Wykresy i Diagramy do Raportów PDF
Ponieważ JavaScript wykonuje się podczas renderowania, możesz użyć dowolnej biblioteki wykresów po stronie klienta, aby generować wizualizacje bezpośrednio w swoich raportach. Wykres jest rastrowany jako część strony i pojawia się w końcowym PDF dokładnie tak, jak na ekranie. Chart.js oferuje doskonałą równowagę między prostotą, możliwościami i dokumentacją dla większości potrzeb raportowania.
Dołącz Chart.js z CDN i skonfiguruj swój wykres z danymi zserializowanymi z modelu C#:
@model SalesReportModel<!-- Include Chart.js library from CDN --><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><!-- Canvas element for chart rendering --><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
<!-- Include Chart.js library from CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Canvas element for chart rendering -->
<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
Podczas renderowania stron zawierających treści generowane przez JavaScript, skonfiguruj renderer, aby czekał na zakończenie wykonania skryptów przed przechwyceniem strony:
using IronPdf;string html = "<h1>Report</h1>";// Configure renderer to wait for JavaScript executionvar renderer = new ChromePdfRenderer();renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to completevar pdf = renderer.RenderHtmlAsPdf(html);
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);
ImportsIronPdfDim html AsString = "<h1>Report</h1>"' Configure renderer to wait for JavaScript executionDim renderer As New ChromePdfRenderer()renderer.RenderingOptions.WaitFor.JavaScript(500) ' Wait 500ms for JS to completeDim 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)
Przykładowy Wynik
Zastosuj Warunkowe Formatowanie i Logikę Biznesową
Raporty inwentaryzacyjne korzystają z wskaźników wizualnych, które od razu przyciągają uwagę do pozycji wymagających działania. Zamiast zmuszać użytkowników do przeglądania setek wierszy w poszukiwaniu problemów, formatowanie warunkowe wyraźnie ukazuje wyjątki. Użyj wewnętrznych wyrażeń Razor, aby zastosować klasy CSS w oparciu o wartości danych:
<!-- Iterate through inventory items sorted by quantity -->@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"> <!-- Badge styling for critical stock items --> <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")"> @item.Quantity </span> </td> </tr>}
<!-- Iterate through inventory items sorted by quantity -->
@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">
<!-- Badge styling for critical stock items -->
<span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
@item.Quantity
</span>
</td>
</tr>
}
HTML
Przykładowy Wynik
Utwórz Podraporty i Przerwy Sekcji
Aby połączyć niezależnie wygenerowane raporty w jeden dokument, użyj funkcji scałania IronPDF:
using IronPdf;// Combine multiple reports into a single PDF documentpublic 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;
}
ImportsIronPdf' Combine multiple reports into a single PDF documentPublic Function GenerateCombinedReport(sales AsSalesReportModel, inventory AsInventoryReportModel) AsByte() 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.BinaryDataEnd Function
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
Przykładowy Wynik
Generuj Spis Treści
IronPDF może automatycznie generować spis treści w oparciu o elementy nagłówków w twoim HTML:
using IronPdf;// Generate PDF with automatic table of contentsvar renderer = new ChromePdfRenderer();renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;var pdf = renderer.RenderHtmlFileAsPdf("report.html");
using IronPdf;
// Generate PDF with automatic table of contents
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;
var pdf = renderer.RenderHtmlFileAsPdf("report.html");
ImportsIronPdf' Generate PDF with automatic table of contentsDim renderer As New ChromePdfRenderer()renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbersDim 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")
Moją ulubioną biblioteką tego typu jest IronPDF. Umożliwia ona szybkie i efektywne manipulowanie plikami PDF. Posiada także wiele cennych funkcji, takich jak eksport do formatu PDF/A i cyfrowe podpisywanie dokumentów PDF.
IronOCR pozwala nam oszczędzić 40 000 USD rocznie na ręcznym przetwarzaniu, jednocześnie zwiększając produktywność i uwalniając zasoby do zadań o wysokim wpływie. Gorąco polecam.
Migracja ustalonego systemu raportowania wymaga starannego planowania, aby zminimalizować zakłócenia, przy jednoczesnym wykorzystaniu okazji do modernizacji i upraszczania. Szybciej poruszysz się, rozumiejąc, jak koncepcje Raporty Crystal Reports mapują się na podejście oparte na HTML, zamiast próbować dosłownie replikówać każdą funkcję lub zachować każdą osobliwość oryginalnych raportów.
Mapowanie Koncepcji Raporty Crystal Reports do IronPDF
Zrozumienie mapowania koncepcyjnego pomaga systematycznie tłumaczyć istniejące raporty:
Raporty Crystal Reports
Odpowiednik IronPDF
Sekcje raportu
HTML divs z właściwościami przerwy strony CSS
Pola parametrów
Właściwości modelu przekazywane do widoków Razor
Pola formuły
C# właściwości obliczane w klasach modeli
Suma bieżąca
Agregacje LINQ
Podraporty
Widoki częściowe lub scałane dokumenty PDF
Grupowanie/sortowanie
Operacje LINQ przed przekazaniem danych do szablonu
Raporty międzypola
Tabele HTML z użyciem zagnieżdżonych pętli
Formatowanie warunkowe
Bloki Razor @if z klasami CSS
Najlepsza Strategia do Konwersji Szablonów .rpt
Nie próbuj programatycznie analizować plików .rpt. Zamiast tego traktuj istniejące wyjścia PDF jako specyfikacje wizualne i odbuduj logikę używając systemowej, czterostopniowej strategii:
Inwentaryzacja: Kataloguj wszystkie pliki .rpt z ich przeznaczeniem, źródłami danych i częstotliwością użytkowania. Usuń nieaktualne raporty, aby zmniejszyć zakres migracji.
Priorytet: Migruj najpierw raporty o wysokiej częstotliwości. Celuj w raporty z prostymi układami lub z ciągłymi problemami z utrzymaniem.
Odniesienie: Eksportuj istniejące Raporty Crystal Reports jako PDF. Użyj ich jako specyfikacji wizualnych, aby programiści mogli je dopasować.
Walidacja: Testuj z produkcyjnymi wolumenami danych. Szablony, które renderują się natychmiast z 10 wierszami, mogą zwolnić z 10 000 wierszy.
Generowanie i Harmonogramowanie Raportów Wsadowych w .NET
Systemy produkcyjne często muszą generować wiele raportów jednocześnie lub uruchamiać zadania raportowe w harmonogramach. Project safe multi-threaded design supports both scenarios efficiently.
Generuj Wiele Raportów Równolegle
Dla przetwarzania batch, użyj Parallel.ForEachAsync lub asynchronicznych wzorców z Task.WhenAll:
using IronPdf;using System.Collections.Concurrent;// Generate multiple invoices in parallel using async processingpublic async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices){ var results = new ConcurrentBag<ReportResult>(); // Process invoices concurrently with controlled parallelism awaitParallel.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();
}
ImportsIronPdfImportsSystem.Collections.ConcurrentImportsSystem.Threading.Tasks' Generate multiple invoices in parallel using async processingPublicAsync Function GenerateInvoiceBatchAsync(invoices AsList(OfInvoiceModel)) AsTask(OfList(OfReportResult)) Dim results As New ConcurrentBag(OfReportResult)() ' Process invoices concurrently with controlled parallelismAwaitTask.Run(Async Function()AwaitParallel.ForEachAsync(invoices, New ParallelOptionsWith {.MaxDegreeOfParallelism = Environment.ProcessorCount},Async Function(invoice, token) ' Each thread gets its own renderer instance Dim renderer As New ChromePdfRenderer() Dim html AsString = BuildInvoiceHtml(invoice) Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html) ' Save individual invoice PDF Dim filename AsString = $"Invoice_{invoice.InvoiceNumber}.pdf"Await pdf.SaveAsAsync(filename) results.Add(New ReportResultWith {.InvoiceNumber = invoice.InvoiceNumber, .Success = True}) End Function) End Function) Return results.ToList()End Function
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
Przykładowy Wynik
Przykład przetwarzania wsadowego generuje wiele faktur równolegle. Oto jedna z wygenerowanych wsadowych faktur:
Integruj Generowanie Raportów z ASP.NET Core Background Services
Zaprogramowane generowanie raportów naturalnie pasuje do infrastruktury hostowanych usług ASP.NET Core:
// Background service for scheduled report generationpublic class DailyReportService : BackgroundService{ private readonly IServiceProvider _serviceProvider; protected override async TaskExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // Calculate next run time (6 AM daily) var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6); awaitTask.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
}
}
}
ImportsSystemImportsSystem.ThreadingImportsSystem.Threading.TasksImportsMicrosoft.Extensions.DependencyInjection' Background service for scheduled report generationPublic Class DailyReportServiceInheritsBackgroundService PrivateReadOnly _serviceProvider AsIServiceProviderProtectedOverridesAsync Function ExecuteAsync(stoppingToken AsCancellationToken) AsTask WhileNot stoppingToken.IsCancellationRequested ' Calculate next run time (6 AM daily) Dim nextRun = DateTime.Now.Date.AddDays(1).AddHours(6)AwaitTask.Delay(nextRun - DateTime.Now, stoppingToken) ' Create scoped service for report generationUsing scope = _serviceProvider.CreateScope() Dim reportService = scope.ServiceProvider.GetRequiredService(OfIReportGenerationService)() ' Generate and distribute daily report Dim salesReport = Await reportService.GenerateDailySalesSummaryAsync() ' Email or save reports as neededEndUsingEndWhile End FunctionEnd Class
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
Pobierz Pełny Projekt Testowy
Wszystkie przykłady kodu z tego samouczka są dostępne w gotowym do uruchomienia projekcie testowym .NET 10. Pobierz zawiera kompletny kod źródłowy, modele danych, szablony HTML i uruchamiacza testów, który generuje wszystkie pokazane wyżej przykładowe PDF.
Kolejne kroki
Przykłady w całym tym przewodniku pokazują, że IronPDF obsługuje pełny zakres potrzeb raportowania biznesowego: proste faktury z pozycjami i sumami, złożone katalogi pracowników z grupowanymi danymi i zdjęciami, raporty inwentarzowe z formatowaniem warunkowym i wykresami, przetwarzanie wsadowe setek dokumentów równolegle i zaprogramowane generowanie za pomocą usług tła.
Jeśli oceniacie alternatywy dla istniejącej implementacji Raporty Crystal Reports, zacznijcie od jednego raportu o wysokiej wartości. Odbudujcie go używając pokazanych tutaj wzorców HTML do PDF, porównajcie doświadczenie z rozwojem i jakość wyjścia, a następnie rozwińcie. Wiele zespołów odkrywa, że ich pierwszy przekształcony raport zajmuje kilka godzin, ponieważ ustanawiają oni wzorce i szablony podstawowe, podczas gdy kolejne raporty zajmują tylko minuty do złożenia, ponieważ wykorzystują szablony Razor i stylizacje. Dla precyzji układu, przewodnik rendowanie pikselowo dokładne obejmuje, jak dopasować wyjście Raporty Crystal Reports dokładnie za pomocą CSS.
IronPDF to biblioteka C#, która umożliwia programistom tworzenie, edytowanie i generowanie dokumentów PDF programowo, stanowiąc nowoczesną alternatywę dla tradycyjnych narzędzi do raportowania, takich jak Crystal Reports.
W jaki sposób IronPDF może służyć jako alternatywa dla Crystal Reports?
IronPDF zapewnia elastyczne i nowoczesne podejście do generowania raportów, umożliwiając programistom korzystanie z szablonów HTML/CSS, które można łatwo stylizować i modyfikować, w przeciwieństwie do bardziej sztywnej struktury Crystal Reports.
Czy mogę tworzyć faktury za pomocą IronPDF?
Tak, za pomocą IronPDF można tworzyć szczegółowe i spersonalizowane faktury przy użyciu szablonów HTML/CSS, co ułatwia projektowanie profesjonalnie wyglądających dokumentów.
Czy za pomocą IronPDF można generować katalogi pracowników?
Oczywiście. IronPDF pozwala generować kompleksowe katalogi pracowników, wykorzystując dane dynamiczne oraz HTML/CSS w celu uzyskania przejrzystej i uporządkowanej prezentacji.
W jaki sposób IronPDF może pomóc w tworzeniu raportów dotyczących zapasów?
IronPDF może usprawnić tworzenie raportów magazynowych dzięki szablonom HTML/CSS, które dynamicznie wypełniają dane, zapewniając aktualne i atrakcyjne wizualnie raporty.
Jakie są zalety korzystania z szablonów HTML/CSS w IronPDF?
Korzystanie z szablonów HTML/CSS w IronPDF zapewnia elastyczność projektowania, łatwość aktualizacji oraz kompatybilność z technologiami internetowymi, co ułatwia utrzymanie i ulepszanie układów raportów.
Czy IronPDF obsługuje .NET 10?
Tak, IronPDF jest kompatybilny z .NET 10, co gwarantuje, że programiści mogą korzystać z najnowszych funkcji i ulepszeń platformy .NET w celu generowania raportów.
W jaki sposób IronPDF przyspiesza generowanie raportów?
IronPDF jest zoptymalizowany pod kątem wydajności, co pozwala mu szybko generować raporty poprzez wydajne przetwarzanie HTML/CSS i renderowanie ich do wysokiej jakości dokumentów PDF.
Is it possible to automate report generation with IronPDF?
IronPDF supports automation of report generation through batch processing and can be integrated with ASP.NET Core Background Services for scheduled report generation tasks.
What is the best approach to migrate from Crystal Reports to IronPDF?
The best approach involves mapping Crystal Reports concepts to their IronPDF equivalents, prioritizing high-use reports, referencing existing outputs as specifications, and validating the new implementations against production data.
Curtis Chau posiada tytuł licencjata z informatyki (Uniwersytet Carleton) i specjalizuje się w front-endowym rozwoju, z ekspertką w Node.js, TypeScript, JavaScript i React. Pasjonuje się tworzeniem intuicyjnych i estetycznie przyjemnych interfejsów użytkownika, Curtis cieszy się pracą z nowoczesnymi frameworkami i tworzeniem dobrze zorganizowanych, atrakcyjnych wizualnie podręczników.