Blazorise PDF

Build PDF document definitions from Razor components or from C# builder code, then generate a PDF file through the PDF generator service.

Installation

Install the Blazorise.Pdf package and register the PDF generator services in your application startup.

NuGet

Install extension from NuGet.
Install-Package Blazorise.Pdf

Imports

In your main _Imports.razor add:
@using Blazorise.Pdf

Services

Register the PDF generator once with your Blazorise services.
builder.Services
    .AddBlazorise()
    .AddBlazorisePdf();

Images and fonts

A PDF can use images and custom fonts that are already available to the application without any additional setup. For an image, set PdfImage.Source to a base64 data URL, such as data:image/png;base64,.... For a custom font, provide its contents through FontSource.Data, or use FontSource.FileName when the application can read the font from the local file system. Images can be JPEG or 8-bit, non-interlaced PNG files. Transparent PNGs remain transparent in the generated PDF.

URL loading is included in Blazorise.Pdf but is not enabled automatically. Enable it when an image or font is available through a URL instead of embedded data or a local file.

WebAssembly

Register HTTP resources in the client project. Downloads are made by the user's browser. Configuring the application base address allows relative URLs, such as images/logo.png, to resolve against the application URL.
using Blazorise.Pdf;

builder.Services
    .AddBlazorise()
    .AddBlazorisePdfHttpResources( httpClient =>
        httpClient.ConfigureHttpClient( client =>
        {
            client.BaseAddress = new Uri( builder.HostEnvironment.BaseAddress );
        } ) );

When a resource is hosted on another domain, that server must allow requests from the application through CORS. Otherwise, the browser blocks the download before the PDF generator can use it.

Server

Register HTTP resources in the server project. Downloads are made by the server, so applications that accept report URLs from users should limit which hosts are allowed and how large each resource can be.
using Blazorise.Pdf;

builder.Services
    .AddBlazorise()
    .AddBlazorisePdfHttpResources(
        httpClient => httpClient
            .ConfigureHttpClient( client =>
            {
                client.BaseAddress = new Uri( "https://cdn.example.com/" );
            } )
            .ConfigurePrimaryHttpMessageHandler( () => new SocketsHttpHandler
            {
                AllowAutoRedirect = false,
            } ),
        options =>
        {
            options.MaxResourceSize = 20 * 1024 * 1024;
            options.ResourceAllowed = uri => uri.Host.Equals( "cdn.example.com", StringComparison.OrdinalIgnoreCase );
        } );

The registration callback provides IHttpClientBuilder for configuring authentication, proxies, retry policies, and other HTTP behavior. Each resource is limited to 20 MB by default; change MaxResourceSize when a different limit is appropriate. Use ResourceAllowed to reject a URL before it is requested. Server registrations disable redirects by default. If you replace the primary HTTP handler, keep automatic redirects disabled so an allowed address cannot redirect to a blocked host. For user-controlled URLs, prefer an allow-list and reject loopback or private-network destinations.

A Blazor Web App may generate a PDF on the server during prerendering and in the browser after WebAssembly becomes interactive. If generation can run in both places, register the HTTP integration in both the server and client projects, using the appropriate HTTP configuration for each host. A failed download, invalid resource, unsupported file format, or rejected URL stops generation and reports the reason.

Images and fonts from URLs

After enabling URL loading, use an HTTP or HTTPS URL in PdfImage.Source or FontSource.Url. Each resource is downloaded once and reused if it appears more than once in the same PDF.
PdfDocumentDefinition document = PdfDocumentBuilder.Create()
    .Title( "Remote resources" )
    .AddFont( "Inter", FontSource.FromUrl( "fonts/Inter-Regular.ttf" ) )
    .Page( page =>
    {
        page.Image( "images/company-logo.png", 48, 48, 120, 48 );

        page.Text( "This font and image were resolved from URLs.", 48, 120, 360, 24 )
            .FontFamily( "Inter" );
    } )
    .Build();

PdfGenerationResult result = await PdfGenerator.Generate( document );

To load resources from another source, such as an embedded application asset or a storage SDK, implement IPdfResourceResolver and register it before AddBlazorisePdf. The resolver supplies the file contents; the PDF renderer still validates and embeds the image or font.

Examples

Declarative document

Use Razor components when a document template should be authored close to the UI layer.
@inject IPdfGenerator PdfGenerator

<PdfDocument @ref="document" Title="Invoice" PageSize="PdfPageSize.A4">
    <PdfPage>
        <PdfText Text="Invoice summary" X="48" Y="48" Width="260" Height="32" FontSize="22" Bold TextColor="#0d6efd" />
        <PdfText Text="Generated by Blazorise.Pdf declarative components." X="48" Y="82" Width="360" Height="18" FontSize="11" />
        <PdfRectangle X="420" Y="48" Width="96" Height="48" BorderColor="#0d6efd" BackgroundColor="#eef5ff" />
        <PdfText Text="BLZ" X="444" Y="64" Width="48" Height="20" FontSize="18" Bold TextAlignment="TextAlignment.Center" TextColor="#0d6efd" />
        <PdfLine X="48" Y="122" Width="468" Height="0" BorderColor="#222222" />
        <PdfTable X="48" Y="152" Width="468" Height="96">
            <PdfTableRow Height="24">
                <PdfTableCell Width="120">
                    <PdfText Text="Number" Bold />
                </PdfTableCell>
                <PdfTableCell Width="228">
                    <PdfText Text="Description" Bold />
                </PdfTableCell>
                <PdfTableCell Width="120">
                    <PdfText Text="Total" Bold TextAlignment="TextAlignment.End" />
                </PdfTableCell>
            </PdfTableRow>
            <PdfTableRow Height="24">
                <PdfTableCell Width="120">
                    <PdfText Text="INV-1001" />
                </PdfTableCell>
                <PdfTableCell Width="228">
                    <PdfText Text="Northwind Traders" />
                </PdfTableCell>
                <PdfTableCell Width="120">
                    <PdfText Text="$1,240.50" TextAlignment="TextAlignment.End" />
                </PdfTableCell>
            </PdfTableRow>
        </PdfTable>
    </PdfPage>
</PdfDocument>

@if ( pdfSource is not null )
{
    <PdfViewerContainer Height="Height.Rem(35)">
        <PdfViewerToolbar />
        <PdfViewer Source="@pdfSource" />
    </PdfViewerContainer>
}
@code {
    private PdfDocument document;

    private string pdfSource;

    private bool generated;

    protected override async Task OnAfterRenderAsync( bool firstRender )
    {
        if ( !generated && document?.Definition is not null )
        {
            generated = true;

            PdfGenerationResult result = await PdfGenerator.GenerateAsync( document.Definition, new()
            {
                FileName = "declarative-invoice.pdf",
            } );

            pdfSource = BuildPdfSource( result.Content );
            await InvokeAsync( StateHasChanged );
        }
    }

    private static string BuildPdfSource( byte[] content )
        => content is null || content.Length == 0
            ? null
            : $"data:application/pdf;base64,{Convert.ToBase64String( content )}";
}

Imperative builder

Use the builder API when a document is produced from an engine, service, or another component such as Reporting.
@inject IPdfGenerator PdfGenerator

<Div Flex="Flex.Row" Gap="Gap.Is2" Margin="Margin.Is3.FromBottom">
    <Button Color="Color.Primary" Clicked="@Build">Build PDF</Button>
</Div>

@if ( pdfSource is not null )
{
    <PdfViewerContainer Height="Height.Rem(35)">
        <PdfViewerToolbar />
        <PdfViewer Source="@pdfSource" />
    </PdfViewerContainer>
}
@code {
    private string pdfSource;

    private async Task Build()
    {
        PdfDocumentDefinition document = BuildInvoice();

        PdfGenerationResult result = await PdfGenerator.GenerateAsync( document, new()
        {
            FileName = "builder-invoice.pdf",
        } );

        pdfSource = BuildPdfSource( result.Content );
    }

    private static PdfDocumentDefinition BuildInvoice()
    {
        return PdfDocumentBuilder.Create()
            .Title( "Invoice" )
            .PageSetup( PdfPageSize.A4 )
            .Page( page =>
            {
                page.Text( "Invoice summary", 48, 48, 260, 32 )
                    .FontSize( 22 )
                    .Bold()
                    .TextColor( "#0d6efd" );

                page.Text( "Generated with PdfDocumentBuilder.", 48, 82, 320, 18 )
                    .FontSize( 11 );

                page.Rectangle( 420, 48, 96, 48 )
                    .BorderColor( "#0d6efd" )
                    .BackgroundColor( "#eef5ff" );

                page.Text( "BLZ", 444, 64, 48, 20 )
                    .FontSize( 18 )
                    .Bold()
                    .TextAlignment( TextAlignment.Center )
                    .TextColor( "#0d6efd" );
            } )
            .Build();
    }

    private static string BuildPdfSource( byte[] content )
        => content is null || content.Length == 0
            ? null
            : $"data:application/pdf;base64,{Convert.ToBase64String( content )}";
}

File generation

Inject IPdfGenerator, receive progress callbacks while the document is generated, and use the resulting PDF bytes.
@inject IPdfGenerator PdfGenerator

<Div Flex="Flex.Row" Gap="Gap.Is2" Margin="Margin.Is3.FromBottom">
    <Button Color="Color.Primary" Clicked="@Generate">Generate PDF</Button>
</Div>

@if ( generationProgress is not null )
{
    <Div Margin="Margin.Is3.FromBottom">
        <Span TextSize="TextSize.Small">@GenerationStatus</Span>
        <Progress Value="@((int)generationProgress.Percentage)" Size="Size.Small" />
    </Div>
}

@if ( pdfSource is not null )
{
    <PdfViewerContainer Height="Height.Rem(35)">
        <PdfViewerToolbar />
        <PdfViewer Source="@pdfSource" />
    </PdfViewerContainer>
}
@code {
    private string pdfSource;

    private PdfGenerationProgress generationProgress;

    private string GenerationStatus => generationProgress.Stage switch
    {
        PdfGenerationStage.PreparingResources => "Preparing resources",
        PdfGenerationStage.RenderingPages => $"Rendering page {generationProgress.CompletedPages} of {generationProgress.TotalPages}",
        PdfGenerationStage.WritingDocument => "Writing PDF document",
        PdfGenerationStage.Completed => "PDF generation completed",
        _ => string.Empty,
    };

    private async Task Generate()
    {
        generationProgress = null;

        PdfDocumentDefinition document = PdfDocumentBuilder.Create()
            .Title( "Invoice" )
            .Page( page =>
            {
                page.Text( "Invoice summary", 48, 48, 260, 32 )
                    .FontSize( 22 )
                    .Bold();
            } )
            .Build();

        PdfGenerationResult result = await PdfGenerator.GenerateAsync( document, new()
        {
            FileName = "invoice.pdf",
            Progress = OnGenerationProgress,
        } );

        pdfSource = BuildPdfSource( result.Content );
    }

    private async Task OnGenerationProgress( PdfGenerationProgress progress )
    {
        generationProgress = progress;

        await InvokeAsync( StateHasChanged );
    }

    private static string BuildPdfSource( byte[] content )
        => content is null || content.Length == 0
            ? null
            : $"data:application/pdf;base64,{Convert.ToBase64String( content )}";
}

Best Practices

Definition validation

PDF generation validates and normalizes a working copy, so the supplied document and generation options are not changed. Recoverable invalid values use safe defaults or are omitted. Inspect PdfGenerationResult.Diagnostics to see what was normalized during in-memory generation.

Large documents and cancellation

Calling GenerateAsync(document) returns the completed PDF as a byte array. When the PDF should go directly to a file, response body, or another writable stream, call GenerateToStreamAsync(document, stream) instead. The stream method avoids creating an additional byte array for the completed document and leaves the supplied stream open.

The built-in renderer still keeps the PDF object definitions, rendered page content, and resolved images and fonts in memory until generation finishes. It is intended for previews and bounded small-to-medium documents, not unbounded document workloads. Use the limits on PdfGenerationOptions to control accepted document and resource sizes. Applications that require constant-memory generation can provide a custom IPdfRenderProvider.

Pass a cancellation token to stop validation, resource loading and decoding, font parsing, page rendering, or output writing. If cancellation occurs while writing to a stream, discard or reset that stream because it can contain an incomplete PDF.

On this page