ASP.NET에서 C#을 사용하여 PDF 생성하는 방법
방송 인보이스, 보고서, 인증서, 티켓을 만드는 현대 웹 애플리케이션에 있어 PDF를 프로그래밍 방식으로 생성하는 것은 중요한 요구 사항입니다. 픽셀 완벽한 렌더링과 Enterprise 기능을 갖춘 강력한 PDF 생성을 수행하려는 ASP.NET Core .NET 개발자라면, 맞는 곳에 계신 것입니다.
이 포괄적인 가이드는 IronPDF를 사용하여 PDF 파일을 생성하는 방법을 안내합니다. IronPDF는 강력한 .NET 라이브러리로, PDF 문서 생성을 쉽게 할 수 있게 합니다. 기본 설정부터 고급 일괄 처리까지 모든 것을 탐색할 것이며, 얼마나 효율적이고 쉽게 통합될 수 있는지를 보여줄 것입니다. 마무리할 때쯤이면 IronPDF를 사용하여 ASP.NET 앱에서 PDF 문서를 생성하는 강력한 PDF 변환기를 갖추게 될 것입니다.
ASP.NET Core에서 PDF를 생성하는 이유는?
서버 측 PDF 생성은 클라이언트 측 대안보다 상당한 이점을 제공합니다. 서버에서 PDF를 생성하면 모든 브라우저와 장치에서 일관된 출력을 보장하며, 클라이언트 쪽 자원의 의존성을 없애고, 민감한 데이터에 대해 더 나은 통제력을 유지할 수 있습니다. HTML을 PDF로 변환하는 일반적인 비즈니스 시나리오에는 다음이 포함됩니다.
- 금융 문서: 인보이스, 명세서 및 거래 영수증
- 준수 보고서: 규제 제출 및 감사 문서
- 사용자 인증서: 교육 완료 및 전문 인증서
- 이벤트 티켓: QR 코드가 포함된 입장권 및 탑승권
- 데이터 내보내기: 분석 보고서 및 대시보드 스냅샷
또한, 서버 측 접근 방식은 PDF가 모든 브라우저와 운영 체제에서 일관되게 보이도록 합니다. 법적 및 금융 문서 측면에서 높이 평가되고 있습니다.
IronPDF가 PDF 생성에 어떻게 변화를 줍니까?
IronPDF는 .NET 생태계에서 두각을 나타내는 PDF 라이브러리로, 내부에는 완전한 Chrome 렌더링 엔진을 사용합니다. 즉, 귀하의 PDF 문서는 Google Chrome에서 보는 것처럼 정확하게 렌더링되며, 최신 CSS3, JavaScript 실행, 웹 글꼴이 완벽히 지원됩니다. 다른 라이브러리와 달리 IronPDF는 기존의 HTML, CSS, JavaScript 지식을 사용하여 ASP.NET Core 애플리케이션 내에서 PDF 생성 기능을 직접 추가할 수 있게 합니다.
개발을 변화시키는 주요 이점:
- Chrome 기반 렌더링으로 픽셀 완벽한 정확성을 제공하며, 간단한 몇 줄의 코드만으로 브라우저에서 보는 것을 HTML을 PDF로 변환할 때 일치시킵니다.
- 전체 HTML5, CSS3 및 JavaScript 지원,에는 최신 프레임워크인 Bootstrap을 포함하여
- 외부 종속성이나 명령 줄 도구 없이 자체 .NET 소개
- .NET 6/7/8, .NET Core 및 .NET Framework 4.6.2+을 지원하는 크로스 플랫폼 호환성을 갖춘 고급 설치 옵션을 제공합니다.
- 포괄적인 API를 통해 발생 후 조작, 포함 m/a href="/how-to/merge-or-split-pdfs/">병합, 워터마킹, 및 디지털 서명을 통해 귀하의 PDF 페이지를 조작할 수 있습니다.
!{--010011000100100101000010010100100100000101010010010110010101111101001110010101010001110100010101010100010111110100100101001110010100110101010001000001010011000100110001001100010111110100001001001100010011110100001101001011--}
ASP.NET Core 프로젝트 설정
PDF 생성에 구성된 새 ASP.NET Core MVC 애플리케이션을 만들어 보겠습니다. 적절한 종속성 주입과 오류 처리를 통해 프로덕션 준비 설정을 구축할 것입니다. 이는 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 Views에서 PDF 생성하기
Razor 뷰를 활용한 PDF 변환은 ASP.NET Core에서 새로운 PDF 문서를 생성하는 가장 강력한 접근 방식입니다. 이를 통해 친숙한 MVC 패턴, 강력하게 타입된 모델, Razor 구문을 사용하여 맞춤형 HTML 파일, 웹 페이지 및 기타 소스에서 동적 PDF를 생성할 수 있습니다. Razor Pages에 대한 Microsoft의 문서에 따르면, 이 접근 방식은 페이지 중심 시나리오에 대한 가장 깔끔한 관심사의 분리를 제공합니다.
데이터 모델 생성
먼저 일반 비즈니스 문서를 나타내는 포괄적인 모델을 생성합시다:
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 헤더 및 푸터를 송장에 추가하여 더욱 스타일리시하게 만들고 정말로 자신의 것으로 만들 수 있습니다.
헤더와 푸터를 추가하는 것에 대한 자세한 정보는 how-to 가이드를 참조하세요.
출력

고성능 배치 PDF 처리 구현 방법은?
수백 개 또는 수천 개의 PDF를 생성해야 합니까? 병렬 처리와 메모리를 효율적으로 관리하여 최적의 성능을 달성하는 방법이 여기에 있습니다. 완전한 작동 예제 다운로드를 통해 이를 액션에서 확인하세요.
여러 PDF를 효율적으로 생성해야 하는 애플리케이션에 대해, 비동기 및 멀티스레딩 기술을 활용한 최적화된 배치 처리 구현 방법이 여기에 있습니다. 이 접근 방식은 C#을 사용하여 ASP.NET Core에서 PDF를 생성할 때 최적의 성능을 위해 Microsoft의 병렬 프로그래밍 모범 사례를 따르고 있습니다:
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 CType(ProcessedCount, Double) / 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
위의 도우미 클래스에서 PDF에 대한 다양한 보안 옵션을 설정하기 위해 SecurityLevel 열거형을 사용합니다. 예를 들어, 낮은 SecurityLevel는 기본 보호를 적용하지만 FullPrintRights 및 PDF에서 복사하고 붙여넣기를 허용하며, 속성 AllowUserCopyPasteContent를 true로 설정합니다. 이 설정은 IronPDF를 통해 PDF 클래스 속성을 조정하여 활성화됩니다. 보안에 대한 전체 속성과 옵션 목록은 how-to 가이드를 참고하세요.
오류 처리 및 문제 해결
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가 너무 오래 걸립니다 | RenderDelay 늘리거나 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 서버 - 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
모범 사례 요약
- PdfDocument 개체를 항상 삭제하여 메모리 누수를 방지
- ChromePdfRenderer 인스턴스를 재사용하여 성능 향상
- 상세한 기록과 함께 적절한 오류 처리 구현
- PDF 생성 전에 모든 사용자 입력을 정리
- 더 나은 확장성을 위해 async/await 패턴 사용
- JavaScript 렌더링을 위한 적절한 타임아웃 구성
- 파일 크기 축소를 위해 이미지 압축
- 생성된 PDF 캐시 - 콘텐츠가 정적일 때
- 배치 처리 중 메모리 사용 모니터링
- 배포 전에 실제와 유사한 데이터 볼륨으로 테스트
오늘 당신의 ASP.NET 애플리케이션을 변형해 보세요
이제 IronPDF를 사용하여 ASP.NET Core에서 PDF를 생성하는 데 대한 종합적인 이해를 갖게 되었습니다. 기본적인 Razor 뷰 변환에서 성능 최적화를 통한 고급 배치 처리에 이르기까지, Chrome에서 보는 것과 정확히 일치하는 전문적인 PDF 생성을 구현할 준비가 되었습니다.
달성한 주요 업적:
- 픽셀 완벽한 렌더링 - HTML 페이지, 웹 페이지 등에서 PDF를 생성할 때 포맷 문제를 제거하는 IronPDF의 Chrome 엔진 사용
- 생산 준비된 템플릿 - 익숙하고 유지할 수 있는 PDF 생성을 제공하는 Razor 뷰
- 기업 수준의 오류 처리 및 메모리 관리 - 신뢰할 수 있는 대규모 작업을 위해
- 최적화된 배치 처리 - 병렬 생성으로 수천 개의 문서를 처리
- 전문적인 보안 기능 - 256비트 암호화로 민감한 문서 보호
이제 전문 PDF를 생성하기 시작하세요
!{--01001100010010010100001001010010010000010101001001011001010111110100011101000101010101000101111101010011010101000100000101010100100101010001000101111101010111010010010101010000010111110101010000010 10010010011110100010001010101010000110101010001011111010100010100100100100100101000001010011000101111101000101010110000101010001010100111001000100010001010100111110100001001001001100010011110100001001001100010011110100001101001011--}
ASP.NET Core 애플리케이션에 PDF 생성 구현을 할 준비가 되셨나요? IronPDF의 무료 체험판을 다운로드하여 30일 동안 워터마크나 제한 없이 전문 PDF 생성의 강력함을 경험하세요. 포괄적인 문서, 응답성 있는 엔지니어링 지원, 30일 환불 보장과 함께 생산 준비된 PDF 솔루션을 자신 있게 구축할 수 있습니다.
여정을 위한 필수 리소스
- 📚 완전한 API 문서 탐색으로 자세한 메서드 참조
- 👥 개발자 커뮤니티에 참여하여 실시간 지원 받기
- 🚀 상업적 라이선스 구매 $799에서 생산 배포 시작
Enterprise급 PDF 생성으로 ASP.NET Core 애플리케이션을 기대에 부응하도록 변형하여 오늘 IronPDF로 시작하세요!
자주 묻는 질문
IronPDF의 ASP.NET 애플리케이션에서의 주요 사용 용도는 무엇인가요?
IronPDF는 주로 PDF 문서를 생성, 편집 및 콘텐츠를 추출하는 데 사용되어 ASP.NET 애플리케이션에서 청구서, 보고서, 인증서 또는 티켓을 만드는 필수 도구입니다.
IronPDF는 픽셀 완벽한 PDF 렌더링을 어떻게 보장합니까?
IronPDF는 고급 렌더링 엔진과 Enterprise 기능을 사용하여 HTML, 이미지 또는 다른 문서 형식을 고품질 PDF로 정확하게 변환함으로써 픽셀 단위로 완벽한 렌더링을 보장합니다.
IronPDF는 ASP.NET Core 애플리케이션과 통합될 수 있나요?
네, IronPDF는 ASP.NET Core 애플리케이션과 원활하게 통합될 수 있으며, 다양한 PDF 작업을 효율적으로 처리할 수 있는 강력한 라이브러리를 개발자에게 제공합니다.
IronPDF를 사용하여 PDF를 생성할 때의 장점은 무엇인가요?
PDF 생성을 위한 IronPDF 사용은 사용의 용이성, 고품질 렌더링, 복잡한 문서 기능에 대한 지원, 애플리케이션 내부에서 PDF 작업을 자동화할 수 있는 능력과 같은 이점을 제공합니다.
IronPDF는 기존 PDF 문서 편집을 지원하나요?
네, IronPDF는 기존 PDF 문서의 편집을 지원하며, 개발자가 프로그래밍적으로 콘텐츠 수정, 주석 추가 및 PDF 메타데이터 업데이트를 할 수 있도록 합니다.
IronPDF는 Enterprise 수준의 PDF 문서를 생성하는 데 적합한가요?
IronPDF는 복잡한 문서 구조 지원 및 암호화 및 디지털 서명과 같은 보안 기능을 포함한 강력한 기능으로 인해 Enterprise 수준의 PDF 문서를 생성하는 데 이상적입니다.
IronPDF는 어떤 파일 형식을 PDF로 변환할 수 있나요?
IronPDF는 HTML, 이미지 및 다른 문서 유형을 포함 다양한 파일 형식을 PDF로 변환할 수 있어 다양한 데이터 소스와의 유연성과 호환성을 보장합니다.
IronPDF는 PDF 콘텐츠 추출을 어떻게 처리하나요?
IronPDF는 텍스트, 이미지 및 메타데이터를 추출하는 API를 제공하여 PDF 문서에서 데이터를 쉽게 검색하고 조작할 수 있도록 합니다.
IronPDF는 PDF 문서 워크플로를 자동화하는 데 사용할 수 있나요?
네, IronPDF는 배치 생성, 변환 및 PDF 파일의 배포와 같은 프로세스를 간소화하여 웹 애플리케이션에서 PDF 문서 워크플로를 자동화하는 데 사용할 수 있습니다.
IronPDF는 개발자를 위한 어떤 지원을 제공하나요?
IronPDF는 통합 및 문제 해결 지원을 위한 자세한 문서, 샘플 코드 및 고객 서비스와 같은 광범위한 지원을 개발자에게 제공합니다.
IronPDF는 .NET 10을 곧바로 지원하나요?
IronPDF는 .NET 10에 대한 추가 발표 지원을 제공하며, 2025년 11월에 예정된 .NET 10 릴리스와 이미 호환됩니다. 개발자는 특수 설정 없이 .NET 10 프로젝트에서 IronPDF를 사용할 수 있습니다.


