How to Build a Razor Class Library for JavaScript Barcode Scanner

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.

Online Barcode Reader and Scanner

https://yushulx.me/Razor-Barcode-Library/

NuGet Package

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.

     let scanner = await Dynamsoft.DBR.BarcodeScanner.createInstance();
    

    This code initializes a new scanner instance.

  2. Get a list of available cameras.

     let cameras = await scanner.getAllCameras();
    

    This retrieves all cameras accessible to the device.

  3. Bind an HTML div element to the scanner.

     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.

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

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:

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:

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#

      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#

      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

      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#

      public async Task SetVideoElement(string videoId)
      {
          await _module.InvokeVoidAsync("setVideoElement", _jsObjectReference, videoId);
      }
    

    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#

      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

      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#

      public async Task CloseCamera()
      {
          await _module.InvokeVoidAsync("closeCamera", _jsObjectReference);
      }
    

    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#

      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

      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.
     @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.
     <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.
     <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

Source Code

https://github.com/yushulx/Razor-Barcode-Library