DEV Community

IronSoftware
IronSoftware

Posted on

Blazor PDF Generation in C# (Developer Guide)

Generating PDFs in Blazor requires understanding the client-server split. Blazor Server runs C# on the server, making PDF generation straightforward. Blazor WebAssembly needs an API endpoint since PDF libraries can't run in the browser.

// Blazor Server component
@page "/generate-pdf"
@using IronPdf
// Install via NuGet: Install-Package IronPdf

<button @onclick="GeneratePdf">Download Report</button>

@code {
    private async Task GeneratePdf()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf("<h1>Report</h1><p>Generated from Blazor</p>");

        // Download to client
        await DownloadFile("report.pdf", pdf.BinaryData);
    }
}
Enter fullscreen mode Exit fullscreen mode

Server-side Blazor has direct access to PDF libraries. WebAssembly needs a different approach.

What's the Difference Between Blazor Server and WebAssembly?

Feature Blazor Server Blazor WebAssembly
Where code runs Server Browser
PDF library access Direct Via API only
Startup time Fast Slower (download .NET runtime)
Server dependency Required Optional
Scalability Limited by server Better

For PDF generation:

  • Blazor Server: Call IronPDF directly in components
  • Blazor WebAssembly: Call an API endpoint that generates the PDF

How Do I Generate PDFs in Blazor Server?

Direct PDF generation in server-side code:

// Pages/Reports.razor
@page "/reports"
@using IronPdf
@inject IJSRuntime JS
// Install via NuGet: Install-Package IronPdf

<h3>Report Generator</h3>

<div class="form-group">
    <label>Report Title:</label>
    <input @bind="reportTitle" class="form-control" />
</div>

<button class="btn btn-primary" @onclick="GenerateReport">
    Generate PDF Report
</button>

@code {
    private string reportTitle = "Monthly Report";

    private async Task GenerateReport()
    {
        var html = $@"
            <html>
            <head>
                <style>
                    body {{ font-family: Arial; padding: 40px; }}
                    h1 {{ color: #333; }}
                    .date {{ color: #666; }}
                </style>
            </head>
            <body>
                <h1>{reportTitle}</h1>
                <p class='date'>Generated: {DateTime.Now:MMMM dd, yyyy}</p>
                <p>Report content goes here...</p>
            </body>
            </html>";

        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf(html);

        await JS.InvokeVoidAsync("downloadFile", "report.pdf", pdf.BinaryData);
    }
}
Enter fullscreen mode Exit fullscreen mode

Add JavaScript interop for downloads:

// wwwroot/js/download.js
window.downloadFile = (filename, byteArray) => {
    const blob = new Blob([new Uint8Array(byteArray)], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    link.click();
    URL.revokeObjectURL(url);
};
Enter fullscreen mode Exit fullscreen mode

How Do I Generate PDFs in Blazor WebAssembly?

WebAssembly can't run IronPDF directly. Use an API:

API Controller:

// Server/Controllers/PdfController.cs
using IronPdf;
using Microsoft.AspNetCore.Mvc;
// Install via NuGet: Install-Package IronPdf

[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
    [HttpPost("generate")]
    public IActionResult Generate([FromBody] PdfRequest request)
    {
        var renderer = new ChromePdfRenderer();

        var html = $@"
            <html>
            <body>
                <h1>{request.Title}</h1>
                <p>{request.Content}</p>
            </body>
            </html>";

        var pdf = renderer.RenderHtmlAsPdf(html);

        return File(pdf.BinaryData, "application/pdf", "document.pdf");
    }
}

public class PdfRequest
{
    public string Title { get; set; }
    public string Content { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

WebAssembly Component:

// Client/Pages/Reports.razor
@page "/reports"
@inject HttpClient Http
@inject IJSRuntime JS

<button @onclick="GeneratePdf">Download PDF</button>

@code {
    private async Task GeneratePdf()
    {
        var request = new { Title = "Report", Content = "Generated from WASM" };

        var response = await Http.PostAsJsonAsync("api/pdf/generate", request);

        if (response.IsSuccessStatusCode)
        {
            var bytes = await response.Content.ReadAsByteArrayAsync();
            await JS.InvokeVoidAsync("downloadFile", "report.pdf", bytes);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The browser calls the API, the server generates the PDF, and returns it as bytes.

How Do I Render Razor Components to PDF?

Convert your existing Blazor components to PDF:

// Services/RazorPdfService.cs
using IronPdf;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
// Install via NuGet: Install-Package IronPdf

public class RazorPdfService
{
    private readonly HtmlRenderer _htmlRenderer;

    public RazorPdfService(IServiceProvider serviceProvider)
    {
        _htmlRenderer = new HtmlRenderer(serviceProvider, serviceProvider.GetRequiredService<ILoggerFactory>());
    }

    public async Task<byte[]> RenderComponentToPdf<TComponent>(Dictionary<string, object> parameters = null)
        where TComponent : IComponent
    {
        var html = await _htmlRenderer.Dispatcher.InvokeAsync(async () =>
        {
            var output = await _htmlRenderer.RenderComponentAsync<TComponent>(
                ParameterView.FromDictionary(parameters ?? new Dictionary<string, object>()));
            return output.ToHtmlString();
        });

        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Register the service:

// Program.cs
builder.Services.AddScoped<RazorPdfService>();
Enter fullscreen mode Exit fullscreen mode

How Do I Create Invoice PDFs?

Complete invoice generation example:

// Pages/Invoices.razor
@page "/invoice/{InvoiceId:int}"
@using IronPdf
@inject IJSRuntime JS
@inject InvoiceService InvoiceService
// Install via NuGet: Install-Package IronPdf

<h3>Invoice #@InvoiceId</h3>
<button @onclick="DownloadInvoice">Download PDF</button>

@code {
    [Parameter]
    public int InvoiceId { get; set; }

    private async Task DownloadInvoice()
    {
        var invoice = await InvoiceService.GetInvoice(InvoiceId);

        var lineItems = string.Join("",
            invoice.Items.Select(i => $@"
                <tr>
                    <td>{i.Description}</td>
                    <td>{i.Quantity}</td>
                    <td>${i.UnitPrice:F2}</td>
                    <td>${i.Total:F2}</td>
                </tr>"));

        var html = $@"
            <html>
            <head>
                <style>
                    body {{ font-family: Arial; padding: 40px; }}
                    table {{ width: 100%; border-collapse: collapse; }}
                    th, td {{ border: 1px solid #ddd; padding: 10px; }}
                    th {{ background: #f5f5f5; }}
                    .total {{ font-size: 1.2em; font-weight: bold; }}
                </style>
            </head>
            <body>
                <h1>Invoice #{invoice.Number}</h1>
                <p>Date: {invoice.Date:MMMM dd, yyyy}</p>
                <p>Customer: {invoice.CustomerName}</p>

                <table>
                    <tr>
                        <th>Description</th>
                        <th>Qty</th>
                        <th>Price</th>
                        <th>Total</th>
                    </tr>
                    {lineItems}
                </table>

                <p class='total'>Total: ${invoice.Total:F2}</p>
            </body>
            </html>";

        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.MarginTop = 20;
        renderer.RenderingOptions.MarginBottom = 20;

        var pdf = renderer.RenderHtmlAsPdf(html);
        await JS.InvokeVoidAsync("downloadFile", $"invoice-{InvoiceId}.pdf", pdf.BinaryData);
    }
}
Enter fullscreen mode Exit fullscreen mode

How Do I Add Headers and Footers?

Consistent branding across PDF pages:

@code {
    private async Task GenerateReportWithBranding()
    {
        var renderer = new ChromePdfRenderer();

        renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
        {
            HtmlFragment = @"
                <div style='text-align:center; font-size:10px;'>
                    Company Name | Confidential
                </div>",
            DrawDividerLine = true
        };

        renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
        {
            HtmlFragment = @"
                <div style='text-align:center; font-size:10px;'>
                    Page {page} of {total-pages}
                </div>"
        };

        var pdf = renderer.RenderHtmlAsPdf(reportHtml);
        await JS.InvokeVoidAsync("downloadFile", "report.pdf", pdf.BinaryData);
    }
}
Enter fullscreen mode Exit fullscreen mode

How Do I Handle Large PDFs?

Async generation for long-running operations:

@page "/reports/large"
@using IronPdf
@inject IJSRuntime JS
// Install via NuGet: Install-Package IronPdf

<button @onclick="GenerateLargeReport" disabled="@isGenerating">
    @(isGenerating ? "Generating..." : "Generate Large Report")
</button>

@if (isGenerating)
{
    <div class="spinner-border" role="status">
        <span class="visually-hidden">Loading...</span>
    </div>
}

@code {
    private bool isGenerating = false;

    private async Task GenerateLargeReport()
    {
        isGenerating = true;
        StateHasChanged();

        try
        {
            // Generate on background thread
            var pdfBytes = await Task.Run(() =>
            {
                var renderer = new ChromePdfRenderer();

                // Generate large content
                var html = GenerateLargeHtml();

                var pdf = renderer.RenderHtmlAsPdf(html);
                return pdf.BinaryData;
            });

            await JS.InvokeVoidAsync("downloadFile", "large-report.pdf", pdfBytes);
        }
        finally
        {
            isGenerating = false;
            StateHasChanged();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Task.Run to avoid blocking the UI thread.

How Do I Use Dependency Injection?

Register IronPDF services properly:

// Program.cs
builder.Services.AddScoped<IPdfService, PdfService>();

// Services/PdfService.cs
using IronPdf;
// Install via NuGet: Install-Package IronPdf

public interface IPdfService
{
    byte[] GenerateFromHtml(string html);
    byte[] GenerateFromUrl(string url);
}

public class PdfService : IPdfService
{
    private readonly ChromePdfRenderer _renderer;

    public PdfService()
    {
        _renderer = new ChromePdfRenderer();
        _renderer.RenderingOptions.MarginTop = 20;
        _renderer.RenderingOptions.MarginBottom = 20;
    }

    public byte[] GenerateFromHtml(string html)
    {
        var pdf = _renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }

    public byte[] GenerateFromUrl(string url)
    {
        var pdf = _renderer.RenderUrlAsPdf(url);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Inject and use:

@inject IPdfService PdfService

@code {
    private async Task Generate()
    {
        var bytes = PdfService.GenerateFromHtml("<h1>Hello</h1>");
        await JS.InvokeVoidAsync("downloadFile", "doc.pdf", bytes);
    }
}
Enter fullscreen mode Exit fullscreen mode

How Do I Preview Before Download?

Display PDF in browser before downloading:

@page "/preview"
@using IronPdf
@inject IJSRuntime JS
// Install via NuGet: Install-Package IronPdf

<button @onclick="PreviewPdf">Preview PDF</button>

@if (!string.IsNullOrEmpty(pdfDataUrl))
{
    <iframe src="@pdfDataUrl" style="width:100%; height:600px;"></iframe>
    <button @onclick="Download">Download</button>
}

@code {
    private string pdfDataUrl;
    private byte[] pdfBytes;

    private async Task PreviewPdf()
    {
        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf("<h1>Preview</h1>");
        pdfBytes = pdf.BinaryData;

        // Create data URL for preview
        var base64 = Convert.ToBase64String(pdfBytes);
        pdfDataUrl = $"data:application/pdf;base64,{base64}";
    }

    private async Task Download()
    {
        await JS.InvokeVoidAsync("downloadFile", "document.pdf", pdfBytes);
    }
}
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Scenario Approach
Blazor Server Direct IronPDF calls in components
Blazor WebAssembly API endpoint that returns PDF bytes
File download JS interop with blob URL
Preview Embed base64 in iframe
Large files Use Task.Run for async

Blazor Server makes PDF generation simple—just call the library. WebAssembly needs an API layer but keeps your server doing the heavy lifting.

For more Blazor integration examples, see the IronPDF Blazor documentation.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)