Build a Blazor WebAssembly Barcode and QR Code Scanner with Dynamsoft
Blazor is a powerful framework by Microsoft that enables developers to build interactive web applications using C# instead of JavaScript, bringing the full power of .NET to the browser. While Blazor allows you to write most of your web app in C#, calling existing JavaScript APIs remains an inevitable part of enhancing Blazor functionalities, especially when integrating with powerful libraries like the Dynamsoft JavaScript Barcode SDK. In this tutorial, we will show you how to build a .NET Blazor application for scanning 1D/2D barcodes by leveraging the interop between C# and JavaScript.
This article is Part 2 in a 5-Part Series.
- Part 1 - How to Scan and Digitize Documents in Blazor WebAssembly with C# and .NET
- Part 2 - Build a Blazor WebAssembly Barcode and QR Code Scanner with Dynamsoft
- Part 3 - How to Build a Blazor WebAssembly PDF Annotation Viewer with Dynamsoft
- Part 4 - Build a C# Blazor WebAssembly Passport MRZ Scanner with Dynamsoft
- Part 5 - How to Implement TWAIN Document Scanning in a .NET Blazor WebAssembly App
What you’ll build: A Blazor WebAssembly application that reads barcodes and QR codes from both static images and a live device camera stream, using Dynamsoft Capture Vision and C#–JavaScript interop.
Key Takeaways
- Blazor WebAssembly integrates the Dynamsoft JavaScript Barcode SDK via
IJSRuntime, enabling 1D/2D barcode decoding without leaving the .NET ecosystem. - The
CaptureVisionRouterAPI provides a unifiedcapture()interface for both image-file and live-camera barcode scanning. - JS-to-C# callbacks are wired through
DotNetObjectReference, so real-time barcode results update Blazor component state directly. - This architecture runs in any modern browser without native plugins, making it suitable for inventory, logistics, and ID-scanning web apps.
Common Developer Questions
How do I build a Blazor WebAssembly barcode scanner using JavaScript interop?
Initialize the Dynamsoft SDK from JavaScript, then call those interop functions from Blazor with IJSRuntime after the component renders. In this sample, Blazor owns the page state while JavaScript handles scanner initialization, file selection, camera access, and decoding.
How do I decode QR codes and barcodes from a webcam stream in a .NET Blazor app?
Bind CameraEnhancer as the input to CaptureVisionRouter, then pass results back into C# through DotNetObjectReference callbacks whenever a barcode is decoded. That is how the live camera stream updates the Razor component without polling from .NET.
Why is the Dynamsoft WASM module slow to load on first use in Blazor WebAssembly?
The first scan is slower when the barcode WebAssembly module is downloaded on demand instead of being preloaded. Calling CoreModule.loadWasm() during SDK setup shifts that cost earlier so the first real decode feels much faster.
See It in Action
Try the Live Demo Online
Try the Online Demo: Free Online Blazor Barcode, MRZ and Document Scanner — hosted on dynamsoft.com with an automatic license, no key needed.
Prerequisites
-
Dynamsoft Capture Vision Bundle: This bundle includes all Dynamsoft JavaScript imaging SDKs, designed to work seamlessly together. It is available on npm.

-
Dynamsoft Capture Vision Trial License: A trial license supports all features (including Barcode, MRZ, Document recognition, and more) of the Dynamsoft Capture Vision Bundle for 30 days. You can apply for a trial license here.
Step 1: Integrate Dynamsoft Capture Vision Bundle into a Blazor WebAssembly Project
- Create a Blazor WebAssembly Project: Start a new Blazor WebAssembly project in Visual Studio or Visual Studio Code.
- Add the Capture Vision Bundle Script: In the
wwwroot/index.htmlfile, include the Dynamsoft Capture Vision Bundle script:<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@3.6.3200/dist/dcv.bundle.min.js"></script> - Create a JavaScript File for Interop: Create a file named
jsInterop.jsin thewwwrootfolder and include it in theindex.htmlas an ES module:<script type="module" src="jsInterop.js"></script> - Configure JavaScript to Set the License: In
jsInterop.js, define asetLicensefunction that specifies the resource paths for the dependencies and sets the license key:window.jsFunctions = { setLicense: async function setLicense(license) { if (isInitialized) return true; try { // Load every engine resource from the jsDelivr CDN copy of the SDK. Dynamsoft.Core.CoreModule.engineResourcePaths = { dcvBundle: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@3.6.3200/dist/", dcvData: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-data@1.2.2/" }; await Dynamsoft.License.LicenseManager.initLicense(license, true); await Dynamsoft.Core.CoreModule.loadWasm(); ... } catch (e) { alert(e); return false; } return true; } };The
loadWasmmethod preloads the WebAssembly modules for the SDK. Capture Vision Router 3.6 expects them to be loaded explicitly; relying on lazy loading can leave the firstcapture()unresolved. Note thatinitLicensemust be awaited before loading the wasm modules. -
Update the Blazor Page to Activate the SDK: Modify the
Pages/Home.razorfile to include HTML and C# code that allows users to activate the SDK with a valid license key:@page "/" @inject IJSRuntime JSRuntime <PageTitle>Blazor Capture Vision</PageTitle> <div class="card"> <h2>Activate the SDK</h2> <p class="card-note"> Get a license key from <a href="https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform" target="_blank" rel="noopener">Dynamsoft</a> and paste it below. An SDK trial key is pre-filled, so you can also just press the button. </p> <div class="license-row"> <input type="text" @bind="LicenseKey" placeholder="Enter your license key" aria-label="License key" /> <button type="button" class="btn-primary" @onclick="SetLicenseKey">Activate SDK</button> </div> </div> @code { private bool initialized = false; private string LicenseKey = "LICENSE-KEY"; private async Task SetLicenseKey() { initialized = await JSRuntime.InvokeAsync<bool>("jsFunctions.setLicense", LicenseKey); StateHasChanged(); } }
Step 2: Implement a Barcode Reader Razor Page
- Create the Barcode Reader Component: Create a new Razor component called
Reader.razorin the Pages folder and add it to your navigation menu inNavMenu.razor:<div class="nav-item"> <NavLink class="nav-link" href="barcodereader"> <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Barcode Reader </NavLink> </div>Ensure that the
hrefattribute value (barcodereader) matches the path specified in the Razor component. -
Implement the Barcode Reader in Reader.razor: Add the following code in
Reader.razorto set up the barcode reader functionality:@page "/barcodereader" @inject IJSRuntime JSRuntime <div class="card"> <h2>Barcode & QR Code Reader</h2> <p class="card-note">Decode barcodes and QR codes from an image file.</p> <div class="control-row"> <button class="btn-primary" @onclick="ReadBarcodes">Select an image</button> </div> <p class="p-result">@result</p> <div id="imageview"> <img id="@imageId" /> <canvas id="@overlayId"></canvas> </div> </div> @code { private String result = ""; private DotNetObjectReference<Reader>? objRef; private String imageId = "image"; private String overlayId = "overlay"; protected override void OnInitialized() { objRef = DotNetObjectReference.Create(this); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Call the JavaScript function when the page is loaded await JSRuntime.InvokeVoidAsync("jsFunctions.initBarcodeReader"); } } public async Task ReadBarcodes() { await JSRuntime.InvokeVoidAsync( "jsFunctions.selectFile", objRef, overlayId, imageId); } [JSInvokable] public void ReturnBarcodeResultsAsync(String text) { result = text; StateHasChanged(); } public void Dispose() { objRef?.Dispose(); } }Explanation
@page "/barcodereader"sets the route for the Razor component.OnInitialized()creates aDotNetObjectReference, enablingJavaScriptto callC#methods.OnAfterRenderAsync()triggers the JavaScript functioninitBarcodeReaderwhen the page loads.ReadBarcodes()invokes the JavaScript functionselectFile, which opens a file dialog to select an image for barcode scanning.ReturnBarcodeResultsAsync()is a C# method that JavaScript calls to return barcode results to the Blazor component.
- Add JavaScript Functions in jsInterop.js: Add the corresponding JavaScript functions in
jsInterop.js:function showResults(result, dotnetRef) { clearOverlay(); let txts = []; try { let localization; let items = result.items if (items.length > 0) { for (var i = 0; i < items.length; ++i) { if (items[i].type !== Dynamsoft.Core.EnumCapturedResultItemType.CRIT_BARCODE) { continue; } let item = items[i]; txts.push(item.text); localization = item.location; drawOverlay( localization, item.text ); } } } catch (e) { alert(e); } let barcoderesults = txts.join(', '); if (txts.length == 0) { barcoderesults = 'No barcode found'; } if (dotnetRef) { dotnetRef.invokeMethodAsync('ReturnBarcodeResultsAsync', barcoderesults); } } function decodeImage(dotnetRef, url, data) { const img = new Image() img.onload = () => { updateOverlay(img.width, img.height); if (cvr) { cvr.capture(url, 'ReadBarcodes_Balance').then((result) => { showResults(result, dotnetRef); }); } } img.src = url } window.jsFunctions = { ... initBarcodeReader: async function () { try { dispose(); cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); } catch (e) { console.log(e); } }, selectFile: async function (dotnetRef, overlayId, imageId) { if (cameraEnhancer) { cameraEnhancer.dispose(); cameraEnhancer = null; } initOverlay(document.getElementById(overlayId)); if (cvr) { let input = document.createElement("input"); input.type = "file"; input.onchange = async function () { try { let file = input.files[0]; var fr = new FileReader(); fr.onload = function () { let image = document.getElementById(imageId); image.src = fr.result; image.style.display = 'block'; decodeImage(dotnetRef, fr.result, file); } fr.readAsDataURL(file); } catch (ex) { alert(ex.message); throw ex; } }; input.click(); } else { alert("The barcode reader is still initializing."); } }, };Explanation
initBarcodeReader()initializes the Capture Vision Router.decodeImage()decodes barcodes from the selected image.showResults()displays the decoded barcode results.selectFile()allows users to select an image file, which is then processed for barcode scanning.
Step 3: Implement a Barcode Scanner Razor Page
- Create the Barcode Scanner Component: Create a new Razor component called
Scanner.razorin the Pages folder and add it to the navigation menu inNavMenu.razor:<div class="nav-item"> <NavLink class="nav-link" href="barcodescanner"> <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Barcode Scanner </NavLink> </div> - Implement the Barcode Scanner in Scanner.razor: Add the following code in
Scanner.razorto set up the barcode scanner functionality:@page "/barcodescanner" @inject IJSRuntime JSRuntime <div class="card"> <h2>Barcode & QR Code Scanner</h2> <p class="card-note">Scan barcodes and QR codes with the camera. Pick a camera and open it to start scanning.</p> <div class="control-row"> <button @onclick="GetCameras">Get Cameras</button> <label for="videoSource">Camera</label> <select id="@videoSourceId"></select> <button class="btn-primary" @onclick="StartCamera">Open Camera</button> <button @onclick="StopCamera">Stop Camera</button> </div> <div id="videoview"> <div class="dce-video-container" id="@videoContainerId"></div> </div> </div> @code { private String result = ""; private DotNetObjectReference<Scanner>? objRef; private string videoSourceId = "videoSource"; private string videoContainerId = "videoContainer"; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { objRef = DotNetObjectReference.Create(this); await JSRuntime.InvokeAsync<Boolean>("jsFunctions.initBarcodeScanner", objRef, videoContainerId, videoSourceId); } } [JSInvokable] public void ReturnBarcodeResultsAsync(String text) { result = text; StateHasChanged(); } public void Dispose() { objRef?.Dispose(); } public async Task GetCameras() { await JSRuntime.InvokeVoidAsync("jsFunctions.getCameras"); } public async Task StartCamera() { await JSRuntime.InvokeVoidAsync("jsFunctions.startCamera"); } public async Task StopCamera() { await JSRuntime.InvokeVoidAsync("jsFunctions.stopCamera"); } }Explanation
@page "/barcodescanner"defines the route for the Razor component.OnAfterRenderAsync()initializes the barcode scanner by calling the JavaScript functioninitBarcodeScanneronce the page is loaded.GetCameras(),StartCamera(), andStopCamera()are methods that interact with JavaScript functions to manage the camera and barcode scanning operations.
-
Add JavaScript Functions in jsInterop.js: Add the corresponding JavaScript functions in your
jsInterop.jsfile:async function openCamera() { clearOverlay(); try { let deviceId = videoSelect.value; if (cameraEnhancer && deviceId !== "") { await cameraEnhancer.selectCamera(deviceId); await cameraEnhancer.open(); cvr.startCapturing('ReadBarcodes_Default'); } } catch(e) { console.log(e); } } window.jsFunctions = { ... initBarcodeScanner: async function (dotnetRef, videoId, selectId) { // Camera mode needs no manual overlay: DCE renders the detection // results on its built-in drawing layer for barcodes and maps the // coordinates to the viewfinder internally. videoSelect = document.getElementById(selectId); videoSelect.onchange = openCamera; try { dispose(); let cameraView = await Dynamsoft.DCE.CameraView.createInstance(); // DCE defaults to objectFit "contain", which renders a portrait // phone stream as a narrow pillar inside a wide viewfinder. // "cover" fills the stage; the viewfinder's own aspect ratio // does the cropping. cameraView.setVideoFit('cover'); cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView); let uiElement = document.getElementById(videoId); uiElement.append(cameraView.getUIElement()); cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-camera')?.setAttribute('style', 'display: none'); cameraView.getUIElement().shadowRoot?.querySelector('.dce-sel-resolution')?.setAttribute('style', 'display: none'); cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); cvr.setInput(cameraEnhancer); cvr.addResultReceiver({ onCapturedResultReceived: (result) => { showResults(result, dotnetRef); }, }); cvr.addResultReceiver({ onDecodedBarcodesReceived: (result) => { if (!result.barcodeResultItems.length) return; }, }); } catch (e) { console.log(e); result = false; } return true; }, getCameras: async function () { if (cameraEnhancer) { let cameras = await cameraEnhancer.getAllCameras(); listCameras(cameras); } }, startCamera: async function() { openCamera(); }, stopCamera: async function () { try { if (cameraEnhancer) { cameraEnhancer.pause(); } } catch (e) { console.log(e); } } };Explanation
initBarcodeScanner()initializes the camera view, camera enhancer, and Capture Vision Router, and registers callback functions to handle barcode results. CallingcameraView.setVideoFit('cover')right after creating the camera view keeps the mobile video stream filling the viewfinder instead of being letterboxed. The detection boxes are drawn by DCE’s built-in drawing layer for barcode results — the SDK maps the coordinates to the viewfinder itself, so no manual canvas overlay is needed in camera mode (the canvas is only used for uploaded images).getCameras()retrieves the list of available cameras.startCamera()opens the selected camera and starts barcode scanning.stopCamera()stops the camera preview, halting any ongoing image processing.
Step 4: Run the Blazor Barcode Reader and Scanner Application
To launch your Blazor application, press F5 in Visual Studio or use your preferred method to start debugging. Once the application is running, you can access the barcode reader and scanner pages to scan barcodes and QR codes effortlessly.
-
Barcode Reader: Use the barcode reader page to select image files for barcode detection.

-
Barcode Scanner: The barcode scanner page allows for real-time scanning using your device’s camera, making it easy to detect barcodes and QR codes.

Common Issues & Edge Cases
- WASM module loads slowly on first scan: Calling
CoreModule.loadWasm()in thesetLicensefunction preloads the WebAssembly binaries ahead of time. Skipping this causes the SDK to download on-demand, adding a 3–5 second delay on the first barcode capture. - Camera blocked on non-HTTPS origins:
CameraEnhancerrequires a secure context (https://orlocalhost). Deploying to plain HTTP will prevent camera access. Always develop onlocalhostand serve over HTTPS in production. - Only the first barcode is returned: The camera example uses the multi-barcode template
ReadBarcodes_Default. If you only need one barcode and want lower latency, switch toReadSingleBarcode; for blurry or low-resolution inputs,ReadBarcodes_Balanceenables multi-pass decoding.