How to Build a .NET Blazor Application for Scanning Barcode and QR Code

Blazor is a powerful framework by Microsoft that enables developers to build interactive web applications using C# instead of JavaScript, bringing the full power of .NET to the browser. While Blazor allows you to write most of your web app in C#, calling existing JavaScript APIs remains an inevitable part of enhancing Blazor functionalities, especially when integrating with powerful libraries like the Dynamsoft JavaScript Barcode SDK. In this tutorial, we will show you how to build a .NET Blazor application for scanning 1D/2D barcodes by leveraging the interop between C# and JavaScript.

Blazor Barcode Scanner Demo Video

Online Demo

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

Prerequisites

  • Dynamsoft Capture Vision Bundle: This bundle includes all Dynamsoft JavaScript imaging SDKs, designed to work seamlessly together. It is available on npm.

    Dynamsoft Capture Vision Bundle

  • Dynamsoft Capture Vision Trial License: A trial license supports all features (including Barcode, MRZ, Document recognition, and more) of the Dynamsoft Capture Vision Bundle for 30 days. You can apply for a trial license here.

Step 1: Integrate Dynamsoft Capture Vision Bundle into a Blazor WebAssembly Project

  1. Create a Blazor WebAssembly Project: Start a new Blazor WebAssembly project in Visual Studio or Visual Studio Code.
  2. Add the Capture Vision Bundle Script: In the wwwroot/index.html file, include the Dynamsoft Capture Vision Bundle script:
     <script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle"></script>
    
  3. Create a JavaScript File for Interop: Create a file named jsInterop.js in the wwwroot folder and include it in the index.html:
     <script src="jsInterop.js"></script>
    
  4. Configure JavaScript to Set the License: In jsInterop.js, define a setLicense function that specifies the resource paths for the dependencies and sets the license key:
     window.jsFunctions = {
         setLicense: async function setLicense(license) {
             try {
                 Dynamsoft.Core.CoreModule.engineResourcePaths = {
                     std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/",
                     dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/",
                     core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/",
                     license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/",
                     cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/",
                     dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/",
                     dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/",
                     dlr: "https://cdn.jsdelivr.net/npm/dynamsoft-label-recognizer@3.2.30/dist/",
                     dcp: "https://cdn.jsdelivr.net/npm/dynamsoft-code-parser@2.2.10/dist/",
                     ddn: "https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@2.2.10/dist/",
                 };
                 Dynamsoft.Core.CoreModule.loadWasm(["dbr"]);
                 Dynamsoft.License.LicenseManager.initLicense(license, true);
                 await initSDK();
             } catch (e) {
                 console.log(e);
                 return false;
             }
            
             return true;
         }
     };
    

    The loadWasm method preloads the WebAssembly module for the Dynamsoft Barcode Reader SDK. If you don’t load it ahead of time, the SDK will load it on-demand, which may cause a delay in the first barcode scanning.

  5. Update the Blazor Page to Activate the SDK: Modify the Pages/Home.razor file to include HTML and C# code that allows users to activate the SDK with 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: Implement a Barcode Reader Razor Page

  1. Create the Barcode Reader Component: Create a new Razor component called Reader.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="barcodereader">
             <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Barcode Reader
         </NavLink>
     </div>
    

    Ensure that the href attribute value (barcodereader) matches the path specified in the Razor component.

  2. Implement the Barcode Reader in Reader.razor: Add the following code in Reader.razor to set up the barcode reader functionality:

     @page "/barcodereader"
     @inject IJSRuntime JSRuntime
        
     <button @onclick="ReadBarcodes">Select an image</button>
     <p class="p-result">@result</p>
        
     <div id="imageview">
         <img id="@imageId" />
         <canvas id="@overlayId"></canvas>
     </div>
        
     @code {
         private String result = "";
         private DotNetObjectReference<Reader>? objRef;
         private String imageId = "image";
         private String overlayId = "overlay";
        
         protected override void OnInitialized()
         {
             objRef = DotNetObjectReference.Create(this);
         }
        
         protected override async Task OnAfterRenderAsync(bool firstRender)
         {
             if (firstRender)
             {
                 // Call the JavaScript function when the page is loaded
                 await JSRuntime.InvokeVoidAsync("jsFunctions.initReader");
             }
         }
        
        
         public async Task ReadBarcodes()
         {
             await JSRuntime.InvokeVoidAsync(
             "jsFunctions.selectFile", objRef, overlayId, imageId);
         }
        
         [JSInvokable]
         public void ReturnBarcodeResultsAsync(String text)
         {
             result = text;
             StateHasChanged();
         }
        
         public void Dispose()
         {
             objRef?.Dispose();
         }
     }
    

    Explanation

    • @page "/barcodereader" sets the route for the Razor component.
    • OnInitialized() creates a DotNetObjectReference, enabling JavaScript to call C# methods.
    • OnAfterRenderAsync() triggers the JavaScript function initReader when the page loads.
    • ReadBarcodes() invokes the JavaScript function selectFile, which opens a file dialog to select an image for barcode scanning.
    • ReturnBarcodeResultsAsync() is a C# method that JavaScript calls to return barcode results to the Blazor component.
  3. Add JavaScript Functions in jsInterop.js: Add the corresponding JavaScript functions in jsInterop.js:
     function showResults(result, dotnetRef) {
         clearOverlay();
        
         let txts = [];
         try {
             let localization;
             let items = result.items
             if (items.length > 0) {
                 for (var i = 0; i < items.length; ++i) {
        
                     if (items[i].type !== Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE) {
                         continue;
                     }
        
                     let item = items[i];
        
                     txts.push(item.text);
                     localization = item.location;
        
                     drawOverlay(
                         localization,
                         item.text
                     );
                 }
        
        
             }
         } catch (e) {
             alert(e);
         }
        
         let barcoderesults = txts.join(', ');
         if (txts.length == 0) {
             barcoderesults = 'No barcode found';
         }
        
         if (dotnetRef) {
             dotnetRef.invokeMethodAsync('ReturnBarcodeResultsAsync', barcoderesults);
         }
     }
    
     function decodeImage(dotnetRef, url, data) {
         const img = new Image()
         img.onload = () => {
             updateOverlay(img.width, img.height);
             if (cvr) {
                 cvr.capture(url, 'ReadBarcodes_Balance').then((result) => {
                     showResults(result, dotnetRef);
                 });
        
             }
         }
         img.src = url
     }
    
     window.jsFunctions = {
         ...
         initReader: async function () {
             try {
                 dispose();
                 cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
        
             } catch (e) {
                 console.log(e);
             }
         },
        
         selectFile: async function (dotnetRef, overlayId, imageId) {
             if (cameraEnhancer) {
                 cameraEnhancer.dispose();
                 cameraEnhancer = null;
             }
             initOverlay(document.getElementById(overlayId));
             if (cvr) {
                 let input = document.createElement("input");
                 input.type = "file";
                 input.onchange = async function () {
                     try {
                         let file = input.files[0];
                         var fr = new FileReader();
                         fr.onload = function () {
                             let image = document.getElementById(imageId);
                             image.src = fr.result;
                             image.style.display = 'block';
        
                             decodeImage(dotnetRef, fr.result, file);
                         }
                         fr.readAsDataURL(file);
        
                     } catch (ex) {
                         alert(ex.message);
                         throw ex;
                     }
                 };
                 input.click();
             } else {
                 alert("The barcode reader is still initializing.");
             }
         },
     };
    

    Explanation

    • initReader() initializes the Capture Vision Router.
    • decodeImage() decodes barcodes from the selected image.
    • showResults() displays the decoded barcode results.
    • selectFile() allows users to select an image file, which is then processed for barcode scanning.

Step 3: Implement a Barcode Scanner Razor Page

  1. Create the Barcode Scanner Component: Create a new Razor component called Scanner.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="barcodescanner">
             <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Barcode Scanner
         </NavLink>
     </div>
    
  2. Implement the Barcode Scanner in Scanner.razor: Add the following code in Scanner.razor to set up the barcode scanner functionality:
     @page "/barcodescanner"
    
     @inject IJSRuntime JSRuntime
        
     <div class="select">
         <button @onclick="GetCameras">Get Cameras</button>
         <label for="videoSource"></label>
         <select id="@videoSourceId"></select>
         <button @onclick="StartCamera">Open Camera</button>
         <button @onclick="StopCamera">Stop Camera</button>
     </div>
        
     <div id="videoview">
         <div class="dce-video-container" id="@videoContainerId"></div>
         <canvas id="@overlayId"></canvas>
     </div>
        
     @code {
         private String result = "";
         private DotNetObjectReference<Scanner>? objRef;
         private string videoSourceId = "videoSource";
         private string overlayId = "overlay";
         private string videoContainerId = "videoContainer";
        
         protected override async Task OnAfterRenderAsync(bool firstRender)
         {
             if (firstRender)
             {
                 objRef = DotNetObjectReference.Create(this);
                 await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initScanner", objRef, videoContainerId, videoSourceId, overlayId);
             }
         }
        
        
         [JSInvokable]
         public void ReturnBarcodeResultsAsync(String text)
         {
             result = text;
             StateHasChanged();
         }
        
         public void Dispose()
         {
             objRef?.Dispose();
         }
        
         public async Task GetCameras()
         {
             await JSRuntime.InvokeVoidAsync("jsFunctions.getCameras");
         }
        
         public async Task StartCamera()
         {
             await JSRuntime.InvokeVoidAsync("jsFunctions.startCamera");
         }
        
         public async Task StopCamera()
         {
             await JSRuntime.InvokeVoidAsync("jsFunctions.stopCamera");
         }
     }
        
    

    Explanation

    • @page "/barcodescanner" defines the route for the Razor component.
    • OnAfterRenderAsync() initializes the barcode scanner by calling the JavaScript function initScanner once the page is loaded.
    • GetCameras(), StartCamera(), and StopCamera() are methods that interact with JavaScript functions to manage the camera and barcode scanning operations.
  3. Add JavaScript Functions in jsInterop.js: Add the corresponding JavaScript functions in your jsInterop.js file:

     async function openCamera() {
         clearOverlay();
        
         try {
             let deviceId = videoSelect.value;
             if (cameraEnhancer && deviceId !== "") {
                 await cameraEnhancer.selectCamera(deviceId);
                 await cameraEnhancer.open();
                 cvr.startCapturing('ReadSingleBarcode');
             }
         }
         catch(e) {
             console.log(e);
         }
     }
    
     window.jsFunctions = {
         ...
         initScanner: async function (dotnetRef, videoId, selectId, overlayId) {
             let canvas = document.getElementById(overlayId);
             initOverlay(canvas);
             videoSelect = document.getElementById(selectId);
             videoSelect.onchange = openCamera;
            
             try {
                 dispose();
            
                 let cameraView = await Dynamsoft.DCE.CameraView.createInstance();
                 cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView);
            
                 let uiElement = document.getElementById(videoId);
                 uiElement.append(cameraView.getUIElement());
            
                 cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-camera')?.setAttribute('style', 'display: none');
                 cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-resolution')?.setAttribute('style', 'display: none');
            
                    
            
                 cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
                 cvr.setInput(cameraEnhancer);
            
                 cvr.addResultReceiver({
                     onCapturedResultReceived: (result) => {
                         showResults(result, dotnetRef);
                     },
                 });
            
                 cvr.addResultReceiver({
                     onDecodedBarcodesReceived: (result) => {
                         if (!result.barcodeResultItems.length) return;
            
                     },
                 });
            
                 cameraEnhancer.on('played', () => {
                     updateResolution();
                 });
            
             } catch (e) {
                 console.log(e);
                 result = false;
             }
             return true;
         },
         getCameras: async function () {
             if (cameraEnhancer) {
                 let cameras = await cameraEnhancer.getAllCameras();
                 listCameras(cameras);
             }
         },
         startCamera: async function() {
             openCamera();
         },
         stopCamera: async function () {
             try {
                 if (cameraEnhancer) {
                     cameraEnhancer.pause();
                 }
             }
             catch (e) {
                 console.log(e);
             }
         }
     };
    

    Explanation

    • initScanner() initializes the camera view, camera enhancer, and Capture Vision Router, and registers callback functions to handle barcode results.
    • getCameras() retrieves the list of available cameras.
    • startCamera() opens the selected camera and starts barcode scanning.
    • stopCamera() stops the camera preview, halting any ongoing image processing.

Step 4: Run the Blazor Barcode Reader and Scanner Application

To launch your Blazor application, press F5 in Visual Studio or use your preferred method to start debugging. Once the application is running, you can access the barcode reader and scanner pages to scan barcodes and QR codes effortlessly.

  • Barcode Reader: Use the barcode reader page to select image files for barcode detection.

    Blazor WebAssembly barcode and QR code reader

  • Barcode Scanner: The barcode scanner page allows for real-time scanning using your device’s camera, making it easy to detect barcodes and QR codes.

    Blazor WebAssembly barcode and QR code scanner

Source Code

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