如何使用C#在ASP.NET中生成PDF
無論是建立發票、報告、證書還是票據,以程式設計方式產生 PDF 都是現代 Web 應用程式的關鍵要求。 如果您是 ASP.NET Core .NET 開發人員,正在尋找能夠實現像素級完美渲染和企業級功能的強大 PDF 生成功能,那麼您來對地方了。
本綜合指南將引導您使用 IronPDF(一個功能強大的 .NET 庫,可輕鬆建立 PDF 文件)來產生專業的 PDF 文件。 我們將從基本設定到高階批量處理進行全面探討,展現其高效能和易於整合的特性。 最後,你將擁有一個功能強大的 PDF 轉換器,可以像專業人士一樣使用 IronPDF 在 ASP.NET 應用程式中產生 PDF 文件。
為什麼要在 ASP.NET Core 中產生 PDF?
伺服器端 PDF 產生相比客戶端產生方式具有顯著優勢。 在伺服器上產生 PDF 可確保在所有瀏覽器和裝置上輸出一致的內容,消除對客戶端資源的依賴,並更好地控制敏感資料。 HTML 轉 PDF的常見業務場景包括:
- 財務文件:發票、對帳單及交易收據
- 合規報告:監管申報文件與稽核文件
- 使用者證書:培訓結業證明與 Professional 認證
- 活動門票:附有 QR 碼的入場證與登機證
- 資料匯出:分析報告與儀表板快照
此外,伺服器端方法可確保 PDF 在所有瀏覽器和作業系統上保持一致。 使其在法律和財務文件方面備受推崇。
IronPDF 如何改變您的 PDF 產生方式?
IronPDF 是一個 PDF 庫,它在 .NET 生態系統中脫穎而出,這得益於它的 HTML 轉換器,該轉換器在底層使用了完整的Chrome 渲染引擎。 這意味著您的 PDF 文件將像在 Google Chrome 中一樣呈現,並完全支援現代 CSS3、 JavaScript 執行和Web 字體。 與其他程式庫不同,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 頁面進行產生後操作,包括合併、添加浮水印和數位簽章。
!{--010011000100100101000010010100100100000101010010010110010101111101001110010101010101010101010101010101010101010 0100010111110100100101001101010100010000010100110001001100010111110100001001001100010011110010101010
設定您的 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();
Imports IronPdf
Imports IronPdf.Extensions.Mvc.Core
Imports Microsoft.AspNetCore.Mvc.ViewFeatures
Dim 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(Of IHttpContextAccessor, HttpContextAccessor)()
builder.Services.AddSingleton(Of ITempDataProvider, CookieTempDataProvider)()
builder.Services.AddSingleton(Of IRazorViewRenderer, RazorViewRenderer)()
' Configure ChromePdfRenderer as a service
builder.Services.AddSingleton(Of ChromePdfRenderer)(
Function(provider)
Dim 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
End Function)
Dim app = builder.Build()
' Configure middleware pipeline
If Not app.Environment.IsDevelopment() Then
app.UseExceptionHandler("/Home/Error")
app.UseHsts()
End If
app.UseHttpsRedirection()
app.UseStaticFiles()
app.UseRouting()
app.UseAuthorization()
app.MapControllerRoute(
name:="default",
pattern:="{controller=Home}/{action=Index}/{id?}")
app.Run()
此配置將 IronPDF 設定為單例服務,確保應用程式有效利用資源。 您可以進一步修改 `RenderingOptions` 以符合您的特定要求。 此時,Visual Studio 解決方案資源管理器中的資料夾結構應如下所示:
從 Razor 視圖產生 PDF
在 ASP.NET Core 中產生新的 PDF 文件最有效的方法是利用Razor 視圖進行 PDF 轉換。 這樣,您就可以利用熟悉的 MVC 模式、強型別模型和 Razor 語法,從自訂 HTML 檔案、網頁和其他來源建立動態 PDF。 根據微軟關於 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;
}
}
Imports System
Imports System.Collections.Generic
Imports System.Linq
Namespace PdfGeneratorApp.Models
Public Class InvoiceModel
Public Property InvoiceNumber As String
Public Property InvoiceDate As DateTime
Public Property DueDate As DateTime
Public Property Vendor As CompanyInfo
Public Property Customer As CompanyInfo
Public Property Items As List(Of InvoiceItem)
Public ReadOnly Property Subtotal As Decimal
Get
Return If(Items?.Sum(Function(x) x.Total), 0)
End Get
End Property
Public Property TaxRate As Decimal
Public ReadOnly Property TaxAmount As Decimal
Get
Return Subtotal * (TaxRate / 100)
End Get
End Property
Public ReadOnly Property Total As Decimal
Get
Return Subtotal + TaxAmount
End Get
End Property
Public Property Notes As String
Public Property PaymentTerms As String
End Class
Public Class CompanyInfo
Public Property Name As String
Public Property Address As String
Public Property City As String
Public Property State As String
Public Property ZipCode As String
Public Property Email As String
Public Property Phone As String
End Class
Public Class InvoiceItem
Public Property Description As String
Public Property Quantity As Integer
Public Property UnitPrice As Decimal
Public ReadOnly Property Total As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
End Class
End Namespace
建構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>
實作帶有錯誤處理的控制器
現在讓我們建立一個功能強大的控制器,用於產生具有全面錯誤處理的 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"
};
}
}
}
Imports IronPdf
Imports IronPdf.Extensions.Mvc.Core
Imports Microsoft.AspNetCore.Mvc
Imports PdfGeneratorApp.Models
Imports System.Diagnostics
Namespace PdfGeneratorApp.Controllers
Public Class InvoiceController
Inherits Controller
Private ReadOnly _logger As ILogger(Of InvoiceController)
Private ReadOnly _viewRenderer As IRazorViewRenderer
Private ReadOnly _pdfRenderer As ChromePdfRenderer
Private ReadOnly _environment As IWebHostEnvironment
Public Sub New(logger As ILogger(Of InvoiceController), viewRenderer As IRazorViewRenderer, pdfRenderer As ChromePdfRenderer, environment As IWebHostEnvironment)
_logger = logger
_viewRenderer = viewRenderer
_pdfRenderer = pdfRenderer
_environment = environment
End Sub
<HttpGet>
Public Function Index() As IActionResult
' Display a form or list of invoices
Return View()
End Function
<HttpGet>
Public Async Function GenerateInvoice(invoiceNumber As String) As Task(Of IActionResult)
Dim stopwatch = Stopwatch.StartNew()
Try
' Validate input
If String.IsNullOrEmpty(invoiceNumber) Then
_logger.LogWarning("Invoice generation attempted without invoice number")
Return BadRequest("Invoice number is required")
End If
' Generate sample data (in production, fetch from database)
Dim 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 With {
.CenterText = $"Invoice {invoice.InvoiceNumber}",
.DrawDividerLine = True,
.Font = IronSoftware.Drawing.FontTypes.Helvetica,
.FontSize = 10
}
' Add footer with page numbers
_pdfRenderer.RenderingOptions.TextFooter = New TextHeaderFooter With {
.LeftText = "{date} {time}",
.RightText = "Page {page} of {total-pages}",
.DrawDividerLine = True,
.Font = IronSoftware.Drawing.FontTypes.Helvetica,
.FontSize = 8
}
' Render the view to PDF
Dim pdf As PdfDocument
Try
pdf = _pdfRenderer.RenderRazorViewToPdf(_viewRenderer, "Views/Invoice/InvoiceTemplate.cshtml", invoice)
Catch renderEx As Exception
_logger.LogError(renderEx, "Failed to render Razor view to PDF")
Throw New InvalidOperationException("PDF rendering failed. Please check the template.", renderEx)
End Try
' 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 As Exception
_logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}")
' In development, return detailed error
If _environment.IsDevelopment() Then
Return StatusCode(500, New With {
.error = "PDF generation failed",
.message = ex.Message,
.stackTrace = ex.StackTrace
})
End If
' In production, return generic error
Return StatusCode(500, "An error occurred while generating the PDF")
End Try
End Function
Private Function CreateSampleInvoice(invoiceNumber As String) As InvoiceModel
Return New InvoiceModel With {
.InvoiceNumber = invoiceNumber,
.InvoiceDate = DateTime.Now,
.DueDate = DateTime.Now.AddDays(30),
.Vendor = New CompanyInfo With {
.Name = "Tech Solutions Inc.",
.Address = "123 Business Ave",
.City = "New York",
.State = "NY",
.ZipCode = "10001",
.Email = "billing@techsolutions.com",
.Phone = "(555) 123-4567"
},
.Customer = New CompanyInfo With {
.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(Of InvoiceItem) From {
New InvoiceItem With {
.Description = "Software Development Services - 40 hours",
.Quantity = 40,
.UnitPrice = 150.0D
},
New InvoiceItem With {
.Description = "Project Management - 10 hours",
.Quantity = 10,
.UnitPrice = 120.0D
},
New InvoiceItem With {
.Description = "Quality Assurance Testing",
.Quantity = 1,
.UnitPrice = 2500.0D
}
},
.TaxRate = 8.875D,
.Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.",
.PaymentTerms = "Net 30"
}
End Function
End Class
End Namespace
程式碼解釋
若要測試並執行上述 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; }
}
}
Imports Microsoft.AspNetCore.Mvc
Imports IronPdf
Imports System.IO
Namespace PdfGeneratorApp.Controllers.Api
<ApiController>
<Route("api/[controller]")>
Public Class PdfApiController
Inherits ControllerBase
Private ReadOnly _pdfRenderer As ChromePdfRenderer
Private ReadOnly _logger As ILogger(Of PdfApiController)
Public Sub New(pdfRenderer As ChromePdfRenderer, logger As ILogger(Of PdfApiController))
_pdfRenderer = pdfRenderer
_logger = logger
End Sub
<HttpPost("generate-report")>
Public Async Function GenerateReport(<FromBody> request As ReportRequest) As Task(Of IActionResult)
Try
' Validate request
If Not ModelState.IsValid Then
Return BadRequest(ModelState)
End If
' Build HTML content dynamically
Dim htmlContent = BuildReportHtml(request)
' Generate PDF with memory-efficient streaming
Using pdfDocument = _pdfRenderer.RenderHtmlAsPdf(htmlContent)
' Apply compression for smaller file size
pdfDocument.CompressImages(60) ' 60% quality
' Stream the PDF directly to response
Dim stream = New MemoryStream()
pdfDocument.SaveAs(stream)
stream.Position = 0
_logger.LogInformation($"Report generated for {request.ReportType}")
Return New FileStreamResult(stream, "application/pdf") With {
.FileDownloadName = $"Report_{DateTime.Now:yyyyMMdd_HHmmss}.pdf"
}
End Using
Catch ex As Exception
_logger.LogError(ex, "Failed to generate report")
Return StatusCode(500, New With {.error = "Report generation failed"})
End Try
End Function
<HttpGet("download/{documentId}")>
Public Async Function DownloadDocument(documentId As String) As Task(Of IActionResult)
Try
' In production, retrieve document from database or storage
Dim documentPath = Path.Combine("wwwroot", "documents", $"{documentId}.pdf")
If Not System.IO.File.Exists(documentPath) Then
Return NotFound(New With {.error = "Document not found"})
End If
Dim memory = New MemoryStream()
Using stream = New FileStream(documentPath, FileMode.Open)
Await stream.CopyToAsync(memory)
End Using
memory.Position = 0
Return File(memory, "application/pdf", $"Document_{documentId}.pdf")
Catch ex As Exception
_logger.LogError(ex, $"Failed to download document {documentId}")
Return StatusCode(500, New With {.error = "Download failed"})
End Try
End Function
Private Function BuildReportHtml(request As ReportRequest) As String
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>"
End Function
Private Function GenerateDataTable(data As List(Of ReportDataItem)) As String
If data Is Nothing OrElse Not data.Any() Then
Return "<p>No data available</p>"
End If
Dim table = "<table class='data-table'><thead><tr>"
' Add headers
For Each prop In GetType(ReportDataItem).GetProperties()
table += $"<th>{prop.Name}</th>"
Next
table += "</tr></thead><tbody>"
' Add data rows
For Each item In data
table += "<tr>"
For Each prop In GetType(ReportDataItem).GetProperties()
Dim value = If(prop.GetValue(item), "")
table += $"<td>{value}</td>"
Next
table += "</tr>"
Next
table += "</tbody></table>"
Return table
End Function
End Class
Public Class ReportRequest
Public Property ReportType As String
Public Property Description As String
Public Property Data As List(Of ReportDataItem)
End Class
Public Class ReportDataItem
Public Property Name As String
Public Property Category As String
Public Property Value As Decimal
Public Property Date As DateTime
End Class
End Namespace
新增專業頁首、頁尾和樣式
專業的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"
};
}
}
}
Imports IronPdf
Imports IronPdf.Extensions.Mvc.Core
Imports Microsoft.AspNetCore.Mvc
Imports PdfGeneratorApp.Models
Imports PdfGeneratorApp.Services
Imports System.Diagnostics
Namespace PdfGeneratorApp.Controllers
Public Class InvoiceController
Inherits Controller
Private ReadOnly _logger As ILogger(Of InvoiceController)
Private ReadOnly _viewRenderer As IRazorViewRenderer
Private ReadOnly _pdfRenderer As ChromePdfRenderer
Private ReadOnly _pdfFormattingService As PdfFormattingService
Private ReadOnly _environment As IWebHostEnvironment
Public Sub New(logger As ILogger(Of InvoiceController),
viewRenderer As IRazorViewRenderer,
pdfRenderer As ChromePdfRenderer,
pdfFormattingService As PdfFormattingService,
environment As IWebHostEnvironment)
_logger = logger
_viewRenderer = viewRenderer
_pdfRenderer = pdfRenderer
_pdfFormattingService = pdfFormattingService
_environment = environment
End Sub
<HttpGet>
Public Function Index() As IActionResult
' Display a form or list of invoices
Return View()
End Function
Private Sub ConfigurePdfRendererOptions(renderer As ChromePdfRenderer, invoice As InvoiceModel, options As PdfStylingOptions)
' 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 Then
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
.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 With {
.CenterText = options.HeaderText,
.Font = IronSoftware.Drawing.FontTypes.Arial,
.FontSize = 12,
.DrawDividerLine = True
}
End If
' Footer
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
.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
End Sub
<HttpGet>
Public Async Function GenerateInvoice(invoiceNumber As String) As Task(Of IActionResult)
Dim stopwatch = Stopwatch.StartNew()
Try
' Validate input
If String.IsNullOrEmpty(invoiceNumber) Then
_logger.LogWarning("Invoice generation attempted without invoice number")
Return BadRequest("Invoice number is required")
End If
' Generate sample data (in production, fetch from database)
Dim 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
Dim options = New PdfStylingOptions With {
.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
Dim pdf As PdfDocument
Try
pdf = _pdfRenderer.RenderRazorViewToPdf(
_viewRenderer,
"Views/Invoice/InvoiceTemplate.cshtml",
invoice)
Catch renderEx As Exception
_logger.LogError(renderEx, "Failed to render Razor view to PDF")
Throw New InvalidOperationException("PDF rendering failed. Please check the template.", renderEx)
End Try
' 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 As Exception
_logger.LogError(ex, $"Error generating PDF for invoice {invoiceNumber}")
' In development, return detailed error
If _environment.IsDevelopment() Then
Return StatusCode(500, New With {
.error = "PDF generation failed",
.message = ex.Message,
.stackTrace = ex.StackTrace
})
End If
' In production, return generic error
Return StatusCode(500, "An error occurred while generating the PDF")
End Try
End Function
Private Function CreateSampleInvoice(invoiceNumber As String) As InvoiceModel
Return New InvoiceModel With {
.InvoiceNumber = invoiceNumber,
.InvoiceDate = DateTime.Now,
.DueDate = DateTime.Now.AddDays(30),
.Vendor = New CompanyInfo With {
.Name = "Tech Solutions Inc.",
.Address = "123 Business Ave",
.City = "New York",
.State = "NY",
.ZipCode = "10001",
.Email = "billing@techsolutions.com",
.Phone = "(555) 123-4567"
},
.Customer = New CompanyInfo With {
.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(Of InvoiceItem) From {
New InvoiceItem With {
.Description = "Software Development Services - 40 hours",
.Quantity = 40,
.UnitPrice = 150.0D
},
New InvoiceItem With {
.Description = "Project Management - 10 hours",
.Quantity = 10,
.UnitPrice = 120.0D
},
New InvoiceItem With {
.Description = "Quality Assurance Testing",
.Quantity = 1,
.UnitPrice = 2500.0D
}
},
.TaxRate = 8.875D,
.Notes = "Payment is due within 30 days. Late payments subject to 1.5% monthly interest.",
.PaymentTerms = "Net 30"
}
End Function
End Class
End Namespace
程式碼解釋
你可以看到基本標題和樣式標題之間的細微差別。 使用 IronPDF,您甚至可以為發票添加自訂 HTML 頁首和頁腳,進一步美化發票樣式,使其真正成為您自己的發票。
有關添加頁眉和頁腳的更多詳細信息,請參閱此操作指南。
輸出
如何實現高效能批次PDF處理?
需要產生成百上千份PDF檔案嗎? 以下是如何在高效管理記憶體的同時,利用並行處理實現最佳效能的方法。 下載我們的完整範例程式碼,即可查看實際應用效果。
對於需要高效產生多個 PDF 的應用程式,這裡提供了一種使用非同步和多執行緒技術的最佳化批量處理實作。 此方法遵循 Microsoft 的平行程式設計最佳實務,旨在使用 C# 於 .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;
}
Imports System.Collections.Concurrent
Imports System.Diagnostics
Public Class BatchPdfProcessor
Private ReadOnly _renderer As ChromePdfRenderer
Private ReadOnly _logger As ILogger(Of BatchPdfProcessor)
Private ReadOnly _semaphore As SemaphoreSlim
Public Sub New(renderer As ChromePdfRenderer, logger As ILogger(Of BatchPdfProcessor))
_renderer = renderer
_logger = logger
' Limit concurrent PDF generation to prevent memory exhaustion
_semaphore = New SemaphoreSlim(Environment.ProcessorCount)
End Sub
Public Async Function ProcessBatchAsync(requests As List(Of BatchPdfRequest), Optional progress As IProgress(Of BatchProgressReport) = Nothing) As Task(Of BatchProcessingResult)
Dim result As New BatchProcessingResult With {
.StartTime = DateTime.UtcNow,
.TotalRequests = requests.Count
}
Dim successfulPdfs As New ConcurrentBag(Of GeneratedPdf)()
Dim errors As New ConcurrentBag(Of ProcessingError)()
Dim stopwatch As Stopwatch = Stopwatch.StartNew()
' Process PDFs in parallel with controlled concurrency
Dim tasks = requests.Select(Async Function(request, index)
Await _semaphore.WaitAsync()
Try
Dim taskStopwatch As Stopwatch = Stopwatch.StartNew()
' Generate PDF
Dim pdf = Await GeneratePdfAsync(request)
taskStopwatch.Stop()
successfulPdfs.Add(New GeneratedPdf With {
.Id = request.Id,
.FileName = request.FileName,
.Data = pdf.BinaryData,
.GenerationTime = taskStopwatch.ElapsedMilliseconds,
.PageCount = pdf.PageCount
})
' Report progress
If progress IsNot Nothing Then
progress.Report(New BatchProgressReport With {
.ProcessedCount = successfulPdfs.Count + errors.Count,
.TotalCount = requests.Count,
.CurrentFile = request.FileName
})
End If
_logger.LogDebug($"Generated PDF {request.Id} in {taskStopwatch.ElapsedMilliseconds}ms")
Catch ex As Exception
errors.Add(New ProcessingError With {
.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()
End Try
End Function)
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 = If(successfulPdfs.Any(), successfulPdfs.Average(Function(p) p.GenerationTime), 0)
result.TotalPages = successfulPdfs.Sum(Function(p) p.PageCount)
result.TotalSizeBytes = successfulPdfs.Sum(Function(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 Then
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
End If
Return result
End Function
Private Async Function GeneratePdfAsync(request As BatchPdfRequest) As Task(Of PdfDocument)
Return Await Task.Run(Function()
' Configure renderer for this specific request
Dim localRenderer As New ChromePdfRenderer()
localRenderer.RenderingOptions.PaperSize = request.PaperSize
localRenderer.RenderingOptions.MarginTop = request.MarginTop
localRenderer.RenderingOptions.MarginBottom = request.MarginBottom
' Generate PDF
Dim pdf = localRenderer.RenderHtmlAsPdf(request.HtmlContent)
' Apply compression if requested
If request.CompressImages Then
pdf.CompressImages(request.CompressionQuality)
End If
Return pdf
End Function)
End Function
End Class
Public Class BatchPdfRequest
Public Property Id As String
Public Property FileName As String
Public Property HtmlContent As String
Public Property PaperSize As IronPdf.Rendering.PdfPaperSize = IronPdf.Rendering.PdfPaperSize.A4
Public Property MarginTop As Integer = 25
Public Property MarginBottom As Integer = 25
Public Property CompressImages As Boolean = True
Public Property CompressionQuality As Integer = 80
End Class
Public Class BatchProcessingResult
Public Property StartTime As DateTime
Public Property EndTime As DateTime
Public Property TotalProcessingTime As Long
Public Property TotalRequests As Integer
Public Property SuccessCount As Integer
Public Property ErrorCount As Integer
Public Property AverageGenerationTime As Double
Public Property TotalPages As Integer
Public Property TotalSizeBytes As Long
Public Property SuccessfulPdfs As List(Of GeneratedPdf)
Public Property Errors As List(Of ProcessingError)
End Class
Public Class GeneratedPdf
Public Property Id As String
Public Property FileName As String
Public Property Data As Byte()
Public Property GenerationTime As Long
Public Property PageCount As Integer
End Class
Public Class ProcessingError
Public Property RequestId As String
Public Property FileName As String
Public Property Error As String
Public Property StackTrace As String
End Class
Public Class BatchProgressReport
Public Property ProcessedCount As Integer
Public Property TotalCount As Integer
Public Property CurrentFile As String
Public ReadOnly Property PercentComplete As Double
Get
Return CDbl(ProcessedCount) / TotalCount * 100
End Get
End Property
End Class
真實世界醫療保健報告範例
以下是產生符合 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; }
}
Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Threading.Tasks
Imports IronPdf
Imports IronPdf.Rendering
Imports Microsoft.Extensions.Logging
Public Class MedicalReportGenerator
Private ReadOnly _renderer As ChromePdfRenderer
Private ReadOnly _logger As ILogger(Of MedicalReportGenerator)
Public Sub New(renderer As ChromePdfRenderer, logger As ILogger(Of MedicalReportGenerator))
_renderer = renderer
_logger = logger
End Sub
Public Async Function GeneratePatientReport(model As PatientReportModel) As Task(Of PdfDocument)
Dim stopwatch = Stopwatch.StartNew()
' Configure for medical document standards
_renderer.RenderingOptions.PaperSize = PdfPaperSize.Letter
_renderer.RenderingOptions.MarginTop = 50
_renderer.RenderingOptions.MarginBottom = 40
' HIPAA-compliant header
_renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
.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
Dim html = GenerateMedicalReportHtml(model)
' Create PDF with encryption for HIPAA compliance
Dim pdf = _renderer.RenderHtmlAsPdf(html)
' Apply 256-bit AES encryption
pdf.SecuritySettings.UserPassword = GenerateSecurePassword()
pdf.SecuritySettings.OwnerPassword = GenerateOwnerPassword()
pdf.SecuritySettings.AllowUserCopyPasteContent = False
pdf.SecuritySettings.AllowUserPrinting = 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
End Function
Private Function GenerateMedicalReportHtml(model As PatientReportModel) As String
' 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>
{(If(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(Function(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>"
End Function
Private Function GenerateSecurePassword() As String
' Generate cryptographically secure password
Using rng = System.Security.Cryptography.RandomNumberGenerator.Create()
Dim bytes = New Byte(31) {}
rng.GetBytes(bytes)
Return Convert.ToBase64String(bytes)
End Using
End Function
Private Function GenerateOwnerPassword() As String
' In production, retrieve from secure configuration
Return "SecureOwnerPassword123!"
End Function
End Class
Public Class PatientReportModel
Public Property PatientName As String
Public Property MedicalRecordNumber As String
Public Property DateOfBirth As DateTime
Public Property Age As Integer
Public Property Gender As String
Public Property AdmissionDate As DateTime
Public Property ProviderName As String
Public Property Department As String
Public Property ReportType As String
Public Property UserId As String
' Vital Signs
Public Property BloodPressure As String
Public Property HeartRate As Integer
Public Property Temperature As Decimal
Public Property RespiratoryRate As Integer
Public Property OxygenSaturation As Integer
Public Property Weight As Decimal
' Medical Information
Public Property HasAllergies As Boolean
Public Property Medications As List(Of Medication)
Public Property ClinicalNotes As String
Public Property TreatmentPlan As String
End Class
Public Class Medication
Public Property Name As String
Public Property Dosage As String
Public Property Frequency As String
Public Property Route As String
Public Property StartDate As DateTime
End Class
安全考量
在生產環境中產生 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
}
}
Imports System.Text.RegularExpressions
Namespace PdfGeneratorApp.Utilities
Public Module PdfSecurityHelper
Public Sub ApplySecuritySettings(pdf As PdfDocument, level As SecurityLevel)
Select Case level
Case SecurityLevel.Low
' Basic protection
pdf.SecuritySettings.AllowUserCopyPasteContent = True
pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.FullPrintRights
Case SecurityLevel.Medium
' Restricted copying
pdf.SecuritySettings.UserPassword = GeneratePassword(8)
pdf.SecuritySettings.AllowUserCopyPasteContent = False
pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.PrintLowQuality
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
End Select
End Sub
Public Enum SecurityLevel
Low
Medium
High
End Enum
End Module
End Namespace
在上面的輔助類別中,我們使用 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;
}
}
}
Imports System
Imports System.Collections.Generic
Public Class PdfMemoryManager
Implements IDisposable
Private ReadOnly _openDocuments As New List(Of PdfDocument)()
Private ReadOnly _logger As ILogger(Of PdfMemoryManager)
Private _disposed As Boolean
Public Sub New(logger As ILogger(Of PdfMemoryManager))
_logger = logger
End Sub
Public Function CreateDocument(renderer As ChromePdfRenderer, html As String) As PdfDocument
Try
Dim pdf = renderer.RenderHtmlAsPdf(html)
_openDocuments.Add(pdf)
Return pdf
Catch ex As OutOfMemoryException
_logger.LogError(ex, "Out of memory while generating PDF")
' Force garbage collection
CleanupMemory()
' Retry with reduced quality
renderer.RenderingOptions.JpegQuality = 50
Dim pdf = renderer.RenderHtmlAsPdf(html)
_openDocuments.Add(pdf)
Return pdf
End Try
End Function
Private Sub CleanupMemory()
' Dispose all open documents
For Each doc In _openDocuments
doc?.Dispose()
Next
_openDocuments.Clear()
' Force garbage collection
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
_logger.LogInformation("Memory cleanup performed")
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
If Not _disposed Then
CleanupMemory()
_disposed = True
End If
End Sub
End Class
字體渲染問題
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";
}
}
Public Class FontTroubleshooter
Public Shared Sub EnsureFontsAvailable(renderer As ChromePdfRenderer)
' Embed fonts in the PDF
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
' Use web-safe fonts as fallback
Dim fontFallback As String = "
<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"
End Sub
End Class
常見例外狀況及解決方案
例外 | 原因 | 解決方案 |
IronPdf.例外s.IronPdfNative例外 | Chrome 引擎初始化失敗 | 確保已安裝 Visual C++ 可再發行元件包 |
System.UnauthorizedAccess例外 | 權限不足 | 授予對臨時資料夾的寫入權限 |
System.Timeout例外 | JavaScript 運行時間過長 | 增加渲染延遲或停用 JavaScript |
System.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}");
}
}
Imports System
Imports System.IO
Imports Microsoft.Extensions.Logging
Public Class PdfDebugger
Private ReadOnly _logger As ILogger(Of PdfDebugger)
Public Sub New(logger As ILogger(Of PdfDebugger))
_logger = logger
End Sub
Public Sub EnableDebugging(renderer As ChromePdfRenderer)
' 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")
End Sub
Public Sub SaveDebugHtml(html As String, fileName As String)
' Save HTML for inspection
Dim debugPath = Path.Combine("debug", $"{fileName}_{DateTime.Now:yyyyMMdd_HHmmss}.html")
Directory.CreateDirectory("debug")
File.WriteAllText(debugPath, html)
_logger.LogDebug($"Debug HTML saved to: {debugPath}")
End Sub
End Class
本地部署最佳實踐
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>
所需依賴項
請確保部署伺服器上已安裝下列元件:
- .NET 運行時- 版本 6.0 或更高版本
- Visual C++ 可再發行元件套件- 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
效能調校
// 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
});
}
Imports IronPdf
Imports Microsoft.Extensions.DependencyInjection
Public Sub ConfigureServices(services As IServiceCollection)
' Configure IronPDF for production
services.AddSingleton(Of ChromePdfRenderer)(Function(provider)
Dim 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
End Function)
' Configure memory cache for generated PDFs
services.AddMemoryCache(Sub(options)
options.SizeLimit = 100000000 ' 100 MB cache
End Sub)
End Sub
最佳實務總結
1.始終釋放 PdfDocument 物件以防止記憶體洩漏 2.重用 ChromePdfRenderer 實例以提高效能 3.實現適當的錯誤處理並進行詳細日誌記錄
- 在產生 PDF 之前,先對所有使用者輸入進行清理。 5.使用 async/await 模式以提高可擴展性 6.配置合適的 JavaScript 渲染超時時間 7.壓縮影像以減小檔案大小
- 當內容為靜態文件時,快取產生的 PDF 檔案。 9.監控批次期間的記憶體使用情況
- 部署前使用類似生產環境的資料量進行測試
立即改造您的 ASP.NET 應用程式
現在您已經全面了解如何使用 IronPDF 在 ASP.NET Core 中使用 C# 產生 PDF。 從基本的 Razor 視圖轉換到具有效能優化的高級批量處理,您可以輕鬆實現與 Chrome 中看到的內容完全一致的專業 PDF 生成。
您已解鎖的主要成就:
- 使用 IronPDF 的 Chrome 引擎進行像素級完美渲染,消除從 HTML 頁面、網頁等建立 PDF 時出現的格式錯誤。
- 提供可直接用於生產的模板,採用 Razor 視圖,可產生熟悉且易於維護的 PDF 文件 *企業級錯誤處理*和記憶體管理,確保大規模可靠運行 優化批次處理,**並行生成,可處理數千份文檔 *專業安全功能,採用 256 位元加密技術保護敏感文件。
立即開始建立專業 PDF
!{--01001100010010010100001001010010010000010101001001011001010 111110100011101000101010101010001011111010100110101010001000001 010100100101010001000101010001000101111101010111010010010101010 001001000010111110101000001010101000010010000101111101010000010 1001001001111010001000101010101000011010101010001011111010101000101001001001001010101010001010010010010010100001010101010101 010101011000010101000100010101001110010001000101010001000101111101000010010011000100111110100010010011000100111100
準備好在 ASP.NET Core 應用程式中實作 PDF 生成功能了嗎? 立即下載 IronPDF 的免費試用版,體驗專業 PDF 生成功能的強大功能,30 天內無浮水印、無限。 憑藉全面的文件、快速回應的工程支援和 30 天退款保證,您可以自信地建立可用於生產的 PDF 解決方案。
旅程必備資源
- 📚 查看完整的 API 文檔,以了解詳細的方法參考
- 👥 加入開發者社區,獲取即時協助
- 🚀購買商業許可證,起價為 $999,用於生產部署
使用 IronPDF 實現企業級 PDF 生成功能,讓您的 ASP.NET Core 應用程式煥然一新,立即開始建置吧!
常見問題解答
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 無需特別配置。



