使用IRONPDF 如何在ASP.NET中使用C#生成PDF Curtis Chau 已更新:十月 26, 2025 下载 IronPDF NuGet 下载 DLL 下载 Windows 安装程序 免费试用 法学硕士副本 法学硕士副本 将页面复制为 Markdown 格式,用于 LLMs 在 ChatGPT 中打开 向 ChatGPT 咨询此页面 在双子座打开 向 Gemini 询问此页面 在 Grok 中打开 向 Grok 询问此页面 打开困惑 向 Perplexity 询问有关此页面的信息 分享 在 Facebook 上分享 分享到 X(Twitter) 在 LinkedIn 上分享 复制链接 电子邮件文章 以编程方式生成PDF是现代Web应用程序的关键要求,无论是创建发票、报告、证书还是票据。 如果您是ASP.NET Core .NET开发人员,希望实现具有像素完美渲染和企业功能的PDF生成,您来对地方了。 本综合指南将逐步带您了解专业PDF文件生成,使用IronPDF这一强大的.NET库,使PDF文档的创建变得简单。 我们将探讨从基本设置到高级批处理的所有内容,展示其高效且易于集成的特性。 到最后,您将拥有一个强大的PDF转换器,用于在ASP.NET应用中像专业人士一样生成PDF文档,使用IronPDF。 为什么在ASP.NET Core中生成PDF? 服务器端PDF生成比客户端替代方案提供显著的优势。 当您在服务器上生成PDF时,您确保了所有浏览器和设备的一致输出,消除了对客户端资源的依赖,并更好地控制敏感数据。 常见的HTML到PDF转换业务场景包括: 财务文件:发票、账单和交易收据 *合规报告:*监管文件和审计文件 用户证书:培训完成情况和专业认证 活动门票:二维码入场券和登机牌 数据导出:**分析报告和仪表盘快照 此外,服务器端方式确保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 在 IronPDF 上查看 NuGet 快速安装。超过 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视图生成PDF 在ASP.NET Core中生成新PDF文档的最强大方法涉及利用Razor视图进行PDF转换。 这使您能够利用熟悉的MVC模式、强类型模型和Razor语法,在定制的HTML文件、网页和其他来源中创建动态PDF。 根据微软关于Razor页面的文档,这种方法为以页面为中心的场景提供了最清晰的关注点分离。 创建数据模型 首先,让我们创建一个表示典型业务文档的全面模型: 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%;'> <!-- Add your logo or dynamic content here --> <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%;'> <!-- Add your logo or dynamic content here --> <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的应用程序,这里有一个使用异步和多线程技术的优化批处理实现。 这种方法遵循微软的并行编程最佳实践,在使用C#在ASP.NET Core中生成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提供基本保护,但仍允许通过将属性AllowUserCopyPasteContent设为true来提供FullPrintRights和复制粘帖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++ RedistributablesSystem.UnauthorizedAccess例外情况权限不足授予临时文件夹的写入权限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,确保正确配置: <!-- web.config --> <configuration> <system.webServer> <!-- Enable 64-bit mode for better memory handling --> <applicationPool> <processModel enable32BitAppOnWin64="false" /> </applicationPool> <!-- Increase request timeout for large PDFs --> <httpRuntime executionTimeout="300" maxRequestLength="51200" /> <!-- Configure temp folder permissions --> <system.web> <compilation tempDirectory="~/App_Data/Temp/" /> </system.web> </system.webServer> </configuration> <!-- web.config --> <configuration> <system.webServer> <!-- Enable 64-bit mode for better memory handling --> <applicationPool> <processModel enable32BitAppOnWin64="false" /> </applicationPool> <!-- Increase request timeout for large PDFs --> <httpRuntime executionTimeout="300" maxRequestLength="51200" /> <!-- Configure temp folder permissions --> <system.web> <compilation tempDirectory="~/App_Data/Temp/" /> </system.web> </system.webServer> </configuration> XML 必要组件 确保在您的部署服务器上安装了以下组件: .NET运行时 - 版本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中生成PDF有了全面了解。 从基本的Razor视图转换到具有性能优化的高级批处理处理,您具备实现与Chrome中所见完全一致的专业PDF生成的能力。 您解锁的关键成就: 像素级完美渲染使用IronPDF的Chrome引擎,消除了在从HTML页面、网页等生成PDF时的格式意外 生产就绪模板使用Razor视图提供熟悉的、可维护的PDF生成 企业级错误处理和内存管理以确保可靠的大规模操作 优化的批量处理通过并行生成,处理成千上万的文档 专业的安全特性利用256位加密保护敏感文档 马上开始创建专业的PDF 立即开始使用 IronPDF。 免费开始 准备好在ASP.NET Core应用程序中实施PDF生成了吗? 今下载IronPDF的免费试用版,体验30天无水印、无限制的专业PDF生成。 凭借全面的文档、响应的工程支持和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 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。 相关文章 已更新一月 22, 2026 如何使用 IronPDF 在 .NET 中创建 PDF 文档:完整指南 发现为开发人员创建PDF文件的有效方法。提升您的编码技能并简化您的项目。立即阅读文章! 阅读更多 已更新一月 21, 2026 如何在VB.NET中合并PDF文件:完整教程 使用IronPDF合并PDF VB NET。学习使用简单的VB.NET代码将多个PDF文件合并为一个文档。包括逐步示例。 阅读更多 已更新一月 21, 2026 C# PDFWriter教程:在.NET中创建PDF文档 使用这份逐步指南了解如何高效地使用C# PDFWriter创建PDF。阅读文章提升您的技能! 阅读更多 如何在C#中从PDF中提取图像如何在.NET中从PDF中提取数据
已更新一月 21, 2026 如何在VB.NET中合并PDF文件:完整教程 使用IronPDF合并PDF VB NET。学习使用简单的VB.NET代码将多个PDF文件合并为一个文档。包括逐步示例。 阅读更多