푸터 콘텐츠로 바로가기
IRONPDF 사용

How to Build a Centralized PDF Generation Service with ASP.NET Core and IronPDF

Build a production-ready .NET PDF API using ASP.NET Core and IronPDF to centralize PDF generation logic, enabling consistent document creation across your applications with RESTful endpoints for HTML-to-PDF conversion, merging, watermarking, and dynamic template processing.

When working with modern applications, .NET developers often need to build a centralized PDF generation service. Whether you're generating invoices, reports, certificates, or contracts, having a dedicated .NET PDF API improves your PDF generation workflow. How does it help? It provides consistency, maintainability, and scalability across your desktop and web applications. Managing document content, PDF pages, and PDF form fields becomes straightforward.

In this tutorial, you'll learn how to build a production-ready PDF API using ASP.NET Core and IronPDF, a powerful .NET PDF library. We'll create RESTful endpoints that generate PDFs from HTML, merge documents, add watermarks, and handle various real-world PDF scenarios in your Web API.

Why Build a Dedicated PDF API?

Architecture diagram showing client apps (web, desktop, mobile) sending HTML content, URLs, or dynamic data to a PDF Controller/Service API layer, which outputs PDF documents.

Before diving into the code, let's understand why creating a dedicated PDF API makes sense:

  • Centralized Logic: All PDF generation logic lives in one place, simplifying maintenance and updates.
  • Microservice Architecture: Perfect for service-oriented architectures where different apps need PDF capabilities.
  • Performance Optimization: Easier to scale and optimize a dedicated service for large PDFs, multiple pages, and dynamic data using async operations and performance techniques.
  • Language Agnostic: Any client application can consume the API regardless of programming language.
  • Consistent Output: Ensures all PDFs across your organization maintain consistent layout, formatting, and content.

Ready to start building? Download IronPDF's free trial and follow along with this tutorial to programmatically create PDF files in your .NET Framework projects.

What Makes IronPDF the Complete .NET PDF Library?

IronPDF for .NET homepage showing C# code examples for converting HTML to PDF with features like HTML rendering, file saving, and NuGet installation command

IronPDF stands out as the premier PDF library for .NET developers, offering a comprehensive set of features that make PDF generation in Web API projects straightforward and reliable. Built on a Chrome rendering engine, it ensures pixel-perfect HTML-to-PDF conversions in just a few lines of code while maintaining all styling, JavaScript execution, and responsive layouts.

Key capabilities that make IronPDF ideal for .NET PDF API development:

How to Set Up Your PDF Document API Project?

Let's start by creating a new ASP.NET Core Web API project and installing the necessary packages.

What Are the Prerequisites?

  • .NET 6.0 SDK or later
  • Visual Studio 2022 or Visual Studio Code
  • Postman or similar API testing tool for testing your PDF REST API

How Do I Create the Project?

First, let's create the project where we'll build our PDF generation tool.

dotnet new webapi -n PdfApiService
cd PdfApiService
dotnet new webapi -n PdfApiService
cd PdfApiService
SHELL

How Do I Install IronPDF?

Next, add IronPDF to your project via NuGet:

dotnet add package IronPDF
dotnet add package IronPDF
SHELL

Or, using the NuGet Package Manager Console in Visual Studio:

Install-Package IronPDF

For advanced installation options, including platform-specific packages, Docker setup, or Linux configurations, check the IronPDF installation documentation.

What Project Structure Should I Use?

Good C# development requires maintaining a clean and well-structured project folder. For example:

Visual Studio Solution Explorer showing the folder structure of a .NET PDF API Service project with Controllers, Models, and Services directories

How to Create Your First PDF Endpoint?

Let's build a simple endpoint that converts HTML to PDF format. First, create the service interface and implementation:

How Do I Create the PDF Service?

First, add the following to your IPdfService.cs file:

public interface IPdfService
{
    byte[] GeneratePdfFromHtml(string htmlContent);
    byte[] GeneratePdfFromUrl(string url);
}
public interface IPdfService
{
    byte[] GeneratePdfFromHtml(string htmlContent);
    byte[] GeneratePdfFromUrl(string url);
}
$vbLabelText   $csharpLabel

In the PdfService.cs file, add this:

using IronPdf;
public class PdfService : IPdfService
{
    private readonly ChromePdfRenderer _renderer;
    public PdfService()
    {
        _renderer = new ChromePdfRenderer();
        // Configure rendering options for optimal PDF generation in .NET
        _renderer.RenderingOptions.MarginTop = 20;
        _renderer.RenderingOptions.MarginBottom = 20;
        _renderer.RenderingOptions.PrintHtmlBackgrounds = true;
    }
    public byte[] GeneratePdfFromHtml(string htmlContent)
    {
        // Generate PDF from HTML using the .NET PDF API
        var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    }
    public byte[] GeneratePdfFromUrl(string url)
    {
        // Convert URL to PDF in the REST API
        var pdf = _renderer.RenderUrlAsPdf(url);
        return pdf.BinaryData;
    }
}
using IronPdf;
public class PdfService : IPdfService
{
    private readonly ChromePdfRenderer _renderer;
    public PdfService()
    {
        _renderer = new ChromePdfRenderer();
        // Configure rendering options for optimal PDF generation in .NET
        _renderer.RenderingOptions.MarginTop = 20;
        _renderer.RenderingOptions.MarginBottom = 20;
        _renderer.RenderingOptions.PrintHtmlBackgrounds = true;
    }
    public byte[] GeneratePdfFromHtml(string htmlContent)
    {
        // Generate PDF from HTML using the .NET PDF API
        var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    }
    public byte[] GeneratePdfFromUrl(string url)
    {
        // Convert URL to PDF in the REST API
        var pdf = _renderer.RenderUrlAsPdf(url);
        return pdf.BinaryData;
    }
}
$vbLabelText   $csharpLabel

The PdfService handles converting HTML into PDF. Using IronPDF's ChromePdfRenderer, this class configures defaults like page margins and background rendering for professional results. For advanced rendering configurations, explore IronPDF's rendering options.

When the controller passes raw HTML, the service renders it into a high-quality PDF and returns the byte data for download. It also converts entire web pages directly into PDFs using URL to PDF conversion.

How Do I Create the Controller?

Now create the controller for your API. This provides an endpoint that generates PDF files from HTML and lets you download and save PDF documents to your system.

// Controllers/PdfController.cs
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
    private readonly IPdfService _pdfService;
    public PdfController(IPdfService pdfService)
    {
        _pdfService = pdfService;
    }
    [HttpPost("html-to-pdf")]
    public IActionResult ConvertHtmlToPdf([FromBody] HtmlRequest request)
    {
        try
        {
            var pdfBytes = _pdfService.GeneratePdfFromHtml(request.HtmlContent);
            // Return as downloadable file
            return File(pdfBytes, "application/pdf", "document.pdf");
        }
        catch (Exception ex)
        {
            return BadRequest($"Error generating PDF: {ex.Message}");
        }
    }
}
// Controllers/PdfController.cs
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
    private readonly IPdfService _pdfService;
    public PdfController(IPdfService pdfService)
    {
        _pdfService = pdfService;
    }
    [HttpPost("html-to-pdf")]
    public IActionResult ConvertHtmlToPdf([FromBody] HtmlRequest request)
    {
        try
        {
            var pdfBytes = _pdfService.GeneratePdfFromHtml(request.HtmlContent);
            // Return as downloadable file
            return File(pdfBytes, "application/pdf", "document.pdf");
        }
        catch (Exception ex)
        {
            return BadRequest($"Error generating PDF: {ex.Message}");
        }
    }
}
$vbLabelText   $csharpLabel

Then, in the HtmlRequest.cs file, add this:

// Models/HtmlRequest.cs
public class HtmlRequest
{
    public string HtmlContent { get; set; }
    public string FileName { get; set; } = "document.pdf";
}
// Models/HtmlRequest.cs
public class HtmlRequest
{
    public string HtmlContent { get; set; }
    public string FileName { get; set; } = "document.pdf";
}
$vbLabelText   $csharpLabel

This sets up an API endpoint that converts HTML into a downloadable PDF. When someone sends HTML to api/pdf/html-to-pdf, the PdfController delegates conversion to the service.

Once created, the controller returns the PDF as a downloadable file. The request uses the HtmlRequest model, containing the HTML and an optional filename. This makes it easy for clients to send HTML and receive a polished PDF.

How Do I Register Services?

Update your Program.cs to register the PDF service:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register PDF service
builder.Services.AddSingleton<IPdfService, PdfService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Register PDF service
builder.Services.AddSingleton<IPdfService, PdfService>();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
$vbLabelText   $csharpLabel

How to Handle Different Response Types?

Your API should support different ways of returning PDFs based on client needs:

[HttpPost("generate")]
public IActionResult GeneratePdf([FromBody] PdfRequest request)
{
    var pdfBytes = _pdfService.GeneratePdfFromHtml(request.HtmlContent);
    switch (request.ResponseType?.ToLower())
    {
        case "base64":
            return Ok(new
            {
                data = Convert.ToBase64String(pdfBytes),
                filename = request.FileName
            });
        case "inline":
            return File(pdfBytes, "application/pdf");
        default: // download
            return File(pdfBytes, "application/pdf", request.FileName);
    }
}
[HttpPost("generate")]
public IActionResult GeneratePdf([FromBody] PdfRequest request)
{
    var pdfBytes = _pdfService.GeneratePdfFromHtml(request.HtmlContent);
    switch (request.ResponseType?.ToLower())
    {
        case "base64":
            return Ok(new
            {
                data = Convert.ToBase64String(pdfBytes),
                filename = request.FileName
            });
        case "inline":
            return File(pdfBytes, "application/pdf");
        default: // download
            return File(pdfBytes, "application/pdf", request.FileName);
    }
}
$vbLabelText   $csharpLabel

This adds a flexible PDF generation endpoint. Instead of forcing downloads, the GeneratePdf method lets clients choose how they receive results: as a download, inline in browser, or Base64 encoded for API use.

The PdfRequest model extends HtmlRequest with a ResponseType option. This gives users control over PDF delivery, making the API more versatile. For handling PDFs in memory without file system access, see IronPDF's memory stream documentation.

When we run our program, we'll see this output on Swagger:

Swagger UI documentation showing PdfApiService endpoints with three POST methods for PDF operations: html-to-pdf, generate, and url-to-pdf, along with request schemas.

How to Implement Common PDF Operations?

Let's expand our service to handle various PDF generation scenarios:

How Do I Convert URLs to PDF?

[HttpPost("url-to-pdf")]
public async Task<IActionResult> ConvertUrlToPdf([FromBody] UrlRequest request)
{
    try
    {
        var pdfBytes = await Task.Run(() =>
            _pdfService.GeneratePdfFromUrl(request.Url));
        return File(pdfBytes, "application/pdf",
            $"{request.FileName ?? "website"}.pdf");
    }
    catch (Exception ex)
    {
        return BadRequest($"Failed to convert URL: {ex.Message}");
    }
}
public class UrlRequest
{
    public string Url { get; set; }
    public string FileName { get; set; }
}
[HttpPost("url-to-pdf")]
public async Task<IActionResult> ConvertUrlToPdf([FromBody] UrlRequest request)
{
    try
    {
        var pdfBytes = await Task.Run(() =>
            _pdfService.GeneratePdfFromUrl(request.Url));
        return File(pdfBytes, "application/pdf",
            $"{request.FileName ?? "website"}.pdf");
    }
    catch (Exception ex)
    {
        return BadRequest($"Failed to convert URL: {ex.Message}");
    }
}
public class UrlRequest
{
    public string Url { get; set; }
    public string FileName { get; set; }
}
$vbLabelText   $csharpLabel

This endpoint converts URLs into downloadable PDFs. When a POST request hits /api/pdf/url-to-pdf, the controller uses _pdfService to convert the URL into PDF bytes in the background, then returns them for download. If conversion fails, it responds with a clear error message. For websites with authentication, check out IronPDF's login documentation.

Let's try using the URL "https://www.apple.com/nz" and test the POST request. Below is the output we obtained:

What Does the Output Look Like?

Apple New Zealand website homepage showing multiple product sections including iPhone 16, MacBook Air, Apple Watch, and AirPods with 'Learn more' and 'Buy' buttons - demonstrating URL to PDF conversion

How Do I Add Custom Watermarks?

public byte[] AddWatermarkFromFile(string filePath, string watermarkText)
{
    // Load PDF directly from file
    var pdf = PdfDocument.FromFile(filePath);
    pdf.ApplyWatermark(
        $"<h1 style='color:red;font-size:72px;'>{watermarkText}</h1>",
        75,
        IronPdf.Editing.VerticalAlignment.Middle,
        IronPdf.Editing.HorizontalAlignment.Center
    );
    return pdf.BinaryData;
}
public byte[] AddWatermarkFromFile(string filePath, string watermarkText)
{
    // Load PDF directly from file
    var pdf = PdfDocument.FromFile(filePath);
    pdf.ApplyWatermark(
        $"<h1 style='color:red;font-size:72px;'>{watermarkText}</h1>",
        75,
        IronPdf.Editing.VerticalAlignment.Middle,
        IronPdf.Editing.HorizontalAlignment.Center
    );
    return pdf.BinaryData;
}
$vbLabelText   $csharpLabel

This manually loads a local file for testing. You can adjust this so your PDF API generates a PDF, then applies custom watermarks easily. For advanced watermarking options, including image watermarks and custom positioning, see the watermarking guide.

What Does the Watermark Output Look Like?

A secure document with 'CONFIDENTIAL' watermark displayed diagonally across a blank page, with Document ID: BA811648DCE1FF2AAA55E7CE shown at the top

How to Add Dynamic Data with Templates?

For real-world applications, you'll often need to generate PDFs from templates with dynamic data:

[HttpPost("from-template")]
public IActionResult GenerateFromTemplate([FromBody] TemplateRequest request)
{
    // Simple template replacement
    var html = request.Template;
    foreach (var item in request.Data)
    {
        html = html.Replace($"{{{{{item.Key}}}}}", item.Value);
    }
    var pdfBytes = _pdfService.GeneratePdfFromHtml(html);
    return File(pdfBytes, "application/pdf", request.FileName);
}
public class TemplateRequest
{
    public string Template { get; set; }
    public Dictionary<string, string> Data { get; set; }
    public string FileName { get; set; } = "document.pdf";
}
[HttpPost("from-template")]
public IActionResult GenerateFromTemplate([FromBody] TemplateRequest request)
{
    // Simple template replacement
    var html = request.Template;
    foreach (var item in request.Data)
    {
        html = html.Replace($"{{{{{item.Key}}}}}", item.Value);
    }
    var pdfBytes = _pdfService.GeneratePdfFromHtml(html);
    return File(pdfBytes, "application/pdf", request.FileName);
}
public class TemplateRequest
{
    public string Template { get; set; }
    public Dictionary<string, string> Data { get; set; }
    public string FileName { get; set; } = "document.pdf";
}
$vbLabelText   $csharpLabel

For advanced template scenarios with Razor, Handlebars, or other engines, check out IronPDF's HTML to PDF documentation. You can also explore CSHTML to PDF conversion for MVC applications and Razor to PDF for Blazor applications. For headless Razor rendering, see the CSHTML headless guide.

How to Optimize Performance?

When building a production PDF API, performance is crucial. Here are key optimization strategies:

Why Use Async Operations?

Use asynchronous coding when your projects involve I/O operations. This helps especially when your PDF content comes from external resources like:

  • Downloading HTML pages (RenderUrlAsPdf)
  • Fetching images, CSS, or fonts over HTTP
  • Reading/writing files to disk or cloud storage

These operations can block threads, but async prevents API threads from waiting idle. For comprehensive async PDF generation patterns, see the async PDF generation guide.

Example:

public async Task<byte[]> GeneratePdfFromHtmlAsync(string htmlContent)
{
    return await Task.Run(() =>
    {
        var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    });
}
public async Task<byte[]> GeneratePdfFromHtmlAsync(string htmlContent)
{
    return await Task.Run(() =>
    {
        var pdf = _renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    });
}
$vbLabelText   $csharpLabel

For parallel PDF generation scenarios, explore multi-threading and parallel processing techniques.

What Rendering Options Should I Configure?

Configure IronPDF for optimal performance:

_renderer.RenderingOptions.EnableJavaScript = false; // If JS not needed
_renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print;
_renderer.RenderingOptions.RenderDelay = 0; // Remove if no JS
_renderer.RenderingOptions.Timeout = 30; // Set reasonable timeout
_renderer.RenderingOptions.EnableJavaScript = false; // If JS not needed
_renderer.RenderingOptions.CssMediaType = PdfCssMediaType.Print;
_renderer.RenderingOptions.RenderDelay = 0; // Remove if no JS
_renderer.RenderingOptions.Timeout = 30; // Set reasonable timeout
$vbLabelText   $csharpLabel

For comprehensive rendering configuration options, including viewport settings, custom paper sizes, and page orientation, see the rendering options documentation.

How to Secure Your PDF API?

Security is essential for any production API. Here's a simple API key authentication approach:

// Middleware/ApiKeyMiddleware.cs
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string ApiKeyHeader = "X-API-Key";
    public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(ApiKeyHeader, out var apiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key required");
            return;
        }
        // Validate API key (in production, check against database)
        var validApiKey = context.RequestServices
            .GetRequiredService<IConfiguration>()["ApiKey"];
        if (apiKey != validApiKey)
        {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsync("Invalid API Key");
            return;
        }
        await _next(context);
    }
}
// In Program.cs
app.UseMiddleware<ApiKeyMiddleware>();
// Middleware/ApiKeyMiddleware.cs
public class ApiKeyMiddleware
{
    private readonly RequestDelegate _next;
    private const string ApiKeyHeader = "X-API-Key";
    public ApiKeyMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(ApiKeyHeader, out var apiKey))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("API Key required");
            return;
        }
        // Validate API key (in production, check against database)
        var validApiKey = context.RequestServices
            .GetRequiredService<IConfiguration>()["ApiKey"];
        if (apiKey != validApiKey)
        {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsync("Invalid API Key");
            return;
        }
        await _next(context);
    }
}
// In Program.cs
app.UseMiddleware<ApiKeyMiddleware>();
$vbLabelText   $csharpLabel

For advanced authentication scenarios, consider:

For PDF-specific security, implement password protection, digital signatures, and PDF sanitization to remove potentially malicious content.

How to Build a Real-World Invoice Generation API?

Let's build a practical invoice generation endpoint demonstrating a complete implementation. This example shows how a production .NET PDF API generates professional invoices with dynamic data.

지금 바로 IronPDF으로 시작하세요.
green arrow pointer

First, create a new file in your Models folder. Here, I've called mine Invoice.cs. Then add the following code:

public class Invoice
{
    public string InvoiceNumber { get; set; }
    public DateTime Date { get; set; }
    public string CustomerName { get; set; }
    public string CustomerAddress { get; set; }
    public List<InvoiceItem> Items { get; set; }
    public decimal Tax { 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;
}
public class Invoice
{
    public string InvoiceNumber { get; set; }
    public DateTime Date { get; set; }
    public string CustomerName { get; set; }
    public string CustomerAddress { get; set; }
    public List<InvoiceItem> Items { get; set; }
    public decimal Tax { get; set; }
}
public class InvoiceItem
{
    public string Description { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal Total => Quantity * UnitPrice;
}
$vbLabelText   $csharpLabel

Next, create a new service file for the invoice generator. In your Services folder, add the following code. I created a file called InvoiceService.cs. This code handles the styling and layout of your Invoice PDF:

public class InvoiceService
{
    private readonly ChromePdfRenderer _renderer;
    public InvoiceService()
    {
        _renderer = new ChromePdfRenderer();
        _renderer.RenderingOptions.MarginTop = 10;
        _renderer.RenderingOptions.MarginBottom = 10;
        _renderer.RenderingOptions.PrintHtmlBackgrounds = true;
    }
    public byte[] GenerateInvoice(Invoice invoice)
    {
        var html = BuildInvoiceHtml(invoice);
        // Add footer with page numbers
        _renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
        {
            MaxHeight = 15,
            HtmlFragment = "<center><i>{page} of {total-pages}</i></center>",
            DrawDividerLine = true
        };
        var pdf = _renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
    private string BuildInvoiceHtml(Invoice invoice)
    {
        var subtotal = invoice.Items.Sum(i => i.Total);
        var taxAmount = subtotal * (invoice.Tax / 100);
        var total = subtotal + taxAmount;
        var itemsHtml = string.Join("", invoice.Items.Select(item =>
            $@"<tr>
                <td>{item.Description}</td>
                <td class='text-center'>{item.Quantity}</td>
                <td class='text-right'>${item.UnitPrice:F2}</td>
                <td class='text-right'>${item.Total:F2}</td>
            </tr>"));
        return $@"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: Arial, sans-serif; }}
                .invoice-header {{
                    background-color: #f8f9fa;
                    padding: 20px;
                    margin-bottom: 20px;
                }}
                table {{
                    width: 100%;
                    border-collapse: collapse;
                }}
                th, td {{
                    padding: 10px;
                    border-bottom: 1px solid #ddd;
                }}
                th {{
                    background-color: #007bff;
                    color: white;
                }}
                .text-right {{ text-align: right; }}
                .text-center {{ text-align: center; }}
                .total-section {{
                    margin-top: 20px;
                    text-align: right;
                }}
            </style>
        </head>
        <body>
            <div class='invoice-header'>
                <h1>Invoice #{invoice.InvoiceNumber}</h1>
                <p>Date: {invoice.Date:yyyy-MM-dd}</p>
            </div>
            <div>
                <h3>Bill To:</h3>
                <p>{invoice.CustomerName}<br/>{invoice.CustomerAddress}</p>
            </div>
            <table>
                <thead>
                    <tr>
                        <th>Description</th>
                        <th>Quantity</th>
                        <th>Unit Price</th>
                        <th>Total</th>
                    </tr>
                </thead>
                <tbody>
                    {itemsHtml}
                </tbody>
            </table>
            <div class='total-section'>
                <p>Subtotal: ${subtotal:F2}</p>
                <p>Tax ({invoice.Tax}%): ${taxAmount:F2}</p>
                <h3>Total: ${total:F2}</h3>
            </div>
        </body>
        </html>";
    }
}
public class InvoiceService
{
    private readonly ChromePdfRenderer _renderer;
    public InvoiceService()
    {
        _renderer = new ChromePdfRenderer();
        _renderer.RenderingOptions.MarginTop = 10;
        _renderer.RenderingOptions.MarginBottom = 10;
        _renderer.RenderingOptions.PrintHtmlBackgrounds = true;
    }
    public byte[] GenerateInvoice(Invoice invoice)
    {
        var html = BuildInvoiceHtml(invoice);
        // Add footer with page numbers
        _renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
        {
            MaxHeight = 15,
            HtmlFragment = "<center><i>{page} of {total-pages}</i></center>",
            DrawDividerLine = true
        };
        var pdf = _renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
    private string BuildInvoiceHtml(Invoice invoice)
    {
        var subtotal = invoice.Items.Sum(i => i.Total);
        var taxAmount = subtotal * (invoice.Tax / 100);
        var total = subtotal + taxAmount;
        var itemsHtml = string.Join("", invoice.Items.Select(item =>
            $@"<tr>
                <td>{item.Description}</td>
                <td class='text-center'>{item.Quantity}</td>
                <td class='text-right'>${item.UnitPrice:F2}</td>
                <td class='text-right'>${item.Total:F2}</td>
            </tr>"));
        return $@"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: Arial, sans-serif; }}
                .invoice-header {{
                    background-color: #f8f9fa;
                    padding: 20px;
                    margin-bottom: 20px;
                }}
                table {{
                    width: 100%;
                    border-collapse: collapse;
                }}
                th, td {{
                    padding: 10px;
                    border-bottom: 1px solid #ddd;
                }}
                th {{
                    background-color: #007bff;
                    color: white;
                }}
                .text-right {{ text-align: right; }}
                .text-center {{ text-align: center; }}
                .total-section {{
                    margin-top: 20px;
                    text-align: right;
                }}
            </style>
        </head>
        <body>
            <div class='invoice-header'>
                <h1>Invoice #{invoice.InvoiceNumber}</h1>
                <p>Date: {invoice.Date:yyyy-MM-dd}</p>
            </div>
            <div>
                <h3>Bill To:</h3>
                <p>{invoice.CustomerName}<br/>{invoice.CustomerAddress}</p>
            </div>
            <table>
                <thead>
                    <tr>
                        <th>Description</th>
                        <th>Quantity</th>
                        <th>Unit Price</th>
                        <th>Total</th>
                    </tr>
                </thead>
                <tbody>
                    {itemsHtml}
                </tbody>
            </table>
            <div class='total-section'>
                <p>Subtotal: ${subtotal:F2}</p>
                <p>Tax ({invoice.Tax}%): ${taxAmount:F2}</p>
                <h3>Total: ${total:F2}</h3>
            </div>
        </body>
        </html>";
    }
}
$vbLabelText   $csharpLabel

Finally, create a new Controller to access and create invoices using the API:

[ApiController]
[Route("api/[controller]")]
public class InvoiceController : ControllerBase
{
    private readonly InvoiceService _invoiceService;
    public InvoiceController(InvoiceService invoiceService)
    {
        _invoiceService = invoiceService;
    }
    [HttpPost("generate")]
    public IActionResult GenerateInvoice([FromBody] Invoice invoice)
    {
        try
        {
            var pdfBytes = _invoiceService.GenerateInvoice(invoice);
            var fileName = $"Invoice_{invoice.InvoiceNumber}.pdf";
            return File(pdfBytes, "application/pdf", fileName);
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Error generating invoice: {ex.Message}");
        }
    }
}
[ApiController]
[Route("api/[controller]")]
public class InvoiceController : ControllerBase
{
    private readonly InvoiceService _invoiceService;
    public InvoiceController(InvoiceService invoiceService)
    {
        _invoiceService = invoiceService;
    }
    [HttpPost("generate")]
    public IActionResult GenerateInvoice([FromBody] Invoice invoice)
    {
        try
        {
            var pdfBytes = _invoiceService.GenerateInvoice(invoice);
            var fileName = $"Invoice_{invoice.InvoiceNumber}.pdf";
            return File(pdfBytes, "application/pdf", fileName);
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Error generating invoice: {ex.Message}");
        }
    }
}
$vbLabelText   $csharpLabel

For advanced invoice features, consider adding barcodes, QR codes, page numbers, and custom headers/footers. You can also implement PDF/A compliance for long-term archival or integrate with electronic signature workflows.

What Does the Invoice Output Look Like?

PDF invoice document showing Invoice #INV-1023 dated 2025-08-22, with a single line item for 'Misc Object' totaling $495.00 including tax

What Are the Container Deployment Considerations?

While this tutorial focuses on local development, here's a brief overview of containerizing your PDF API:

How Do I Create a Basic Dockerfile?

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PdfApiService.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# IronPDF requires additional dependencies on Linux
RUN apt-get update && apt-get install -y \
    libgdiplus \
    libc6-dev \
    libx11-dev \
    && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["dotnet", "PdfApiService.dll"]

For detailed deployment guides for your .NET PDF API, see:

What Are the Error Handling Best Practices?

For fault-tolerant programs, implement a global error handler for consistent error responses:

// Middleware/ErrorHandlingMiddleware.cs
public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;
    public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An error occurred processing request {Path}", context.Request.Path);
            await HandleExceptionAsync(context, ex);
        }
    }
    private static async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = ex switch
        {
            ArgumentNullException => 400,
            UnauthorizedAccessException => 401,
            _ => 500
        };
        var response = new
        {
            error = "An error occurred processing your request",
            message = ex.Message,
            statusCode = context.Response.StatusCode
        };
        await context.Response.WriteAsync(JsonSerializer.Serialize(response));
    }
}
// Middleware/ErrorHandlingMiddleware.cs
public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;
    public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An error occurred processing request {Path}", context.Request.Path);
            await HandleExceptionAsync(context, ex);
        }
    }
    private static async Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = ex switch
        {
            ArgumentNullException => 400,
            UnauthorizedAccessException => 401,
            _ => 500
        };
        var response = new
        {
            error = "An error occurred processing your request",
            message = ex.Message,
            statusCode = context.Response.StatusCode
        };
        await context.Response.WriteAsync(JsonSerializer.Serialize(response));
    }
}
$vbLabelText   $csharpLabel

For specific IronPDF troubleshooting scenarios, refer to:

Ready to Build Your Production .NET PDF API?

You've now built a robust .NET PDF API using ASP.NET Core and IronPDF that handles various document generation scenarios. This REST API provides a solid foundation for centralized PDF operations in your applications.

Key takeaways:

  • IronPDF makes PDF generation in Web API projects straightforward with Chrome-based rendering.
  • Easily edit existing PDFs with IronPDF's advanced editing tools.
  • RESTful design principles ensure your PDF API is intuitive and maintainable.
  • Proper error handling and security measures are essential for production.
  • Performance optimization through async operations and caching improves scalability.
  • Full support for desktop and web applications with scalable document solutions.

IronPDF lets developers create PDFs, save PDF files, and convert HTML efficiently, making it the essential PDF API for modern .NET Framework applications.

What Are the Next Steps?

Ready to implement IronPDF in your production .NET PDF API? Here are your next actions:

  1. Start your free trial - Test IronPDF with full functionality in your development environment.
  2. Explore advanced features - Check out digital signatures, PDF forms, PDF/A compliance, metadata management, and other advanced PDF features.
  3. Scale with confidence - Review licensing options for your production API needs, including extensions and upgrades.

Build your .NET PDF API today and streamline document generation across your entire application ecosystem with IronPDF!

자주 묻는 질문

.NET 애플리케이션에서 IronPDF는 어떤 용도로 사용되나요?

IronPDF는 .NET 애플리케이션 내에서 PDF를 생성, 조작 및 변환하는 데 사용되므로 중앙 집중식 PDF 서비스를 만드는 데 이상적입니다.

IronPDF를 ASP.NET Core와 어떻게 통합할 수 있나요?

IronPDF NuGet 패키지를 설치하여 IronPDF를 ASP.NET Core와 통합할 수 있으므로 중앙 집중식 PDF 생성 서비스를 구축할 수 있습니다.

PDF 생성을 위한 IronPDF의 주요 기능은 무엇인가요?

IronPDF의 주요 기능으로는 HTML을 PDF로 변환, PDF 병합 및 분할, 머리글 및 바닥글 추가, 텍스트 및 이미지 추출 등이 있습니다.

IronPDF는 복잡한 PDF 레이아웃을 처리할 수 있나요?

예, IronPDF는 HTML 및 CSS 콘텐츠를 정확하게 렌더링된 PDF 문서로 변환할 수 있기 때문에 복잡한 PDF 레이아웃을 처리할 수 있습니다.

IronPDF로 PDF 생성을 사용자 지정할 수 있나요?

예, IronPDF를 사용하면 페이지 크기, 여백 설정, 워터마크 또는 주석 추가 등 PDF 생성 시 사용자 정의가 가능합니다.

IronPDF는 PDF 보안 기능을 지원하나요?

IronPDF는 생성된 PDF 문서를 보호하기 위해 비밀번호 보호 및 암호화와 같은 PDF 보안 기능을 지원합니다.

IronPDF는 어떤 형식을 PDF로 변환할 수 있나요?

IronPDF는 HTML, URL, ASPX 파일 등 다양한 형식을 PDF로 변환할 수 있어 다양한 사용 사례에 다용도로 활용할 수 있습니다.

IronPDF는 대규모 PDF 생성을 어떻게 처리하나요?

IronPDF는 성능에 최적화되어 있어 .NET 애플리케이션 내에서 대규모 PDF 생성 작업을 효율적으로 처리할 수 있습니다.

IronPDF를 클라우드 기반 애플리케이션에서 사용할 수 있나요?

예, IronPDF는 클라우드 기반 애플리케이션에서 사용할 수 있으며 확장 가능한 PDF 서비스를 위해 Azure 및 AWS와 같은 플랫폼에 배포를 지원합니다.

IronPDF는 어떤 버전의 .NET을 지원하나요?

IronPDF는 .NET Core 및 .NET Framework를 포함한 여러 .NET 버전을 지원하므로 다양한 프로젝트와의 호환성을 보장합니다.

커티스 차우
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.