How to Build Web MRZ Scanner App with .NET Blazor WebAssembly

If you are a .NET developer, you might consider using Blazor for web development, as it allows you to write web apps using C# and HTML. A significant advantage of Blazor is its ability to reuse existing .NET libraries and open-source C# code. However, using Blazor doesn’t mean you can completely bypass JavaScript. JS interop is still essential when calling existing JavaScript APIs. This article guides you through building a web-based MRZ (Machine Readable Zone) detection app with .NET Blazor WebAssembly and Dynamsoft Label Recognizer SDK. You will learn how to integrate Dynamsoft Label Recognizer SDK into a Blazor WebAssembly project and how to use C# code to extract MRZ data from passport images.

Try Online Demo

https://yushulx.me/dotnet-blazor-MRZ-scanner/

About Dynamsoft Label Recognizer SDK

Dynamsoft Label Recognizer SDK is an OCR library designed to extract text from images. Its MRZ model capable of recognizing MRZ data from passports, driver’s licenses, visas, and other ID cards. The SDK supports Windows, Linux, Android, iOS, and web. Its web edition is a standalone JavaScript library that can be integrated into any web application and is available on NPM. Before using the SDK, you must first request a trial license.

Steps to Build a Blazor WebAssembly Project for MRZ Recognition

“In the following sections, you will learn how to construct a Blazor WebAssembly project for MRZ recognition. This project will comprise two Razor components: MrzReader and MrzScanner. The former is designed for uploading images and extracting MRZ data, while the latter facilitates the scanning of MRZ data directly from a webcam video stream.

Blazor WebAssembly Project and Razor Components

A Blazor project can be created with a template in Visual Studio or via .NET command line:

dotnet new blazorwasm -o BlazorMrzSample

By running the app using dotnet watch run, you can enable hot reload, allowing for automatic updates in the browser as changes are made.

cd BlazorMrzSample
dotnet watch run

After the scaffolding is complete, you can add two Razor components: one representing the MRZ reader and the other, the scanner.

cd BlazorMrzSample
dotnet new razorcomponent -n MrzReader -o Pages
dotnet new razorcomponent -n MrzScanner -o Pages

JavaScript Interop for Invoking Dynamsoft Label Recognizer API in Blazor WebAssembly

Include Dynamsoft Label Recognizer, Dynamsoft Camera Enhancer and a custom JavaScript file in index.html. Dynamsoft Camera Enhancer is used for accessing the webcam video stream. The custom JavaScript file is for bridging the Dynamsoft Label Recognizer API in Blazor WebAssembly.

<script src="https://cdn.jsdelivr.net/npm/dynamsoft-label-recognizer@2.2.31/dist/dlr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.5/dist/dce.js"></script>
<script src="jsInterop.js"></script>

To simplify the JavaScript interop, the core logic is implemented in JavaScript, while C# is used solely to call JavaScript methods. Methods bound to the window object can be called from C#. We have defined four main methods in wwwroot/jsInterop.js:

window.jsFunctions = {
    initSDK: async function (licenseKey) {
        ...
    },
    initReader: async function (dotnetRef) {
        ...
    },
    initScanner: async function (dotnetRef, videoId, selectId, overlayId) {
        ...
    },
    selectFile: async function (dotnetRef, overlayId, imageId) {
        ...
    },
};
  • initSDK() initializes Dynamsoft Label Recognizer SDK with a valid license key. This method should be called only once.
      let recognizer = null;
        
      async function init() {
          recognizer = await Dynamsoft.DLR.LabelRecognizer.createInstance();
          await recognizer.updateRuntimeSettingsFromString("MRZ");
      }
    
      initSDK: async function (licenseKey) {
          let result = true;
    
          if (recognizer != null) {
              return result;
          }
            
          try {
              Dynamsoft.DLR.LabelRecognizer.initLicense(licenseKey);
                
          } catch (e) {
              console.log(e);
              result = false;
          }
            
          await init();
    
          return result;
      }
    
  • initReader() initializes a MRZ reader instance. Calling stopScanning() can stop the MRZ reader from camera scanning.
      initReader: async function (dotnetRef) {
          dotnetHelper = dotnetRef;
          if (recognizer != null) {
              recognizer.stopScanning();
              await recognizer.updateRuntimeSettingsFromString("MRZ");
          }
          else {
              await init();
          }
    
          return true;
      }
    
  • initScanner() initializes a camera enhancer instance and binds it to the MRZ reader instance. The camera enhancer instance facilitates camera operations. The onImageRead() event is triggered when MRZ data is recognized.

      initScanner: async function (dotnetRef, videoId, selectId, overlayId) {
          if (recognizer == null) {
              await init();
          }
          let canvas = document.getElementById(overlayId);
          initOverlay(canvas);
          videoContainer = document.getElementById(videoId);
          videoSelect = document.getElementById(selectId);
          videoSelect.onchange = openCamera;
          dotnetHelper = dotnetRef;
    
          try {
              enhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance();
              await enhancer.setUIElement(document.getElementById(videoId));
              await recognizer.setImageSource(enhancer, { });
              await recognizer.startScanning(true);
              let cameras = await enhancer.getAllCameras();
              listCameras(cameras);
              await openCamera();
    
              recognizer.onImageRead = results => {
                  clearOverlay();
                  showResults(results);
              };
              enhancer.on("played", playCallBackInfo => {
                  updateResolution();
              });
    
          } catch (e) {
              console.log(e);
              result = false;
          }
          return true;
      }
    
  • selectFile() selects an image file and passes it to the MRZ reader instance. We dynamically create an HTML input element and bind the onchange event to a function. After the file is selected, we read it as a data URL and forward it to the MRZ reader instance.
      selectFile: async function (dotnetRef, overlayId, imageId) {
          initOverlay(document.getElementById(overlayId));
          if (recognizer) {
              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 mrz reader is still initializing.");
          }
      },
    

Reuse C# Code for Parsing MRZ Data

MRZ data parsing is a essential component of the MRZ recognition process. There’s no need to rewrite the MRZ data parsing code in JavaScript. The two existing C# classes, MrzParser and MrzResult, packaged in MrzScannerSDK, can be directly incorporated into a Blazor WebAssembly project. These C# files can be added to the project’s root directory and referenced in a .razor file.

@using Dynamsoft

MRZ Reader

Open Pages/MrzReader.razor and add the following code:

@page "/mrzreader"
@inject IJSRuntime JSRuntime
@using Dynamsoft

<button class="btn" @onclick="ReadMrz">Load file</button>

<div class="container">
    <div id="imageview">
        <img id="image" />
        <canvas id="overlay"></canvas>

    </div>
    <div class="mrz-result">@result</div>
</div>
@code {
    private MarkupString result = new MarkupString("");
    private DotNetObjectReference<MrzReader>? objRef;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initReader", objRef);
        }
    }

    public async Task ReadMrz()
    {
        result = new MarkupString("");
        await JSRuntime.InvokeVoidAsync(
        "jsFunctions.selectFile", objRef, "overlay", "image");
    }

    [JSInvokable]
    public void ReturnMrzResultsAsync(string results)
    {
        string[] lines = results.Split("\n");
        MrzResult mrzResult = MrzParser.Parse(lines);

        result = new MarkupString(mrzResult.ToString().Replace("\n", "<br>"));
        StateHasChanged();
    }

    public void Dispose()
    {
        objRef?.Dispose();
    }
}
  • OnAfterRenderAsync() is invoked after the component has been rendered. It initializes the MRZ reader instance.
  • ReadMrz() is invoked when the button is clicked. It triggers the selectFile() method in JavaScript to select an image file and then passes it to the MRZ reader instance.
  • ReturnMrzResultsAsync() is invoked when MRZ data is recognized. The MrzParser.Parse() method processes the MRZ data and produces a MrzResult object. The MrzResult.ToString() method then converts this object into a string.

.NET Blazor MRZ reader

MRZ Scanner

Open Pages/MrzScanner.razor and add the following code:

@page "/mrzscanner"
@inject IJSRuntime JSRuntime
@using Dynamsoft

<div class="select">
    <label for="videoSource">Video source: </label>
    <select id="videoSource"></select>
</div>

<div class="container">
    <div id="videoview">
        <div class="dce-video-container" id="videoContainer"></div>
        <canvas id="overlay"></canvas>
    </div>
    <div class="mrz-result">@result</div>
</div>

@code {
    private MarkupString result = new MarkupString("");
    private DotNetObjectReference<MrzScanner>? objRef;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);
            await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initScanner", objRef, "videoContainer", "videoSource", "overlay");
        }
    }

    [JSInvokable]
    public void ReturnMrzResultsAsync(string results)
    {
        string[] lines = results.Split("\n");
        MrzResult mrzResult = MrzParser.Parse(lines);

        result = new MarkupString(mrzResult.ToString().Replace("\n", "<br>"));
        StateHasChanged();
    }

    public void Dispose()
    {
        objRef?.Dispose();
    }
}
  • In OnAfterRenderAsync(), the HTML element IDs are passed to the initScanner() method in JavaScript to initialize the camera enhancer instance.
  • Similar to the MrzReader component, ReturnMrzResultsAsync() is invoked when MRZ data is recognized.

.NET Blazor MRZ scanner

Source Code

https://github.com/yushulx/dotnet-blazor-MRZ-scanner