How to Build a Blazor WebAssembly PDF Annotation Viewer with Dynamsoft

Dynamsoft Document Viewer SDK provides a comprehensive set of APIs for viewing and annotating PDFs and images in web applications. By integrating the SDK into your Blazor web app, you can create a powerful document management system with advanced features such as PDF rendering, page navigation, image quality enhancement, and document saving. This article will guide you through the process of building a Blazor web app for PDF viewing and annotation using the Dynamsoft Document Viewer SDK.

What you’ll build: A Blazor WebAssembly app that loads, renders, and annotates PDFs in the browser and optionally captures documents from a live camera — all powered by the Dynamsoft Document Viewer SDK with C#/JavaScript interop.

Key Takeaways

  • The Dynamsoft Document Viewer SDK provides ready-to-use EditViewer, CaptureViewer, and PerspectiveViewer components that can be embedded in Blazor WebAssembly via JavaScript interop with minimal boilerplate.
  • PDF rendering, page navigation, image filtering, and full annotation tools (highlights, stamps, text boxes) are enabled by a single npm package (dynamsoft-document-viewer).
  • Page lifecycle in Blazor (OnAfterRenderAsync) is the correct hook for initializing JavaScript-based viewers — calling JS interop before the DOM is ready will silently fail.
  • The same viewer components work on both desktop and mobile browsers; the SDK’s isMobile() helper selects the appropriate responsive UI config automatically.

Common Developer Questions

  • How do I add PDF annotation tools to a Blazor WebAssembly app?
  • How do I call JavaScript from C# in Blazor to initialize a document viewer?
  • How do I scan a document with a camera and save it as a PDF in Blazor WebAssembly?

Blazor Document PDF Viewer Demo Video

Online Demo

https://yushulx.me/blazor-barcode-mrz-document-scanner/

Prerequisites

  • Dynamsoft Document Viewer: This package offers JavaScript APIs for viewing and annotating a wide range of document formats, including PDFs and images like JPEG, PNG, TIFF, and BMP. Key features include PDF rendering, page navigation, image quality enhancement, and document saving capabilities. You can find the SDK on npm.

  • Dynamsoft Capture Vision Trial License: The trial license provides access to all features of the Dynamsoft Capture Vision Bundle, including Barcode, MRZ, Document recognition, and more, for a period of 30 days. Get a 30-day free trial license for Dynamsoft Document Viewer.

Step 1: Set Up Blazor WebAssembly with Dynamsoft Document Viewer

  1. Create a Blazor WebAssembly Project: Start a new Blazor WebAssembly project using Visual Studio or Visual Studio Code.
  2. Add the Dynamsoft Document Viewer Script and CSS: To enable the Dynamsoft Document Viewer, include its script and CSS in the wwwroot/index.html file:
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/ddv.css">
     <script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/ddv.js"></script>
    

    Note: Including the CSS file is essential to ensure that the highly customized UI components of the viewer render correctly.

  3. Create a JavaScript File for C# and JavaScript Interop: Add a JavaScript file named jsInterop.js in the wwwroot folder and include it in the index.html:
     <script src="jsInterop.js"></script>
    
  4. Set the License: In jsInterop.js, define a setLicense function that sets the license key and configures the necessary resource paths:
     window.jsFunctions = {
         setLicense: async function setLicense(license) {
             try {
                 Dynamsoft.DDV.Core.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/engine";
                 Dynamsoft.License.LicenseManager.initLicense(license, true);
                 Dynamsoft.DDV.setProcessingHandler("imageFilter", new Dynamsoft.DDV.ImageFilter());
                 await Dynamsoft.DDV.Core.init();
    
             } catch (e) {
                 console.log(e);
                 return false;
             }
            
             return true;
         }
     };
    
  5. Update the Blazor Page to Enable SDK Activation: In the Pages/Home.razor file, add the following HTML and C# code to allow users to activate the SDK using a valid license key:

     @page "/"
     @inject IJSRuntime JSRuntime
        
     <PageTitle>Home</PageTitle>
        
     <p>Click <a href="https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform" target="_blank">here</a> to obtain a Dynamsoft Capture Vision Trial License.</p>
        
     <EditForm Model="@this">
         <InputText @bind-Value="LicenseKey" placeholder="Enter your license key" />
         <button type="button" class="btn btn-primary" @onclick="SetLicenseKey">Activate the SDK</button>
     </EditForm>
        
     @code {
         Boolean initialized = false;
        
         private string LicenseKey = "LICENSE-KEY";
        
         private async Task SetLicenseKey()
         {
             initialized = await JSRuntime.InvokeAsync<Boolean>("jsFunctions.setLicense", LicenseKey);
             StateHasChanged();
         }
     }
    

Step 2: Build the PDF Viewer and Annotation Razor Page

Setting up a document viewer with the Dynamsoft Document Viewer SDK can be accomplished quickly by following the sample code provided in the SDK’s README file. Below are the steps to create a PDF viewer in just a few minutes.

  1. Create a Document Viewer Component: Create a new Razor component called DocumentViewer.razor in the Pages folder and add it to your navigation menu in NavMenu.razor:
     <div class="nav-item px-3">
         <NavLink class="nav-link" href="documentviewer">
             <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Document Viewer
         </NavLink>
     </div>
    
  2. Implement the Document Viewer in DocumentViewer.razor: Add the following code in DocumentViewer.razor to set up the document viewer:

     @page "/documentviewer"
     @inject IJSRuntime JSRuntime
        
     @if (isLoading)
     {
         <div id="loading-indicator" class="loading-indicator">
             <div class="spinner"></div>
         </div>
     }
        
     <div id="@containerId" class="container"></div>
        
     @code {
         private Boolean isLoading = true;
         private String containerId = "document_viewer";
         protected override async Task OnAfterRenderAsync(bool firstRender)
         {
             if (firstRender)
             {
                 await JSRuntime.InvokeVoidAsync("jsFunctions.initDocumentViewer", containerId);
                 isLoading = false;
                 StateHasChanged();
             }
         }
     }
    
    

    Explanation

    • @page "/documentviewer" sets the route for the Razor component.
    • OnAfterRenderAsync() calls the JavaScript function initDocumentViewer when the page loads to initialize the document viewer.
    • The loading indicator is displayed until the document viewer is fully initialized.
  3. Add JavaScript Functions in jsInterop.js: Add the corresponding JavaScript functions in jsInterop.js:
     window.jsFunctions = {
         ...
         initDocumentViewer: async function (containerId) {
             if (!isInitialized) {
                 alert("Please set the license first.");
                 return;
             }
             try {
                 let config = Dynamsoft.DDV.getDefaultUiConfig("editViewer", { includeAnnotationSet: true });
                 let editViewer = new Dynamsoft.DDV.EditViewer({
                     container: containerId,
                     uiConfig: config,
                 });
             }
             catch (e) {
                 alert(e);
             }
         },
     };
    

    Explanation

    • initDocumentViewer() initializes the document viewer by binding it to the specified container and using the default UI configuration provided by the SDK.
    • The EditViewer instance is set up with tools for viewing and annotating PDFs and images, allowing developers to achieve full functionality with minimal code.

Step 3: Add a Camera-Based Document Scanner Page

Implementing a document scanner in Blazor is straightforward, especially if you leverage the existing .NET MAUI Blazor Hybrid App with the Dynamsoft Document Viewer SDK. You can find the source code on GitHub. Here’s how to migrate the code to your Blazor WebAssembly project:

  1. Create a Document Scanner Component: Create a new Razor component called DocumentScanner.razor in the Pages folder and add it to the navigation menu in NavMenu.razor:
     <div class="nav-item px-3">
         <NavLink class="nav-link" href="documentscanner">
             <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Document Scanner
         </NavLink>
     </div>
    
  2. Implement the Document Scanner in DocumentScanner.razor: Add the following code in DocumentScanner.razor to set up the document scanner functionality:
     @page "/documentscanner"
     @inject IJSRuntime JSRuntime
        
     @if (isLoading)
     {
         <div id="loading-indicator" class="loading-indicator">
             <div class="spinner"></div>
         </div>
     }
        
     <div id="@containerId" class="container"></div>
        
     @code {
         private Boolean isLoading = true;
         private String containerId = "document_scanner";
         protected override async Task OnAfterRenderAsync(bool firstRender)
         {
             if (firstRender)
             {
                 await JSRuntime.InvokeVoidAsync("jsFunctions.initDocumentScanner", containerId);
                 isLoading = false;
                 StateHasChanged();
             }
         }
     }
    

    Explanation

    • @page "/documentscanner" sets the route for the component.
    • OnAfterRenderAsync() initializes the document scanner by calling the JavaScript function initDocumentScanner when the page loads.
  3. Reuse JavaScript Code: Copy uiConfig.js and utils.js from the .NET MAUI Blazor Hybrid App to the Blazor WebAssembly project. These files contain essential UI configuration and utility functions for the document scanner. Next, add the document scanner creation code to jsInterop.js:
     window.jsFunctions = {
         ...
         initDocumentScanner: async function (containerId) {
             if (!isInitialized) {
                 alert("Please set the license first.");
                 return;
             }
        
             try {
                 await initDocDetectModule(Dynamsoft.DDV, Dynamsoft.CVR);
        
                 const captureViewer = new Dynamsoft.DDV.CaptureViewer({
                     container: containerId,
                     uiConfig: isMobile() ? mobileCaptureViewerUiConfig : pcCaptureViewerUiConfig,
                     viewerConfig: {
                         acceptedPolygonConfidence: 60,
                         enableAutoDetect: true,
                     }
                 });
        
                 await captureViewer.play({ resolution: [1920, 1080] });
        
                 captureViewer.on("showPerspectiveViewer", () => switchViewer(0, 1, 0));
                    
                 const perspectiveViewer = new Dynamsoft.DDV.PerspectiveViewer({
                     container: containerId,
                     groupUid: captureViewer.groupUid,
                     uiConfig: isMobile() ? mobilePerspectiveUiConfig : pcPerspectiveUiConfig,
                     viewerConfig: { scrollToLatest: true }
                 });
        
                 perspectiveViewer.hide();
                 perspectiveViewer.on("backToCaptureViewer", () => {
                     switchViewer(1, 0, 0);
                     captureViewer.play();
                 });
        
                 perspectiveViewer.on("showEditViewer", () => switchViewer(0, 0, 1));
        
                 const editViewer = new Dynamsoft.DDV.EditViewer({
                     container: containerId,
                     groupUid: captureViewer.groupUid,
                     uiConfig: isMobile() ? mobileEditViewerUiConfig : pcEditViewerUiConfig
                 });
        
                 editViewer.hide();
                 editViewer.on("backToPerspectiveViewer", () => switchViewer(0, 1, 0));
        
                 const switchViewer = (c, p, e) => {
                     captureViewer.hide();
                     perspectiveViewer.hide();
                     editViewer.hide();
                     if (c) captureViewer.show();
                     else captureViewer.stop();
                     if (p) perspectiveViewer.show();
                     if (e) editViewer.show();
                 };
                    
             }
             catch (e) {
                 console.log({
                     container: containerId,
                     uiConfig: isMobile() ? mobilePerspectiveUiConfig : pcPerspectiveUiConfig,
                     viewerConfig: { scrollToLatest: true }
                 });
                 alert(e);
             }
         }
     };
    

    Explanation

    • initDocumentScanner() initializes the document scanner by creating instances of CaptureViewer, PerspectiveViewer, and EditViewer. These viewers allow users to capture images, adjust perspectives, and edit images before saving them as PDFs.
    • The switchViewer() function manages transitions between the capture, perspective, and edit viewers based on user actions.

Step 4: Run and Verify the PDF Annotation Application

To run the application, press F5 in Visual Studio. This will launch the Blazor application in your default web browser, starting on the home page where you can activate the SDK by entering your Dynamsoft Capture Vision trial license key and clicking the Activate the SDK button.

  • Document Viewer: Load a PDF by clicking the add file button. You can navigate through pages, zoom, and annotate the document using various tools.

    Blazor WebAssembly document PDF viewer

  • Document Scanner: Use a connected camera to scan a document, then edit, crop, and save the scanned image as a PDF.

    Blazor WebAssembly document scanner

Common Issues & Edge Cases

  • Viewer renders blank or throws “container not found”: The Dynamsoft viewers must be initialized after the DOM element exists. Always place initialization inside OnAfterRenderAsync(bool firstRender) and guard with if (firstRender) — calling JS interop in OnInitializedAsync will fail because the container <div> has not yet been added to the DOM.
  • Camera not starting on HTTPS-required browsers: CaptureViewer.play() requires a secure context (HTTPS or localhost). When deploying to production, ensure your Blazor app is served over HTTPS; otherwise the browser will silently block camera access and no error will surface in the UI.
  • License initialization fails silently: Dynamsoft.License.LicenseManager.initLicense() is asynchronous internally. Always await Dynamsoft.DDV.Core.init() after calling initLicense before creating any viewer instance — skipping the await causes sporadic “not initialized” errors that are hard to reproduce.

Source Code

https://github.com/yushulx/blazor-barcode-mrz-document-scanner