Twain.Wia.Sane.Scanner 2.1.0

dotnet add package Twain.Wia.Sane.Scanner --version 2.1.0
                    
NuGet\Install-Package Twain.Wia.Sane.Scanner -Version 2.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Twain.Wia.Sane.Scanner" Version="2.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Twain.Wia.Sane.Scanner" Version="2.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Twain.Wia.Sane.Scanner" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Twain.Wia.Sane.Scanner --version 2.1.0
                    
#r "nuget: Twain.Wia.Sane.Scanner, 2.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Twain.Wia.Sane.Scanner@2.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Twain.Wia.Sane.Scanner&version=2.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Twain.Wia.Sane.Scanner&version=2.1.0
                    
Install as a Cake Tool

.NET Document Scanner for TWAIN, WIA, SANE, ICA, and eSCL

This .NET package provides a wrapper for calling the Dynamic Web TWAIN Service REST API. It enables developers to create desktop or cross-platform applications to scan and digitize documents using:

  • TWAIN (32-bit / 64-bit)
  • WIA (Windows Image Acquisition)
  • SANE (Linux)
  • ICA (macOS)
  • eSCL (AirScan / Mopria)

Demo Video

https://github.com/yushulx/dotnet-twain-wia-sane-scanner/assets/2202306/1046f5f4-2009-4905-95b5-c750195df715


โš™๏ธ Prerequisites

โœ… Install Dynamic Web TWAIN Service

๐Ÿ”‘ Get a License

Request a free trial license.


๐Ÿงฉ Configuration

After installation, open http://127.0.0.1:18625/ in your browser to configure the host and port settings.

By default, the service is bound to 127.0.0.1. To access it across the LAN, change the host to your local IP (e.g., 192.168.8.72).

dynamic-web-twain-service-config


๐Ÿ“ก REST API Endpoints

https://www.dynamsoft.com/web-twain/docs/info/api/restful.html

๐Ÿงช Quick Start

Replace the license key in the following code and run it in a .NET project:

using Newtonsoft.Json;
using Twain.Wia.Sane.Scanner;

public class Program
{
    private static string licenseKey = "LICENSE-KEY";
    private static ScannerController scannerController = new ScannerController();
    private static List<Dictionary<string, object>> devices = new List<Dictionary<string, object>>();
    private static string host = "http://127.0.0.1:18622";
    private static string questions = @"
Please select an operation:
1. Get scanners
2. Acquire documents by scanner index
3. Quit
";

    public static async Task Main()
    {
        var info = await scannerController.GetServerInfo(host);
        Console.WriteLine($"Server info: {info}");
        await AskQuestion();
    }

    private static async Task<int> AskQuestion()
    {
        while (true)
        {
            Console.WriteLine(".............................................");
            Console.WriteLine(questions);
            string? answer = Console.ReadLine();

            if (string.IsNullOrEmpty(answer))
            {
                continue;
            }

            if (answer == "3")
            {
                break;
            }
            else if (answer == "1")
            {
                var scannerInfo = await scannerController.GetDevices(host, ScannerType.TWAINSCANNER | ScannerType.TWAINX64SCANNER);
                devices.Clear();

                try { 
                    var scanners = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(scannerInfo);
                    for (int i = 0; i < scanners.Count; i++)
                    {
                        var scanner = scanners[i];
                        devices.Add(scanner);
                        Console.WriteLine($"\nIndex: {i}, Name: {scanner["name"]}");
                    }
                } catch (Exception ex) { 
                   Console.WriteLine($"Error: {ex.Message}");
                }
                
            }
            else if (answer == "2")
            {
                if (devices.Count == 0)
                {
                    Console.WriteLine("Please get scanners first!\n");
                    continue;
                }

                Console.Write($"\nSelect an index (<= {devices.Count - 1}): ");
                int index;
                if (!int.TryParse(Console.ReadLine(), out index))
                {
                    Console.WriteLine("Invalid input. Please enter a number.");
                    continue;
                }

                if (index < 0 || index >= devices.Count)
                {
                    Console.WriteLine("It is out of range.");
                    continue;
                }

                var parameters = new Dictionary<string, object>
                {
                    {"license", licenseKey},
                    {"device", devices[index]["device"]},
                    {"autoRun", true}
                };

                parameters["config"] = new Dictionary<string, object>
                {
                    {"IfShowUI", false},
                    {"PixelType", 2},
                    {"Resolution", 200},
                    {"IfFeederEnabled", false},
                    {"IfDuplexEnabled", false}
                };

                var jobInfo = await scannerController.CreateJob(host, parameters);
                string jobId = "";
                try
                {
                    var job = JsonConvert.DeserializeObject<Dictionary<string, object>>(jobInfo);
                    jobId = (string)job["jobuid"];

                    if (string.IsNullOrEmpty(jobId))
                    {
                        Console.WriteLine("Failed to create job.");
                        continue;
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error: {ex.Message}");
                    continue;
                }

                var fetchTasks = new List<Task>();
                int imageIndex = 0;

                while (true)
                {
                    var imageInfo = await scannerController.GetImageInfo(host, jobId);
                    if (string.IsNullOrEmpty(imageInfo)) break;

                    Dictionary<string, object>? image;
                    try { image = JsonConvert.DeserializeObject<Dictionary<string, object>>(imageInfo); }
                    catch { break; }

                    if (image == null || !image.ContainsKey("url")) break;

                    int currentIndex = imageIndex++;
                    fetchTasks.Add(Task.Run(async () =>
                    {
                        string filename = await scannerController.GetImageFileByIndex(host, jobId, currentIndex, "./", "image/jpeg");
                        Console.WriteLine($"Image {currentIndex}: {filename}");
                    }));
                }

                await Task.WhenAll(fetchTasks);
                await scannerController.DeleteJob(host, jobId);
            }
            else
            {
                continue;
            }
        }
        return 0;
    }
}

๐Ÿ“ Examples


๐Ÿงฉ API Reference

๐ŸŽ›๏ธ Scanner APIs

  • GetDevices(string host, int? scannerType = null) โ€” Returns a JSON string listing all available scanner devices. Optionally filter by scanner type using the ScannerType constants (e.g., ScannerType.TWAINSCANNER | ScannerType.WIASCANNER).
  • GetDevicesHttpResponse(string host, int? scannerType = null) โ€” Returns the raw HttpResponseMessage from the devices endpoint. Use when you need access to HTTP status codes or headers directly.
  • CreateJob(string host, Dictionary<string, object> parameters) โ€” Creates a scan job with the specified parameters (license key, device ID, scanner config) and returns a JSON string containing the jobuid.
  • CreateJobHttpResponse(string host, Dictionary<string, object> parameters) โ€” Returns the raw HttpResponseMessage for job creation.
  • CheckJob(string host, string jobId) โ€” Returns the current status of a scan job as a JSON string (e.g., running, completed, canceled).
  • UpdateJob(string host, string jobId, Dictionary<string, object> parameters) โ€” Updates a job using HTTP PATCH, e.g., to change the status to JobStatus.CANCELED.
  • DeleteJob(string host, string jobId) โ€” Deletes a scan job and frees its resources on the service. Returns the HttpResponseMessage.
  • GetScannerCapabilities(string host, string jobId) โ€” Returns the scanner's capabilities (resolution ranges, pixel types, duplex support, etc.) as a JSON string.
  • GetImageInfo(string host, string jobId) โ€” Polls the service for metadata about the next available scanned page. Blocks until a page is ready and returns a JSON string containing the page url and other attributes. Returns an empty string when no more pages are available.
  • GetServerInfo(string host) โ€” Returns the Dynamic Web TWAIN Service version information as a JSON string.

๐Ÿ“ธ Image APIs

Blocking

These methods use the /next-page endpoint, which blocks until the scanner delivers the next page. They process pages sequentially and return 204 / empty when scanning is complete.

  • GetImageStreamHttpResponse(string host, string jobId) โ€” Returns the raw HttpResponseMessage from the blocking /next-page endpoint. Status 200 means a page is ready; 204 means scanning is complete.
  • GetImageStream(string host, string jobId) โ€” Fetches the next scanned page as a byte[]. Returns an empty array when no more pages are available.
  • GetImageStreams(string host, string jobId) โ€” Repeatedly calls GetImageStream until no more pages are available and returns all page bytes as List<byte[]>.
  • GetImageFile(string host, string jobId, string directory) โ€” Saves the next scanned page to a JPEG file in directory. Returns the filename, or an empty string when scanning is complete.
  • GetImageFiles(string host, string jobId, string directory) โ€” Repeatedly calls GetImageFile until scanning is complete and returns the list of all saved filenames.
Non-blocking

These methods use the /content?page={index} endpoint, which returns immediately. Pair with GetImageInfo in a loop to detect when each page is ready, then fetch pages concurrently with Task.Run.

  • GetImageContentHttpResponse(string host, string jobId, int index, string imageType = "image/jpeg") โ€” Returns the raw HttpResponseMessage for the page at the given zero-based index. Status 200 means the page is available; 204 means no page exists at that index yet. Supports "image/jpeg" and "image/png".
  • GetImageFileByIndex(string host, string jobId, int index, string directory, string imageType = "image/jpeg") โ€” Saves the page at index to disk using the non-blocking content API. Returns the saved filename (e.g., image_0.jpg), or an empty string on 204 or error.

๐Ÿ“„ Document APIs

  • CreateDocument(string host, Dictionary<string, object> parameters) โ€” Creates a new document container in the service and returns a JSON string with the document uid.
  • GetDocumentInfo(string host, string docId) โ€” Returns document metadata (pages, creation time, etc.) as a JSON string.
  • DeleteDocument(string host, string docId) โ€” Deletes a document and all its pages from the service. Returns the HttpResponseMessage.
  • GetDocumentFile(string host, string docId, string directory) โ€” Downloads the document as a PDF and saves it to directory. Returns the saved filename.
  • GetDocumentStream(string host, string docId) โ€” Downloads the document content as a raw byte[] (PDF format).
  • InsertPage(string host, string docId, Dictionary<string, object> parameters) โ€” Inserts a scanned image into a document. Required parameters: source (image URL from GetImageInfo) and password (empty string if none). Returns a JSON string with the updated page list.
  • DeletePage(string host, string docId, string pageId) โ€” Deletes a specific page from a document by its page UID. Returns the HttpResponseMessage.

๐Ÿ“ฆ Build the NuGet Package

dotnet build --configuration Release
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0 149 3/11/2026
2.0.1 930 5/8/2025
2.0.0 304 4/1/2025
1.2.0 571 8/15/2024
1.1.0 736 10/25/2023
1.0.1 295 10/10/2023
1.0.0 294 10/10/2023

- Added GetImageContentHttpResponse: non-blocking page fetch by zero-based index via /content?page={index} endpoint.
     - Added GetImageFileByIndex: saves a scanned page to disk by index using the non-blocking API.