How to Build a Razor Class Library for JavaScript Barcode Scanner
Tags: webdev, dotnet, csharp, blazor
Published: 2023-11-20 00:51:29

In our last article, we discussed how to create a Razor Class Library and implement related C# interfaces for a JavaScript Barcode Reader. Today, we will further enhance the library by integrating JavaScript Barcode Scanner interfaces. This will enable Blazor developers to easily create applications for both barcode reading and scanning using C# code.

<video src="https://user-images.githubusercontent.com/2202306/283659769-ac3c333f-7895-420a-94ee-6debe805a8b7.mp4" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## Online Barcode Reader and Scanner
[https://yushulx.me/Razor-Barcode-Library/](https://yushulx.me/Razor-Barcode-Library/)

## NuGet Package
[https://www.nuget.org/packages/RazorBarcodeLibrary](https://www.nuget.org/packages/RazorBarcodeLibrary)

## Camera Access in JavaScript
To access a camera in JavaScript, we typically use the `navigator.mediaDevices.getUserMedia()` method, a component of the `WebRTC API` (Web Real-Time Communications). The camera access logic is built into the `Dynamsoft.DBR.BarcodeScanner` object. Using the Dynamsoft JavaScript Barcode SDK, you can open a camera for barcode scanning in just a few steps:

1. Create an instance of the scanner.
    
    ```javascript
    let scanner = await Dynamsoft.DBR.BarcodeScanner.createInstance();
    ```

    This code initializes a new scanner instance.

2. Get a list of available cameras.
    
    ```javascript
    let cameras = await scanner.getAllCameras();
    ```
    
    This retrieves all cameras accessible to the device.

3. Bind an HTML div element to the scanner.
    
    ```javascript
    let element = document.getElementById(videoId);
    element.className = "dce-video-container";
    await scanner.setUIElement(element);
    ```
    
    This attaches the scanner to a specific HTML element on your page, designated by `videoId`.

4. Start scanning barcodes from the camera video stream.
    
    ```javascript
    await scanner.setCurrentCamera(cameras[0]);
    await scanner.show();
    ```
    
    This selects the first available camera and starts the barcode scanning process.

## Camera Class in C#

In JavaScript, a camera object typically has properties like `deviceId`, `label`, `groupId`, and `kind`. To facilitate C# developers in managing camera properties, we can create a `Camera` class in C#:

```csharp
public class Camera
{
    public string DeviceId { get; set; } = string.Empty;
    public string Label { get; set; } = string.Empty;
}
```

In this class:
- The `DeviceId` property uniquely identifies a camera, essential for selecting the correct camera in code.
- The `Label` property is useful for displaying the camera name in the UI, aiding users in camera selection.

Note: Here, we've only included `DeviceId` and `Label` for simplicity. Depending on your application's requirements, you might also want to incorporate the groupId and kind properties, which represent the camera group and type, respectively.

## Barcode Scanner Class in C#
Similarly to how we create the JavaScript barcode reader object, we add a `CreateBarcodeScanner()` method in the `BarcodeJsInterop.cs` file. This method creates a JavaScript barcode scanner object and saves it in a `BarcodeScanner` class:

```csharp
public async Task<BarcodeScanner> CreateBarcodeScanner()
{
    var module = await moduleTask.Value;
    IJSObjectReference jsObjectReference = await module.InvokeAsync<IJSObjectReference>("createBarcodeScanner");
    BarcodeScanner scanner = new BarcodeScanner(module, jsObjectReference);
    return scanner;
}
```

The corresponding JavaScript code is as follows:

```javascript
export async function createBarcodeScanner() {
    if (!Dynamsoft) return;

    try {
        let scanner = await Dynamsoft.DBR.BarcodeScanner.createInstance();
        await scanner.updateRuntimeSettings("speed");
        scanner.barcodeFillStyle = "transparent";
        scanner.barcodeStrokeStyle = "transparent";
        scanner.barcodeFillStyleBeforeVerification = "transparent";
        scanner.barcodeStrokeStyleBeforeVerification = "transparent";
        scanner.onUnduplicatedRead = (txt, result) => { };
        scanner.onPlayed = function () {

        }
        return scanner;
    }
    catch (ex) {
        console.error(ex);
    }
    return null;
}
```

According to the JavaScript barcode scanner API, the C# barcode scanner class can be created with the following functionalities:
- **Initialization**: It initializes a new instance of the `BarcodeScanner` class using a JavaScript module and a scanner object. For C# callback functions that are invoked in JavaScript, the `DotNetObjectReference` object is utilized.
    
    **C#**

    ```csharp
    public BarcodeScanner(IJSObjectReference module, IJSObjectReference scanner)
    {
        _module = module;
        _jsObjectReference = scanner;
        objRef = DotNetObjectReference.Create(this);
    }
    ```

- **Camera Listing**: The class can retrieve a list of available cameras, with the return type being a `JsonElement`. The `TryGetProperty()` method is used to extract specific camera properties from this list.
    
    **C#**

    ```csharp
    public async Task<List<Camera>> GetCameras()
    {
        JsonElement? result = await _module.InvokeAsync<JsonElement>("getCameras", _jsObjectReference);

        if (result != null)
        {
            JsonElement element = result.Value;

            if (element.ValueKind == JsonValueKind.Array)
            {
                foreach (JsonElement item in element.EnumerateArray())
                {
                    Camera camera = new Camera();
                    if (item.TryGetProperty("deviceId", out JsonElement devideIdValue))
                    {
                        string? value = devideIdValue.GetString();
                        if (value != null)
                        {
                            camera.DeviceId = value;
                        }
                    }

                    if (item.TryGetProperty("label", out JsonElement labelValue))
                    {
                        string? value = labelValue.GetString();
                        if (value != null)
                        {
                            camera.Label = value;
                        }
                    }
                    _cameras.Add(camera);
                }
            }
        }

        return _cameras;
    }
    ```
    
    **JavaScript**
    
    ```javascript
    export async function getCameras(scanner) {
        if (!Dynamsoft) return;
    
        try {
            return await scanner.getAllCameras();
        }
        catch (ex) {
            console.error(ex);
        }
    }
    ```
- **Video Container Setting**: It sets a specified div element as the video container for displaying the camera feed.
    
    **C#**

    ```csharp
    public async Task SetVideoElement(string videoId)
    {
        await _module.InvokeVoidAsync("setVideoElement", _jsObjectReference, videoId);
    }
    ```
    
    **JavaScript**
    
    ```javascript
    export async function setVideoElement(scanner, videoId) {
        if (!Dynamsoft) return;
    
        try {
            let element = document.getElementById(videoId);
            element.className = "dce-video-container";
            await scanner.setUIElement(element);
        }
        catch (ex) {
            console.error(ex);
        }
    }
    ```

- **Camera Activation**: The class can open a specific camera for barcode scanning. It uses the `OnSizeChanged` callback function to obtain the video resolution, crucial for rendering barcode results on the video stream.
    
    **C#**

    ```csharp
    public async Task OpenCamera(Camera camera)
    {
        await _module.InvokeVoidAsync("openCamera", _jsObjectReference, camera, objRef, "OnSizeChanged");
    }
    
    [JSInvokable]
    public Task OnSizeChanged(int width, int height)
    {
        SourceWidth = width;
        SourceHeight = height;

        return Task.CompletedTask;
    }
    ```
    
    **JavaScript**
    
    ```javascript
    export async function openCamera(scanner, cameraInfo, dotNetHelper, callback) {
        if (!Dynamsoft) return;
    
        try {
            await scanner.setCurrentCamera(cameraInfo);
            scanner.onPlayed = function () {
                let resolution = scanner.getResolution();
                dotNetHelper.invokeMethodAsync(callback, resolution[0], resolution[1]);
            }
            await scanner.show();
        }
        catch (ex) {
            console.error(ex);
        }
    }
    ```

- **Camera Closure**: It provides functionality to close the currently active camera.
    
    **C#**

    ```csharp
    public async Task CloseCamera()
    {
        await _module.InvokeVoidAsync("closeCamera", _jsObjectReference);
    }
    ```
    
    **JavaScript**
    
    ```javascript
    export async function closeCamera(scanner) {
        if (!Dynamsoft) return;

        try {
            await scanner.close();
        }
        catch (ex) {
            console.error(ex);
        }
    }
    ```

- **Callback Interface**: The class defines an interface for registering a callback function. This function is designed to return barcode scanning results to the C# environment.
    
    **C#**

    ```csharp
    public interface ICallback
    {
        Task OnCallback(List<BarcodeResult> results);
    }

    [JSInvokable]
    public Task OnResultReady(object message)
    {
        List<BarcodeResult> results = BarcodeResult.WrapResult((JsonElement)message);
        if (_callback != null)
        {
            _callback.OnCallback(results);
        }

        return Task.CompletedTask;
    }

    public async Task RegisterCallback(ICallback callback)
    {
        _callback = callback;
        await _module.InvokeVoidAsync("registerCallback", _jsObjectReference, objRef, "OnResultReady");
    }
    ```
    
    **JavaScript**
    
    ```javascript
    export async function registerCallback(scanner, dotNetHelper, callback) {
        if (!Dynamsoft) return;
    
        try {
            scanner.onFrameRead = results => {
                dotNetHelper.invokeMethodAsync(callback, results);
            };
        }
        catch (ex) {
            console.error(ex);
        }
        return null;
    }
    ```

## Empower Blazor Apps with Barcode Scanning Functionality
Now, let's leverage the barcode scanner class to enhance our Blazor application with barcode scanning capabilities:
1. Instantiate and Register Callback in `Index.razor`: 
    - In the Index.razor file, create an instance of the barcode scanner object.
    - Register the callback function within this instance to handle barcode detection results. This function will process and display the results obtained from the barcode scanner.
    
    ```csharp
    @implements BarcodeScanner.ICallback

    <div id="videoContainer"></div>
    <canvas id="videoOverlay" class="overlay"></canvas>
    
    @code {
        public async Task Activate()
        {    
            scanner = await barcodeJsInterop.CreateBarcodeScanner();
            await scanner.RegisterCallback(this);
        }
    
        public async Task OnCallback(List<BarcodeResult> results)
        {
            if (barcodeJsInterop != null && scanner != null)
            {
                string text = "";
                foreach (BarcodeResult result in results)
                {
                    text += "format: " + result.Format + ", ";
                    text += "text: " + result.Text + "<br>";
                }
                result = new MarkupString(text);
    
                await barcodeJsInterop.DrawCanvas("videoOverlay", scanner.SourceWidth, scanner.SourceHeight, results);
            }
            StateHasChanged();
        }
    }
    ```

2. Camera Selection UI:
    - Implement a dropdown select element in your UI to list all available cameras.
    
    ```csharp
    <button @onclick="GetCameras">Get Cameras</button>
    <select id="sources" @bind="selectedValue">
        @foreach (var camera in cameras)
        {
            <option value="@camera.DeviceId">@camera.Label</option>
        }
    </select>

    @code {
        private string selectedValue { get; set; } = string.Empty;
        private List<Camera> cameras = new List<Camera>();

        public async Task GetCameras()
        {
            if (barcodeJsInterop == null || scanner == null) return;
            try
            {
                cameras = await scanner.GetCameras();
                if (cameras.Count >= 0)
                {
                    selectedValue = cameras[0].Label;
                }
    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
    ```

3. Activate Camera for Scanning:
    - Upon camera selection, trigger the barcode scanner to start capturing and analyzing the video feed from the chosen camera.
    
    ```csharp
    <button @onclick="OpenCamera">Open Camera</button>

    @code {
        public async Task OpenCamera()
        {
            if (barcodeJsInterop == null || scanner == null) return;
            try
            {
                await scanner.SetVideoElement("videoContainer");

                int selectedIndex = cameras.FindIndex(camera => camera.Label == selectedValue);
                await scanner.OpenCamera(cameras[selectedIndex]);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
    ```
    
    Remember to handle any potential exceptions or errors, such as cases where no cameras are available, or permission to access the camera is denied. This will make your application more robust and user-friendly.

    ![blazor barcode qrcode scanner](https://www.dynamsoft.com/codepool/img/2023/11/blazor-barcode-qrcode-scanner.png)
    
## Source Code
[https://github.com/yushulx/Razor-Barcode-Library](https://github.com/yushulx/Razor-Barcode-Library)