Ücretsiz PDF Kütüphaneleri için .NET: Gizli Maliyetler ve C#'ta Daha İyi Alternatifler
.NET için ücretsiz PDF kütüphaneleri, gizli maliyetlerle gelir: AGPL lisans tuzakları, eksik HTML desteği, yamalanmamış CVE'lerle birlikte eski bağımlılıklar, gelir eşikleri ve genellikle ticari lisans maliyetlerini aşan operasyonel karmaşıklık.
Onlardan herhangi birine taahhüt etmeden önce, bunu bir terminalde çalıştırın:
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>.NET uygulamanızwkhtmltopdfveya onun bir sarıcısını — DinkToPdf, TuesPechkin, Rotativa, NReco.PdfGenerator — kullanıyorsa, bu HTML cloud sağlayıcınızın meta veri uç noktasında bir Sunucu Tarafı Talep Sahteciliği gerçekleştirecektir. AWS IAM kimlik bilgileri, Azure yönetilen kimlik belirteçleri, GCP hizmet hesabı anahtarları. Hepsi açığa çıktı. Proje Ocak 2023'te arsivlendi. Hicbir yama gelmiyor.
"Ücretsiz" maliyeti uretimde bu kadar.
Hizli Başlangic: .NET Projeniz icin PDF Kutuphanelerini Degerlendirin
// Install: dotnet add package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><style>h1{font-family:Inter}</style>");
pdf.SaveAs("output.pdf");// Install: dotnet add package IronPdf
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><style>h1{font-family:Inter}</style>");
pdf.SaveAs("output.pdf");' Install: dotnet add package IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1><style>h1{font-family:Inter}</style>")
pdf.SaveAs("output.pdf")var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
gfx.DrawString("Hello World", new XFont("Arial", 20), XBrushes.Black, 72, 72);
document.Save("output.pdf");var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
gfx.DrawString("Hello World", new XFont("Arial", 20), XBrushes.Black, 72, 72);
document.Save("output.pdf");Dim document As New PdfDocument()
Dim page = document.AddPage()
Dim gfx = XGraphics.FromPdfPage(page)
gfx.DrawString("Hello World", New XFont("Arial", 20), XBrushes.Black, 72, 72)
document.Save("output.pdf")Bu iki örnek arasindaki fark, cogunlukla .NET geliştiricilerinin ihtiyaç duydugu sey ile cogunlukla ücretsiz kutuphanelerin sundugu sey arasindaki farktir.
"Ücretsiz" Eger .NET PDF Kutuphanelerinde Gerçekten Ne Anlama Geliyor?
"PDF" icin NuGet aramasi yapin ve .NET dağıtimlari icin farkli kisitlamalari olan bes lisanslama modelinde kutuphaneler bulacaksiniz:
MIT/Apache (gercekten musamaha gösteren): PdfSharp. Ticari uygulamalarda, Docker kapsayicilarinda, Azure Functions, AWS Lambda'da kullanin — hicbir sinirlama yok, hicbir gelir esigi yok, hicbir kaynak kodu aciklamasi yok. Isin yakin: HTML'yi PDF'e dönüştüremez.
AGPL (copyleft tuzagi):iText Core(eski iTextSharp). Herhangi bir agda erisilebilir uygulamada dağıtin — web uygulamalari, REST API'leri, mikrohizmetler — ve tüm kaynak kodunuzu AGPL altinda yayimlamaniz gerekecek. SaaS şirketleri icin muafiyet yok.
Gelir-kapali:QuestPDFTopluluk Lisansi. Yillik $1M brut gelir altinda ücretsiz. Bu esigi gecerseniz ticari bir lisans gerekir. Gecis asamali değil — bir ucurum.
Terk edilmis:wkhtmltopdfve tüm .NET cevreleri. Dokunulmamis CVE'ler 9.8 Kritik ile arsivlenmis. Sifir guvenlik bakimi. Herhangi bir uyumluluk denetiminde bir sorumluluk.
Isletim olarak pahali:PuppeteerSharpve Playwright for .NET. Lisans kisitlamasi yok, tam modern CSS destegi var — ancak uretimde harici tarayici sureclerle, Chromium indirmeleriyle ve bellek omru ile ilgileniyorsunuz.
Her kategori, .NET dağıtimlari icin farkli riskler oluşturur. Bu makalenin geri kalani, bu riskleri kod, sayilar ve NuGet ekosistem verileri ile aciklamaktadir.
Neden iText'in Fiyatlandirilmasi Asil Hikaye?
Cogu iText hakkinda makale, AGPL uygulama acisina odaklanir. Baska yerlerde ele alinmistir. .NET takimlarinin PDF kutuphanelerini degerlendirirken daha alakali olan soru, ticari bir lisansa ihtiyaç duydugunuzda neler oldugu.
Ticari iText Gerçekten Ne Kadara Mal Olur?
Nisan 2020'de, iText süresiz lisanslamadan abone bazli modellere gecti. Vendr'in işlem veritabanindan alinmis ucuncu taraf fiyat verileri su konulari göstermektedir:
- Ortalama yillik sözleşme: ~$45,000
- Yuksek ucretli sözleşmeler: PDF hacmine gore $210,000 kadar
- Fiyat modeli: Hacme dayali — uygulamanizin yilda generate ettigi PDF sayisiyla maliyetler artar
Bu hacme dayali model, buyuyen uygulamalar icin kontrol edilemez butceler yaratir. Q1 doneminde ayda 10,000 PDF oluşturan ve Q4 rominde 100,000'e kadar artan .NET mikrohizmeti, lisans faturasinin buna gore olculduklerini gorecek — ve iText'in fiyatlandirma kademeleri acik degildir.
IronPDF'nin yayimlanmis fiyatlarini karsilastirin: süresiz Lite lisansi icin $749. Yillik abone yok. Hacim olcme yok. Uygulamaniz buyudukce surpriz yok.
Abonelik Modeli .NET Takimlarini Nasıl Etkiliyor?
Süresiz lisanslamadan abone modellere gecis, TCO hesaplamasini değiştiriyor:
| Faktör | iText Abonelik | IronPDFSureli |
|---|---|---|
| 1. yil maliyeti | ~$45.000 | $749 - $2.999 |
| 3. yil maliyeti | ~$135.000 | $749 - $2.999(bir seferlik) |
| Hacim olclenmesi | Maliyetler artiyor | Hacim olcme yok |
| Butce tahmin edilebilirligi | Değişken | Fixed |
| Iptal riski | Erişim kaybedilir | Süresiz sahiplik |
.NET takimi icin bir SaaS urunu geliştirirken, bes yillik delta $200,000'i asabilir. Bu, AGPL uygulama tartismalarindan daha onemli olan fiyat hikayesidir.
PdfSharp'in .NET Dağıtimlari Icin Sinirlamalari Nelerdir?
PdfSharp ücretsiz kutuphaneler arasinda bir istisna: MIT lisansi, 34+ milyon NuGet indirilmesi, ticari kullanim icin gercekten musamaha gösteren. Gelir eşikleri yok. Kaynak kodu aciklamasi yok.
Sinirlama mimari derinliktedir.PdfSharpPDF koordinat seviyesinde calismaktadir. HTML parcayicisi, CSS motoru, DOM görüntülemesi yok.
var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
// Every element needs manual coordinates
var titleFont = new XFont("Arial", 18, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 10);
gfx.DrawString("INVOICE #2024-0847", titleFont, XBrushes.Black, 72, 72);
gfx.DrawString("Date: 2024-12-15", bodyFont, XBrushes.Gray, 72, 100);
// Table header - manual line drawing
gfx.DrawLine(XPens.Black, 72, 140, 540, 140);
gfx.DrawString("Item", bodyFont, XBrushes.Black, 72, 155);
gfx.DrawString("Qty", bodyFont, XBrushes.Black, 300, 155);
gfx.DrawString("Price", bodyFont, XBrushes.Black, 400, 155);
gfx.DrawLine(XPens.Black, 72, 170, 540, 170);
// Row data - every cell is a manual coordinate
gfx.DrawString("Annual License", bodyFont, XBrushes.Black, 72, 185);
gfx.DrawString("1", bodyFont, XBrushes.Black, 300, 185);
gfx.DrawString("$749.00", bodyFont, XBrushes.Black, 400, 185);
document.Save("invoice.pdf");var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
// Every element needs manual coordinates
var titleFont = new XFont("Arial", 18, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 10);
gfx.DrawString("INVOICE #2024-0847", titleFont, XBrushes.Black, 72, 72);
gfx.DrawString("Date: 2024-12-15", bodyFont, XBrushes.Gray, 72, 100);
// Table header - manual line drawing
gfx.DrawLine(XPens.Black, 72, 140, 540, 140);
gfx.DrawString("Item", bodyFont, XBrushes.Black, 72, 155);
gfx.DrawString("Qty", bodyFont, XBrushes.Black, 300, 155);
gfx.DrawString("Price", bodyFont, XBrushes.Black, 400, 155);
gfx.DrawLine(XPens.Black, 72, 170, 540, 170);
// Row data - every cell is a manual coordinate
gfx.DrawString("Annual License", bodyFont, XBrushes.Black, 72, 185);
gfx.DrawString("1", bodyFont, XBrushes.Black, 300, 185);
gfx.DrawString("$749.00", bodyFont, XBrushes.Black, 400, 185);
document.Save("invoice.pdf");Imports PdfSharp.Pdf
Imports PdfSharp.Drawing
Dim document As New PdfDocument()
Dim page As PdfPage = document.AddPage()
Dim gfx As XGraphics = XGraphics.FromPdfPage(page)
' Every element needs manual coordinates
Dim titleFont As New XFont("Arial", 18, XFontStyleEx.Bold)
Dim bodyFont As New XFont("Arial", 10)
gfx.DrawString("INVOICE #2024-0847", titleFont, XBrushes.Black, 72, 72)
gfx.DrawString("Date: 2024-12-15", bodyFont, XBrushes.Gray, 72, 100)
' Table header - manual line drawing
gfx.DrawLine(XPens.Black, 72, 140, 540, 140)
gfx.DrawString("Item", bodyFont, XBrushes.Black, 72, 155)
gfx.DrawString("Qty", bodyFont, XBrushes.Black, 300, 155)
gfx.DrawString("Price", bodyFont, XBrushes.Black, 400, 155)
gfx.DrawLine(XPens.Black, 72, 170, 540, 170)
' Row data - every cell is a manual coordinate
gfx.DrawString("Annual License", bodyFont, XBrushes.Black, 72, 185)
gfx.DrawString("1", bodyFont, XBrushes.Black, 300, 185)
gfx.DrawString("$749.00", bodyFont, XBrushes.Black, 400, 185)
document.Save("invoice.pdf")Bu, stil, duyarlı yerleşim, CSS olmadan tek satirlik bir fatura icin 20 satirdir. Simdi dinamik veriler, grafikler ve kurumsal marka ile 15 sayfalik bir uyumluluk raporu oluşturmayi dusunun.
Platformlar Arasi Dağıtim Dusunceleri
PdfSharp, .NET 6+ platformlar arasi senaryolarda iyi calisir — yerli bagimlilik yok, Chromium ikilileri yok, harici surecler yok. Minimal kapsayici boyutuyla Docker, Azure Functions, ve AWS Lambda'ya temizce dağıtilir.
Sadece yapisal verilerden programatik PDF oluşturma gerektiren uygulamalar icin — sevkiyat etiketleri, basit makbuzlar, koordinatla cizilmis diyagramlar —PdfSharpuygun bir secimdir. NuGet sagligi guclu: aktif bagislama, duyarlı bakici, düzenli yayinlar.
HTML icerigi, web semalari veya modern CSS ile ilgili herhangi bir sey icinPdfSharpyanlis aracdir.
QuestPDFNe Zaman Ücretsiz Olmaktan Cikar?
QuestPDF, PdfSharp'tan farkli bir tasarim yaklasimi aldi: koordinat matematigi yerine bir yerlesim tanimi gibi okunan akici bir API. API tasarımı gerçekten iyi.
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Content().Column(column =>
{
column.Item().Text("Invoice #2024-0847").FontSize(18).Bold();
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
table.Cell().Text("Item").Bold();
table.Cell().Text("Qty").Bold();
table.Cell().Text("Price").Bold();
table.Cell().Text("Annual License");
table.Cell().Text("1");
table.Cell().Text("$749.00");
});
});
});
}).GeneratePdf("invoice.pdf");Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Content().Column(column =>
{
column.Item().Text("Invoice #2024-0847").FontSize(18).Bold();
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
table.Cell().Text("Item").Bold();
table.Cell().Text("Qty").Bold();
table.Cell().Text("Price").Bold();
table.Cell().Text("Annual License");
table.Cell().Text("1");
table.Cell().Text("$749.00");
});
});
});
}).GeneratePdf("invoice.pdf");Document.Create(Sub(container)
container.Page(Sub(page)
page.Size(PageSizes.A4)
page.Margin(2, Unit.Centimetre)
page.Content().Column(Sub(column)
column.Item().Text("Invoice #2024-0847").FontSize(18).Bold()
column.Item().Table(Sub(table)
table.ColumnsDefinition(Sub(columns)
columns.RelativeColumn(3)
columns.RelativeColumn(1)
columns.RelativeColumn(1)
End Sub)
table.Cell().Text("Item").Bold()
table.Cell().Text("Qty").Bold()
table.Cell().Text("Price").Bold()
table.Cell().Text("Annual License")
table.Cell().Text("1")
table.Cell().Text("$749.00")
End Sub)
End Sub)
End Sub)
End Sub).GeneratePdf("invoice.pdf")PdfSharp'ın koordinat sisteminden daha ifadelidir. Ancak ayni temel sinirlamayi paylasir: HTML'yi görüntüleme yok.
Gelir Ucurumu
QuestPDF'nin Topluluk Lisansi, yillik brut geliri $1,000,000 altinda olan şirketler icin ucretsizdir. Bu esigi gecerseniz, $699/yil icin Profesyonel veya $1,999/yil icin Kurumsal lisans gerekir.
Bir başlar icin, bu bir buyume zaman cizelgesi senaryosu oluşturuyor:
- 1. Yil ($200K gelir): Ücretsiz. QuestPDF'nin akici API'si başlangic geliştirmeleri hizlandirir.
- 2. Yil ($600K gelir): Hala ücretsiz. API kod tabaniniza derinlemesine entegre edilmis.
- 3. Yil ($1.1M gelir): Lisans gerekli. Simdi API'ye kilitlenmis durumdasiniz ve onemli gecis maliyetleri.
Gecis lisans maliyetiyle ilgili değil, bir araya gelmiş gecis maliyetiyle ilgilidir. 3. yil itibariyla, PDF oluşturma katmaniniz, baska kutuphanelerde eslemesi olmayan akici API cagrisiyla onlarca dosyayi kapsayabilir.
HTML yanilgisina
Web cercevelerinden gelen geliştiriciler, modern bir .NET PDF kutuphanesinin HTML girdisini kabul edecegini beklener.QuestPDFkesinlikle HTML'den PDF'e donusumu desteklemez. API tamamen kod - her yerlesim oge bir metod cagrisidir, isaretleme değil.
Bu uyumsuzluk,QuestPDFlisanslari satin alan (veya Topluluk surumune dayali) takimlari, projede ortasında adim attiklarin yerleşik HTML fatura semalari, e-posta-PDF iş akışları, veya rapor generatorlerinin QuestPDF'yi hic kullanamayacaklarını keşfedene kadar tuzağa düser.
IronPDF Chrome'da görüntülen HTML, ayni sekilde bir PDF icinde görüntülenmeniz icin HTML, CSS, ve JavaScript'i girdi olarak kabul eder, cunku bir Chromium render motoru gomuludur.
wkhtmltopdf'yi .NET'de Herhangi Bir Dağıtimda Neden Kullanmayacaksiniz?
Bu makaleyi, bir neden icin CVE-2022-35583 ile actim. SSRF zafiyeti teorik değil — kanit konsepti istismarlar kamusal olarak mevcut ve aktif bir sekilde kullaniliyor.
Tam Guvenlik Tablosu
wkhtmltopdf artik asla duzelmeyecek iki onarilamamis CVE tasir:
CVE-2022-35583 (CVSS 9.8 Kritik): ifame enjeksiyonu yolundan sunucu-taraffni taleplerini sahtekarlik. Uzerinde bulut ortamlari, AWS IAM kimlik bilgileri, Azure yonetilen kimlik belirtecleri, GCP hizmet hesap anahtarlari, Kubernetes hizmet hesap belirtecleri bu sunucu metada verilerini aciklar.
CVE-2020-21365 (CVSS 7.5 Yuksek): ozenli HTML girdisi araciligiyla yerel dosyalari okumak icin uzaktan saldirganlarin dizin gecis izni.
Her iki zafiyet de halka acik exploit kodu içerir. Her ikisi de aktif olarak istismar edilmektedir. Hiçbiri bir yamaya sahip olmayacak.
.NET Sarıcı Ekosistem Sağlığı
wkhtmltopdf için her .NET sarmalayıcısı bu güvenlik açıklarını devralır ve kendi bakım borcunu ekler:
| Sarici | Son Anlamlı Commit | Açık Sorunlar | .NET 8 Desteği |
|---|---|---|---|
| DinkToPdf | 2018 | 300'den fazla yanıtsız | Hayır |
| TuesPechkin | 2015 | Terk edilmiş | Hayır |
| Rotativa | 2019 | Sadece MVC | Hayır |
| NReco.PdfGenerator | Aktif | Ticari | Sınırlı |
NReco, halawkhtmltopdfikili dosyasına bağlı olduğu için, seyahat eden CVE'ler ile, aktif olarak bakımı yapılan tek sarmalayıcıdır.
Rendering, 2013'te Dondurulmuştur
Güvenliğin ötesinde, wkhtmltopdf'nin Qt WebKit motoru, 2013 döneminin web standartlarında dondurulmuştur. CSS Flexbox yok. CSS Grid yok. CSS Değişkenleri yok. Hayır calc(). ES6+ JavaScript çalıştırılamıyor.
Tailwind CSS, Bootstrap 5 veya modern CSS çerçeveleri kullanan herhangi bir .NET uygulaması bozuk çıktı üretecektir. Kapsayıcı dağıtımını hedefleyen bir .NET 8 uygulaması için, modern web standartlarını desteklemeyen bakım yapılmayan bir ikili dosya, taşımayı seçtiğiniz teknik borçtur.
PuppeteerSharp'ın Gerçek Operasyonel Maliyeti Nedir?
Bu bölüm, çoğu "ücretsiz PDF kitaplığı" makalesinde yanlış anlaşılmaktadır.PuppeteerSharpve .NET için Playwright teknik olarak mükemmeldir — gerçek Chromium üzerinden HTML'yi işler, her CSS özelliğini ve JavaScript API'sini destekler. Lisanslama kısıtlamaları yok. Gelir eşikleri yok.
Maliyet operasyoneldir. İşte üretimPuppeteerSharpPDF üretiminin aslında nasıl göründüğü:
using PuppeteerSharp;
public class PuppeteerPdfService : IDisposable
{
private IBrowser _browser;
private readonly SemaphoreSlim _semaphore;
private long _pdfCount = 0;
public PuppeteerPdfService()
{
// Limit concurrent pages to prevent memory exhaustion
_semaphore = new SemaphoreSlim(3, 3);
}
public async Task InitializeAsync()
{
// Step 1: Download Chromium binary (~280MB)
var fetcher = new BrowserFetcher();
await fetcher.DownloadAsync();
// Step 2: Launch with production-hardened flags
_browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox", // Required in Docker
"--disable-setuid-sandbox", // Required in Docker
"--disable-dev-shm-usage", // Prevent /dev/shm exhaustion
"--disable-gpu",
"--no-zygote",
"--single-process",
"--disable-extensions",
"--max_old_space_size=4096"
},
Timeout = 30000
});
}
public async Task<byte[]> GeneratePdfAsync(string html)
{
await _semaphore.WaitAsync();
IPage page = null;
try
{
Interlocked.Increment(ref _pdfCount);
page = await _browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 },
Timeout = 20000
});
var pdfBytes = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "20mm", Bottom = "20mm",
Left = "15mm", Right = "15mm"
}
});
return pdfBytes;
}
finally
{
if (page != null)
{
try { await page.CloseAsync(); }
catch { /* Disposal can hang — GitHub issue #1489 */ }
}
_semaphore.Release();
}
}
// Restart browser periodically to reclaim leaked memory
public async Task RecycleBrowserAsync()
{
var oldBrowser = _browser;
await InitializeAsync();
try { oldBrowser?.Dispose(); } catch { }
}
public void Dispose()
{
_browser?.Dispose();
_semaphore?.Dispose();
}
}using PuppeteerSharp;
public class PuppeteerPdfService : IDisposable
{
private IBrowser _browser;
private readonly SemaphoreSlim _semaphore;
private long _pdfCount = 0;
public PuppeteerPdfService()
{
// Limit concurrent pages to prevent memory exhaustion
_semaphore = new SemaphoreSlim(3, 3);
}
public async Task InitializeAsync()
{
// Step 1: Download Chromium binary (~280MB)
var fetcher = new BrowserFetcher();
await fetcher.DownloadAsync();
// Step 2: Launch with production-hardened flags
_browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[]
{
"--no-sandbox", // Required in Docker
"--disable-setuid-sandbox", // Required in Docker
"--disable-dev-shm-usage", // Prevent /dev/shm exhaustion
"--disable-gpu",
"--no-zygote",
"--single-process",
"--disable-extensions",
"--max_old_space_size=4096"
},
Timeout = 30000
});
}
public async Task<byte[]> GeneratePdfAsync(string html)
{
await _semaphore.WaitAsync();
IPage page = null;
try
{
Interlocked.Increment(ref _pdfCount);
page = await _browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 },
Timeout = 20000
});
var pdfBytes = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true,
MarginOptions = new MarginOptions
{
Top = "20mm", Bottom = "20mm",
Left = "15mm", Right = "15mm"
}
});
return pdfBytes;
}
finally
{
if (page != null)
{
try { await page.CloseAsync(); }
catch { /* Disposal can hang — GitHub issue #1489 */ }
}
_semaphore.Release();
}
}
// Restart browser periodically to reclaim leaked memory
public async Task RecycleBrowserAsync()
{
var oldBrowser = _browser;
await InitializeAsync();
try { oldBrowser?.Dispose(); } catch { }
}
public void Dispose()
{
_browser?.Dispose();
_semaphore?.Dispose();
}
}Imports PuppeteerSharp
Imports System.Threading
Public Class PuppeteerPdfService
Implements IDisposable
Private _browser As IBrowser
Private ReadOnly _semaphore As SemaphoreSlim
Private _pdfCount As Long = 0
Public Sub New()
' Limit concurrent pages to prevent memory exhaustion
_semaphore = New SemaphoreSlim(3, 3)
End Sub
Public Async Function InitializeAsync() As Task
' Step 1: Download Chromium binary (~280MB)
Dim fetcher = New BrowserFetcher()
Await fetcher.DownloadAsync()
' Step 2: Launch with production-hardened flags
_browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
.Headless = True,
.Args = {
"--no-sandbox", ' Required in Docker
"--disable-setuid-sandbox", ' Required in Docker
"--disable-dev-shm-usage", ' Prevent /dev/shm exhaustion
"--disable-gpu",
"--no-zygote",
"--single-process",
"--disable-extensions",
"--max_old_space_size=4096"
},
.Timeout = 30000
})
End Function
Public Async Function GeneratePdfAsync(html As String) As Task(Of Byte())
Await _semaphore.WaitAsync()
Dim page As IPage = Nothing
Try
Interlocked.Increment(_pdfCount)
page = Await _browser.NewPageAsync()
Await page.SetContentAsync(html, New NavigationOptions With {
.WaitUntil = {WaitUntilNavigation.Networkidle0},
.Timeout = 20000
})
Dim pdfBytes = Await page.PdfAsync(New PdfOptions With {
.Format = PaperFormat.A4,
.PrintBackground = True,
.MarginOptions = New MarginOptions With {
.Top = "20mm", .Bottom = "20mm",
.Left = "15mm", .Right = "15mm"
}
})
Return pdfBytes
Finally
If page IsNot Nothing Then
Try
Await page.CloseAsync()
Catch
' Disposal can hang — GitHub issue #1489
End Try
End If
_semaphore.Release()
End Try
End Function
' Restart browser periodically to reclaim leaked memory
Public Async Function RecycleBrowserAsync() As Task
Dim oldBrowser = _browser
Await InitializeAsync()
Try
oldBrowser?.Dispose()
Catch
End Try
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_browser?.Dispose()
_semaphore?.Dispose()
End Sub
End ClassBir PDF oluşturulmadan önce 80'den fazla satır vardır. Ve hala hata kurtarma, sağlık kontrolleri, metrikler ve üretim sistemlerinin ihtiyaç duyduğu bellek geri dönüşüm zamanlayıcısı eksiktir.
Bu Karmaşıklık, .NET Dağıtımları İçin Neden Önemlidir?
Operasyonel yük, dağıtım hedefi ile ölçeklenmektedir:
Docker: Chromium'u kapsayıcı imajınıza eklemek zorundasınız. Bu, imaja ~280MB ekler, çekim sürelerini, kayıt depolama maliyetlerini ve soğuk başlangıç gecikmesini arttırır. Dockerfile'ınız, Chromium'un sistem bağımlılıkları için açık apt-get install komutlarına ihtiyaç duyar — libgbm-dev, libasound2, libatk-bridge2.0-0 ve temel imaja göre değişen yaklaşık 15 diğerleri.
Azure Functions / AWS Lambda: Sunucusuz ortamlar bellek ve yürütme süresini kısıtlar. Chromium'un soğuk başlangıcı — tarayıcı sürecini indirip başlatmak — 5-10 saniye ve 500MB'den fazla bellek tüketebilir. Lambda'nın 250MB dağıtım paketi sınırı, Chromium'un zar zor sığmasına neden olur ve Azure Tüketim planının 1.5GB bellek sınırı, gerçek PDF üretimi için çok az yer bırakır.
Kubernetes: Tarayıcı süreçleri, kapsayıcı orkestrasyonu ile iyi oynamaz. Uygulama kodunuz için uygun görünen bellek sınırları, Chromium işleyici süreçlerini başlattığında yetersiz hale gelir. Yüksek bellek talepleri ayarlamazsanız, Pod OOMKills sık görülen bir olay haline gelir, uygulamanıza gerçekten ihtiyaç duyduğu bellekten daha fazla bellek talepleriyle birlikte.
EşdeğerIronPDFKodu
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")Altı satır. IronPDF, Chromium'u dahili olarak gömülü olarak kullanır — tarayıcı yaşam döngüsü, bellek yönetimi ve süreç havuzlama, kütüphane tarafından düzenlenir. SemaphoreSlim yok. Tarayıcı geri dönüşümü yok. Dockerfile değişiklikleri yok. NuGet paketi her şeyi içerir.
Karşılığında gerçek bir durum var: IronPDF'nin NuGet paketi, Chromium ikili dosyasını içerdiği için PdfSharp'tan daha büyüktür. Motor başlatıldığında ilk PDF gecikmesi 2-5 saniyedir, sonra sonraki üretimler için 100-500ms'dir. Dağıtım boyutunun birincil sınırlayıcı olduğu uygulamalar için bu önemlidir. Geliştirici zamanı ve operasyonel güvenilirliğin daha önemli olduğu uygulamalar için gömülü yaklaşım kazanır.
.NET Ekosistem Sağlığı: NuGet Paketi Karşılaştırması
Bir kütüphane seçmeden önce NuGet ekosistem sağlığını kontrol edin. Bağımlılık zincirleri, sürüm sıklığı ve sorun çözme süresi, özellik listelerinden daha fazlasını anlatır:
| Kütüphane | NuGet İndirmeleri | Son Sürüm | Açık Sorunlar | .NET 8 TFM | Yerel Bağımlılıklar |
|---|---|---|---|---|---|
| PdfSharp | 34M+ | Aktif | Low | ✅ | None |
| QuestPDF | 8M+ | Aktif | Low | ✅ | None |
| iText Core | 30M+ | Aktif | Orta | ✅ | None |
| IronPDF | 10M+ | Aktif | Low | ✅ | Chromium (bütünleşik) |
| DinkToPdf | 5M+ | 2018 | 300+ | ❌ | wkhtmltopdfbinary |
| PuppeteerSharp | 15M+ | Aktif | Orta | ✅ | Chromium (harici) |
DinkToPdf gibi terkedilmiş paketlerdeki yüksek indirme sayıları, mevcut sağlığı değil, miras olarak benimsenmeyi yansıtır.300+yanıtlanmayan açık problem, gerçek hikayeyi anlatıyor.
.NET 8 uygulamaları net8.0 TFM'i hedefliyorsa: PdfSharp, QuestPDF,iText CoreveIronPDFbunu doğrudan destekler.wkhtmltopdfsarmalayıcıları desteklemez — DllNotFoundException ve NU1202 hedef çerçeve uyum hataları beklenebilir.
Karar Matrisi
| Gereksinim | PdfSharp | QuestPDF | iText Core | wkhtmltopdf | PuppeteerSharp | IronPDF |
|---|---|---|---|---|---|---|
| Gerçekten ücretsiz (MIT/hoşgörülü) | ✅ | ❌ Gelir kapısı | ❌ AGPL | ⚠️ Terk edilmiş | ✅ | ❌ Ticari |
| HTML to PDF | ❌ | ❌ | ⚠️ Sınırlı | ⚠️ Bozuk CSS | ✅ | ✅ |
| Modern CSS (Flexbox/Grid) | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| JavaScript yürütme | ❌ | ❌ | ❌ | ⚠️ Yalnızca ES5 | ✅ | ✅ |
| Tarayıcı yönetimi yok | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Aktif güvenlik yamaları | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
| Gelir eşik yok | ✅ | ❌ | N/A | N/A | ✅ | ✅ |
| Tahmin edilebilir lisans maliyeti | Ücretsiz | 1M $'da uçurum | Yıllık ortalama ~45K $ | N/A | Ücretsiz | 749 $ süresiz |
| Docker dostu | ✅ Küçük | ✅ Küçük | ✅ Küçük | ⚠️ İkili bağımlılık | ⚠️ +280MB | ✅ Kendine yeten |
| Sunucusuz uyumlu | ✅ | ✅ | ✅ | ❌ | ⚠️ Soğuk başlatma | ✅ |
Yalnızca yapılandırılmış verilerden programatik PDF oluşturma ihtiyaçınız varsa:PdfSharp(MIT, kısıtlama yok) veyaQuestPDF(daha iyi API, gelir eşiklerine dikkat edin).
Eğer modern CSS ile HTML'den PDF'ye ihtiyaçınız var ve tarayıcı altyapısını yönetmek istemiyorsanız: IronPDF. Dahili Chromium, işleme motoru yaşam döngüsünü ele alır. Yayınlanmış fiyatlandırma, iText'in abonelik modelinin bir kısmında.
HTML'den PDF'ye ihtiyaçınız varsa ve tarayıcı süreçlerini yönetmekte rahatsanıza:PuppeteerSharpsize tam kontrol sağlar. Operasyonel yük için bütçeler oluşturun.
Şu andawkhtmltopdfveya herhangi bir sarmalayıcı kullanıyorsanız: Geçiş yapın. Sadece güvenlik açığı bile çabayı haklı kılıyor — ve her ay gecikirsiniz, CVE listesi yamalanmadan kalır.
