Building Cross-platform DotNet Core Document Scanning with MVC

ASP.NET Core enables C# application to run on *nix operating systems. In this tutorial, we are going to play with ASP.NET Core and Dynamic Web TWAIN SDK to build a DotNet Core document scanning application for Windows, Linux, and macOS.

What You Should Know


First and foremost, we should install Dynamic Web TWAIN in our environment. If you have never installed the library, please refer to to download the latest version.

Secondly, we need to install .NET Core SDK. Please keep in mind that developers need the SDK instead of Runtime. The download page can visit from

We highly recommend Windows developers to use Visual Studio since it can automate some steps for us.

Creating ASP DotNet Core Document Scanning Project

Our first step is to create the project. We have two approaches to develop the project.

Creating .NET Core Project With Visual Studio

For Windows users, creating an ASP.NET Core MVC application is as simple as pouring a Java mug. Visual Studio provides a project creation guide for developers. When you launch the Visual Studio, you will see the welcome dialog on the right-bottom button to create a new project.

Select the last option of the right panel (which is Get Started) on Welcome page

Select the last option of the right panel (Get Started) on Welcome page

Then, we select ASP.NET Core Web Application and go next.

Choose ASP.NET Core Web Application as the template

Choose ASP.NET Core Web Application as the template.

Finally, we have to specify the project name. We just created the DotNet Core document scanning project, but have not finished the initialization yet.

Name the project with "dwtDotCore"

Name the project with “dwtDotCore”.

Here, we recommend using the MVC model. The controller returns the scanning view directly. Therefore, we don’t have to write any code for our Home controller. We just need to implement an API to achieve file upload later.

Initialize the ASP.NET Core web application with Model-View-Controller template

Initialize the ASP.NET Core web application with Model-View-Controller template.

Once you select the model and hit the Create button, you finish creating the project.

Creating With CLI

Linux users can not benefit from Visual Studio, but there is a CLI tool to create a project.

Open a terminal and type the following command to create a project.

dotnet new webapp -o dwtDotNet --no-https

The command means creating a .NET Core application with template webapp, which is the template for ASP.NET Core MVC, with the name dwtDotNet, and disabling HTTPS.


Before heading into the development stage, we need to configure our project to ensure the dependencies could be loaded properly.

Copying Resource Files of Web TWAIN SDK

The resource files are located in your installation folder, which is ‘C:\Program Files (x86)\Dynamsoft\Dynamic Web TWAIN SDK <version>’. Copy the entire Resources folder and paste to ‘/wwwroot/lib’ in your project folder. It is better to rename the ‘Resources’ into a meaningful name, such as ‘dwt’.

Configuring Resources Path

Dynamic Web TWAIN relies on extra scripts and stylesheets to support its running. Developers should specify where these supporting files are. Going into the copied folder, you will see a file named ‘dynamsoft.webtwain.config.js’. Let’s open it and make some changes.

Firstly, uncomment the line of Dynamic.DWT.ResourcesPath. Then, specifying its value to ‘/wwwroot/lib/dwt’, which is the path of Dynamic Web TWAIN.

Dynamsoft.DWT.ResourcesPath = 'lib/dwt';
Dynamsoft.DWT.ProductKey = '<Your Product Key>';

Secondly, we have to tell WebTWAIN about the DOM name, which contains the Dynamic Web TWAIN viewer. You may want to assign another name or use the default one. It must be consistent between HTML and config.js.

Dynamsoft.DWT.Containers = [{ ContainerId: containerName, Width: viewerWidth, Height: viewerHeight }];


We are so excited because our configuration work is not much and has all been done. We can start to write down the first line of code. Before coding, let me introduce the structure of the project.

Project structure of our project

Project structure of our project

Our app is a typical Model-View-Controller app, in which the Controller is responsible for handling requests and responding with corresponding views. The model is the abstraction of our business. In this tutorial, we don’t have any data to store, so a model and a database are unnecessary. Note that dotnet CLI tool may not create the project with controllers, models, and views. If you are using Linux/macOS, or prefer CLI on Windows, you can use this as a reference to add them manually or refer to dotnet-aspnet-codegenerator tool. We will attach the source code at the end so that you can also build your application based on our demo.

We will implement the UI in Index.cshtml and logic in HomeController.cs.

Implementing The Scanning View

We delete the predefined code and put down our code in Index.cshtml.

<div class="container-fluid">
    <div id="control-panel">
        <button class="btn btn-primary" onclick="AcquireImage()">Scan</button>
        <br />
        <button class="btn btn-outline-secondary" onclick="Upload()">Upload</button>
        <input type="radio" value="jpg" name="format"/>JPG
        <input type="radio" value="pdf" name="format"/>PDF
        <input type="radio" value="tif" name="format"/>TIFF
        <label for="filename-input">File Name: </label>
        <input type="text" id="filename-input"/>
    <div id="dwt-container">


<!-- DWT script here -->
<script src="~/lib/dwt/dynamsoft.webtwain.initiate.js"></script>
<script src="~/lib/dwt/dynamsoft.webtwain.config.js"></script>
    var DWObj;
    var viewerWidth = 960, viewerHeight = 960;
    var containerName = 'dwt-container';
    Dynamsoft.DWT.ResourcesPath = 'lib/dwt';
    Dynamsoft.DWT.ProductKey = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //
    function initDWT() {
            Dynamsoft.DWT.UseLocalService = true;
            Dynamsoft.DWT.AutoLoad = true;
            Dynamsoft.DWT.Containers = [{ ContainerId: containerName, Width: viewerWidth, Height: viewerHeight }];
            Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', function () {
                DWObj = Dynamsoft.DWT.GetWebTwain(containerName);
                if (DWObj) {
                    DWObj.Width = viewerWidth;
                    DWObj.Height = viewerHeight;
                    DWObj.MouseShape = true;
                    @* addThumbnailViewer(); *@
    function AcquireImage() {
        if (DWObj) {
            if (DWObj.UseLocalService) {
                DWObj.SelectSource(function () {
                    var OnAcquireImageSuccess = OnAcquireImageFailure = function () {
                    DWObj.IfDisableSourceAfterAcquire = true
                    DWObj.AcquireImage(OnAcquireImageSuccess, OnAcquireImageFailure)
                }, function () {
                    console.log('SelectSource failed')
            } else {
                DWObj.LoadImageEx('', -1)
    function Upload() {
        const host = location.hostname
        const protocol = location.protocol
        const uploadPath = '/api/File'
        let uploadFileName = document.getElementById('filename-input').value
        const port = location.port || (protocol === 'https:' ? 443 : 80)

        var formatSelector = document.getElementsByName('format')
        let format = (selector => {
            let select = ''
            selector.forEach(e => {
                if (e.checked) {
                    select = e.value
            uploadFileName = uploadFileName + '.' + select
            switch (select) {
                case 'jpg': { return Dynamsoft.DWT.EnumDWT_ImageType.IT_JPG }
                case 'pdf': { return Dynamsoft.DWT.EnumDWT_ImageType.IT_PDF }
                case 'tif': { return Dynamsoft.DWT.EnumDWT_ImageType.IT_TIF }

        let uploadFormat = Dynamsoft.DWT.EnumDWT_UploadDataFormat.Binary

        if (DWObj) {
            DWObj.HTTPPort = port
            DWObj.IfSSL = true
            let indices = DWObj.SelectedImagesIndices
                protocol + '//' + host + ':' + port + uploadPath,
                () => { alert('success') },
                (errCode, errStr, res) => {
                    console.error(`${errCode}: ${errStr}. Server return: ${ res }`)

We put two buttons, one input box, and a group of radio selections on the page. Our application would provide scanning and uploading functionalities to the end-users. Just a reminder that the script files must be loaded sequentially. Besides, you may find other HTTP upload methods from API documentation on our product website. But we are going to unify them into HTTPUpload. It is better not to use other methods so that you don’t need to make significant changes once we deprecate those APIs.

In HomeController, we don’t have to do any manipulation. To implement the file upload feature, the server-side should be able to process files. We make it as an API to serve clients. Right-click on the Controller folder and move the cursor to Add option, then select “New Scaffolded item…”.

Select "New Scaffolded Item" to create a new controller

Select “New Scaffolded Item” to create a new controller.

In the popup window, we choose API Controller with read/write actions.

Create an API Controller with read/write actions

Create an API Controller with read/write actions.

Name the controller as FileController.cs.

Name the controller as “FileController.cs”.

Visual Studio will create the controller with some initial code. We do not need Get, Delete, and Put by far. So, we delete them from our source code.

Then, we add some code to the Post method. Don’t forget to resolve the dependencies issues.

    public class FileController : ControllerBase
        // POST api/<FileController>
        public async Task<IActionResult> Upload()
            var files = Request.Form.Files;
            var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload");
            if (!Directory.Exists(path))
                catch (Exception e)
                    return Unauthorized("not able to create");
            foreach (var uploadFile in files)
                var fileName = uploadFile.FileName;
                using (var stream = System.IO.File.Create(Path.Combine(path, fileName)))
                    await uploadFile.CopyToAsync(stream);
            return Ok();

Testing DotNet Core Document Scanning

Now, we have finished all the implementation work. It is time to check our outcome.

In Visual Studio, you run the application by simply hitting the Run button on the tool bar.

Hit the run button with label "IIS Express" to run the project

Hit the run button with label “IIS Express” to run the project.

Or, CLI users just enter the following commands to start your app.

dotnet restore
dotnet run

Once the service up, you would see the following page.

First page of the application

Here is the first page of the application that successfully loads Web TWAIN SDK.

Select the scanning source by clicking "Scan" button

We can select the scanning source by clicking “Scan” button.

DotNet core document scanning

Scan a document from the source.

DotNet core document upload

Upload the scanned document to the server and see your uploaded files in your upload destination folder.

The uploaded file located at the specified

Source Code