Building a .NET RESTful Service with Dynamsoft's Document, Barcode, and MRZ SDKs

RESTful service can be accessed from anywhere with an internet connection, irrespective of platform or device. Deploying a commercial SDK as a RESTful service can simplify integration, improve accessibility, and provide better control over the deployment environment and usage. In this article, we’ll delve into how you can effortlessly integrate Dynamsoft’s Document, Barcode, and MRZ SDKs into a RESTful service with .NET.

Prerequisites

Initiate a .NET Web API Project

In .NET SDK, a Web API template is used to create a web service that follows the REST architectural style. We can use the following command to quickly create a new project in the terminal:

dotnet new webapi -n DynamsoftRestfulService

Then install the dependencies for the project:

cd DynamsoftRestfulService
dotnet add package Twain.Wia.Sane.Scanner --version 1.0.1
dotnet add package DocumentScannerSDK --version 1.1.0
dotnet add package BarcodeQRCodeSDK --version 2.3.4
dotnet add package MrzScannerSDK --version 1.3.0

dotnet add package OpenCvSharp4 --version 4.6.0.20220608
dotnet add package OpenCvSharp4.Extensions --version 4.5.5.20211231
dotnet add package OpenCvSharp4.runtime.win --version 4.6.0.20220608 

The OpenCV package is used to decode the image stream uploaded by the client.

By default, the application will only respond to requests originating from the same machine on which it’s running. This means it listens only to localhost (127.0.0.1). To allow the application to listen to requests from other machines, we need to add the following code to the Program.cs file:

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(System.Net.IPAddress.Any, 5000);
});

CORS (Cross-Origin Resource Sharing) is a mechanism that allows a web application running at one origin to access the resources from a server running at a different origin. We need to enable CORS and configure it to accept requests from other origins.

  1. Install the required package:

       dotnet add package Microsoft.AspNetCore.Cors
    
  2. Add the following code to the Program.cs file:

       builder.Services.AddCors(options =>
       {
           options.AddPolicy("AllowSpecificOrigin",
               builder =>
               {
                   builder.AllowAnyOrigin()
                     .AllowAnyHeader()
                     .AllowAnyMethod();
               });
       });
    
       app.UseCors("AllowSpecificOrigin");
    

Now, run dotnet run to start the app, then open your browser and go to http://localhost:5000/WeatherForecast. If you see the following response, the app is functioning as expected:

[
    {
        "date": "2023-10-25",
        "temperatureC": 25,
        "temperatureF": 76,
        "summary": "Cool"
    },
    {
        "date": "2023-10-26",
        "temperatureC": -20,
        "temperatureF": -3,
        "summary": "Hot"
    },
    {
        "date": "2023-10-27",
        "temperatureC": -3,
        "temperatureF": 27,
        "summary": "Warm"
    },
    {
        "date": "2023-10-28",
        "temperatureC": -17,
        "temperatureF": 2,
        "summary": "Balmy"
    },
    {
        "date": "2023-10-29",
        "temperatureC": 21,
        "temperatureF": 69,
        "summary": "Warm"
    }
]

dotnet web api project

The /WeatherForecast endpoint is defined in the WeatherForecastController.cs file. We can follow the same pattern to add our own endpoints.

Integrating Dynamsoft SDKs into the RESTful Service

Create a LicenseManager.cs file to manage the license keys for Dynamsoft SDKs:

using System.ComponentModel;
using Dynamsoft;


public class LicenseManager
{
    public static string dwt = "LICENSE-KEY";
    public static string dbr = "LICENSE-KEY";
    public static string dlr = "LICENSE-KEY";
    public static string ddn = "LICENSE-KEY";

    public static void Init()
    {
        BarcodeQRCodeReader.InitLicense(dbr);
        MrzScanner.InitLicense(dlr);
        DocumentScanner.InitLicense(ddn);
    }
}

Then add LicenseManager.Init(); to the program.cs file.

In the Controllers folder, create five files: ProductController.cs, DwtController.cs, DbrController.cs, DlrController.cs, and DdnController.cs. These files will contain the endpoints for the respective SDKs.

Product List Endpoints

Add an HTTP GET endpoint to retrieve the list of available Dynamsoft products:

[Route("dynamsoft/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private static List<String> products = new List<String>
    {
        "Dynamic Web TWAIN",
        "Dynamsoft Barcode Reader",
        "Dynamsoft Label Recognizer",
        "Dynamsoft Document Normalizer",
    };

    [HttpGet]
    public ActionResult<IEnumerable<String>> Get()
    {
        return products;
    }

}

Dynamic Web TWAIN Endpoints

Import the packages:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Twain.Wia.Sane.Scanner;
using System.Text.Json;

Initialize the controller:

[Route("dynamsoft/[controller]")]
[ApiController]
public class DwtController : ControllerBase
{
    private static ScannerController scannerController = new ScannerController();
    private static string host = "http://127.0.0.1:18622";
}

The host variable is the address of the Dynamsoft service. If you’re running the service on a different machine, you’ll need to change the address accordingly.

Add an HTTP GET endpoint to retrieve the list of available scanners:

[HttpGet]
[Route("GetDevices")]
public async Task<ActionResult<Dictionary<string, object>>> GetDevices()
{
    var scanners = await scannerController.GetDevices(host);
    if (scanners.Count == 0)
    {
        return Ok("No devices found");
    }
    return Ok(scanners);
}

Add an HTTP POST endpoint to initiate a scanning job and return the job ID:

[HttpPost]
[Route("ScanDocument")]
public async Task<IActionResult> ScanDocument()
{
    if (Request.ContentType == null) return BadRequest("No content type specified");

    if (Request.ContentType.Contains("application/json"))
    {
        using (StreamReader reader = new StreamReader(Request.Body))
        {
            var json = await reader.ReadToEndAsync();

            Dictionary<string, object>? parameters = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
            if (parameters == null) return BadRequest("No content found");

            parameters["license"] = LicenseManager.dwt;
            string jobId = await scannerController.ScanDocument(host, parameters);

            return Ok(jobId);
        }
    }
    else
    {
        return BadRequest("Unsupported content type");
    }
}

Add an HTTP GET endpoint to fetch the image stream for a specific job and return the image stream:

[HttpGet]
[Route("GetImageStream/{jobId}")]
public async Task<ActionResult<Dictionary<string, object>>> GetImageStream(string jobId)
{
    byte[] bytes = await scannerController.GetImageStream(host, jobId);
    if (bytes.Length == 0)
    {
        return Ok("No image found");
    }
    else
    {
        return File(bytes, "application/octet-stream");
    }
}

Dynamsoft Barcode Reader Endpoints

Import the packages:

using Dynamsoft;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using static Dynamsoft.BarcodeQRCodeReader;
using BarcodeResult = Dynamsoft.BarcodeQRCodeReader.Result;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Runtime.InteropServices;

Initialize the controller:

[Route("dynamsoft/[controller]")]
[ApiController]
public class DbrController : ControllerBase
{
    private BarcodeQRCodeReader barcodeScanner;

    public DbrController()
    {
        barcodeScanner = BarcodeQRCodeReader.Create();
    }
}

Add an HTTP POST endpoint to decode barcodes from an uploaded image stream and return the reading results:

[HttpPost]
[Route("DecodeBarcode")]
public async Task<IActionResult> DecodeBarcode()
{
    if (Request.ContentType == null) return BadRequest("No content type specified");

    if (Request.ContentType.Contains("multipart/form-data"))
    {
        var form = await Request.ReadFormAsync();
        var file = form.Files[0];
        if (file.Length == 0)
        {
            return BadRequest("Empty file received");
        }

        using (var memoryStream = new MemoryStream())
        {
            await file.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            Mat mat = Mat.FromStream(memoryStream, ImreadModes.Color);

            int length = mat.Cols * mat.Rows * mat.ElemSize();
            byte[] bytes = new byte[length];
            Marshal.Copy(mat.Data, bytes, 0, length);
            BarcodeResult[]? results = barcodeScanner.DecodeBuffer(bytes, mat.Cols, mat.Rows, (int)mat.Step(), BarcodeQRCodeReader.ImagePixelFormat.IPF_RGB_888);
            if (results == null || results.Length == 0)
            {
                return Ok("No barcode found");
            }

            return Ok(results);
        }
    }
    else
    {
        return BadRequest("Unsupported content type");
    }
}

Dynamsoft Label Recognizer Endpoints

Import the packages:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Dynamsoft;
using static Dynamsoft.MrzScanner;
using Result = Dynamsoft.MrzScanner.Result;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using System.Runtime.InteropServices;

Initialize the controller:

[Route("dynamsoft/[controller]")]
[ApiController]
public class DlrController : ControllerBase
{
    private MrzScanner mrzScanner;

    public DlrController()
    {
        mrzScanner = MrzScanner.Create();
        mrzScanner.LoadModel();
    }
}

Add an HTTP POST endpoint to recognize MRZ (Machine Readable Zone) from an uploaded image stream and return the MRZ text results:

[HttpPost]
[Route("DetectMrz")]
public async Task<IActionResult> DetectMrz()
{
    if (Request.ContentType == null) return BadRequest("No content type specified");

    if (Request.ContentType.Contains("multipart/form-data"))
    {
        var form = await Request.ReadFormAsync();
        var file = form.Files[0];
        if (file.Length == 0)
        {
            return BadRequest("Empty file received");
        }

        using (var memoryStream = new MemoryStream())
        {
            await file.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            Mat mat = Mat.FromStream(memoryStream, ImreadModes.Color);

            int length = mat.Cols * mat.Rows * mat.ElemSize();
            byte[] bytes = new byte[length];
            Marshal.Copy(mat.Data, bytes, 0, length);
            Result[]? results = mrzScanner.DetectBuffer(bytes, mat.Cols, mat.Rows, (int)mat.Step(), MrzScanner.ImagePixelFormat.IPF_RGB_888);
            if (results == null || results.Length == 0)
            {
                return Ok("No MRZ found");
            }

            string[] lines = new string[results.Length];
            var index = 0;
            foreach (Result result in results)
            {
                lines[index++] = result.Text;
            }

            MrzResult info = MrzParser.Parse(lines);

            return Ok(info.ToJson());
        }
    }
    else
    {
        return BadRequest("Unsupported content type");
    }
}

Dynamsoft Document Normalizer Endpoints

Import the packages:

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Dynamsoft;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using static Dynamsoft.DocumentScanner;
using DocResult = Dynamsoft.DocumentScanner.Result;
using System.Runtime.InteropServices;

Initialize the controller:

public class DdnController : ControllerBase
{
    private DocumentScanner documentScanner;

    public DdnController()
    {
        documentScanner = DocumentScanner.Create();
        documentScanner.SetParameters(DocumentScanner.Templates.color);
    }
}

Add an HTTP POST endpoint to rectify a document from an uploaded image stream and return a rectified image:

[HttpPost]
[Route("rectifyDocument")]
public async Task<IActionResult> rectifyDocument()
{
    if (Request.ContentType == null) return BadRequest("No content type specified");

    if (Request.ContentType.Contains("multipart/form-data"))
    {
        var form = await Request.ReadFormAsync();
        var file = form.Files[0];
        if (file.Length == 0)
        {
            return BadRequest("Empty file received");
        }

        using (var memoryStream = new MemoryStream())
        {
            await file.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            Mat mat = Mat.FromStream(memoryStream, ImreadModes.Color);

            int length = mat.Cols * mat.Rows * mat.ElemSize();
            byte[] bytes = new byte[length];
            Marshal.Copy(mat.Data, bytes, 0, length);
            DocResult[]? results = documentScanner.DetectBuffer(bytes, mat.Cols, mat.Rows, (int)mat.Step(), DocumentScanner.ImagePixelFormat.IPF_RGB_888);

            if (results != null)
            {
                DocResult result = results[0];

                NormalizedImage image = documentScanner.NormalizeBuffer(bytes, mat.Cols, mat.Rows, (int)mat.Step(), DocumentScanner.ImagePixelFormat.IPF_RGB_888, result.Points);
                if (image != null && image.Data != null)
                {
                    Mat newMat;
                    if (image.Stride < image.Width)
                    {
                        // binary
                        byte[] data = image.Binary2Grayscale();
                        newMat = new Mat(image.Height, image.Width, MatType.CV_8UC1, data);
                    }
                    else if (image.Stride >= image.Width * 3)
                    {
                        // color
                        newMat = new Mat(image.Height, image.Width, MatType.CV_8UC3, image.Data);
                    }
                    else
                    {
                        // grayscale
                        newMat = new Mat(image.Height, image.Stride, MatType.CV_8UC1, image.Data);
                    }

                    byte[] buffer;
                    Cv2.ImEncode(".jpg", newMat, out buffer);
                    return File(buffer, "application/octet-stream");
                }

            }
        }

        return Ok("No document found");
    }
    else
    {
        return BadRequest("Unsupported content type");
    }
}

REST API Table

Method Endpoint Parameters Response Description
GET /dynamsoft/product - Product list Retrieve all Dynamsoft products.
GET /dynamsoft/dwt/GetDevices - Scanner list List available scanners.
POST /dynamsoft/dwt/ScanDocument Scanner config Job ID Initiate a scanning job.
GET /dynamsoft/dwt/GetImageStream/{jobId} jobId Image stream Fetch image stream for a specific job.
POST /dynamsoft/dbr/DecodeBarcode Image stream Barcode results Decode barcodes using the Dynamsoft Barcode Reader.
POST /dynamsoft/ddn/rectifyDocument Image stream Mrz results Detect MRZ from an image with Dynamsoft Label Recognizer.
POST /dynamsoft/dlr/DetectMrz Image stream Rectified document image Rectify a document with Dynamsoft Document Normalizer.

Creating an HTML5 Web App for Testing the RESTful Service

Create an input element for entering the RESTful service URL. As the connection is established, the available products will be listed in the dropdown menu.

HTML

<div class="row">
    <div>
        <label>Enter host address: </label>
        <input type="text" id="host" placeholder="http://192.168.8.72:5000/">
        <button onclick="connect()">connect</button>
    </div>
</div>

<div class="row">
    <div>
        <select onchange="selectChanged()" id="dropdown">
        </select>
    </div>
</div>

JavaScript

let host = placeholder = 'http://192.168.8.72:5000/';
let dropdown = document.getElementById("dropdown");
function selectChanged() {
    switchProduct(dropdown.value)
}

async function connect() {
    dropdown.innerHTML = "";
    host = document.getElementById("host").value == "" ? placeholder : document.getElementById("host").value;

    try {
        const response = await fetch(host + 'dynamsoft/product', {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        });

        if (response.status == 200) {
            const products = await response.json();

            products.forEach(element => {
                let optionElement = document.createElement("option");
                optionElement.value = element;
                optionElement.text = element;
                dropdown.appendChild(optionElement);
            });
            switchProduct(dropdown.value)
        }
        else {
            hideAll();
        }

    } catch (error) {
        console.log(error);
    }
}

Create a container for testing Dynamic Web TWAIN endpoints

HTML

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

JavaScript

let selectSources = document.getElementById("sources");
let devices = [];

async function getDevices() {
    document.getElementById("loading-indicator").style.display = "flex";
    selectSources.innerHTML = "";
    let url = host + 'dynamsoft/dwt/getdevices';
    const response = await fetch(url, { "method": "GET" });

    if (response.status == 200) {

        try {
            let json = await response.json();
            if (json) {
                devices = json;
                json.forEach(element => {
                    let option = document.createElement("option");
                    option.text = element['name'];
                    option.value = element['name'];
                    selectSources.add(option);
                });
            }
        } catch (error) {
            console.log(error)
        }

    }
    document.getElementById("loading-indicator").style.display = "none";
}

async function acquireImage() {
    let url = host + 'dynamsoft/dwt/ScanDocument';
    if (devices.length > 0 && selectSources.selectedIndex >= 0) {
        let parameters = {
            device: devices[selectSources.selectedIndex]['device'],
            config: {
                IfShowUI: false,
                PixelType: 2,
                //XferCount: 1,
                //PageSize: 1,
                Resolution: 200,
                IfFeederEnabled: false,
                IfDuplexEnabled: false,
            }
        };

        let response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(parameters)
        });

        const contentType = response.headers.get('Content-Type');
        const result = await response.text();

        url = host + 'dynamsoft/dwt/GetImageStream/' + result;

        response = await fetch(url, { method: 'GET' });

        let imageBlob = await response.blob();
        let img = document.getElementById('scanner-image');
        url = URL.createObjectURL(imageBlob);
        img.src = url;

        let option = document.createElement("option");
        option.selected = true;
        option.text = url;
        option.value = url;

        let thumbnails = document.getElementById("thumb-box");
        let newImage = document.createElement('img');
        newImage.setAttribute('src', url);
        if (thumbnails != null) {
            thumbnails.appendChild(newImage);
            newImage.addEventListener('click', e => {
                if (e != null && e.target != null) {
                    let target = e.target;
                    img.src = target.src;
                }
            });
        }
    }

}

Create a container for testing Dynamsoft Barcode Reader endpoints

HTML

<div class="container" id="dbr">
    <div class="row">
        <div>
            <input type="file" id="barcode-file" accept="image/*" />
            <button onclick="decodeBarcode()">Read Barcode</button>
        </div>

    </div>

    <div class="row">
        <div id="imageview">
            <img id="barcode-image" src="https://via.placeholder.com/640x480" />
        </div>
    </div>

    <div class="row">
        <div>
            <textarea id="barcode-result"></textarea>
        </div>
    </div>

</div>

JavaScript

document.getElementById("barcode-file").addEventListener("change", function () {
    document.getElementById('barcode-result').innerHTML = '';
    let currentFile = this.files[0];
    if (currentFile == null) {
        return;
    }
    var fr = new FileReader();
    fr.onload = function () {
        let image = document.getElementById('barcode-image');
        image.src = fr.result;
    }
    fr.readAsDataURL(currentFile);
});

async function decodeBarcode() {
    let url = host + 'dynamsoft/dbr/DecodeBarcode';

    const input = document.getElementById('barcode-file');
    const file = input.files[0];

    if (!file) {
        return;
    }

    const formData = new FormData();
    formData.append('image', file);

    let response = await fetch(url, {
        method: 'POST',
        body: formData
    });

    if (response.headers.get('Content-Type').includes('application/json')) {
        let data = await response.json();
        let content = 'total barcode(s) found: ' + data.length;
        let index = 0;
        data.forEach(element => {
            content += '\n';
            content += index + ". text: " + element['text'] + ", format: " + element['format1'];
            index += 1;
        });
        document.getElementById('barcode-result').innerHTML = content;
    }
    else if (response.headers.get('Content-Type').includes('text/plain')) {
        let data = await response.text();
        document.getElementById('barcode-result').innerHTML = data;
    }
}

Create a container for testing Dynamsoft Label Recognizer endpoints

HTML

<div class="container" id="dlr">
    <div class="row">
        <div>
            <input type="file" id="mrz-file" accept="image/*" />
            <button onclick="detectMrz()">Detect MRZ</button>
        </div>

    </div>

    <div class="row">
        <div id="imageview">
            <img id="mrz-image" src="https://via.placeholder.com/640x480" />
        </div>
    </div>

    <div class="row">
        <div>
            <textarea id="mrz-result"></textarea>
        </div>
    </div>

</div>

JavaScript

document.getElementById("mrz-file").addEventListener("change", function () {
    document.getElementById('barcode-result').innerHTML = '';
    let currentFile = this.files[0];
    if (currentFile == null) {
        return;
    }
    var fr = new FileReader();
    fr.onload = function () {
        let image = document.getElementById('mrz-image');
        image.src = fr.result;
    }
    fr.readAsDataURL(currentFile);
});

async function detectMrz() {
    let url = host + 'dynamsoft/dlr/DetectMrz';

    const input = document.getElementById('mrz-file');
    const file = input.files[0];

    if (!file) {
        return;
    }

    const formData = new FormData();
    formData.append('image', file);

    let response = await fetch(url, {
        method: 'POST',
        body: formData
    });

    if (response.headers.get('Content-Type').includes('application/json')) {
        let data = await response.json();
        document.getElementById('mrz-result').innerHTML = JSON.stringify(data);
    }
    else if (response.headers.get('Content-Type').includes('text/plain')) {
        let data = await response.text();
        document.getElementById('mrz-result').innerHTML = data;
    }
}

Create a container for testing Dynamsoft Document Normalizer endpoints

HTML

<div class="container" id="ddn">
    <div class="row">
        <div>
            <input type="file" id="document-file" accept="image/*" />
            <button onclick="rectifyDocument()">Rectify Document</button>
        </div>

    </div>

    <div class="row">
        <div id="imageview">
            <img id="document-image" src="https://via.placeholder.com/640x480" />
        </div>
    </div>

    <div class="row">
        <div>
            <textarea id="document-result"></textarea>
            <img id="document-rectified-image" />
        </div>
    </div>

</div>

JavaScript

document.getElementById("document-file").addEventListener("change", function () {
    let currentFile = this.files[0];
    if (currentFile == null) {
        return;
    }
    var fr = new FileReader();
    fr.onload = function () {
        let image = document.getElementById('document-image');
        image.src = fr.result;
    }
    fr.readAsDataURL(currentFile);
});

async function rectifyDocument() {
    let url = host + 'dynamsoft/ddn/rectifyDocument';

    const input = document.getElementById('document-file');
    const file = input.files[0];

    if (!file) {
        return;
    }

    const formData = new FormData();
    formData.append('image', file);

    let response = await fetch(url, {
        method: 'POST',
        body: formData
    });

    if (response.headers.get('Content-Type').includes('text/plain')) {
        let data = await response.text();
        document.getElementById('document-result').innerHTML = data;

        let divElement = document.getElementById("document-result");
        divElement.style.display = "block";

        divElement = document.getElementById("document-rectified-image");
        divElement.style.display = "none";
    }
    else if (response.headers.get('Content-Type').includes('application/octet-stream')) {
        let data = await response.blob();
        let img = document.getElementById('document-rectified-image');
        let url = URL.createObjectURL(data);
        img.src = url;

        let divElement = document.getElementById("document-rectified-image");
        divElement.style.display = "block";

        divElement = document.getElementById("document-result");
        divElement.style.display = "none";
    }
}

Source Code

https://github.com/yushulx/dynamsoft-sdk-dotnet-webapi-restful-service