Blazor is a framework for building an interactive client-side web interface with .NET1. Users can create interactive interfaces in C # instead of JavaScript. Server-side and client-side application logic can be written to a .NET file.

In this article, we are going to build a web document scanner Dynamic Network TWAIN (DWT). Dynamic Web TWAIN is a JavaScript library designed for scanning documents and managing documents. We can easily integrate it with Blazor.

Build an online document reader with Blazor

There are two types of Blazor applications: Blazor WebAssembly and Blazor Server.

Blazor WebAssembly applications run purely on the client side. C # code files and Razor files are compiled into .NET configurations. Configurations and .NET runtime are loaded into the browser.

Blazor server applications have a real-time connection (Signal R) to the server. The server executes the C # code in the application and updates the user interface on the client side.

You can learn about the advantages and disadvantages of Blazor WebAss Assembly and Blazor Server at University of Blazor.

In this article, we are going to create a WebAssembly version as well as a server version.

Web document reader with Blazor WebAssembly

  1. Open Visual Studio, create a Blazor WebAssembly project. Be sure to select .NET 5.

  2. download and install Dynamic Web TWAIN. Copy Resources folder wwwroot folder.

  3. Change dynamsoft.webtwain.config.js that Resources folder. Establish ProductKey and change Dynamsoft.DWT.AutoLoad incorrect because we want to load the DWT manually.

  4. Change wwwroot/index.html.

    Include DWT JavaScript files in the header:

     <script src="https://dzone.com/articles/Resources/dynamsoft.webtwain.initiate.js"></script>
     <script src="Resources/dynamsoft.webtwain.config.js"></script>
    

    Add the following JavaScript to interact with the C # code in the body (read more about JavaScript interoperability here):

    <script type="text/javascript">
         var DWObject = null;
    
         function CreateDWT() {
             var height = 580;
             var width = 500;
    
             if (Dynamsoft.Lib.env.bMobile) {
                 height = 350;
                 width = 270;
             }
    
             Dynamsoft.DWT.CreateDWTObjectEx({
                 WebTwainId: 'dwtcontrol'
             },
                 function (obj) {
                     DWObject = obj;
                     DWObject.Viewer.bind(document.getElementById('dwtcontrolContainer'));
                     DWObject.Viewer.height = height;
                     DWObject.Viewer.width = width;
                     DWObject.Viewer.show();
                 },
                 function (err) {
                     console.log(err);
                 }
             );
         }
    
         function Scan() {
             if (DWObject) {
                 DWObject.SelectSource(function () {
                     DWObject.OpenSource();
                     DWObject.AcquireImage();
                 },
                     function () {
                         console.log("SelectSource failed!");
                     }
                 );
             }
         }
    
         function LoadImage() {
             if (DWObject) {
                 DWObject.LoadImageEx('', 5,
                     function () {
                         console.log('success');
                     },
                     function (errCode, error) {
                         alert(error);
                     }
                 );
             }
         }
    
         function Save() {
             DWObject.IfShowFileDialog = true;
             // The path is selected in the dialog, therefore we only need the file name
             DWObject.SaveAllAsPDF("Sample.pdf",
                 function () {
                     console.log('Successful!');
                 },
                 function (errCode, errString) {
                     console.log(errString);
                 }
             );
         }
    
         function isDesktop() {
             if (Dynamsoft.Lib.env.bMobile) {
                 return false;
             } else {
                 return true;
             }
         }
    
     </script>

  5. Add a new Razor component Scanner.razor with the following code:

    @inject IJSRuntime JS
     @page "/scanner"
    
     <h1>Scanner</h1>
    
     @if (isDesktop)
     {
     <button class="btn btn-primary" @onclick="Scan">Scan</button>
     }
     <button class="btn btn-primary" @onclick="LoadImage">Load Image</button>
     <button class="btn btn-primary" @onclick="Save">Save</button>
    
     <div id="dwtcontrolContainer"></div>
    
     @code{
         private Boolean isDesktop;
         protected override async void OnInitialized()
         {
             await CreateDWT();
             isDesktop = await JS.InvokeAsync<Boolean>("isDesktop");
             StateHasChanged();
         }
    
         private async Task CreateDWT()
         {
             await JS.InvokeVoidAsync("CreateDWT");
         }
    
         private async Task Scan()
         {
             await JS.InvokeVoidAsync("Scan");
         }
    
         private async Task LoadImage()
         {
             await JS.InvokeVoidAsync("LoadImage");
         }
    
         private async Task Save()
         {
             await JS.InvokeVoidAsync("Save");
         }
     }

    Add a component Index.razor:

    @page "https://dzone.com/"
    <h1>Hello, world!</h1>
    Welcome to your new app.
    <Scanner/>
    

  6. Run the application and see the result. When the application is running, any changes made to the source code are detected and the application is updated. The application can capture documents from the scanner as well as download local images. It can create a PDF output to save the result.
  7. We can also use the application with the following command:

    We can use the app on our mobile phones if the phone and server are on the same network.

    We may also need to change Properties/launchSettings.json so that the application is available outside the local network.

    Replace this line:

     "applicationUrl": "https://localhost:5001;http://localhost:5000",
    

    Over here:

     "applicationUrl": "https://+:5001;http://+:5000",
    

    Because mobile phones cannot directly control the scanners, the scan button will not load. Whether the device is recognized by a mobile device Dynamsoft.Lib.env.bMobile.

Web document scanner with Blazor server

Creating a Blazor server version is about the same.

The difference is that it is not Index.html and instead is _Host.cshtml. We need to add DWT JavaScript to this file.

The website is connected to the server. If it loses connection to the server, the modem will appear and the page will not work.

JavaScript isolation

.NET 5 has a new feature called JavaScript isolation.

JS isolation offers the following advantages2:

  • The imported JS no longer pollutes the global namespace.
  • Consumers of the library and components do not need to bring the associated JS.

PS: Using JavaScript isolation may cause the webpage to not work in older browsers.

Let’s use JavaScript isolation in the WebAssembly project we just made.

  1. Create a new JS file named DWT.js below wwwroot/js with the following content:

    var DWObject = null;
    
     export function CreateDWT() {
         var height = 580;
         var width = 500;
    
         if (Dynamsoft.Lib.env.bMobile) {
             height = 350;
             width = 270;
         }
    
         Dynamsoft.DWT.CreateDWTObjectEx({
             WebTwainId: 'dwtcontrol'
         },
             function (obj) {
                 DWObject = obj;
                 DWObject.Viewer.bind(document.getElementById('dwtcontrolContainer'));
                 DWObject.Viewer.height = height;
                 DWObject.Viewer.width = width;
                 DWObject.Viewer.show();
             },
             function (err) {
                 console.log(err);
             }
         );
     }
    
     export function Scan() {
         if (DWObject) {
             DWObject.SelectSource(function () {
                 DWObject.OpenSource();
                 DWObject.AcquireImage();
             },
                 function () {
                     console.log("SelectSource failed!");
                 }
             );
         }
     }
    
     export function LoadImage() {
         if (DWObject) {
             DWObject.LoadImageEx('', 5,
                 function () {
                     console.log('success');
                 },
                 function (errCode, error) {
                     alert(error);
                 }
             );
         }
     }
    
     export function Save() {
         DWObject.IfShowFileDialog = true;
         // The path is selected in the dialog, therefore we only need the file name
         DWObject.SaveAllAsPDF("Sample.pdf",
             function () {
                 console.log('Successful!');
             },
             function (errCode, errString) {
                 console.log(errString);
             }
         );
     }
    
     export function isDesktop() {
         if (Dynamsoft.Lib.env.bMobile) {
             return false;
         } else {
             return true;
         }
     }

    JavaScript we added wwwroot/index.html can now be deleted.

  2. Change Scanner.razor use the JS file.

    @page "/scanner"
     @implements IAsyncDisposable
     @inject IJSRuntime JS
    
     <h1>Scanner</h1>
    
     @if (isDesktop)
     {
     <button class="btn btn-primary" @onclick="Scan">Scan</button>
     }
     <button class="btn btn-primary" @onclick="LoadImage">Load Image</button>
     <button class="btn btn-primary" @onclick="Save">Save</button>
    
     <div id="dwtcontrolContainer"></div>
    
    
    
     @code{ 
         private Boolean isDesktop;
         private IJSObjectReference module;
         protected override async void OnAfterRender(bool firstRender)
         {
             if (firstRender)
             {
                 module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/DWT.js");
                 isDesktop = await IsDesktop();
                 StateHasChanged();
                 await CreateDWT();
             }
         }
            
         private async Task CreateDWT()
             {
                 await module.InvokeVoidAsync("CreateDWT");
         }
    
         private async Task Scan()
         {
             await module.InvokeVoidAsync("Scan");
         }
    
         private async Task LoadImage()
         {
             await module.InvokeVoidAsync("LoadImage");
         }
    
         private async Task Save()
         {
             await module.InvokeVoidAsync("Save");
         }
    
         private async Task<Boolean> IsDesktop()
         {
             return await module.InvokeAsync<Boolean>("isDesktop");
         }
    
         async ValueTask IAsyncDisposable.DisposeAsync()
         {
             await module.DisposeAsync();
         }
     }

Create a C # wrapper

We can take this a step further by creating a wrapper to make it easier to use.

  1. Create a C # class file named DWT.

  2. Add the following code to the category file:

    public class DWT
     {
         private IJSObjectReference module;
         [Inject]
         IJSRuntime JS { get; set; }
         private DWT()
         {
         }
         public static async Task<DWT> CreateAsync(IJSRuntime JS)
         {
             DWT dwt = new DWT();
             dwt.JS = JS;
             await dwt.LoadJSAsync();
             return dwt;
         }
    
         private async Task LoadJSAsync()
         {
             module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/DWT.js");
         }
    
         public async Task CreateDWT()
         {
             await module.InvokeVoidAsync("CreateDWT");
         }
    
         public async Task Scan()
         {
             await module.InvokeVoidAsync("Scan");
         }
    
         public async Task LoadImage()
         {
             await module.InvokeVoidAsync("LoadImage");
         }
    
         public async Task Save()
         {
             await module.InvokeVoidAsync("Save");
         }
    
         public async Task<Boolean> IsDesktop()
         {
             return await module.InvokeAsync<Boolean>("isDesktop");
         }
    
         protected virtual async ValueTask DisposeAsync()
         {
             await module.DisposeAsync();
         }
     }

    Since the constructor cannot be asynchronous, we create a CreateAsync way to create instances3.

  3. Update Scanner.razor use DWT class:

    @inject IJSRuntime JS
     @page "/scanner"
    
     <h1>Scanner</h1>
    
     @if (isDesktop)
     {
     <button class="btn btn-primary" @onclick="Scan">Scan</button>}
     <button class="btn btn-primary" @onclick="LoadImage">Load Image</button>
     <button class="btn btn-primary" @onclick="Save">Save</button>
    
     <div id="dwtcontrolContainer"></div>
    
     @code{ 
         private Boolean isDesktop;
         private DWT dwt;
         protected override async void OnAfterRender(bool firstRender)
         {
             if (firstRender)
             {
                 dwt = await DWT.CreateAsync(JS);
                 isDesktop = await dwt.IsDesktop();
                 StateHasChanged();
                 await dwt.CreateDWT();
             }
         }
    
         private async Task Scan()
         {
             await dwt.Scan();
         }
    
         private async Task LoadImage()
         {
             await dwt.LoadImage();
         }
    
         private async Task Save()
         {
             await dwt.Save();
         }
     }

Source code

https://github.com/xulihang/BlazorTWAIN

References

  1. https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-5.0
  2. https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet?view=aspnetcore-5.0#javascript-isolation-in-javascript-modules
  3. https://stackoverflow.com/questions/25816064/how-can-i-await-a-task-within-a-class-constructor-timer-callback

.

LEAVE A REPLY

Please enter your comment!
Please enter your name here