Blazor WebAssembly: Building Web Apps for Digitizing Documents with C# and .NET
Tags: blazor, csharp, dotnet, webdev
Published: 2023-11-02 00:48:15

Last week, we developed a RESTful service using .NET C# and [Twain.Wia.Sane.Scanner](https://www.nuget.org/packages/Twain.Wia.Sane.Scanner/) library to scan documents from TWAIN, WIA, SANE, and eSCL scanners. This week, we will repurpose the C# code to construct a Blazor WebAssembly application that digitizes documents directly from a web browser, all without the need for JavaScript.


## Prerequisites
1. Install the Dynamsoft Service on a machine that is connected to scanners and hosts a web service.
    - Windows: [Dynamsoft-Service-Setup.msi](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.msi)
    - macOS: [Dynamsoft-Service-Setup.pkg](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.pkg)
    - Linux: 
        - [Dynamsoft-Service-Setup.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.deb)
        - [Dynamsoft-Service-Setup-arm64.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-arm64.deb)
        - [Dynamsoft-Service-Setup-mips64el.deb](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup-mips64el.deb)
        - [Dynamsoft-Service-Setup.rpm](https://demo.dynamsoft.com/DWT/DWTResources/dist/DynamsoftServiceSetup.rpm)

2. Modify the host IP address to make it publicly accessible. 

    ![dynamsoft-service-config](https://user-images.githubusercontent.com/2202306/266243200-e2b1292e-dfbd-4821-bf41-70e2847dd51e.png)
    
    You can test it by visiting `http://[host-ip]:18626/DWTAPI/Scanners` in a browser. If you see the following page, it means the Dynamsoft Service is successfully installed and running.
    
    ![dynamsoft-service-scanner-list](https://www.dynamsoft.com/codepool/img/2023/11/dynamsoft-service-scanner-list.png)

3. Request a [free trial license](https://www.dynamsoft.com/customer/license/trialLicense?product=dwt) for Dynamsoft Service.


## Steps to Create a Web-Based Document Scanning Application Using Blazor WebAssembly
In the following paragraphs, we will demonstrate the process of constructing a web-based document digitization application from scratch. For clarity, we will start by establishing an empty Blazor WebAssembly project, which will only encompass an `Index.razor` file.

### Initiate the Blazor Project
1. Create the Blazor project using Visual Studio or the command line:

    ```bash
    dotnet new blazorwasm-empty -o blazorwasm-empty-project
    ```
2. Install the NuGet package:

    ```bash
    dotnet add package Twain.Wia.Sane.Scanner --version 1.1.0
    ```

### Implement the App UI in HTML
We construct the app UI as follow:

```html
<div id="loading-indicator" class="loading-indicator">
    <div class="spinner"></div>
</div>

<div class="connection">
    <div class="row">
        <div>
            <label>License key: </label>
            <div class="filler"></div>
            <input type="text" placeholder="">
        </div>
    </div>
     <div class="row">
        <div>
            <label>Enter host address: </label>
            <div class="filler"></div>
            <input type="text" id="host" placeholder="">
        </div>
    </div>
</div>

<div class="container" id="dwt">
    <div class="row">
        <div>
            <button onclick="getDevices()">Get Devices</button>
            <select id="sources">
            </select>
            <button onclick="acquireImage()">Scan Documents</button>
        </div>

    </div>

    <div class="row">
        <div class="full-img">
            <img id="scanner-image">
        </div>
    </div>

    <div class="row">
        <div class="thumb-bar" id="thumb-bar">
            <div class="thumb-box" id="thumb-box">
            </div>
        </div>
    </div>
</div>
```

The `loading-indicator` is used to show the loading animation. The `connection` section is for users to enter the license key and host address. The `dwt` section is for the document scanning part.

### Convert HTML to Razor
Razor is a markup syntax that enables developers to embed C# code within HTML, facilitating the creation of dynamic web pages using C#. In the following sections, we will convert the above HTML code to comply with the Razor syntax.

**How to import a .NET library into a Blazor project?**

Import the `Twain.Wia.Sane.Scanner` namespace in the `Index.razor` file to use the `ScannerController` class:

```csharp
@page "/"
@using Twain.Wia.Sane.Scanner

@code {
    private ScannerController scannerController = new ScannerController();
}
```

**How to append options to select element in Blazor?**

To append options to a select element in a Blazor app using C#, we typically use the Blazor rendering system, rather than manipulating the DOM directly with **JavaScript**.

```csharp
<button @onclick="GetDevices">Get Devices</button>
<select id="sources" @bind="selectedValue">
    @foreach (var device in devices)
    {
        <option value="@device["name"].ToString()">@device["name"].ToString()</option>
    }
</select>

@code {
    private bool isLoading = false;

    private string host = "http://127.0.0.1:18622"; 
    private List<Dictionary<string, object>> devices = new List<Dictionary<string, object>>();
    private string licenseKey = "LICENSE-KEY";
    
    private string jobId = "";
    private string selectedValue { get; set; } = string.Empty;
    
    private List<string> imageUrls { get; set; } = new List<string>();
    public async Task GetDevices()
    {
        isLoading = true;
    
        try
        {
            devices = await scannerController.GetDevices(host);
            if (devices.Count >= 0)
            {
                selectedValue = devices[0]["name"].ToString();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    
        isLoading = false;
    }
}
```

- The `host` and `licenseKey` need to be modified to your own values.
- The `List<Dictionary<string, object>> devices` stores the options for the select element.
- The `@foreach` directive is used to render each option in the devices list.
- The `@bind` directive is used for two-way data binding. It bind the selected value to the `selectedValue` property.

**How to show the loading indicator?**

To show a loading indicator when pressing the button and hide it once the function completes, we use a boolean flag that tracks whether data is being loaded. Based on the value of this flag, the loading indicator can be rendered conditionally.

```csharp
<div id="loading-indicator" class="loading-indicator" style="@(isLoading ? "display: flex;" : "display: none;")">
    <div class="spinner"></div>
</div>
```

**How to acquire the image stream and display it in the browser?**

The image stream is of type `byte[]`. To display such a `byte[]` image stream in an `<img>` element in Blazor, it can be converted to a `Base64 string`. Using the `data: URL` scheme, this string can be embedded directly into the `src` attribute of the `<img>` element. All image URLs are stored in the `imageUrls` list. By clicking on a thumbnail, the corresponding image will be displayed in the `<img>` element.

```csharp

<button @onclick="AcquireImage">Scan Documents</button>

<div class="row">
    <div class="full-img">
        <img id="scanner-image" src="@imageDataUrl">
    </div>
</div>

<div class="row">
    <div class="thumb-bar" id="thumb-bar">
        <div class="thumb-box" id="thumb-box">
            @foreach (var url in imageUrls)
            {
                <img src="@url" @onclick="() => OnImageClick(url)" />
            }
        </div>
    </div>
</div>

@code {
    public async Task AcquireImage()
    {
        if (devices.Count == 0)
        {
            return;
        }
    
        int selectedIndex = devices.FindIndex(device => device["name"].ToString() == selectedValue);
        if (selectedIndex < 0) return;
        var parameters = new Dictionary<string, object>
                {
                    {"license", licenseKey},
                    {"device", devices[selectedIndex]["device"]}
                };
    
        parameters["config"] = new Dictionary<string, object>
                {
                    {"IfShowUI", false},
                    {"PixelType", 2},
                    {"Resolution", 200},
                    {"IfFeederEnabled", false},
                    {"IfDuplexEnabled", false}
                };
        jobId = await scannerController.ScanDocument(host, parameters);
        byte[] bytes = await scannerController.GetImageStream(host, jobId);
        imageDataUrl = $"data:image/png;base64,{Convert.ToBase64String(bytes)}";
        AddImageUrl(imageDataUrl);
    }
    
    private void AddImageUrl(string url)
    {
        imageUrls.Add(url);
    }
    
    private void OnImageClick(string url)
    {
        imageDataUrl = url;
    }
}
```

## Online Demo Deployed on GitHub Pages
After completing the Blazor project, we can deploy it to GitHub Pages.

1. Create a new repository on GitHub.
2. Push the Blazor project to the repository.
3. Navigate to the repository: `Settings > Actions > General > Workflow permissions` and enable `Read and write permissions`.
4. Create a new workflow file named `main.yml` in the `.github/workflows` folder. Replace `dotnet-blazor-digitize-document` with your own repository name.
    
    ```yml
    name: blazorwasm
    on:
      push:
        branches: [ main ]
      pull_request:
        branches: [ main ]
    
      workflow_dispatch:
    
    jobs:
      build:
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v3
          
          - name: Setup .NET Core SDK
            uses: actions/setup-dotnet@v2
            with:
              dotnet-version: '7.0.x'
              include-prerelease: true
              
          - name: Publish .NET Core Project
            run: dotnet publish blazorwasm-empty-project.csproj -c Release -o release --nologo 
            
          - name: Change base-tag in index.html from / to dotnet-blazor-digitize-document
            run: sed -i 's/<base href="\/" \/>/<base href="\/dotnet-blazor-digitize-document\/" \/>/g' release/wwwroot/index.html
            
          - name: copy index.html to 404.html
            run: cp release/wwwroot/index.html release/wwwroot/404.html
            
          - name: Add .nojekyll file
            run: touch release/wwwroot/.nojekyll
          
          - name: Commit wwwroot to GitHub Pages
            uses: JamesIves/github-pages-deploy-action@3.7.1
            with:
              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              BRANCH: gh-pages
              FOLDER: release/wwwroot
    ```

5. To test your local Dynamsoft service on a GitHub page, install [ngrok](https://ngrok.com/download) and run `ngrok http 18622` to expose the local port `18622` to the internet. 

## Online Demo

Visit [https://yushulx.me/dotnet-blazor-digitize-document/](https://yushulx.me/dotnet-blazor-digitize-document/) and use your own license key. 

The host address can either be Dynamsoft's test URL: `https://demo.scannerproxy.com/ddm/18626` or your personal one.

![blazor-digitize-document](https://www.dynamsoft.com/codepool/img/2023/11/blazor-web-scanner-digitize-document.png)

## Source Code
[https://github.com/yushulx/dotnet-blazor-digitize-document](https://github.com/yushulx/dotnet-blazor-digitize-document)