How to Build Web MRZ Scanner App with .NET Blazor WebAssembly
Tags: blazor, dotnet, webdev, mrz
Published: 2023-08-15 05:30:40

If you are a .NET developer, you might consider using [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/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](https://www.dynamsoft.com/Products/label-recognizer-javascript.aspx). 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.

<video src="https://user-images.githubusercontent.com/2202306/259962035-af19cdb7-9588-4223-9b1b-aa0a64c1b889.mp4" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## Try Online Demo
[https://yushulx.me/dotnet-blazor-MRZ-scanner/](https://yushulx.me/dotnet-blazor-MRZ-scanner/)

## About Dynamsoft Label Recognizer SDK
[Dynamsoft Label Recognizer SDK](https://www.dynamsoft.com/label-recognition/overview/) 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](https://www.npmjs.com/package/dynamsoft-label-recognizer). Before using the SDK, you must first request a [trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dlr&source=codepool).


## 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:

```bash
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.

```bash
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.

```bash
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.

```html
<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`:

```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.
    ```js
    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. 
    ```javascript
    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.

    ```javascript
    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.

    ```javascript
    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](https://github.com/yushulx/dotnet-mrz-sdk/blob/main/MrzParser.cs) and [MrzResult](https://github.com/yushulx/dotnet-mrz-sdk/blob/main/MrzResult.cs), packaged in [MrzScannerSDK](https://www.nuget.org/packages/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.

```csharp
@using Dynamsoft
```

### MRZ Reader
Open `Pages/MrzReader.razor` and add the following code:

```csharp
@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](https://www.dynamsoft.com/codepool/img/2023/08/dotnet-blazor-mrz-reader.png)

### MRZ Scanner

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

```csharp
@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](https://www.dynamsoft.com/codepool/img/2023/08/dotnet-blazor-mrz-scanner.png)

## Source Code

[https://github.com/yushulx/dotnet-blazor-MRZ-scanner](https://github.com/yushulx/dotnet-blazor-MRZ-scanner)