Advanced Silent PDF Printing with Tray, Paper Size, Rotation & Pages Range Settings from Blazor
Product JSPrintManager for Blazor Published 07/08/2021 Updated 11/28/2024 Author Neodynamic
Overview
In this walkthrough, you'll learn how to silently print PDF files from Blazor directly to the client printer without displaying a print dialog. You'll be able to print PDF files to the Default client printer as well as to any other installed printer at the client machine with advanced settings like Tray Name, Paper Size, Print Rotation, Pages Range and more! This solution works with any browser on Windows OS like IE, Edge, Chrome, Firefox, Opera & Safari as well as on Linux, Raspberry Pi & Mac systems!
Try Blazor Server Online Demo...
Try Blazor WebAssembly Online Demo...
Follow up these steps
- Be sure you install in your dev machine JSPrintManager (JSPM) (Available for Windows, Linux, Raspberry Pi & Mac)
This small app must be installed on each client that will print from your website! - In your Blazor project...
- Add a NuGet reference to the JSPrintManager Razor Component
- Add the JSPrintManager service...
- Add the following statement at the top of your
Startup
orProgram
file
using Neodynamic.Blazor;
- For Blazor Server
Add the following line in theStartup's ConfigureServices
method
services.AddJSPrintManager();
For .NET 8+ you must use Interactive Server components and render mode, so add these settings:
builder.Services.AddRazorComponents() .AddInteractiveServerComponents();
app.MapRazorComponents<App>() .AddInteractiveServerRenderMode()
- For Blazor WebAssembly
Add the following line in theProgram's Main
method
builder.Services.AddJSPrintManager();
- For Blazor Server
- Add the following statement at the top of your
- Add the following statement in the
_Imports.razor
file
@using Neodynamic.Blazor
- Add a new Razor Page and copy/paste the following code. Please read the source code comments to understand the printing logic!
For .NET 8+ Blazor Server you must use Interactive Server render mode, so add these settings:
@page "/" @rendermode InteractiveServer @inject JSPrintManager JSPrintManager
@page "/" @inject JSPrintManager JSPrintManager @using System @using System.IO <div> <strong>JSPM </strong><span>WebSocket Status </span> @if (JSPrintManager.Status == JSPMWSStatus.Open) { <span class="badge badge-success"> <i class="fa fa-check" /> Open </span> } else if (JSPrintManager.Status == JSPMWSStatus.Closed) { <span class="badge badge-danger"> <i class="fa fa-exclamation-circle" /> Closed! </span> <div> <strong>JSPrintManager (JSPM) App</strong> is not installed or not running! <a href="https://neodynamic.com/downloads/jspm" target="_blank">Download JSPM Client App...</a> </div> } else if (JSPrintManager.Status == JSPMWSStatus.Blocked) { <span class="badge badge-warning"> <i class="fa fa-times-circle" /> This Website is Blocked! </span> } else if (JSPrintManager.Status == JSPMWSStatus.WaitingForUserResponse) { <span class="badge badge-warning"> <i class="fa fa-user-circle" /> Waiting for user response... </span> } </div> @if (JSPrintManager.Status == JSPMWSStatus.Open) { @if (JSPrintManager.PrintersInfo == null) { <hr /> <div class="spinner-border text-info" role="status"> <span class="sr-only">Please wait...</span> </div> <strong><em>Getting local printers...</em></strong> } else { <div class="row"> <div class="col-md-12"> <h2 class="text-center"> <i class="fa fa-file-pdf-o" /> Advanced PDF Printing </h2> <hr /> </div> </div> <EditForm Model="@MyCPJ"> <div class="row"> <div class="col-md-6"> <div class="bg-light"> <strong>Print Local PDF File</strong> <br /> <InputFile OnChange="@LoadFiles" class="form-control-file" accept=".pdf" /> <br /> @if (isLoading) { <p><em><small>Loading files...</small></em></p> <div class="progress"> <div class="progress-bar bg-info" role="progressbar" style="width: @(loadingStep)%;" aria-valuenow="@(loadingStep)" aria-valuemin="0" aria-valuemax="100">@(loadingStep)%</div> </div> } </div> </div> <div class="col-md-6"> <div class="bg-light"> <strong>Print PDF File from URL</strong> <br /> <div> <small> <strong>Predefined Sample:</strong> </small> <button type="button" class="btn btn-warning btn-sm" @onclick="@(() => SetFilePath("https://neodynamic.com/temp/mixed-page-orientation.pdf"))"> <i class="fa fa-arrow-circle-down" /> mixed-page-orientation.pdf </button> </div> <InputText Value="@MyFilePath" ValueExpression="@(() => MyFilePath)" ValueChanged="@SetFilePath" class="form-control form-control-sm text-monospace" /> </div> </div> </div> </EditForm> <div class="row"> <div class="col-md-12"> <br /> <div class="alert alert-info"> <strong>Target Printer & PDF Printing Settings</strong> </div> </div> </div> <div class="row"> <div class="col-md-3"> <label>Printer:</label> <EditForm Model="@MyPrinter"> <InputSelect Value="MyPrinter.PrinterName" ValueChanged="@((string s)=>PrinterChanged(s))" ValueExpression="@(()=>MyPrinter.PrinterName)" class="form-control form-control-sm"> @foreach (var p in JSPrintManager.PrintersInfo) { <option value="@p.Name">@p.Name</option> } </InputSelect> </EditForm> </div> <div class="col-md-3"> <label>Tray:</label> <EditForm Model="@MyPrinter"> <InputSelect @bind-Value="MyPrinter.TrayName" class="form-control form-control-sm"> @if (trays != null) { @foreach (var t in trays) { <option value="@t">@t</option> } } </InputSelect> </EditForm> </div> <div class="col-md-3"> <label>Paper:</label> <EditForm Model="@MyPrinter"> <InputSelect @bind-Value="MyPrinter.PaperName" class="form-control form-control-sm"> @if (papers != null) { @foreach (var p in papers) { <option value="@p">@p</option> } } </InputSelect> </EditForm> </div> <div class="col-md-3"> <label>Print Rotation (Clockwise):</label> <EditForm Model="@MyPdfFile"> <InputSelect @bind-Value="MyPdfFile.PrintRotation" class="form-control form-control-sm"> @foreach (var pr in Enum.GetValues(typeof(PrintRotation))) { <option value="@pr">@pr</option> } </InputSelect> </EditForm> </div> </div> <br /> <div class="row"> <div class="col-md-3"> <label>Pages Range: [e.g. 1,2,3,10-13]</label> <EditForm Model="@MyPdfFile"> <InputText @bind-Value="MyPdfFile.PrintRange" class="form-control form-control-sm" /> </EditForm> </div> <div class="col-md-3"> <EditForm Model="@MyPdfFile"> <label> Auto Center <InputCheckbox @bind-Value="MyPdfFile.AutoCenter" /> </label> </EditForm> <EditForm Model="@MyPdfFile"> <label> Auto Rotate <InputCheckbox @bind-Value="MyPdfFile.AutoRotate" /> </label> </EditForm> </div> <div class="col-md-3"> <label class="@(builtInDuplexSupport ? "" : "propDisabled")"> Use Driver Duplex Printing <input type="checkbox" @bind="useDriverDuplex" disabled="@(!builtInDuplexSupport)" /> </label> <EditForm Model="@MyPdfFile"> <label> Use Manual Duplex Printing <InputCheckbox @bind-Value="MyPdfFile.ManualDuplex" /> </label> </EditForm> </div> <div class="col-md-3"> <label>Page Sizing:</label> <EditForm Model="@MyPdfFile"> <InputSelect @bind-Value="MyPdfFile.PageSizing" class="form-control form-control-sm"> @foreach (var pr in Enum.GetValues(typeof(Sizing))) { <option value="@pr">@pr</option> } </InputSelect> </EditForm> </div> </div> <br /> <div class="row"> <div class="col-md-3"> <EditForm Model="@MyPdfFile"> <label> Print In Reverse Order <InputCheckbox @bind-Value="MyPdfFile.PrintInReverseOrder" /> </label> </EditForm> </div> <div class="col-md-3"> <EditForm Model="@MyPdfFile"> <label> Print Annotations <InputCheckbox @bind-Value="MyPdfFile.PrintAnnotations" /> </label> </EditForm> </div> <div class="col-md-3"> <EditForm Model="@MyPdfFile"> <label> Print As Grayscale <InputCheckbox @bind-Value="MyPdfFile.PrintAsGrayscale" /> </label> </EditForm> </div> <div class="col-md-3"> </div> </div> <div class="row"> <div class="col-md-12"> <br /> <div class="text-center"> <button class="btn btn-success btn-lg" @onclick="DoPrinting"> <i class="fa fa-print" /> Print Now... </button> </div> </div> </div> } } @code { protected override void OnAfterRender(bool firstRender) { if (firstRender) { // Handle OnGetPrinters event... JSPrintManager.OnGetPrintersInfo += () => { if (JSPrintManager.PrintersInfo != null && JSPrintManager.PrintersInfo.Length > 0) { // Display installed printers... StateHasChanged(); } else { Console.WriteLine("No printers found..."); } }; // Handle OnStatusChanged event to detect any WSS status change JSPrintManager.OnStatusChanged += () => { StateHasChanged(); // Status = Open means that JSPM Client App is up and running! if (JSPrintManager.Status == JSPMWSStatus.Open) { //Try getting local printers info... JSPrintManager.TryGetPrintersInfo("", PrinterIcon.Large); } }; // Start WebSocket comm JSPrintManager.Start(); } base.OnAfterRender(firstRender); } private string[] trays = null; private string[] papers = null; private bool builtInDuplexSupport = false; private bool useDriverDuplex = false; private void PrinterChanged(string printerName) { trays = JSPrintManager.PrintersInfo.FirstOrDefault(p => p.Name == printerName).Trays; papers = JSPrintManager.PrintersInfo.FirstOrDefault(p => p.Name == printerName).Papers; builtInDuplexSupport = JSPrintManager.PrintersInfo.FirstOrDefault(p => p.Name == printerName).Duplex; MyPrinter.PrinterName = printerName; } private ClientPrintJob MyCPJ { get; set; } = new(); private InstalledPrinter MyPrinter { get; set; } = new(); private PrintFilePDF MyPdfFile { get; set; } = new(); private void DoPrinting() { // Set built-in duplex printing if required if (builtInDuplexSupport) { MyPrinter.Duplex = useDriverDuplex ? Duplex.Default : Duplex.Simplex; } // set target printer MyCPJ.ClientPrinter = MyPrinter; // set pdf file source... if (FileFromUrl) { MyPdfFile.FileContentType = FileSourceType.URL; MyPdfFile.FileContent = MyFilePath; MyPdfFile.FileName = MyFilePath.Substring(MyFilePath.LastIndexOf('/') + 1); } else { foreach (var fileEntry in loadedFiles) { MyPdfFile.FileContentType = FileSourceType.Base64; MyPdfFile.FileContent = fileEntry.Value; MyPdfFile.FileName = fileEntry.Key; } } // Add the pdf file to the print job MyCPJ.Files.Clear(); MyCPJ.Files.Add(MyPdfFile); // Send job to the client! JSPrintManager.SendClientPrintJob(MyCPJ); } #region File Source Handling private bool FileFromUrl = false; private string MyFilePath { get; set; } private void SetFilePath(string filePath) { MyFilePath = filePath; FileFromUrl = true; } private Dictionary<string, byte[]> loadedFiles = new(); private int maxAllowedFiles = 1; private int maxSizeFile = 5000000; //5MB private bool isLoading = false; private int loadingStep = 0; private async Task LoadFiles(InputFileChangeEventArgs e) { isLoading = true; loadedFiles.Clear(); loadingStep = 0; var numOfFiles = Math.Min(e.FileCount, maxAllowedFiles); var i = 1; foreach (var file in e.GetMultipleFiles(maxAllowedFiles)) { try { await using MemoryStream ms = new(); await file.OpenReadStream(maxSizeFile).CopyToAsync(ms); loadedFiles.Add(file.Name, ms.ToArray()); loadingStep = (int)(((float)i / (float)numOfFiles) * 100f); StateHasChanged(); } catch (Exception ex) { } i++; } FileFromUrl = false; isLoading = false; } #endregion }
- That's it! Run your website and test it. Click on Print Now... to print the PDF file without print dialog. You can print it to the Default client printer or you can get a list of the installed printers available at the client machine.