使用IRONPDF 如何使用C#在ASP.NET中生成PDF Curtis Chau 更新:2026年2月10日 下載 IronPDF NuGet 下載 DLL 下載 Windows Installer 開始免費試用 LLM副本 LLM副本 將頁面複製為 Markdown 格式,用於 LLMs 在 ChatGPT 中打開 請向 ChatGPT 諮詢此頁面 在雙子座打開 請向 Gemini 詢問此頁面 在 Grok 中打開 向 Grok 詢問此頁面 打開困惑 向 Perplexity 詢問有關此頁面的信息 分享 在 Facebook 上分享 分享到 X(Twitter) 在 LinkedIn 上分享 複製連結 電子郵件文章 以程式方式生成PDF文件是現代 web 應用程式的重要需求,無論您是在創建發票、報告、證書或票券。 如果您是一位ASP.NET Core .NET開發者,正在尋找實現像素級精確渲染和企業功能的強大PDF生成,那麼您來對地方了。 這本全面的指南將帶您了解如何使用IronPDF生成專業的PDF文件,這是一個強大的.NET程式庫,可以輕鬆創建PDF文件。 我們將從基本設定到高級批次處理進行探索,展示其高效且易於整合的特性。 最後,您將擁有一個強大的PDF轉換器,可以在您的ASP.NET應用程式中使用IronPDF專業地生成PDF文件。 為什麼要在ASP.NET Core中生成PDF? 伺服器端PDF生成相比於客戶端替代方案具有顯著的優勢。 當您在伺服器上生成PDF時,您可以確保在所有瀏覽器和設備上的一致輸出,消除對客戶端資源的依賴,並且能更好地控制敏感數據。 HTML轉換為PDF的常見商業場景包括:HTML to PDF conversion 財務文件:發票、報表和交易收據 合規報告:法規申報和審計文件 用戶證書:培訓結業和專業證書 活動門票:QR碼進場入場券和登機證 資料匯出:分析報告和儀表板快照 此外,伺服器端的方法確保PDF在所有瀏覽器和作業系統中保持一致。 使其在法律和財務文件方面備受推崇。 IronPDF 如何改變您的PDF生成? IronPDF是在.NET生態系統中脫穎而出的PDF程式庫,這得益於其HTML轉換器,該轉換器在後端使用完整的Chrome渲染引擎。 這意味著您的PDF文件渲染效果將與在Google Chrome中完全相同,完全支援現代CSS3、JavaScript執行和網路字型。 與其他程式庫不同,IronPDF讓您能夠直接將現有的HTML、CSS和JavaScript知識轉化為在ASP.NET Core應用程式中的PDF生成能力。 改變您開發的關鍵優勢: 基於Chrome的渲染可提供像素級的準確性,與您在瀏覽器中進行HTML轉PDF轉換任務時所看到的效果相符(通常只需幾行代碼即可完成)。 完全支持HTML5、CSS3和JavaScript,包括像Bootstrap這樣的現代框架。 原生 .NET 整合,無需外部依賴或命令行工具 跨平台相容性,支援.NET 6/7/8、.NET Core,甚至.NET Framework 4.6.2+的舊版應用程式 全面的API,提供生成後操作功能,包括合併、浮水印以及PDF頁面的電子簽名。 用 NuGet 安裝 PM > Install-Package IronPdf 在 NuGet 查看 https://www.nuget.org/packages/IronPdf 以快速安裝。超過 1000 萬次下載,它正在用 C# 改變 PDF 開發。 您還可以下載 DLL 或 Windows 安裝程序。 設定您的ASP.NET Core專案 讓我們創建一個新的ASP.NET Core MVC應用程式,並配置為PDF生成。 我們將建立一個適合生產環境的設置,具有適當的相依性注入和錯誤處理。 這將是一個在 Visual Studio 中的新 .NET 專案。 不過,您也可以使用現有的專案。 建立專案 我們將透過執行以下指令來創建這個專案並給予適當的專案名稱: dotnet new mvc -n PdfGeneratorApp cd PdfGeneratorApp 安裝 IronPDF 在我們開始在ASP.NET中生成PDF文件之前,請透過在NuGet套件管理器主控台中執行以下命令將IronPDF添加到您的專案: Install-Package IronPdf Install-Package IronPdf.Extensions.Mvc.Core 有關詳細的安裝選項,包括NuGet套件配置和Windows安裝程式,請參閱官方文件。 在 Program.cs 中配置服務 using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc.ViewFeatures; var builder = WebApplication.CreateBuilder(args); // Configure IronPDF license (use your license key) License.LicenseKey = "your-license-key"; // Add MVC services builder.Services.AddControllersWithViews(); // Register IronPDF services builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); builder.Services.AddSingleton<ITempDataProvider, CookieTempDataProvider>(); builder.Services.AddSingleton<IRazorViewRenderer, RazorViewRenderer>(); // Configure ChromePdfRenderer as a service builder.Services.AddSingleton<ChromePdfRenderer>(provider => { var renderer = new ChromePdfRenderer(); // Configure rendering options renderer.RenderingOptions.MarginTop = 25; renderer.RenderingOptions.MarginBottom = 25; renderer.RenderingOptions.MarginLeft = 20; renderer.RenderingOptions.MarginRight = 20; renderer.RenderingOptions.EnableJavaScript = true; renderer.RenderingOptions.RenderDelay = 500; // Wait for JS execution return renderer; }); var app = builder.Build(); // Configure middleware pipeline if (!app.Environment.IsDevelopment()) { app.Use例外Handler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc.ViewFeatures; var builder = WebApplication.CreateBuilder(args); // Configure IronPDF license (use your license key) License.LicenseKey = "your-license-key"; // Add MVC services builder.Services.AddControllersWithViews(); // Register IronPDF services builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); builder.Services.AddSingleton<ITempDataProvider, CookieTempDataProvider>(); builder.Services.AddSingleton<IRazorViewRenderer, RazorViewRenderer>(); // Configure ChromePdfRenderer as a service builder.Services.AddSingleton<ChromePdfRenderer>(provider => { var renderer = new ChromePdfRenderer(); // Configure rendering options renderer.RenderingOptions.MarginTop = 25; renderer.RenderingOptions.MarginBottom = 25; renderer.RenderingOptions.MarginLeft = 20; renderer.RenderingOptions.MarginRight = 20; renderer.RenderingOptions.EnableJavaScript = true; renderer.RenderingOptions.RenderDelay = 500; // Wait for JS execution return renderer; }); var app = builder.Build(); // Configure middleware pipeline if (!app.Environment.IsDevelopment()) { app.Use例外Handler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); $vbLabelText $csharpLabel 此配置將IronPDF設置為單例服務,以確保在您的應用程式中高效使用資源。 您可以進一步修改 RenderingOptions\ 以符合您的特定需求。 在此時,您在 Visual Studio 的方案總管中的資料夾結構應該看起來像: 從 Razor Views 生成 PDF 在ASP.NET Core中生成新的PDF文件的最強大方法涉及使用Razor視圖進行PDF轉換。 這使您可以利用熟悉的MVC模式、強類型模型和Razor語法,從自訂HTML檔案、網頁和其他來源建立動態PDF。 根據Microsoft關於Razor Pages的文件,這種方法為頁面專注的情境提供了最清晰的關注點分離。 建立數據模型 首先,讓我們創建一個代表典型商業文件的綜合模型: namespace PdfGeneratorApp.Models { public class InvoiceModel { public string InvoiceNumber { get; set; } public DateTime InvoiceDate { get; set; } public DateTime DueDate { get; set; } public CompanyInfo Vendor { get; set; } public CompanyInfo Customer { get; set; } public List<InvoiceItem> Items { get; set; } public decimal Subtotal => Items?.Sum(x => x.Total) ?? 0; public decimal TaxRate { get; set; } public decimal TaxAmount => Subtotal * (TaxRate / 100); public decimal Total => Subtotal + TaxAmount; public string Notes { get; set; } public string PaymentTerms { get; set; } } public class CompanyInfo { public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string ZipCode { get; set; } public string Email { get; set; } public string Phone { get; set; } } public class InvoiceItem { public string Description { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Total => Quantity * UnitPrice; } } namespace PdfGeneratorApp.Models { public class InvoiceModel { public string InvoiceNumber { get; set; } public DateTime InvoiceDate { get; set; } public DateTime DueDate { get; set; } public CompanyInfo Vendor { get; set; } public CompanyInfo Customer { get; set; } public List<InvoiceItem> Items { get; set; } public decimal Subtotal => Items?.Sum(x => x.Total) ?? 0; public decimal TaxRate { get; set; } public decimal TaxAmount => Subtotal * (TaxRate / 100); public decimal Total => Subtotal + TaxAmount; public string Notes { get; set; } public string PaymentTerms { get; set; } } public class CompanyInfo { public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string ZipCode { get; set; } public string Email { get; set; } public string Phone { get; set; } } public class InvoiceItem { public string Description { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal Total => Quantity * UnitPrice; } } $vbLabelText $csharpLabel 構建Razor視圖 在 Views/Invoice/InvoiceTemplate.cshtml 中建立一個視圖,以呈現您的PDF內容: @model PdfGeneratorApp.Models.InvoiceModel @{ Layout = null; // PDFs should not use the site layout } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Invoice @Model.InvoiceNumber</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } .invoice-container { max-width: 800px; margin: 0 auto; padding: 30px; } .invoice-header { display: flex; justify-content: space-between; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #2c3e50; } .company-details { flex: 1; } .company-details h1 { color: #2c3e50; margin-bottom: 10px; } .invoice-details { text-align: right; } .invoice-details h2 { color: #2c3e50; font-size: 28px; margin-bottom: 10px; } .invoice-details p { margin: 5px 0; font-size: 14px; } .billing-details { display: flex; justify-content: space-between; margin-bottom: 40px; } .billing-section { flex: 1; } .billing-section h3 { color: #2c3e50; margin-bottom: 10px; font-size: 16px; text-transform: uppercase; } .items-table { width: 100%; border-collapse: collapse; margin-bottom: 40px; } .items-table thead { background-color: #2c3e50; color: white; } .items-table th, .items-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } .items-table tbody tr:hover { background-color: #f5f5f5; } .text-right { text-align: right; } .invoice-summary { display: flex; justify-content: flex-end; margin-bottom: 40px; } .summary-table { width: 300px; } .summary-table tr { border-bottom: 1px solid #ddd; } .summary-table td { padding: 8px; } .summary-table .total-row { font-weight: bold; font-size: 18px; color: #2c3e50; border-top: 2px solid #2c3e50; } .invoice-footer { margin-top: 60px; padding-top: 20px; border-top: 1px solid #ddd; } .footer-notes { margin-bottom: 20px; } .footer-notes h4 { color: #2c3e50; margin-bottom: 10px; } @@media print { .invoice-container { padding: 0; } } </style> </head> <body> <div class="invoice-container"> <div class="invoice-header"> <div class="company-details"> <h1>@Model.Vendor.Name</h1> <p>@Model.Vendor.Address</p> <p>@Model.Vendor.City, @Model.Vendor.State @Model.Vendor.ZipCode</p> <p>Email: @Model.Vendor.Email</p> <p>Phone: @Model.Vendor.Phone</p> </div> <div class="invoice-details"> <h2>INVOICE</h2> <p><strong>Invoice #:</strong> @Model.InvoiceNumber</p> <p><strong>Date:</strong> @Model.InvoiceDate.ToString("MMM dd, yyyy")</p> <p><strong>Due Date:</strong> @Model.DueDate.ToString("MMM dd, yyyy")</p> </div> </div> <div class="billing-details"> <div class="billing-section"> <h3>Bill To:</h3> <p><strong>@Model.Customer.Name</strong></p> <p>@Model.Customer.Address</p> <p>@Model.Customer.City, @Model.Customer.State @Model.Customer.ZipCode</p> <p>@Model.Customer.Email</p> </div> </div> <table class="items-table"> <thead> <tr> <th>Description</th> <th class="text-right">Quantity</th> <th class="text-right">Unit Price</th> <th class="text-right">Total</th> </tr> </thead> <tbody> @foreach (var item in Model.Items) { <tr> <td>@item.Description</td> <td class="text-right">@item.Quantity</td> <td class="text-right">@item.UnitPrice.ToString("C")</td> <td class="text-right">@item.Total.ToString("C")</td> </tr> } </tbody> </table> <div class="invoice-summary"> <table class="summary-table"> <tr> <td>Subtotal:</td> <td class="text-right">@Model.Subtotal.ToString("C")</td> </tr> <tr> <td>Tax (@Model.TaxRate%):</td> <td class="text-right">@Model.TaxAmount.ToString("C")</td> </tr> <tr class="total-row"> <td>Total:</td> <td class="text-right">@Model.Total.ToString("C")</td> </tr> </table> </div> @if (!string.IsNullOrEmpty(Model.Notes)) { <div class="invoice-footer"> <div class="footer-notes"> <h4>Notes</h4> <p>@Model.Notes</p> </div> </div> } @if (!string.IsNullOrEmpty(Model.PaymentTerms)) { <div class="footer-notes"> <h4>Payment Terms</h4> <p>@Model.PaymentTerms</p> </div> } </div> </body> </html> @model PdfGeneratorApp.Models.InvoiceModel @{ Layout = null; // PDFs should not use the site layout } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Invoice @Model.InvoiceNumber</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } .invoice-container { max-width: 800px; margin: 0 auto; padding: 30px; } .invoice-header { display: flex; justify-content: space-between; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #2c3e50; } .company-details { flex: 1; } .company-details h1 { color: #2c3e50; margin-bottom: 10px; } .invoice-details { text-align: right; } .invoice-details h2 { color: #2c3e50; font-size: 28px; margin-bottom: 10px; } .invoice-details p { margin: 5px 0; font-size: 14px; } .billing-details { display: flex; justify-content: space-between; margin-bottom: 40px; } .billing-section { flex: 1; } .billing-section h3 { color: #2c3e50; margin-bottom: 10px; font-size: 16px; text-transform: uppercase; } .items-table { width: 100%; border-collapse: collapse; margin-bottom: 40px; } .items-table thead { background-color: #2c3e50; color: white; } .items-table th, .items-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } .items-table tbody tr:hover { background-color: #f5f5f5; } .text-right { text-align: right; } .invoice-summary { display: flex; justify-content: flex-end; margin-bottom: 40px; } .summary-table { width: 300px; } .summary-table tr { border-bottom: 1px solid #ddd; } .summary-table td { padding: 8px; } .summary-table .total-row { font-weight: bold; font-size: 18px; color: #2c3e50; border-top: 2px solid #2c3e50; } .invoice-footer { margin-top: 60px; padding-top: 20px; border-top: 1px solid #ddd; } .footer-notes { margin-bottom: 20px; } .footer-notes h4 { color: #2c3e50; margin-bottom: 10px; } @@media print { .invoice-container { padding: 0; } } </style> </head> <body> <div class="invoice-container"> <div class="invoice-header"> <div class="company-details"> <h1>@Model.Vendor.Name</h1> <p>@Model.Vendor.Address</p> <p>@Model.Vendor.City, @Model.Vendor.State @Model.Vendor.ZipCode</p> <p>Email: @Model.Vendor.Email</p> <p>Phone: @Model.Vendor.Phone</p> </div> <div class="invoice-details"> <h2>INVOICE</h2> <p><strong>Invoice #:</strong> @Model.InvoiceNumber</p> <p><strong>Date:</strong> @Model.InvoiceDate.ToString("MMM dd, yyyy")</p> <p><strong>Due Date:</strong> @Model.DueDate.ToString("MMM dd, yyyy")</p> </div> </div> <div class="billing-details"> <div class="billing-section"> <h3>Bill To:</h3> <p><strong>@Model.Customer.Name</strong></p> <p>@Model.Customer.Address</p> <p>@Model.Customer.City, @Model.Customer.State @Model.Customer.ZipCode</p> <p>@Model.Customer.Email</p> </div> </div> <table class="items-table"> <thead> <tr> <th>Description</th> <th class="text-right">Quantity</th> <th class="text-right">Unit Price</th> <th class="text-right">Total</th> </tr> </thead> <tbody> @foreach (var item in Model.Items) { <tr> <td>@item.Description</td> <td class="text-right">@item.Quantity</td> <td class="text-right">@item.UnitPrice.ToString("C")</td> <td class="text-right">@item.Total.ToString("C")</td> </tr> } </tbody> </table> <div class="invoice-summary"> <table class="summary-table"> <tr> <td>Subtotal:</td> <td class="text-right">@Model.Subtotal.ToString("C")</td> </tr> <tr> <td>Tax (@Model.TaxRate%):</td> <td class="text-right">@Model.TaxAmount.ToString("C")</td> </tr> <tr class="total-row"> <td>Total:</td> <td class="text-right">@Model.Total.ToString("C")</td> </tr> </table> </div> @if (!string.IsNullOrEmpty(Model.Notes)) { <div class="invoice-footer"> <div class="footer-notes"> <h4>Notes</h4> <p>@Model.Notes</p> </div> </div> } @if (!string.IsNullOrEmpty(Model.PaymentTerms)) { <div class="footer-notes"> <h4>Payment Terms</h4> <p>@Model.PaymentTerms</p> </div> } </div> </body> </html> $vbLabelText $csharpLabel 實施控制器與錯誤處理 現在讓我們來創建一個具有全面錯誤處理功能的強大控制器來生成PDF: using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc; using PdfGeneratorApp.Models; using System.Diagnostics; namespace PdfGeneratorApp.Controllers { public class InvoiceController : Controller { private readonly ILogger<InvoiceController> _logger; private readonly IRazorViewRenderer _viewRenderer; private readonly ChromePdfRenderer _pdfRenderer; private readonly IWebHostEnvironment _environment; public InvoiceController( ILogger<InvoiceController> logger, IRazorViewRenderer viewRenderer, ChromePdfRenderer pdfRenderer, IWebHostEnvironment environment) { _logger = logger; _viewRenderer = viewRenderer; _pdfRenderer = pdfRenderer; _environment = environment; } [HttpGet] public IActionResult Index() { // Display a form or list of invoices return View(); } [HttpGet] public async Task<IActionResult> GenerateInvoice(string invoiceNumber) { var stopwatch = Stopwatch.StartNew(); try { // Validate input if (string.IsNullOrEmpty(invoiceNumber)) { _logger.LogWarning("Invoice generation attempted without invoice number"); return BadRequest("Invoice number is required"); } // Generate sample data (in production, fetch from database) var invoice = CreateSampleInvoice(invoiceNumber); // Log the generation attempt _logger.LogInformation($"Generating PDF for invoice {invoiceNumber}"); // Configure PDF rendering options _pdfRenderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; _pdfRenderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; _pdfRenderer.RenderingOptions.PrintHtmlBackgrounds = true; _pdfRenderer.RenderingOptions.CreatePdfFormsFromHtml = false; // Add custom header with page numbers _pdfRenderer.RenderingOptions.TextHeader = new TextHeaderFooter { CenterText = $"Invoice {invoice.InvoiceNumber}", DrawDividerLine = true, Font = IronSoftware.Drawing.FontTypes.Helvetica, FontSize = 10 }; // Add footer with page numbers _pdfRenderer.RenderingOptions.TextFooter = new TextHeaderFooter { LeftText = "{date} {time}", RightText = "Page {page} of {total-pages}", DrawDividerLine = true, Font = IronSoftware.Drawing.FontTypes.Helvetica, FontSize = 8 }; // Render the view to PDF PdfDocument pdf; try { pdf = _pdfRenderer.RenderRazorViewToPdf( _viewRenderer, "Views/Invoice/InvoiceTemplate.cshtml", invoice); } catch (例外 renderEx) { _logger.LogError(renderEx, "Failed to render Razor view to PDF"); throw new InvalidOperation例外("PDF rendering failed. Please check the template.", renderEx); } // Apply metadata pdf.MetaData.Author = "PdfGeneratorApp"; pdf.MetaData.Title = $"Invoice {invoice.InvoiceNumber}"; pdf.MetaData.Subject = $"Invoice for {invoice.Customer.Name}"; pdf.MetaData.Keywords = "invoice, billing, payment"; pdf.MetaData.CreationDate = DateTime.UtcNow; pdf.MetaData.ModifiedDate = DateTime.UtcNow; // Optional: Add password protection // pdf.SecuritySettings.UserPassword = "user123"; // pdf.SecuritySettings.OwnerPassword = "owner456"; // pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; // Log performance metrics stopwatch.Stop(); _logger.LogInformation($"PDF generated successfully for invoice {invoiceNumber} in {stopwatch.ElapsedMilliseconds}ms"); // Return the PDF file Response.Headers.Add("Content-Disposition", $"inline; filename=Invoice_{invoiceNumber}.pdf"); return File(pdf.BinaryData, "application/pdf", $"Invoice_{invoiceNumber}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}"); // In development, return detailed error if (_environment.IsDevelopment()) { return StatusCode(500, new { error = "PDF generation failed", message = ex.Message, stackTrace = ex.StackTrace }); } // In production, return generic error return StatusCode(500, "An error occurred while generating the PDF"); } } private InvoiceModel CreateSampleInvoice(string invoiceNumber) { return new InvoiceModel { InvoiceNumber = invoiceNumber, InvoiceDate = DateTime.Now, DueDate = DateTime.Now.AddDays(30), Vendor = new CompanyInfo { Name = "Tech 解決方案s Inc.", Address = "123 Business Ave", City = "New York", State = "NY", ZipCode = "10001", Email = "billing@techsolutions.com", Phone = "(555) 123-4567" }, Customer = new CompanyInfo { Name = "Acme Corporation", Address = "456 Commerce St", City = "Los Angeles", State = "CA", ZipCode = "90001", Email = "accounts@acmecorp.com", Phone = "(555) 987-6543" }, Items = new List<InvoiceItem> { new InvoiceItem { Description = "Software Development Services - 40 hours", Quantity = 40, UnitPrice = 150.00m }, new InvoiceItem { Description = "Project Management - 10 hours", Quantity = 10, UnitPrice = 120.00m }, new InvoiceItem { Description = "Quality Assurance Testing", Quantity = 1, UnitPrice = 2500.00m } }, TaxRate = 8.875m, Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.", PaymentTerms = "Net 30" }; } } } using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc; using PdfGeneratorApp.Models; using System.Diagnostics; namespace PdfGeneratorApp.Controllers { public class InvoiceController : Controller { private readonly ILogger<InvoiceController> _logger; private readonly IRazorViewRenderer _viewRenderer; private readonly ChromePdfRenderer _pdfRenderer; private readonly IWebHostEnvironment _environment; public InvoiceController( ILogger<InvoiceController> logger, IRazorViewRenderer viewRenderer, ChromePdfRenderer pdfRenderer, IWebHostEnvironment environment) { _logger = logger; _viewRenderer = viewRenderer; _pdfRenderer = pdfRenderer; _environment = environment; } [HttpGet] public IActionResult Index() { // Display a form or list of invoices return View(); } [HttpGet] public async Task<IActionResult> GenerateInvoice(string invoiceNumber) { var stopwatch = Stopwatch.StartNew(); try { // Validate input if (string.IsNullOrEmpty(invoiceNumber)) { _logger.LogWarning("Invoice generation attempted without invoice number"); return BadRequest("Invoice number is required"); } // Generate sample data (in production, fetch from database) var invoice = CreateSampleInvoice(invoiceNumber); // Log the generation attempt _logger.LogInformation($"Generating PDF for invoice {invoiceNumber}"); // Configure PDF rendering options _pdfRenderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; _pdfRenderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; _pdfRenderer.RenderingOptions.PrintHtmlBackgrounds = true; _pdfRenderer.RenderingOptions.CreatePdfFormsFromHtml = false; // Add custom header with page numbers _pdfRenderer.RenderingOptions.TextHeader = new TextHeaderFooter { CenterText = $"Invoice {invoice.InvoiceNumber}", DrawDividerLine = true, Font = IronSoftware.Drawing.FontTypes.Helvetica, FontSize = 10 }; // Add footer with page numbers _pdfRenderer.RenderingOptions.TextFooter = new TextHeaderFooter { LeftText = "{date} {time}", RightText = "Page {page} of {total-pages}", DrawDividerLine = true, Font = IronSoftware.Drawing.FontTypes.Helvetica, FontSize = 8 }; // Render the view to PDF PdfDocument pdf; try { pdf = _pdfRenderer.RenderRazorViewToPdf( _viewRenderer, "Views/Invoice/InvoiceTemplate.cshtml", invoice); } catch (例外 renderEx) { _logger.LogError(renderEx, "Failed to render Razor view to PDF"); throw new InvalidOperation例外("PDF rendering failed. Please check the template.", renderEx); } // Apply metadata pdf.MetaData.Author = "PdfGeneratorApp"; pdf.MetaData.Title = $"Invoice {invoice.InvoiceNumber}"; pdf.MetaData.Subject = $"Invoice for {invoice.Customer.Name}"; pdf.MetaData.Keywords = "invoice, billing, payment"; pdf.MetaData.CreationDate = DateTime.UtcNow; pdf.MetaData.ModifiedDate = DateTime.UtcNow; // Optional: Add password protection // pdf.SecuritySettings.UserPassword = "user123"; // pdf.SecuritySettings.OwnerPassword = "owner456"; // pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; // Log performance metrics stopwatch.Stop(); _logger.LogInformation($"PDF generated successfully for invoice {invoiceNumber} in {stopwatch.ElapsedMilliseconds}ms"); // Return the PDF file Response.Headers.Add("Content-Disposition", $"inline; filename=Invoice_{invoiceNumber}.pdf"); return File(pdf.BinaryData, "application/pdf", $"Invoice_{invoiceNumber}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}"); // In development, return detailed error if (_environment.IsDevelopment()) { return StatusCode(500, new { error = "PDF generation failed", message = ex.Message, stackTrace = ex.StackTrace }); } // In production, return generic error return StatusCode(500, "An error occurred while generating the PDF"); } } private InvoiceModel CreateSampleInvoice(string invoiceNumber) { return new InvoiceModel { InvoiceNumber = invoiceNumber, InvoiceDate = DateTime.Now, DueDate = DateTime.Now.AddDays(30), Vendor = new CompanyInfo { Name = "Tech 解決方案s Inc.", Address = "123 Business Ave", City = "New York", State = "NY", ZipCode = "10001", Email = "billing@techsolutions.com", Phone = "(555) 123-4567" }, Customer = new CompanyInfo { Name = "Acme Corporation", Address = "456 Commerce St", City = "Los Angeles", State = "CA", ZipCode = "90001", Email = "accounts@acmecorp.com", Phone = "(555) 987-6543" }, Items = new List<InvoiceItem> { new InvoiceItem { Description = "Software Development Services - 40 hours", Quantity = 40, UnitPrice = 150.00m }, new InvoiceItem { Description = "Project Management - 10 hours", Quantity = 10, UnitPrice = 120.00m }, new InvoiceItem { Description = "Quality Assurance Testing", Quantity = 1, UnitPrice = 2500.00m } }, TaxRate = 8.875m, Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.", PaymentTerms = "Net 30" }; } } } $vbLabelText $csharpLabel 程式碼說明 要測試並運行上面的PDF生成器,請啟動專案並輸入以下URL:https://localhost:[port]/Invoice/GenerateInvoice?invoiceNumber=055 來生成發票。請記得將埠替換為您託管應用程式的實際埠號。 輸出 如何從 Web API 端點最大化效率返回 PDF? 對於需要通過RESTful API提供PDF的應用程式,以下是如何實施有效率的PDF傳送以及正確的記憶體管理。 當您在構建微服務或需要在ASP.NET Core Web API控制器中生成PDF時,這種方法特別有用。 using Microsoft.AspNetCore.Mvc; using IronPdf; using System.IO; namespace PdfGeneratorApp.Controllers.Api { [ApiController] [Route("api/[controller]")] public class PdfApiController : ControllerBase { private readonly ChromePdfRenderer _pdfRenderer; private readonly ILogger<PdfApiController> _logger; public PdfApiController( ChromePdfRenderer pdfRenderer, ILogger<PdfApiController> logger) { _pdfRenderer = pdfRenderer; _logger = logger; } [HttpPost("generate-report")] public async Task<IActionResult> GenerateReport([FromBody] ReportRequest request) { try { // Validate request if (!ModelState.IsValid) { return BadRequest(ModelState); } // Build HTML content dynamically var htmlContent = BuildReportHtml(request); // Generate PDF with memory-efficient streaming using var pdfDocument = _pdfRenderer.RenderHtmlAsPdf(htmlContent); // Apply compression for smaller file size pdfDocument.CompressImages(60); // 60% quality // Stream the PDF directly to response var stream = new MemoryStream(); pdfDocument.SaveAs(stream); stream.Position = 0; _logger.LogInformation($"Report generated for {request.ReportType}"); return new FileStreamResult(stream, "application/pdf") { FileDownloadName = $"Report_{DateTime.Now:yyyyMMdd_HHmmss}.pdf" }; } catch (例外 ex) { _logger.LogError(ex, "Failed to generate report"); return StatusCode(500, new { error = "Report generation failed" }); } } [HttpGet("download/{documentId}")] public async Task<IActionResult> DownloadDocument(string documentId) { try { // In production, retrieve document from database or storage var documentPath = Path.Combine("wwwroot", "documents", $"{documentId}.pdf"); if (!System.IO.File.Exists(documentPath)) { return NotFound(new { error = "Document not found" }); } var memory = new MemoryStream(); using (var stream = new FileStream(documentPath, FileMode.Open)) { await stream.CopyToAsync(memory); } memory.Position = 0; return File(memory, "application/pdf", $"Document_{documentId}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Failed to download document {documentId}"); return StatusCode(500, new { error = "Download failed" }); } } private string BuildReportHtml(ReportRequest request) { return $@" <!DOCTYPE html> <html> <head> <style> body {{ font-family: Arial, sans-serif; margin: 40px; }} h1 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }} .report-date {{ color: #7f8c8d; font-size: 14px; }} .data-table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }} .data-table th, .data-table td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }} .data-table th {{ background-color: #3498db; color: white; }} </style> </head> <body> <h1>{request.ReportType} Report</h1> <p class='report-date'>Generated: {DateTime.Now:MMMM dd, yyyy HH:mm}</p> <p>{request.Description}</p> {GenerateDataTable(request.Data)} </body> </html>"; } private string GenerateDataTable(List<ReportDataItem> data) { if (data == null || !data.Any()) return "<p>No data available</p>"; var table = "<table class='data-table'><thead><tr>"; // Add headers foreach (var prop in typeof(ReportDataItem).GetProperties()) { table += $"<th>{prop.Name}</th>"; } table += "</tr></thead><tbody>"; // Add data rows foreach (var item in data) { table += "<tr>"; foreach (var prop in typeof(ReportDataItem).GetProperties()) { var value = prop.GetValue(item) ?? ""; table += $"<td>{value}</td>"; } table += "</tr>"; } table += "</tbody></table>"; return table; } } public class ReportRequest { public string ReportType { get; set; } public string Description { get; set; } public List<ReportDataItem> Data { get; set; } } public class ReportDataItem { public string Name { get; set; } public string Category { get; set; } public decimal Value { get; set; } public DateTime Date { get; set; } } } using Microsoft.AspNetCore.Mvc; using IronPdf; using System.IO; namespace PdfGeneratorApp.Controllers.Api { [ApiController] [Route("api/[controller]")] public class PdfApiController : ControllerBase { private readonly ChromePdfRenderer _pdfRenderer; private readonly ILogger<PdfApiController> _logger; public PdfApiController( ChromePdfRenderer pdfRenderer, ILogger<PdfApiController> logger) { _pdfRenderer = pdfRenderer; _logger = logger; } [HttpPost("generate-report")] public async Task<IActionResult> GenerateReport([FromBody] ReportRequest request) { try { // Validate request if (!ModelState.IsValid) { return BadRequest(ModelState); } // Build HTML content dynamically var htmlContent = BuildReportHtml(request); // Generate PDF with memory-efficient streaming using var pdfDocument = _pdfRenderer.RenderHtmlAsPdf(htmlContent); // Apply compression for smaller file size pdfDocument.CompressImages(60); // 60% quality // Stream the PDF directly to response var stream = new MemoryStream(); pdfDocument.SaveAs(stream); stream.Position = 0; _logger.LogInformation($"Report generated for {request.ReportType}"); return new FileStreamResult(stream, "application/pdf") { FileDownloadName = $"Report_{DateTime.Now:yyyyMMdd_HHmmss}.pdf" }; } catch (例外 ex) { _logger.LogError(ex, "Failed to generate report"); return StatusCode(500, new { error = "Report generation failed" }); } } [HttpGet("download/{documentId}")] public async Task<IActionResult> DownloadDocument(string documentId) { try { // In production, retrieve document from database or storage var documentPath = Path.Combine("wwwroot", "documents", $"{documentId}.pdf"); if (!System.IO.File.Exists(documentPath)) { return NotFound(new { error = "Document not found" }); } var memory = new MemoryStream(); using (var stream = new FileStream(documentPath, FileMode.Open)) { await stream.CopyToAsync(memory); } memory.Position = 0; return File(memory, "application/pdf", $"Document_{documentId}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Failed to download document {documentId}"); return StatusCode(500, new { error = "Download failed" }); } } private string BuildReportHtml(ReportRequest request) { return $@" <!DOCTYPE html> <html> <head> <style> body {{ font-family: Arial, sans-serif; margin: 40px; }} h1 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }} .report-date {{ color: #7f8c8d; font-size: 14px; }} .data-table {{ width: 100%; border-collapse: collapse; margin-top: 20px; }} .data-table th, .data-table td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }} .data-table th {{ background-color: #3498db; color: white; }} </style> </head> <body> <h1>{request.ReportType} Report</h1> <p class='report-date'>Generated: {DateTime.Now:MMMM dd, yyyy HH:mm}</p> <p>{request.Description}</p> {GenerateDataTable(request.Data)} </body> </html>"; } private string GenerateDataTable(List<ReportDataItem> data) { if (data == null || !data.Any()) return "<p>No data available</p>"; var table = "<table class='data-table'><thead><tr>"; // Add headers foreach (var prop in typeof(ReportDataItem).GetProperties()) { table += $"<th>{prop.Name}</th>"; } table += "</tr></thead><tbody>"; // Add data rows foreach (var item in data) { table += "<tr>"; foreach (var prop in typeof(ReportDataItem).GetProperties()) { var value = prop.GetValue(item) ?? ""; table += $"<td>{value}</td>"; } table += "</tr>"; } table += "</tbody></table>"; return table; } } public class ReportRequest { public string ReportType { get; set; } public string Description { get; set; } public List<ReportDataItem> Data { get; set; } } public class ReportDataItem { public string Name { get; set; } public string Category { get; set; } public decimal Value { get; set; } public DateTime Date { get; set; } } } $vbLabelText $csharpLabel 添加專業的頁眉、頁腳和樣式 專業的PDF需要一致的頁眉、頁腳和樣式。 IronPDF提供簡單的基於文本的選項和高級的基於HTML的選項。 通過使用CSS樣式來設計HTML標記,我們可以創建自定義PDF的頁眉和頁腳。 以下程式碼片段探討我們如何在目前的專案中使用此功能: using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc; using PdfGeneratorApp.Models; using PdfGeneratorApp.Services; using System.Diagnostics; namespace PdfGeneratorApp.Controllers { public class InvoiceController : Controller { private readonly ILogger<InvoiceController> _logger; private readonly IRazorViewRenderer _viewRenderer; private readonly ChromePdfRenderer _pdfRenderer; private readonly PdfFormattingService _pdfFormattingService; private readonly IWebHostEnvironment _environment; public InvoiceController( ILogger<InvoiceController> logger, IRazorViewRenderer viewRenderer, ChromePdfRenderer pdfRenderer, PdfFormattingService pdfFormattingService, IWebHostEnvironment environment) { _logger = logger; _viewRenderer = viewRenderer; _pdfRenderer = pdfRenderer; _pdfFormattingService = pdfFormattingService; _environment = environment; } [HttpGet] public IActionResult Index() { // Display a form or list of invoices return View(); } private void ConfigurePdfRendererOptions(ChromePdfRenderer renderer, InvoiceModel invoice, PdfStylingOptions options) { // Margins renderer.RenderingOptions.MarginTop = options.MarginTop; renderer.RenderingOptions.MarginBottom = options.MarginBottom; renderer.RenderingOptions.MarginLeft = options.MarginLeft; renderer.RenderingOptions.MarginRight = options.MarginRight; // Header if (options.UseHtmlHeader) { renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { MaxHeight = 50, HtmlFragment = $@" <div style='width: 100%; font-size: 12px; font-family: Arial;'> <div style='float: left; width: 50%;'> <img src='https://ironpdf.com/img/products/ironpdf-logo-text-dotnet.svg' height='40' /> </div> <div style='float: right; width: 50%; text-align: right;'> <strong>Invoice {invoice.InvoiceNumber}</strong><br/> Generated: {DateTime.Now:yyyy-MM-dd} </div> </div>", LoadStylesAndCSSFromMainHtmlDocument = true }; } else { renderer.RenderingOptions.TextHeader = new TextHeaderFooter { CenterText = options.HeaderText, Font = IronSoftware.Drawing.FontTypes.Arial, FontSize = 12, DrawDividerLine = true }; } // Footer renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter { MaxHeight = 30, HtmlFragment = @" <div style='width: 100%; font-size: 10px; color: #666;'> <div style='float: left; width: 33%;'> © 2025 Your Company </div> <div style='float: center; width: 33%; text-align: center;'> yourwebsite.com </div> <div style='float: right; width: 33%; text-align: right;'> Page {page} of {total-pages} </div> </div>" }; // Optional: Add watermark here (IronPDF supports adding after PDF is generated, so keep it as-is) // Margins, paper size etc., can also be set here if needed renderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; renderer.RenderingOptions.PrintHtmlBackgrounds = true; } [HttpGet] public async Task<IActionResult> GenerateInvoice(string invoiceNumber) { var stopwatch = Stopwatch.StartNew(); try { // Validate input if (string.IsNullOrEmpty(invoiceNumber)) { _logger.LogWarning("Invoice generation attempted without invoice number"); return BadRequest("Invoice number is required"); } // Generate sample data (in production, fetch from database) var invoice = CreateSampleInvoice(invoiceNumber); // Log the generation attempt _logger.LogInformation($"Generating PDF for invoice {invoiceNumber}"); // Configure PDF rendering options _pdfRenderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; _pdfRenderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; _pdfRenderer.RenderingOptions.PrintHtmlBackgrounds = true; _pdfRenderer.RenderingOptions.CreatePdfFormsFromHtml = false; var options = new PdfStylingOptions { MarginTop = 25, MarginBottom = 25, MarginLeft = 20, MarginRight = 20, UseHtmlHeader = true, HeaderText = $"Invoice {invoice.InvoiceNumber}", AddWatermark = false, ForcePageBreaks = false }; // Apply the styling to the renderer BEFORE rendering PDF ConfigurePdfRendererOptions(_pdfRenderer, invoice, options); // Render the view to PDF PdfDocument pdf; try { pdf = _pdfRenderer.RenderRazorViewToPdf( _viewRenderer, "Views/Invoice/InvoiceTemplate.cshtml", invoice); } catch (例外 renderEx) { _logger.LogError(renderEx, "Failed to render Razor view to PDF"); throw new InvalidOperation例外("PDF rendering failed. Please check the template.", renderEx); } // Apply metadata pdf.MetaData.Author = "PdfGeneratorApp"; pdf.MetaData.Title = $"Invoice {invoice.InvoiceNumber}"; pdf.MetaData.Subject = $"Invoice for {invoice.Customer.Name}"; pdf.MetaData.Keywords = "invoice, billing, payment"; pdf.MetaData.CreationDate = DateTime.UtcNow; pdf.MetaData.ModifiedDate = DateTime.UtcNow; // Optional: Add password protection // pdf.SecuritySettings.UserPassword = "user123"; // pdf.SecuritySettings.OwnerPassword = "owner456"; // pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; // Log performance metrics stopwatch.Stop(); _logger.LogInformation($"PDF generated successfully for invoice {invoiceNumber} in {stopwatch.ElapsedMilliseconds}ms"); // Return the PDF file Response.Headers.Add("Content-Disposition", $"inline; filename=Invoice_{invoiceNumber}.pdf"); return File(pdf.BinaryData, "application/pdf", $"Invoice_{invoiceNumber}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}"); // In development, return detailed error if (_environment.IsDevelopment()) { return StatusCode(500, new { error = "PDF generation failed", message = ex.Message, stackTrace = ex.StackTrace }); } // In production, return generic error return StatusCode(500, "An error occurred while generating the PDF"); } } private InvoiceModel CreateSampleInvoice(string invoiceNumber) { return new InvoiceModel { InvoiceNumber = invoiceNumber, InvoiceDate = DateTime.Now, DueDate = DateTime.Now.AddDays(30), Vendor = new CompanyInfo { Name = "Tech 解決方案s Inc.", Address = "123 Business Ave", City = "New York", State = "NY", ZipCode = "10001", Email = "billing@techsolutions.com", Phone = "(555) 123-4567" }, Customer = new CompanyInfo { Name = "Acme Corporation", Address = "456 Commerce St", City = "Los Angeles", State = "CA", ZipCode = "90001", Email = "accounts@acmecorp.com", Phone = "(555) 987-6543" }, Items = new List<InvoiceItem> { new InvoiceItem { Description = "Software Development Services - 40 hours", Quantity = 40, UnitPrice = 150.00m }, new InvoiceItem { Description = "Project Management - 10 hours", Quantity = 10, UnitPrice = 120.00m }, new InvoiceItem { Description = "Quality Assurance Testing", Quantity = 1, UnitPrice = 2500.00m } }, TaxRate = 8.875m, Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.", PaymentTerms = "Net 30" }; } } } using IronPdf; using IronPdf.Extensions.Mvc.Core; using Microsoft.AspNetCore.Mvc; using PdfGeneratorApp.Models; using PdfGeneratorApp.Services; using System.Diagnostics; namespace PdfGeneratorApp.Controllers { public class InvoiceController : Controller { private readonly ILogger<InvoiceController> _logger; private readonly IRazorViewRenderer _viewRenderer; private readonly ChromePdfRenderer _pdfRenderer; private readonly PdfFormattingService _pdfFormattingService; private readonly IWebHostEnvironment _environment; public InvoiceController( ILogger<InvoiceController> logger, IRazorViewRenderer viewRenderer, ChromePdfRenderer pdfRenderer, PdfFormattingService pdfFormattingService, IWebHostEnvironment environment) { _logger = logger; _viewRenderer = viewRenderer; _pdfRenderer = pdfRenderer; _pdfFormattingService = pdfFormattingService; _environment = environment; } [HttpGet] public IActionResult Index() { // Display a form or list of invoices return View(); } private void ConfigurePdfRendererOptions(ChromePdfRenderer renderer, InvoiceModel invoice, PdfStylingOptions options) { // Margins renderer.RenderingOptions.MarginTop = options.MarginTop; renderer.RenderingOptions.MarginBottom = options.MarginBottom; renderer.RenderingOptions.MarginLeft = options.MarginLeft; renderer.RenderingOptions.MarginRight = options.MarginRight; // Header if (options.UseHtmlHeader) { renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { MaxHeight = 50, HtmlFragment = $@" <div style='width: 100%; font-size: 12px; font-family: Arial;'> <div style='float: left; width: 50%;'> <img src='https://ironpdf.com/img/products/ironpdf-logo-text-dotnet.svg' height='40' /> </div> <div style='float: right; width: 50%; text-align: right;'> <strong>Invoice {invoice.InvoiceNumber}</strong><br/> Generated: {DateTime.Now:yyyy-MM-dd} </div> </div>", LoadStylesAndCSSFromMainHtmlDocument = true }; } else { renderer.RenderingOptions.TextHeader = new TextHeaderFooter { CenterText = options.HeaderText, Font = IronSoftware.Drawing.FontTypes.Arial, FontSize = 12, DrawDividerLine = true }; } // Footer renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter { MaxHeight = 30, HtmlFragment = @" <div style='width: 100%; font-size: 10px; color: #666;'> <div style='float: left; width: 33%;'> © 2025 Your Company </div> <div style='float: center; width: 33%; text-align: center;'> yourwebsite.com </div> <div style='float: right; width: 33%; text-align: right;'> Page {page} of {total-pages} </div> </div>" }; // Optional: Add watermark here (IronPDF supports adding after PDF is generated, so keep it as-is) // Margins, paper size etc., can also be set here if needed renderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; renderer.RenderingOptions.PrintHtmlBackgrounds = true; } [HttpGet] public async Task<IActionResult> GenerateInvoice(string invoiceNumber) { var stopwatch = Stopwatch.StartNew(); try { // Validate input if (string.IsNullOrEmpty(invoiceNumber)) { _logger.LogWarning("Invoice generation attempted without invoice number"); return BadRequest("Invoice number is required"); } // Generate sample data (in production, fetch from database) var invoice = CreateSampleInvoice(invoiceNumber); // Log the generation attempt _logger.LogInformation($"Generating PDF for invoice {invoiceNumber}"); // Configure PDF rendering options _pdfRenderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait; _pdfRenderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4; _pdfRenderer.RenderingOptions.PrintHtmlBackgrounds = true; _pdfRenderer.RenderingOptions.CreatePdfFormsFromHtml = false; var options = new PdfStylingOptions { MarginTop = 25, MarginBottom = 25, MarginLeft = 20, MarginRight = 20, UseHtmlHeader = true, HeaderText = $"Invoice {invoice.InvoiceNumber}", AddWatermark = false, ForcePageBreaks = false }; // Apply the styling to the renderer BEFORE rendering PDF ConfigurePdfRendererOptions(_pdfRenderer, invoice, options); // Render the view to PDF PdfDocument pdf; try { pdf = _pdfRenderer.RenderRazorViewToPdf( _viewRenderer, "Views/Invoice/InvoiceTemplate.cshtml", invoice); } catch (例外 renderEx) { _logger.LogError(renderEx, "Failed to render Razor view to PDF"); throw new InvalidOperation例外("PDF rendering failed. Please check the template.", renderEx); } // Apply metadata pdf.MetaData.Author = "PdfGeneratorApp"; pdf.MetaData.Title = $"Invoice {invoice.InvoiceNumber}"; pdf.MetaData.Subject = $"Invoice for {invoice.Customer.Name}"; pdf.MetaData.Keywords = "invoice, billing, payment"; pdf.MetaData.CreationDate = DateTime.UtcNow; pdf.MetaData.ModifiedDate = DateTime.UtcNow; // Optional: Add password protection // pdf.SecuritySettings.UserPassword = "user123"; // pdf.SecuritySettings.OwnerPassword = "owner456"; // pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; // Log performance metrics stopwatch.Stop(); _logger.LogInformation($"PDF generated successfully for invoice {invoiceNumber} in {stopwatch.ElapsedMilliseconds}ms"); // Return the PDF file Response.Headers.Add("Content-Disposition", $"inline; filename=Invoice_{invoiceNumber}.pdf"); return File(pdf.BinaryData, "application/pdf", $"Invoice_{invoiceNumber}.pdf"); } catch (例外 ex) { _logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}"); // In development, return detailed error if (_environment.IsDevelopment()) { return StatusCode(500, new { error = "PDF generation failed", message = ex.Message, stackTrace = ex.StackTrace }); } // In production, return generic error return StatusCode(500, "An error occurred while generating the PDF"); } } private InvoiceModel CreateSampleInvoice(string invoiceNumber) { return new InvoiceModel { InvoiceNumber = invoiceNumber, InvoiceDate = DateTime.Now, DueDate = DateTime.Now.AddDays(30), Vendor = new CompanyInfo { Name = "Tech 解決方案s Inc.", Address = "123 Business Ave", City = "New York", State = "NY", ZipCode = "10001", Email = "billing@techsolutions.com", Phone = "(555) 123-4567" }, Customer = new CompanyInfo { Name = "Acme Corporation", Address = "456 Commerce St", City = "Los Angeles", State = "CA", ZipCode = "90001", Email = "accounts@acmecorp.com", Phone = "(555) 987-6543" }, Items = new List<InvoiceItem> { new InvoiceItem { Description = "Software Development Services - 40 hours", Quantity = 40, UnitPrice = 150.00m }, new InvoiceItem { Description = "Project Management - 10 hours", Quantity = 10, UnitPrice = 120.00m }, new InvoiceItem { Description = "Quality Assurance Testing", Quantity = 1, UnitPrice = 2500.00m } }, TaxRate = 8.875m, Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.", PaymentTerms = "Net 30" }; } } } $vbLabelText $csharpLabel 程式碼說明 您可以看到基本標題和樣式標題之間的簡要差異。 使用IronPDF,您甚至可以添加自訂的HTML頁首和頁尾到您的發票,以進一步美化並真正使其獨具特色。 如需有關添加頁眉和頁尾的更深入資訊,請參閱此操作指南。 輸出 如何實作高性能批次PDF處理? 需要生成數百或數千個PDF嗎? 以下是如何在有效管理記憶體的同時,通過並行處理來實現最佳效能。 下載我們完整的工作範例,以查看此操作。 對於需要高效生成多個PDF的應用程式,這裡是一個使用非同步與多執行緒技術的優化批次處理實作。 這種方法遵循Microsoft的並行程式設計最佳實踐,以在ASP.NET Core中使用C#生成PDF時達到最佳效能: using System.Collections.Concurrent; using System.Diagnostics; public class BatchPdfProcessor { private readonly ChromePdfRenderer _renderer; private readonly ILogger<BatchPdfProcessor> _logger; private readonly SemaphoreSlim _semaphore; public BatchPdfProcessor( ChromePdfRenderer renderer, ILogger<BatchPdfProcessor> logger) { _renderer = renderer; _logger = logger; // Limit concurrent PDF generation to prevent memory exhaustion _semaphore = new SemaphoreSlim(Environment.ProcessorCount); } public async Task<BatchProcessingResult> ProcessBatchAsync( List<BatchPdfRequest> requests, IProgress<BatchProgressReport> progress = null) { var result = new BatchProcessingResult { StartTime = DateTime.UtcNow, TotalRequests = requests.Count }; var successfulPdfs = new ConcurrentBag<GeneratedPdf>(); var errors = new ConcurrentBag<ProcessingError>(); var stopwatch = Stopwatch.StartNew(); // Process PDFs in parallel with controlled concurrency var tasks = requests.Select(async (request, index) => { await _semaphore.WaitAsync(); try { var taskStopwatch = Stopwatch.StartNew(); // Generate PDF var pdf = await GeneratePdfAsync(request); taskStopwatch.Stop(); successfulPdfs.Add(new GeneratedPdf { Id = request.Id, FileName = request.FileName, Data = pdf.BinaryData, GenerationTime = taskStopwatch.ElapsedMilliseconds, PageCount = pdf.PageCount }); // Report progress progress?.Report(new BatchProgressReport { ProcessedCount = successfulPdfs.Count + errors.Count, TotalCount = requests.Count, CurrentFile = request.FileName }); _logger.LogDebug($"Generated PDF {request.Id} in {taskStopwatch.ElapsedMilliseconds}ms"); } catch (例外 ex) { errors.Add(new ProcessingError { RequestId = request.Id, FileName = request.FileName, Error = ex.Message, StackTrace = ex.StackTrace }); _logger.LogError(ex, $"Failed to generate PDF for request {request.Id}"); } finally { _semaphore.Release(); } }); await Task.WhenAll(tasks); stopwatch.Stop(); // Compile results result.EndTime = DateTime.UtcNow; result.TotalProcessingTime = stopwatch.ElapsedMilliseconds; result.SuccessfulPdfs = successfulPdfs.ToList(); result.Errors = errors.ToList(); result.SuccessCount = successfulPdfs.Count; result.ErrorCount = errors.Count; result.AverageGenerationTime = successfulPdfs.Any() ? successfulPdfs.Average(p => p.GenerationTime) : 0; result.TotalPages = successfulPdfs.Sum(p => p.PageCount); result.TotalSizeBytes = successfulPdfs.Sum(p => p.Data.Length); // Log summary _logger.LogInformation($"Batch processing completed: {result.SuccessCount} successful, " + $"{result.ErrorCount} errors, {result.TotalProcessingTime}ms total time"); // Clean up memory after large batch if (requests.Count > 100) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } return result; } private async Task<PdfDocument> GeneratePdfAsync(BatchPdfRequest request) { return await Task.Run(() => { // Configure renderer for this specific request var localRenderer = new ChromePdfRenderer(); localRenderer.RenderingOptions.PaperSize = request.PaperSize; localRenderer.RenderingOptions.MarginTop = request.MarginTop; localRenderer.RenderingOptions.MarginBottom = request.MarginBottom; // Generate PDF var pdf = localRenderer.RenderHtmlAsPdf(request.HtmlContent); // Apply compression if requested if (request.CompressImages) { pdf.CompressImages(request.CompressionQuality); } return pdf; }); } } public class BatchPdfRequest { public string Id { get; set; } public string FileName { get; set; } public string HtmlContent { get; set; } public IronPdf.Rendering.PdfPaperSize PaperSize { get; set; } = IronPdf.Rendering.PdfPaperSize.A4; public int MarginTop { get; set; } = 25; public int MarginBottom { get; set; } = 25; public bool CompressImages { get; set; } = true; public int CompressionQuality { get; set; } = 80; } public class BatchProcessingResult { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public long TotalProcessingTime { get; set; } public int TotalRequests { get; set; } public int SuccessCount { get; set; } public int ErrorCount { get; set; } public double AverageGenerationTime { get; set; } public int TotalPages { get; set; } public long TotalSizeBytes { get; set; } public List<GeneratedPdf> SuccessfulPdfs { get; set; } public List<ProcessingError> Errors { get; set; } } public class GeneratedPdf { public string Id { get; set; } public string FileName { get; set; } public byte[] Data { get; set; } public long GenerationTime { get; set; } public int PageCount { get; set; } } public class ProcessingError { public string RequestId { get; set; } public string FileName { get; set; } public string Error { get; set; } public string StackTrace { get; set; } } public class BatchProgressReport { public int ProcessedCount { get; set; } public int TotalCount { get; set; } public string CurrentFile { get; set; } public double PercentComplete => (double)ProcessedCount / TotalCount * 100; } using System.Collections.Concurrent; using System.Diagnostics; public class BatchPdfProcessor { private readonly ChromePdfRenderer _renderer; private readonly ILogger<BatchPdfProcessor> _logger; private readonly SemaphoreSlim _semaphore; public BatchPdfProcessor( ChromePdfRenderer renderer, ILogger<BatchPdfProcessor> logger) { _renderer = renderer; _logger = logger; // Limit concurrent PDF generation to prevent memory exhaustion _semaphore = new SemaphoreSlim(Environment.ProcessorCount); } public async Task<BatchProcessingResult> ProcessBatchAsync( List<BatchPdfRequest> requests, IProgress<BatchProgressReport> progress = null) { var result = new BatchProcessingResult { StartTime = DateTime.UtcNow, TotalRequests = requests.Count }; var successfulPdfs = new ConcurrentBag<GeneratedPdf>(); var errors = new ConcurrentBag<ProcessingError>(); var stopwatch = Stopwatch.StartNew(); // Process PDFs in parallel with controlled concurrency var tasks = requests.Select(async (request, index) => { await _semaphore.WaitAsync(); try { var taskStopwatch = Stopwatch.StartNew(); // Generate PDF var pdf = await GeneratePdfAsync(request); taskStopwatch.Stop(); successfulPdfs.Add(new GeneratedPdf { Id = request.Id, FileName = request.FileName, Data = pdf.BinaryData, GenerationTime = taskStopwatch.ElapsedMilliseconds, PageCount = pdf.PageCount }); // Report progress progress?.Report(new BatchProgressReport { ProcessedCount = successfulPdfs.Count + errors.Count, TotalCount = requests.Count, CurrentFile = request.FileName }); _logger.LogDebug($"Generated PDF {request.Id} in {taskStopwatch.ElapsedMilliseconds}ms"); } catch (例外 ex) { errors.Add(new ProcessingError { RequestId = request.Id, FileName = request.FileName, Error = ex.Message, StackTrace = ex.StackTrace }); _logger.LogError(ex, $"Failed to generate PDF for request {request.Id}"); } finally { _semaphore.Release(); } }); await Task.WhenAll(tasks); stopwatch.Stop(); // Compile results result.EndTime = DateTime.UtcNow; result.TotalProcessingTime = stopwatch.ElapsedMilliseconds; result.SuccessfulPdfs = successfulPdfs.ToList(); result.Errors = errors.ToList(); result.SuccessCount = successfulPdfs.Count; result.ErrorCount = errors.Count; result.AverageGenerationTime = successfulPdfs.Any() ? successfulPdfs.Average(p => p.GenerationTime) : 0; result.TotalPages = successfulPdfs.Sum(p => p.PageCount); result.TotalSizeBytes = successfulPdfs.Sum(p => p.Data.Length); // Log summary _logger.LogInformation($"Batch processing completed: {result.SuccessCount} successful, " + $"{result.ErrorCount} errors, {result.TotalProcessingTime}ms total time"); // Clean up memory after large batch if (requests.Count > 100) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } return result; } private async Task<PdfDocument> GeneratePdfAsync(BatchPdfRequest request) { return await Task.Run(() => { // Configure renderer for this specific request var localRenderer = new ChromePdfRenderer(); localRenderer.RenderingOptions.PaperSize = request.PaperSize; localRenderer.RenderingOptions.MarginTop = request.MarginTop; localRenderer.RenderingOptions.MarginBottom = request.MarginBottom; // Generate PDF var pdf = localRenderer.RenderHtmlAsPdf(request.HtmlContent); // Apply compression if requested if (request.CompressImages) { pdf.CompressImages(request.CompressionQuality); } return pdf; }); } } public class BatchPdfRequest { public string Id { get; set; } public string FileName { get; set; } public string HtmlContent { get; set; } public IronPdf.Rendering.PdfPaperSize PaperSize { get; set; } = IronPdf.Rendering.PdfPaperSize.A4; public int MarginTop { get; set; } = 25; public int MarginBottom { get; set; } = 25; public bool CompressImages { get; set; } = true; public int CompressionQuality { get; set; } = 80; } public class BatchProcessingResult { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public long TotalProcessingTime { get; set; } public int TotalRequests { get; set; } public int SuccessCount { get; set; } public int ErrorCount { get; set; } public double AverageGenerationTime { get; set; } public int TotalPages { get; set; } public long TotalSizeBytes { get; set; } public List<GeneratedPdf> SuccessfulPdfs { get; set; } public List<ProcessingError> Errors { get; set; } } public class GeneratedPdf { public string Id { get; set; } public string FileName { get; set; } public byte[] Data { get; set; } public long GenerationTime { get; set; } public int PageCount { get; set; } } public class ProcessingError { public string RequestId { get; set; } public string FileName { get; set; } public string Error { get; set; } public string StackTrace { get; set; } } public class BatchProgressReport { public int ProcessedCount { get; set; } public int TotalCount { get; set; } public string CurrentFile { get; set; } public double PercentComplete => (double)ProcessedCount / TotalCount * 100; } $vbLabelText $csharpLabel 實際醫療報告範例 這是生成符合HIPAA標準的醫療報告的具體實現: public class MedicalReportGenerator { private readonly ChromePdfRenderer _renderer; private readonly ILogger<MedicalReportGenerator> _logger; public MedicalReportGenerator( ChromePdfRenderer renderer, ILogger<MedicalReportGenerator> logger) { _renderer = renderer; _logger = logger; } public async Task<PdfDocument> GeneratePatientReport(PatientReportModel model) { var stopwatch = Stopwatch.StartNew(); // Configure for medical document standards _renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter; _renderer.RenderingOptions.MarginTop = 50; _renderer.RenderingOptions.MarginBottom = 40; // HIPAA-compliant header _renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { Height = 45, HtmlFragment = $@" <div style='width: 100%; font-size: 10px;'> <div style='float: left;'> <strong>CONFIDENTIAL MEDICAL RECORD</strong><br/> Patient: {model.PatientName} | MRN: {model.MedicalRecordNumber} </div> <div style='float: right; text-align: right;'> Generated: {{date}} {{time}}<br/> Provider: {model.ProviderName} </div> </div>" }; // Generate report HTML var html = GenerateMedicalReportHtml(model); // Create PDF with encryption for HIPAA compliance var pdf = _renderer.RenderHtmlAsPdf(html); // Apply 256-bit AES encryption pdf.SecuritySettings.UserPassword = GenerateSecurePassword(); pdf.SecuritySettings.OwnerPassword = GenerateOwnerPassword(); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.NoPrint; pdf.SecuritySettings.AllowUserFormData = false; pdf.SecuritySettings.AllowUserAnnotations = false; // Add audit metadata pdf.MetaData.Author = model.ProviderName; pdf.MetaData.Title = $"Medical Report - {model.PatientName}"; pdf.MetaData.Keywords = "medical,confidential,hipaa"; pdf.MetaData.CustomProperties.Add("ReportType", model.ReportType); pdf.MetaData.CustomProperties.Add("GeneratedBy", model.UserId); pdf.MetaData.CustomProperties.Add("Timestamp", DateTime.UtcNow.ToString("O")); stopwatch.Stop(); _logger.LogInformation($"Medical report generated in {stopwatch.ElapsedMilliseconds}ms for patient {model.MedicalRecordNumber}"); return pdf; } private string GenerateMedicalReportHtml(PatientReportModel model) { // Generate comprehensive medical report HTML // This would typically use a Razor view in production return $@" <!DOCTYPE html> <html> <head> <style> body {{ font-family: 'Segoe UI', Arial, sans-serif; margin: 0; padding: 20px; }} .header {{ background: #f0f4f8; padding: 20px; margin-bottom: 30px; }} .patient-info {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; }} .section {{ margin-bottom: 30px; }} .section h2 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }} .vital-signs {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }} .vital-box {{ background: #ecf0f1; padding: 15px; border-radius: 5px; }} .medication-table {{ width: 100%; border-collapse: collapse; }} .medication-table th, .medication-table td {{ border: 1px solid #ddd; padding: 10px; text-align: left; }} .medication-table th {{ background: #3498db; color: white; }} .alert {{ background: #e74c3c; color: white; padding: 10px; border-radius: 5px; margin-bottom: 20px; }} </style> </head> <body> <div class='header'> <h1>Patient Medical Report</h1> <p>Report Date: {DateTime.Now:MMMM dd, yyyy}</p> </div> {(model.HasAllergies ? "<div class='alert'>⚠ PATIENT HAS KNOWN ALLERGIES - SEE ALLERGY SECTION</div>" : "")} <div class='patient-info'> <div> <strong>Patient Name:</strong> {model.PatientName}<br/> <strong>Date of Birth:</strong> {model.DateOfBirth:MM/dd/yyyy}<br/> <strong>Age:</strong> {model.Age} years<br/> <strong>Gender:</strong> {model.Gender} </div> <div> <strong>MRN:</strong> {model.MedicalRecordNumber}<br/> <strong>Admission Date:</strong> {model.AdmissionDate:MM/dd/yyyy}<br/> <strong>Provider:</strong> {model.ProviderName}<br/> <strong>Department:</strong> {model.Department} </div> </div> <div class='section'> <h2>Vital Signs</h2> <div class='vital-signs'> <div class='vital-box'> <strong>Blood Pressure</strong><br/> {model.BloodPressure} </div> <div class='vital-box'> <strong>Heart Rate</strong><br/> {model.HeartRate} bpm </div> <div class='vital-box'> <strong>Temperature</strong><br/> {model.Temperature}°F </div> <div class='vital-box'> <strong>Respiratory Rate</strong><br/> {model.RespiratoryRate} /min </div> <div class='vital-box'> <strong>O2 Saturation</strong><br/> {model.OxygenSaturation}% </div> <div class='vital-box'> <strong>Weight</strong><br/> {model.Weight} lbs </div> </div> </div> <div class='section'> <h2>Current Medications</h2> <table class='medication-table'> <thead> <tr> <th>Medication</th> <th>Dosage</th> <th>Frequency</th> <th>Route</th> <th>Start Date</th> </tr> </thead> <tbody> {string.Join("", model.Medications.Select(m => $@" <tr> <td>{m.Name}</td> <td>{m.Dosage}</td> <td>{m.Frequency}</td> <td>{m.Route}</td> <td>{m.StartDate:MM/dd/yyyy}</td> </tr> "))} </tbody> </table> </div> <div class='section'> <h2>Clinical Notes</h2> <p>{model.ClinicalNotes}</p> </div> <div class='section'> <h2>Treatment Plan</h2> <p>{model.TreatmentPlan}</p> </div> </body> </html>"; } private string GenerateSecurePassword() { // Generate cryptographically secure password using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); var bytes = new byte[32]; rng.GetBytes(bytes); return Convert.ToBase64String(bytes); } private string GenerateOwnerPassword() { // In production, retrieve from secure configuration return "SecureOwnerPassword123!"; } } public class PatientReportModel { public string PatientName { get; set; } public string MedicalRecordNumber { get; set; } public DateTime DateOfBirth { get; set; } public int Age { get; set; } public string Gender { get; set; } public DateTime AdmissionDate { get; set; } public string ProviderName { get; set; } public string Department { get; set; } public string ReportType { get; set; } public string UserId { get; set; } // Vital Signs public string BloodPressure { get; set; } public int HeartRate { get; set; } public decimal Temperature { get; set; } public int RespiratoryRate { get; set; } public int OxygenSaturation { get; set; } public decimal Weight { get; set; } // Medical Information public bool HasAllergies { get; set; } public List<Medication> Medications { get; set; } public string ClinicalNotes { get; set; } public string TreatmentPlan { get; set; } } public class Medication { public string Name { get; set; } public string Dosage { get; set; } public string Frequency { get; set; } public string Route { get; set; } public DateTime StartDate { get; set; } } public class MedicalReportGenerator { private readonly ChromePdfRenderer _renderer; private readonly ILogger<MedicalReportGenerator> _logger; public MedicalReportGenerator( ChromePdfRenderer renderer, ILogger<MedicalReportGenerator> logger) { _renderer = renderer; _logger = logger; } public async Task<PdfDocument> GeneratePatientReport(PatientReportModel model) { var stopwatch = Stopwatch.StartNew(); // Configure for medical document standards _renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter; _renderer.RenderingOptions.MarginTop = 50; _renderer.RenderingOptions.MarginBottom = 40; // HIPAA-compliant header _renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter { Height = 45, HtmlFragment = $@" <div style='width: 100%; font-size: 10px;'> <div style='float: left;'> <strong>CONFIDENTIAL MEDICAL RECORD</strong><br/> Patient: {model.PatientName} | MRN: {model.MedicalRecordNumber} </div> <div style='float: right; text-align: right;'> Generated: {{date}} {{time}}<br/> Provider: {model.ProviderName} </div> </div>" }; // Generate report HTML var html = GenerateMedicalReportHtml(model); // Create PDF with encryption for HIPAA compliance var pdf = _renderer.RenderHtmlAsPdf(html); // Apply 256-bit AES encryption pdf.SecuritySettings.UserPassword = GenerateSecurePassword(); pdf.SecuritySettings.OwnerPassword = GenerateOwnerPassword(); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.NoPrint; pdf.SecuritySettings.AllowUserFormData = false; pdf.SecuritySettings.AllowUserAnnotations = false; // Add audit metadata pdf.MetaData.Author = model.ProviderName; pdf.MetaData.Title = $"Medical Report - {model.PatientName}"; pdf.MetaData.Keywords = "medical,confidential,hipaa"; pdf.MetaData.CustomProperties.Add("ReportType", model.ReportType); pdf.MetaData.CustomProperties.Add("GeneratedBy", model.UserId); pdf.MetaData.CustomProperties.Add("Timestamp", DateTime.UtcNow.ToString("O")); stopwatch.Stop(); _logger.LogInformation($"Medical report generated in {stopwatch.ElapsedMilliseconds}ms for patient {model.MedicalRecordNumber}"); return pdf; } private string GenerateMedicalReportHtml(PatientReportModel model) { // Generate comprehensive medical report HTML // This would typically use a Razor view in production return $@" <!DOCTYPE html> <html> <head> <style> body {{ font-family: 'Segoe UI', Arial, sans-serif; margin: 0; padding: 20px; }} .header {{ background: #f0f4f8; padding: 20px; margin-bottom: 30px; }} .patient-info {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; }} .section {{ margin-bottom: 30px; }} .section h2 {{ color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }} .vital-signs {{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }} .vital-box {{ background: #ecf0f1; padding: 15px; border-radius: 5px; }} .medication-table {{ width: 100%; border-collapse: collapse; }} .medication-table th, .medication-table td {{ border: 1px solid #ddd; padding: 10px; text-align: left; }} .medication-table th {{ background: #3498db; color: white; }} .alert {{ background: #e74c3c; color: white; padding: 10px; border-radius: 5px; margin-bottom: 20px; }} </style> </head> <body> <div class='header'> <h1>Patient Medical Report</h1> <p>Report Date: {DateTime.Now:MMMM dd, yyyy}</p> </div> {(model.HasAllergies ? "<div class='alert'>⚠ PATIENT HAS KNOWN ALLERGIES - SEE ALLERGY SECTION</div>" : "")} <div class='patient-info'> <div> <strong>Patient Name:</strong> {model.PatientName}<br/> <strong>Date of Birth:</strong> {model.DateOfBirth:MM/dd/yyyy}<br/> <strong>Age:</strong> {model.Age} years<br/> <strong>Gender:</strong> {model.Gender} </div> <div> <strong>MRN:</strong> {model.MedicalRecordNumber}<br/> <strong>Admission Date:</strong> {model.AdmissionDate:MM/dd/yyyy}<br/> <strong>Provider:</strong> {model.ProviderName}<br/> <strong>Department:</strong> {model.Department} </div> </div> <div class='section'> <h2>Vital Signs</h2> <div class='vital-signs'> <div class='vital-box'> <strong>Blood Pressure</strong><br/> {model.BloodPressure} </div> <div class='vital-box'> <strong>Heart Rate</strong><br/> {model.HeartRate} bpm </div> <div class='vital-box'> <strong>Temperature</strong><br/> {model.Temperature}°F </div> <div class='vital-box'> <strong>Respiratory Rate</strong><br/> {model.RespiratoryRate} /min </div> <div class='vital-box'> <strong>O2 Saturation</strong><br/> {model.OxygenSaturation}% </div> <div class='vital-box'> <strong>Weight</strong><br/> {model.Weight} lbs </div> </div> </div> <div class='section'> <h2>Current Medications</h2> <table class='medication-table'> <thead> <tr> <th>Medication</th> <th>Dosage</th> <th>Frequency</th> <th>Route</th> <th>Start Date</th> </tr> </thead> <tbody> {string.Join("", model.Medications.Select(m => $@" <tr> <td>{m.Name}</td> <td>{m.Dosage}</td> <td>{m.Frequency}</td> <td>{m.Route}</td> <td>{m.StartDate:MM/dd/yyyy}</td> </tr> "))} </tbody> </table> </div> <div class='section'> <h2>Clinical Notes</h2> <p>{model.ClinicalNotes}</p> </div> <div class='section'> <h2>Treatment Plan</h2> <p>{model.TreatmentPlan}</p> </div> </body> </html>"; } private string GenerateSecurePassword() { // Generate cryptographically secure password using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); var bytes = new byte[32]; rng.GetBytes(bytes); return Convert.ToBase64String(bytes); } private string GenerateOwnerPassword() { // In production, retrieve from secure configuration return "SecureOwnerPassword123!"; } } public class PatientReportModel { public string PatientName { get; set; } public string MedicalRecordNumber { get; set; } public DateTime DateOfBirth { get; set; } public int Age { get; set; } public string Gender { get; set; } public DateTime AdmissionDate { get; set; } public string ProviderName { get; set; } public string Department { get; set; } public string ReportType { get; set; } public string UserId { get; set; } // Vital Signs public string BloodPressure { get; set; } public int HeartRate { get; set; } public decimal Temperature { get; set; } public int RespiratoryRate { get; set; } public int OxygenSaturation { get; set; } public decimal Weight { get; set; } // Medical Information public bool HasAllergies { get; set; } public List<Medication> Medications { get; set; } public string ClinicalNotes { get; set; } public string TreatmentPlan { get; set; } } public class Medication { public string Name { get; set; } public string Dosage { get; set; } public string Frequency { get; set; } public string Route { get; set; } public DateTime StartDate { get; set; } } $vbLabelText $csharpLabel 安全考量 在生產環境中生成PDF或處理敏感資訊時,安全性至關重要。 IronPDF提供多種安全功能: 應用安全設置 using System.Text.RegularExpressions; namespace PdfGeneratorApp.Utilities { public static class PdfSecurityHelper { public static void ApplySecuritySettings(PdfDocument pdf, SecurityLevel level) { switch (level) { case SecurityLevel.Low: // Basic protection pdf.SecuritySettings.AllowUserCopyPasteContent = true; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; break; case SecurityLevel.Medium: // Restricted copying pdf.SecuritySettings.UserPassword = GeneratePassword(8); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.PrintLowQuality; break; case SecurityLevel.High: // Maximum security pdf.SecuritySettings.UserPassword = GeneratePassword(16); pdf.SecuritySettings.OwnerPassword = GeneratePassword(16); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.NoPrint; pdf.SecuritySettings.AllowUserAnnotations = false; pdf.SecuritySettings.AllowUserFormData = false; break; } } public enum SecurityLevel { Low, Medium, High } } using System.Text.RegularExpressions; namespace PdfGeneratorApp.Utilities { public static class PdfSecurityHelper { public static void ApplySecuritySettings(PdfDocument pdf, SecurityLevel level) { switch (level) { case SecurityLevel.Low: // Basic protection pdf.SecuritySettings.AllowUserCopyPasteContent = true; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights; break; case SecurityLevel.Medium: // Restricted copying pdf.SecuritySettings.UserPassword = GeneratePassword(8); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.PrintLowQuality; break; case SecurityLevel.High: // Maximum security pdf.SecuritySettings.UserPassword = GeneratePassword(16); pdf.SecuritySettings.OwnerPassword = GeneratePassword(16); pdf.SecuritySettings.AllowUserCopyPasteContent = false; pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.NoPrint; pdf.SecuritySettings.AllowUserAnnotations = false; pdf.SecuritySettings.AllowUserFormData = false; break; } } public enum SecurityLevel { Low, Medium, High } } $vbLabelText $csharpLabel 在上面的輔助類別中,我們使用SecurityLevel列舉來設定PDF的不同安全選項。 例如,低 SecurityLevel 提供基本保護,但仍允許 FullPrintRights 以及通過將屬性 AllowUserCopyPasteContent 設為 true 來進行 PDF 複製和粘貼。 這些設定可以透過調整IronPDF的PDF類屬性來啟用。 如需完整的安全性屬性和選項列表,請參考操作指南。 錯誤處理和故障排除 有關在ASP.NET Core中生成PDF的常見問題及其解決方案已在開發者社群中廣泛討論。 以下是針對最常見挑戰的有效解決方案: 記憶體管理 有關處理IronPDF中的記憶體洩漏的詳細指導,請實施以下模式: public class PdfMemoryManager : IDisposable { private readonly List<PdfDocument> _openDocuments = new(); private readonly ILogger<PdfMemoryManager> _logger; private bool _disposed; public PdfMemoryManager(ILogger<PdfMemoryManager> logger) { _logger = logger; } public PdfDocument CreateDocument(ChromePdfRenderer renderer, string html) { try { var pdf = renderer.RenderHtmlAsPdf(html); _openDocuments.Add(pdf); return pdf; } catch (OutOfMemory例外 ex) { _logger.LogError(ex, "Out of memory while generating PDF"); // Force garbage collection CleanupMemory(); // Retry with reduced quality renderer.RenderingOptions.JpegQuality = 50; var pdf = renderer.RenderHtmlAsPdf(html); _openDocuments.Add(pdf); return pdf; } } private void CleanupMemory() { // Dispose all open documents foreach (var doc in _openDocuments) { doc?.Dispose(); } _openDocuments.Clear(); // Force garbage collection GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); _logger.LogInformation("Memory cleanup performed"); } public void Dispose() { if (!_disposed) { CleanupMemory(); _disposed = true; } } } public class PdfMemoryManager : IDisposable { private readonly List<PdfDocument> _openDocuments = new(); private readonly ILogger<PdfMemoryManager> _logger; private bool _disposed; public PdfMemoryManager(ILogger<PdfMemoryManager> logger) { _logger = logger; } public PdfDocument CreateDocument(ChromePdfRenderer renderer, string html) { try { var pdf = renderer.RenderHtmlAsPdf(html); _openDocuments.Add(pdf); return pdf; } catch (OutOfMemory例外 ex) { _logger.LogError(ex, "Out of memory while generating PDF"); // Force garbage collection CleanupMemory(); // Retry with reduced quality renderer.RenderingOptions.JpegQuality = 50; var pdf = renderer.RenderHtmlAsPdf(html); _openDocuments.Add(pdf); return pdf; } } private void CleanupMemory() { // Dispose all open documents foreach (var doc in _openDocuments) { doc?.Dispose(); } _openDocuments.Clear(); // Force garbage collection GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); _logger.LogInformation("Memory cleanup performed"); } public void Dispose() { if (!_disposed) { CleanupMemory(); _disposed = true; } } } $vbLabelText $csharpLabel 字體渲染問題 public class FontTroubleshooter { public static void EnsureFontsAvailable(ChromePdfRenderer renderer) { // Embed fonts in the PDF renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print; // Use web-safe fonts as fallback var fontFallback = @" <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } @font-face { font-family: 'CustomFont'; src: url('data:font/woff2;base64,YOUR_BASE64_FONT_HERE') format('woff2'); } </style>"; // Add to HTML head renderer.RenderingOptions.CustomCssUrl = "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap"; } } public class FontTroubleshooter { public static void EnsureFontsAvailable(ChromePdfRenderer renderer) { // Embed fonts in the PDF renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print; // Use web-safe fonts as fallback var fontFallback = @" <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } @font-face { font-family: 'CustomFont'; src: url('data:font/woff2;base64,YOUR_BASE64_FONT_HERE') format('woff2'); } </style>"; // Add to HTML head renderer.RenderingOptions.CustomCssUrl = "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap"; } } $vbLabelText $csharpLabel 常見例外及解決方案 **例外****原因****解決方案**IronPdf.例外s.IronPdfNative例外Chrome引擎初始化失敗確保已安裝Visual C++可再發佈套件System.UnauthorizedAccess例外權限不足授予temp資料夾的寫入權限System.Timeout例外JavaScript花費太多時間增加RenderDelay或禁用JavaScriptSystem.OutOfMemory例外大型PDF或批次處理實施分頁或降低圖片品質IronPdf.例外s.IronPdfLicensing例外無效或過期的授權在配置中驗證授權金鑰 調試技巧 使用IronPDF,您可以輕鬆地除錯及記錄應用程式運行的所有過程,以便搜尋和驗證任何輸入和輸出。 若要啟用日誌記錄,請將 EnableDebugging\ 設定為 true,並透過指定 LogFilePath 屬性的值來指定日誌檔案路徑。 這裡有一個快速的程式碼片段顯示如何做到這一點。 public class PdfDebugger { private readonly ILogger<PdfDebugger> _logger; public PdfDebugger(ILogger<PdfDebugger> logger) { _logger = logger; } public void EnableDebugging(ChromePdfRenderer renderer) { // Enable detailed logging IronPdf.Logging.Logger.EnableDebugging = true; IronPdf.Logging.Logger.LogFilePath = "IronPdf.log"; IronPdf.Logging.Logger.LoggingMode = IronPdf.Logging.Logger.LoggingModes.All; // Log rendering settings _logger.LogDebug($"Paper Size: {renderer.RenderingOptions.PaperSize}"); _logger.LogDebug($"Margins: T{renderer.RenderingOptions.MarginTop} " + $"B{renderer.RenderingOptions.MarginBottom} " + $"L{renderer.RenderingOptions.MarginLeft} " + $"R{renderer.RenderingOptions.MarginRight}"); _logger.LogDebug($"JavaScript Enabled: {renderer.RenderingOptions.EnableJavaScript}"); _logger.LogDebug($"Render Delay: {renderer.RenderingOptions.RenderDelay}ms"); } public void SaveDebugHtml(string html, string fileName) { // Save HTML for inspection var debugPath = Path.Combine("debug", $"{fileName}_{DateTime.Now:yyyyMMdd_HHmmss}.html"); Directory.CreateDirectory("debug"); File.WriteAllText(debugPath, html); _logger.LogDebug($"Debug HTML saved to: {debugPath}"); } } public class PdfDebugger { private readonly ILogger<PdfDebugger> _logger; public PdfDebugger(ILogger<PdfDebugger> logger) { _logger = logger; } public void EnableDebugging(ChromePdfRenderer renderer) { // Enable detailed logging IronPdf.Logging.Logger.EnableDebugging = true; IronPdf.Logging.Logger.LogFilePath = "IronPdf.log"; IronPdf.Logging.Logger.LoggingMode = IronPdf.Logging.Logger.LoggingModes.All; // Log rendering settings _logger.LogDebug($"Paper Size: {renderer.RenderingOptions.PaperSize}"); _logger.LogDebug($"Margins: T{renderer.RenderingOptions.MarginTop} " + $"B{renderer.RenderingOptions.MarginBottom} " + $"L{renderer.RenderingOptions.MarginLeft} " + $"R{renderer.RenderingOptions.MarginRight}"); _logger.LogDebug($"JavaScript Enabled: {renderer.RenderingOptions.EnableJavaScript}"); _logger.LogDebug($"Render Delay: {renderer.RenderingOptions.RenderDelay}ms"); } public void SaveDebugHtml(string html, string fileName) { // Save HTML for inspection var debugPath = Path.Combine("debug", $"{fileName}_{DateTime.Now:yyyyMMdd_HHmmss}.html"); Directory.CreateDirectory("debug"); File.WriteAllText(debugPath, html); _logger.LogDebug($"Debug HTML saved to: {debugPath}"); } } $vbLabelText $csharpLabel 本地部署最佳實踐 IIS配置 對於部署到 IIS,請確保正確配置: <configuration> <system.webServer> <applicationPool> <processModel enable32BitAppOnWin64="false" /> </applicationPool> <httpRuntime executionTimeout="300" maxRequestLength="51200" /> <system.web> <compilation tempDirectory="~/App_Data/Temp/" /> </system.web> </system.webServer> </configuration> <configuration> <system.webServer> <applicationPool> <processModel enable32BitAppOnWin64="false" /> </applicationPool> <httpRuntime executionTimeout="300" maxRequestLength="51200" /> <system.web> <compilation tempDirectory="~/App_Data/Temp/" /> </system.web> </system.webServer> </configuration> XML 必要的相依性 請確保這些元件已安裝在您的部署伺服器上: .NET Runtime - 版本6.0或更高 Visual C++ Redistributables - 2015-2022 (x64) Windows Server - 建議使用2012 R2或更高版本 檔案系統權限 # Grant IIS_IUSRS write access to temp folder icacls "C:\inetpub\wwwroot\YourApp\App_Data\Temp" /grant "IIS_IUSRS:(OI)(CI)M" /T # Grant access to IronPDF cache folder icacls "C:\Windows\Temp\IronPdf" /grant "IIS_IUSRS:(OI)(CI)M" /T # Grant IIS_IUSRS write access to temp folder icacls "C:\inetpub\wwwroot\YourApp\App_Data\Temp" /grant "IIS_IUSRS:(OI)(CI)M" /T # Grant access to IronPDF cache folder icacls "C:\Windows\Temp\IronPdf" /grant "IIS_IUSRS:(OI)(CI)M" /T SHELL 效能調整 // Startup.cs or Program.cs public void ConfigureServices(IServiceCollection services) { // Configure IronPDF for production services.AddSingleton<ChromePdfRenderer>(provider => { var renderer = new ChromePdfRenderer(); // Production optimizations renderer.RenderingOptions.RenderDelay = 50; // Minimize delay renderer.RenderingOptions.Timeout = 120; // 2 minutes max renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print; // Enable caching for static resources Installation.ChromeGpuMode = IronPdf.Engines.Chrome.ChromeGpuModes.Disabled; Installation.LinuxAndDockerDependenciesAutoConfig = false; return renderer; }); // Configure memory cache for generated PDFs services.AddMemoryCache(options => { options.SizeLimit = 100_000_000; // 100 MB cache }); } // Startup.cs or Program.cs public void ConfigureServices(IServiceCollection services) { // Configure IronPDF for production services.AddSingleton<ChromePdfRenderer>(provider => { var renderer = new ChromePdfRenderer(); // Production optimizations renderer.RenderingOptions.RenderDelay = 50; // Minimize delay renderer.RenderingOptions.Timeout = 120; // 2 minutes max renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print; // Enable caching for static resources Installation.ChromeGpuMode = IronPdf.Engines.Chrome.ChromeGpuModes.Disabled; Installation.LinuxAndDockerDependenciesAutoConfig = false; return renderer; }); // Configure memory cache for generated PDFs services.AddMemoryCache(options => { options.SizeLimit = 100_000_000; // 100 MB cache }); } $vbLabelText $csharpLabel 最佳實踐摘要 請務必釋放 PdfDocument 物件以防止記憶體洩漏 重用 ChromePdfRenderer 實例 以提高效能 實施適當的錯誤處理,並進行詳細的記錄 在PDF生成之前清理所有用戶輸入 使用 async/await 模式來提高可擴展性 為JavaScript渲染配置適當的超時設定 壓縮圖片 以減少檔案大小 當內容為靜態時,快取生成的PDF 監控記憶體使用情況 在批次處理期間 在部署之前,用類似生產環境的資料量進行測試 立即改造您的ASP.NET應用程式 您現在已全面了解如何使用IronPDF在ASP.NET Core中使用C#生成PDF。 從基礎的Razor視圖轉換到高級的批次處理和效能優化,您已具備實施專業PDF生成的能力,該生成結果能精確匹配您在Chrome中所看到的內容。 您已解鎖的主要成就: 像素精準渲染 使用IronPDF的Chrome引擎,消除從HTML頁面、網頁等創建PDF時的格式驚訝。 生產就緒模板使用Razor視圖提供熟悉且可維護的PDF生成 企業級錯誤處理和記憶體管理,以確保在大規模操作下的可靠性能。 優化的批次處理,使用平行生成以處理數千份文件 專業安全功能以256位元加密保護敏感文件 現在開始構建專業的PDF 現在開始使用 IronPDF。 免費啟動 準備好在您的ASP.NET Core應用程式中實現PDF生成了嗎? 立即下載IronPDF的免費試用版,體驗專業的PDF生成功能,無浮水印或限制,為期30天。 憑藉全面的文件、即時的工程支援和30天退款保證,您可以自信地構建可投入生產的PDF解決方案。 您旅程中的重要資源 📚 探索完整的API文檔以獲取詳細的方法參考 👥 加入開發者社區以獲得即時協助 🚀 購買商業授權,生產部署起價 $799 透過企業級PDF產生功能轉型您的ASP.NET Core應用程式,其運作如您所預期,今天就開始使用IronPDF開始建構吧! 常見問題解答 IronPDF 在 ASP.NET 應用中的主要用途是什麼? IronPDF 主要用於從 PDF 文件中生成、編輯和提取內容,是在 ASP.NET 應用程式中創建發票、報告、證書或票據的關鍵工具。 IronPDF 如何確保像素完美的 PDF 渲染? IronPDF 通過利用高級渲染引擎和企業功能來確保像素完美的渲染,準確地將 HTML、圖片或其他文件格式轉換為高質量的 PDF。 IronPDF 可以與 ASP.NET Core 應用整合嗎? 是的,IronPDF 可以無縫集成到 ASP.NET Core 應用程式中,為開發人員提供了一個強大的程式庫來有效地處理各種 PDF 任務。 使用IronPDF生成PDF的好處是什麼? 使用 IronPDF 進行 PDF 生成提供的優勢包括易用性、高質量渲染、支持複雜文檔功能以及能夠在應用中自動處理 PDF 任務。 IronPDF 支持編輯現有 PDF 文件嗎? 支持,IronPDF 支持編輯現有的 PDF 文件,允許開發者以程式化方式修改內容、添加註釋和更新 PDF 元數據。 IronPDF 是否適合創建企業級 PDF 文件? IronPDF 非常適合創建企業級 PDF 文件,由於其強大的功能,包括支持複雜的文檔結構和安全特性,如加密和數字簽名。 IronPDF 可以將哪些文件格式轉換為 PDF? IronPDF 可以將多種文件格式轉換為 PDF,包括 HTML、圖片和其他文件類型,確保靈活性和不同數據源的兼容性。 IronPDF 如何處理 PDF 內容提取? IronPDF 通過提供 API 提取文本、圖片和元數據,方便從 PDF 文件中檢索並操作數據來處理內容提取。 IronPDF 可以用於自動化 PDF 文檔工作流程嗎? 可以,IronPDF 可以用於自動化 PDF 文檔工作流程,簡化如批量生成、轉換和在 Web 應用中分發 PDF 文件等過程。 IronPDF 為開發者提供了什麼樣的支持? IronPDF 為開發者提供了廣泛的支持,包括詳細的文檔、範例代碼和快速響應的客戶服務,以協助整合和排除故障。 IronPDF 是否即時支援 .NET 10? IronPDF 提供 .NET 10 的預發布支援,並已經符合 2025 年 11 月預期的 .NET 10 版本發布。開發者可以在 .NET 10 項目中使用 IronPDF 無需特別配置。 Curtis Chau 立即與工程團隊聊天 技術作家 Curtis Chau 擁有卡爾頓大學計算機科學學士學位,專注於前端開發,擅長於 Node.js、TypeScript、JavaScript 和 React。Curtis 熱衷於創建直觀且美觀的用戶界面,喜歡使用現代框架並打造結構良好、視覺吸引人的手冊。除了開發之外,Curtis 對物聯網 (IoT) 有著濃厚的興趣,探索將硬體和軟體結合的創新方式。在閒暇時間,他喜愛遊戲並構建 Discord 機器人,結合科技與創意的樂趣。 相關文章 更新2026年3月1日 如何在.NET中使用IronPDF創建PDF檔案(C#教程) 發現用於創建C# PDF文件的有效方法,提升您的編碼技能並簡化您的項目。立即閱讀文章! 閱讀更多 更新2026年2月27日 如何在C#中合併PDF文件 使用 IronPDF 合併 PDF 文件。學習如何使用簡單的 VB.NET 程式碼將多個 PDF 文件合併成一個文檔。包含逐步範例。 閱讀更多 更新2026年3月1日 C# PDFWriter教程,適用於.NET 10開發者 通過這個面向開發人員的逐步指南,學習如何使用C# PDFWriter高效創建PDF。閱讀本文以提高您的技能! 閱讀更多 如何在C#中從PDF中提取圖像如何在.NET中從PDF中提取資料