Jak tworzyć pliki PDF w .NET przy użyciu IronPDF (samouczek w języku C#)
Problem z Ręcznymi Raportami Cyklu
Każda organizacja ma jakąś wersję tego samego poniedziałkowego rytuału: analityk otwiera bazę danych raportów, wykonuje te same zapytania co tydzień temu, wkleja liczby do szablonu Excel, formatuje tabele, eksportuje do formatu PDF i wysyła je na listę dystrybucyjną kierownictwa, mając nadzieję, że nic się nie zepsuje, a plik zostanie wysłany przed porannym spotkaniem o 9:00. Jeśli działa, jest to niewidoczny podatek na czyimś poranku. Kiedy nie działa, jak na przykład z przestarzałymi danymi pozostawionymi w karcie, błędem w formule w wierszu podsumowania, raportem, który dociera o 10:30 zamiast o 7:00, jest widoczny dla dokładnie niewłaściwego odbiorcy.
Istniejące narzędzia BI oferują dostarczanie według harmonogramu, ale zazwyczaj w postaci zrzutów ekranów pulpitów nawigacyjnych lub osadzonych linków, które wymagają logowania. Wiceprezes ds. sprzedaży podczas dojazdu do pracy nie chce się logować do narzędzia BI na swoim telefonie, chce raportu PDF, który można przeczytać na tablecie, przesłać potencjalnemu klientowi lub wydrukować przed spotkaniem zarządu. Subskrypcje harmonogramowe SSRS generują samodzielne dokumenty PDF, ale wynik wygląda jak oprogramowanie przedsiębiorstwa z 2010 roku, a aktualizacja szablonu wymaga nawigacji po projektancie raportów, z którym większość współczesnych zespołów .NET nie ma już doświadczenia.
Budowanie własnego potoku od podstaw to właściwe rozwiązanie architektoniczne, ale wymaga połączenia harmonogramu, warstwy zapytań, silnika szablonów i nadawcy e-maili, z których każdy jest potencjalnym punktem awarii, który budzi kogoś o 6:00, gdy tygodniowe podsumowanie P&L dyrektora finansowego nie dochodzi.
Rozwiązanie: Potok Harmonogramowy z IronPDF jako Krok Renderowania
IronPDF to biblioteka PDF dla .NET, która działa jako silnik renderowania w środowisku .NET, wykonującym zapytania SQL do bazy danych raportów, mapującym wyniki na szablon HTML i CSS, i wysyłającym ukończony PDF na listę dystrybucji w harmonogramie cron, bez ręcznych kroków, bez potrzeby analityka.
Hangfire, Quartz.NET lub BackgroundService uruchamiają zadanie. ADO.NET lub Dapper wyciągają liczby. Szablon HTML zamienia je w raport z marką. ChromePdfRenderer generuje PDF. E-mail wysyłany jest zanim ktokolwiek otworzy swojego laptopa. Brak zrzutów ekranów narzędzi BI, brak definicji raportów SSRS, brak opłat API na raport. Renderowanie uruchamia się wewnątrz istniejącej aplikacji .NET, jeden pakiet NuGet, brak zewnętrznych procesów.
Jak to działa w praktyce
1. Harmonogram Cron Uruchamia Zadanie
Każdy raport ma własny harmonogram: zdjęcie pipeline sprzedaży uruchamia się codziennie o 6:30, podsumowanie P&L uruchamia się w poniedziałek o 6:00, a miesięczny dashboard wykonawczy uruchamia się 1. dnia miesiąca. Zadanie jest rejestrowane z wyrażeniem cron w Hangfire lub Quartz.NET podczas uruchomienia aplikacji, lub zaimplementowane jako zaplanowany BackgroundService dla prostszych wdrożeń.
Gdy zadanie się uruchamia, otwiera połączenie z bazą danych raportów i wykonuje odpowiednie zapytania SQL lub procedury składowane na dany okres, dane dotyczące przychodów i wydatków, metryki KPI, liczbę etapów pipeline, wskaźniki konwersji lub wskaźniki realizacji, w zależności od typu raportu.
2. Wyniki Zapytania Zasilają Szablon HTML
Wyniki zapytania są mapowane do modelu lub słownika i wstrzykiwane do szablonu HTML. Szablon definiuje wszystko dotyczące wyglądu raportu: nagłówek z logo firmy, etykieta okresu ("Tydzień od 27 marca 2025"), karty metryk podsumowujących na górze, jedna lub więcej tabel danych z naprzemiennymi kolorami wierszy i stopką z czasem generowania oraz notką o poufności. Ten kod przykładowy IronPDF utworzy pliki PDF, które dokładnie to pokażą.
using Dapper;
using IronPdf;
using System.Data.SqlClient;
var reportData = new List<dynamic>();
using (var conn = new SqlConnection(_connectionString))
{
reportData = (await conn.QueryAsync(@"
SELECT Region, SUM(Revenue) AS Revenue, COUNT(OrderId) AS Orders
FROM SalesOrders
WHERE OrderDate >= @Start AND OrderDate < @End
GROUP BY Region ORDER BY Revenue DESC",
new { Start = weekStart, End = weekStart.AddDays(7) })).ToList();
}
var tableRows = string.Concat(reportData.Select(r =>
$"<tr><td>{r.Region}</td><td>{r.Revenue:C}</td><td>{r.Orders}</td></tr>"));
string html = (await File.ReadAllTextAsync("Templates/sales-weekly.html"))
.Replace("{{Period}}", $"Week of {weekStart:MMMM d, yyyy}")
.Replace("{{TotalRevenue}}", reportData.Sum(r => (decimal)r.Revenue).ToString("C"))
.Replace("{{TableRows}}", tableRows);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
PdfDocument report = renderer.RenderHtmlAsPdf(html);
using Dapper;
using IronPdf;
using System.Data.SqlClient;
var reportData = new List<dynamic>();
using (var conn = new SqlConnection(_connectionString))
{
reportData = (await conn.QueryAsync(@"
SELECT Region, SUM(Revenue) AS Revenue, COUNT(OrderId) AS Orders
FROM SalesOrders
WHERE OrderDate >= @Start AND OrderDate < @End
GROUP BY Region ORDER BY Revenue DESC",
new { Start = weekStart, End = weekStart.AddDays(7) })).ToList();
}
var tableRows = string.Concat(reportData.Select(r =>
$"<tr><td>{r.Region}</td><td>{r.Revenue:C}</td><td>{r.Orders}</td></tr>"));
string html = (await File.ReadAllTextAsync("Templates/sales-weekly.html"))
.Replace("{{Period}}", $"Week of {weekStart:MMMM d, yyyy}")
.Replace("{{TotalRevenue}}", reportData.Sum(r => (decimal)r.Revenue).ToString("C"))
.Replace("{{TableRows}}", tableRows);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
PdfDocument report = renderer.RenderHtmlAsPdf(html);
Imports Dapper
Imports IronPdf
Imports System.Data.SqlClient
Dim reportData As New List(Of Object)()
Using conn As New SqlConnection(_connectionString)
reportData = (Await conn.QueryAsync("
SELECT Region, SUM(Revenue) AS Revenue, COUNT(OrderId) AS Orders
FROM SalesOrders
WHERE OrderDate >= @Start AND OrderDate < @End
GROUP BY Region ORDER BY Revenue DESC",
New With {.Start = weekStart, .End = weekStart.AddDays(7)})).ToList()
End Using
Dim tableRows = String.Concat(reportData.Select(Function(r)
$"<tr><td>{r.Region}</td><td>{r.Revenue:C}</td><td>{r.Orders}</td></tr>"))
Dim html As String = (Await File.ReadAllTextAsync("Templates/sales-weekly.html")) _
.Replace("{{Period}}", $"Week of {weekStart:MMMM d, yyyy}") _
.Replace("{{TotalRevenue}}", reportData.Sum(Function(r) CType(r.Revenue, Decimal)).ToString("C")) _
.Replace("{{TableRows}}", tableRows)
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
Dim report As PdfDocument = renderer.RenderHtmlAsPdf(html)
Wygenerowany Plik PDF

3. Dokument PDF Wysłany E-mailem i Zarchiwizowany
using System.Net.Mail;
using System.IO;
var recipients = await _configService.GetDistributionListAsync("sales-weekly");
string subject = $"Weekly Sales Report — {weekStart:MMM d, yyyy}";
using var stream = new MemoryStream(report.BinaryData);
using var attachment = new Attachment(stream, $"Sales-Report-{weekStart:yyyyMMdd}.pdf", "application/pdf");
var message = new MailMessage
{
From = new MailAddress("reports@yourcompany.com"),
Subject = subject,
Body = $"Please find the weekly sales report for the week of {weekStart:MMMM d, yyyy} attached."
};
foreach (var recipient in recipients)
message.To.Add(recipient);
message.Attachments.Add(attachment);
using var smtp = new SmtpClient("smtp.yourprovider.com");
await smtp.SendMailAsync(message);
// Archive to blob storage for historical access
string archivePath = $"reports/sales-weekly/{weekStart:yyyy/MM/dd}.pdf";
report.SaveAs(archivePath);
using System.Net.Mail;
using System.IO;
var recipients = await _configService.GetDistributionListAsync("sales-weekly");
string subject = $"Weekly Sales Report — {weekStart:MMM d, yyyy}";
using var stream = new MemoryStream(report.BinaryData);
using var attachment = new Attachment(stream, $"Sales-Report-{weekStart:yyyyMMdd}.pdf", "application/pdf");
var message = new MailMessage
{
From = new MailAddress("reports@yourcompany.com"),
Subject = subject,
Body = $"Please find the weekly sales report for the week of {weekStart:MMMM d, yyyy} attached."
};
foreach (var recipient in recipients)
message.To.Add(recipient);
message.Attachments.Add(attachment);
using var smtp = new SmtpClient("smtp.yourprovider.com");
await smtp.SendMailAsync(message);
// Archive to blob storage for historical access
string archivePath = $"reports/sales-weekly/{weekStart:yyyy/MM/dd}.pdf";
report.SaveAs(archivePath);
Imports System.Net.Mail
Imports System.IO
Dim recipients = Await _configService.GetDistributionListAsync("sales-weekly")
Dim subject As String = $"Weekly Sales Report — {weekStart:MMM d, yyyy}"
Using stream As New MemoryStream(report.BinaryData)
Using attachment As New Attachment(stream, $"Sales-Report-{weekStart:yyyyMMdd}.pdf", "application/pdf")
Dim message As New MailMessage With {
.From = New MailAddress("reports@yourcompany.com"),
.Subject = subject,
.Body = $"Please find the weekly sales report for the week of {weekStart:MMMM d, yyyy} attached."
}
For Each recipient In recipients
message.To.Add(recipient)
Next
message.Attachments.Add(attachment)
Using smtp As New SmtpClient("smtp.yourprovider.com")
Await smtp.SendMailAsync(message)
End Using
End Using
End Using
' Archive to blob storage for historical access
Dim archivePath As String = $"reports/sales-weekly/{weekStart:yyyy/MM/dd}.pdf"
report.SaveAs(archivePath)
Przykładowy E-mail z Załączonym PDF
Listy dystrybucyjne są pobierane z tabeli konfiguracyjnej, a nie zakodowane na sztywno, więc dodanie nowego odbiorcy do tygodniowego raportu sprzedaży nie wymaga zmiany kodu ani ponownego wdrożenia. Każdy zarchiwizowany PDF jest identyfikowany według nazwy raportu i daty, tworząc bibliotekę wcześniejszych raportów dostępną z dysku współdzielonego lub wewnętrznego portalu.
Zadanie loguje swój status wykonania - czas zapytania, czas renderowania, wynik wysłania e-maila oraz wszelkie błędy - do strukturalnego logowania. Alert jest wysyłany, jeśli zadanie nie zakończy się powodzeniem, więc pierwszym sygnałem problemu nie jest pytanie kierownictwa, dlaczego nie dotarł ich raport.
Korzyści w praktyce
Zero wysiłku manualnego. Raporty generują się i dostarczają się same zgodnie z harmonogramem. Żaden analityk nie wykonuje zapytań w poniedziałkowy poranek, nikt nie formatuje tabeli, nikt nie pamięta o wysłaniu. Proces jest niewidoczny, gdy działa, i zgłaszany w przypadku awarii.
Terminowość. Raport trafia do skrzynek pocztowych przed rozpoczęciem dnia pracy. Dyrektor finansowy sprawdzający e-maile podczas drogi do biura ma dane P&L zanim jakiekolwiek spotkanie ich wymaga, nie po tym, jak analityk wypije kawę.
Możliwość czytania offline. Samodzielny PDF, który kierownictwo może przeczytać w samolocie, wydrukować na spotkanie zarządu lub przekazać dalej bez wymagania od odbiorcy zalogowania się do systemu. Brak adresu URL do pulpitu nawigacyjnego, brak ściany uwierzytelniania, brak "ten link działa tylko w sieci korporacyjnej".
Spójność marki. Każdy raport generowany przez potok używa tego samego zatwierdzonego szablonu HTML i CSS, to samo logo, typografii, stylizacji tabel i języka w stopce we wszystkich tygodniowych, dziennych i miesięcznych raportach rozprowadzanych przez organizację.
Archiwum historyczne. Każdy wygenerowany PDF jest przechowywany według daty w uporządkowanej ścieżce. Sześć miesięcy tygodniowych raportów sprzedaży to tylko przeglądanie folderów od przeglądu trendów lub audytu, brak wymaganego zapytania do narzędzia BI.
Brak kosztów na raport. Renderowanie działa wewnątrz istniejącej aplikacji .NET. Nie ma potrzeby premium seat w narzędziu BI dla dostaw według harmonogramu ani mierzenia API raportowania, które skaluje się wraz z objętością raportu lub wielkością listy dystrybucyjnej.
Zakończenie
Powtarzalny raport, który dociera niezawodnie przed rozpoczęciem dnia pracy, to mały szczegół operacyjny, który wpływa na podejmowanie decyzji. Kierownictwo, które ma liczby przed porannym spotkaniem, prowadzi innego rodzaju spotkanie niż kierownictwo, które czeka, aż analityk zakończy formatowanie arkusza kalkulacyjnego.
Potok, który do tego prowadzi - harmonogram, zapytanie, szablon, renderowanie, e-mail - mieści się wygodnie w jedynym .NET background job. IronPDF obsługuje pełen cykl życia generowania PDF w C# na ironpdf.com, od renderowania szablonów HTML po zapisywanie, streaming i manipulację dokumentami. Jeśli jesteś gotów zastąpić ręczny proces raportowania takim, który działa samodzielnie, rozpocznij bezpłatny 30-dniowy okres próbny, to wystarczający czas na połączenie harmonogramu, podłączenie go do bazy danych i potwierdzenie, że odpowiednie PDFy trafiają na właściwe skrzynki pocztowe na czas.




