C# Drukowanie formularza do pliku PDF — kompletny przewodnik dla programistów
Problem z równoległymi szablonami PDF
Widoki Razor zostały już zbudowane. Strona szczegółów faktury renderuje pozycje linii, oblicza sumy i stosuje arkusz stylów firmy. Strona statusu projektu pokazuje rozkład zadań z warunkowymi sekcjami, które pojawiają się tylko wtedy, gdy kamienie milowe są opóźnione. Widok paska wypłaty formatuje zarobki, potrącenia i wartości YTD w tabelę, którą zespół HR dopracowywał przez dwa tygodnie. Wszystkie te prace są wykonane, a wtedy interesariusz prosi o przycisk "Pobierz jako PDF".
Standardową odpowiedzią jest zbudowanie drugiego szablonu: ciągu HTML lub definicji raportu, która odtwarza ten sam układ na ścieżce PDF. Ten drugi szablon zaczyna jako kopia pierwszego, a następnie natychmiast zaczyna się różnicować. Projektant UI aktualizuje styl tabeli widoku faktury w Sprint 14. Nikt nie aktualizuje szablonu PDF, dopóki użytkownik nie zgłosi, że pobrana faktura wygląda inaczej niż na ekranie. Teraz są dwa źródła prawdy, a jedno z nich zawsze jest nieco błędne.
Biblioteki PDF po stronie klienta unikają duplikacji, ale tracą dane renderowane na serwerze, uwierzytelnione dane, serwerowo obliczane sumy i sekcje warunkowe kontrolowane przez ViewModel nie przechodzą do rendereru po stronie przeglądarki. Automatyzacja przeglądarki bez głowy z serwera jest krucha, dodaje obciążenie infrastruktury i nieprzewidywalnie zawodzi w kontenerowych środowiskach. Drukowanie przez przeglądarkę do PDF działa dla jednego użytkownika drukującego ręcznie; to nie jest przycisk "Pobierz PDF" w aplikacji produkcyjnej.
Prawdziwe scenariusze ukazują prawdziwy koszt: administrator e-commerce pobierający stronę szczegółów zamówienia do realizacji, klient eksportujący stronę statusu projektu z narzędzia zarządzania projektami, pracownik pobierający swój pasek płac, dyspozytor drukujący podsumowanie trasy. Wszyscy oczekują, że PDF będzie wyglądał dokładnie tak, jak to, co widzą na ekranie.
Rozwiązanie: Renderuj istniejący widok, a nie jego kopię
IronPDF pozwala aplikacjom ASP.NET Core renderować istniejący widok Razor — ten sam, który obsługuje przeglądarkę — bezpośrednio do PDF. Akcja kontrolera PDF renderuje widok Razor do ciągu HTML za pomocą standardowego silnika widoków, przekazuje ten ciąg do ChromePdfRenderer.RenderHtmlAsPdf(), i zwraca wynik jako pobranie pliku.
Jeden widok, dwa wyjścia. Gdy widok Razor się zmienia, wyniki PDF zmieniają się wraz z nim, automatycznie, bez konieczności koordynacji. Nie ma równoległych szablonów do utrzymania, żadnych obejść po stronie klienta do debugowania i żadnego procesu przeglądarki bez głowy do obsługi. Renderowanie działa wewnątrz istniejącej aplikacji .NET jako pojedynczy pakiet NuGet.
Jak to działa w praktyce
1. Widok już istnieje: Akcja PDF to, co jest nowe
Strona szczegółów faktury w /invoices/{id} renderuje ten sam model danych, niezależnie od tego, czy obsługuje przeglądarkę, czy produkuje PDF. Model zawiera pozycje linii, sumy, szczegóły klienta i branding firmy, wszystkie dane potrzebne widokowi. Obecny InvoicesController ma akcję Details, która wypełnia ten model. Akcja PDF jest jego równoległą akcją, a nie zamiennikiem.
Gdy użytkownik klika "Pobierz PDF", żądanie trafia do /invoices/{id}/pdf. Akcja PDF pobiera ten sam ViewModel przy użyciu tego samego wywołania serwisowego, model jest identyczny. To, co różni się, to to, co dzieje się dalej.
2. Widok Razor renderowany na ciąg HTML
Zamiast zwracać ViewResult, akcja PDF używa usługi renderowania widoku, aby wywołać silnik Razor na pliku widoku i ViewModel, przechwytując wynik jako ciąg. Jest to częsty wzorzec w ASP.NET Core, IViewRenderService wprowadzony do kontrolera, który wywołuje ICompositeViewEngine, wykonuje widok w fałszywym ActionContext i zwraca wyrenderowany HTML.
Wyrenderowany ciąg HTML jest kompletny: wszystkie dane są wypełnione, wszystkie sekcje warunkowe są rozstrzygnięte, wszystkie nazwy klas CSS są obecne. To ten sam HTML, który przeglądarka otrzymałaby, przechwycony po stronie serwera.
3. ChromePdfRenderer konwertuje ciąg HTML do formatu PDF
using IronPdf;
[HttpGet("{id}/pdf")]
public async Task<IActionResult> DownloadInvoicePdf(int id)
{
var model = await _invoiceService.GetInvoiceViewModelAsync(id);
// Render the existing Razor view to an HTML string
string html = await _viewRenderer.RenderToStringAsync("Invoices/Details", model);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.MarginTop = 15;
renderer.RenderingOptions.MarginBottom = 15;
PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
return File(pdf.BinaryData, "application/pdf"
$"Invoice-{model.InvoiceNumber}.pdf");
}
using IronPdf;
[HttpGet("{id}/pdf")]
public async Task<IActionResult> DownloadInvoicePdf(int id)
{
var model = await _invoiceService.GetInvoiceViewModelAsync(id);
// Render the existing Razor view to an HTML string
string html = await _viewRenderer.RenderToStringAsync("Invoices/Details", model);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.MarginTop = 15;
renderer.RenderingOptions.MarginBottom = 15;
PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
return File(pdf.BinaryData, "application/pdf"
$"Invoice-{model.InvoiceNumber}.pdf");
}
Imports IronPdf
Imports Microsoft.AspNetCore.Mvc
<HttpGet("{id}/pdf")>
Public Async Function DownloadInvoicePdf(id As Integer) As Task(Of IActionResult)
Dim model = Await _invoiceService.GetInvoiceViewModelAsync(id)
' Render the existing Razor view to an HTML string
Dim html As String = Await _viewRenderer.RenderToStringAsync("Invoices/Details", model)
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
renderer.RenderingOptions.MarginTop = 15
renderer.RenderingOptions.MarginBottom = 15
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(html)
Return File(pdf.BinaryData, "application/pdf", $"Invoice-{model.InvoiceNumber}.pdf")
End Function
Wygenerowany dokument PDF
CssMediaType.Print stosuje wszelkie reguły @media print już obecne w arkuszu stylów widoku: ukrywając pasek nawigacyjny, tłumiąc przyciski akcji i stosując specjalne odstępy dla drukowania, bez konieczności dokonywania jakichkolwiek zmian w samym widoku Razor.
4. Dopasowanie szczegółów wyjścia PDF bez dotykania widoku
Dostosowania specjalne dla PDF, takie jak numery stron, niestandardowe marginesy, nagłówki z tytułem dokumentu, są konfigurowane na rendererze, a nie w widoku Razor. To utrzymuje logikę drukowania z dala od szablonu:
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = @"
<div style='font-size:9px; color:#888; text-align:center; width:100%;'>
Invoice — Page {page} of {total-pages}
</div>",
DrawDividerLine = true
};
PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
HtmlFragment = @"
<div style='font-size:9px; color:#888; text-align:center; width:100%;'>
Invoice — Page {page} of {total-pages}
</div>",
DrawDividerLine = true
};
PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
Imports IronPdf
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
.HtmlFragment = "
<div style='font-size:9px; color:#888; text-align:center; width:100%;'>
Invoice — Page {page} of {total-pages}
</div>",
.DrawDividerLine = True
}
Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(html)
Plik PDF wyjściowy
Widok Razor nie musi wiedzieć, czy renderuje do przeglądarki, czy do PDF. Akcja kontrolera posiada konfigurację specyficzną dla PDF, a widok pozostaje czystym szablonem wyświetlania.
Korzyści w praktyce
Żadnej duplikacji szablonów. Widok Razor to jedyne źródło prawdy dla układu i treści dokumentu. Przeglądarka i PDF są renderowane z tego samego pliku, nie ma drugiego szablonu do utrzymania i nie ma dryfowania do poprawienia.
Natychmiastowe wdrożenie. Jeśli widok już istnieje, eksport PDF jest oddalony o jedną akcję kontrolera. Nie trzeba przeprojektowywania układów, odbudowywania szablonów ani przenoszenia logiki warunkowej do innego systemu renderowania.
Precyzyjne wyjście pikselowe. Renderowanie oparte na Chromium oznacza, że siatka CSS, flexbox, fonty webowe i zapytania mediów działają w PDF. Wynik odpowiada temu, co produkuje przeglądarka, a nie zdegradowanej aproksymacji.
Stylizacja specyficzna dla druku. Reguły @media print już obecne w arkuszu stylów widoku kontrolują, co pojawia się w PDF: ukrywanie nawigacji, dostosowywanie szerokości kolumn dla papieru lub przekształcanie treści. Brak osobnego szablonu, brak stylów drukowania inline do osobnego zarządzania.
Możliwość utrzymania. Zaktualizuj widok Razor, a zarówno wyjście przeglądarki, jak i wyjście PDF odzwierciedlą zmianę. Nie ma drugiego systemu do koordynowania aktualizacji, brak ryzyka, że zmiana projektanta dotrze do przeglądarki, ale nie do PDF.
Bez kosztów na dokument. Renderowanie odbywa się wewnątrz aplikacji webowej. Nie ma zewnętrznych wywołań API, brak mierzenia użycia i brak modelu cenowego, który skaluje się w stosunku do wielkości pobrań.
Zakończenie
Jeśli widok Razor jest już zbudowany, eksport PDF to nie nowa funkcja, to nowa ścieżka dostawy dla istniejącej pracy. Ten sam model, ten sam widok, te same style: jedynym dodatkiem jest akcja kontrolera, która przechwytuje wyjście HTML widoku i przekazuje je przez renderer przed zwróceniem jako plik.
Ta architektura utrzymuje kod czystym, a wyjście PDF permanentnie synchronizowane z przeglądarką. IronPDF obsługuje pełen cykl życia generacji PDF w C# na stronie ironpdf.com, od renderowania HTML po zapisywanie, strumieniowanie i manipulowanie dokumentami. Jeśli jesteś gotów dodać eksport PDF do istniejących widoków Razor, rozpocznij bezpłatny 30-dniowy okres próbny i zweryfikuj wyjście w stosunku do obecnego renderowania przeglądarki przed wysłaniem funkcji.




