DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

How to Build a VIN Scanner App for Android and iOS with .NET MAUI

A Vehicle Identification Number (VIN) scanner application is a useful tool for car owners and car manufacturers, enabling quick retrieval of a vehicle’s unique identifier. In this tutorial, we’ll walk through creating a cross-platform VIN scanner using .NET MAUI and Dynamsoft Capture Vision, covering setup, UI implementation, and VIN recognition logic for Android and iOS.

Demo: VIN Scanner App for Android

Prerequisites

Before starting, ensure you have these tools and resources:

  • Visual Studio 2022 or Visual Studio Code (with C# support)
  • .NET SDK
  • MAUI Workloads configured via CLI:

    dotnet workload install maui 
  • A free trial license key for Dynamsoft Capture Vision.

Step 1: Set Up the .NET MAUI Project

1.1 Create a New MAUI Project

Generate a cross-platform MAUI project using the command line:

dotnet new maui -n VINScanner 
Enter fullscreen mode Exit fullscreen mode

1.2 Add Platform-Specific Dependencies

Open the project file (VINScanner.csproj) and include these NuGet packages with Android/iOS-only conditions:

<PackageReference Include="Dynamsoft.CaptureVisionBundle.Maui" Version="2.6.1001" Condition="'$(TargetFramework)' == 'net9.0-android' OR '$(TargetFramework)' == 'net9.0-ios'" /> <PackageReference Include="Dynamsoft.VIN.Maui" Version="3.4.201" Condition="'$(TargetFramework)' == 'net9.0-android' OR '$(TargetFramework)' == 'net9.0-ios'" /> 
Enter fullscreen mode Exit fullscreen mode

1.3 Configure Target Frameworks

To avoid Windows/macOS compatibility issues, restrict the project to mobile targets in VINScanner.csproj:

<TargetFrameworks>net9.0-android;net9.0-ios;</TargetFrameworks> 
Enter fullscreen mode Exit fullscreen mode

1.4 Register Camera View Handler

In MauiProgram.cs, register the CameraView handler for cross-platform camera integration:

using Microsoft.Extensions.Logging; using Dynamsoft.CameraEnhancer.Maui; using Dynamsoft.CameraEnhancer.Maui.Handlers; namespace VINScanner; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }) .ConfigureMauiHandlers(handlers => { handlers.AddHandler(typeof(CameraView), typeof(CameraViewHandler)); }); #if DEBUG  builder.Logging.AddDebug(); #endif  return builder.Build(); } } 
Enter fullscreen mode Exit fullscreen mode

Step 2: Design the App Architecture

The app features three core pages:

  • MainPage.xaml: Entry point with a scan initiation button.
  • CameraPage.xaml: Live camera feed with a VIN scanning region overlay.
  • ResultPage.xaml: Displays parsed VIN details in a structured format.

2.1 Implement the Main Page (Entry Point)

UI Layout (MainPage.xaml)

<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="VINScanner.MainPage"> <StackLayout VerticalOptions="Center" HorizontalOptions="Center"> <Button x:Name="CameraBtn" Text="Start Scanning" SemanticProperties.Hint="Open Camera" Clicked="OnCameraClicked" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/> <Label x:Name="errorMessage" TextColor="Red" VerticalOptions="Center" HorizontalOptions="Center" FontSize="Medium" Margin="0,20,0,0"/> </StackLayout> </ContentPage> 
Enter fullscreen mode Exit fullscreen mode

License Initialization (MainPage.xaml.cs)

using Dynamsoft.License.Maui; namespace VINScanner; public partial class MainPage : ContentPage, ILicenseVerificationListener { public MainPage() { InitializeComponent(); LicenseManager.InitLicense("LICENSE-KEY", this); } private async void OnCameraClicked(object sender, EventArgs e) { await Navigation.PushAsync(new CameraPage()); } public void OnLicenseVerified(bool isSuccess, string message) { if (!isSuccess) { MainThread.BeginInvokeOnMainThread(() => { errorMessage.Text = "License initialization failed: " + message; }); } } } 
Enter fullscreen mode Exit fullscreen mode

2.2 Build the Camera Page (Live Scanning)

Camera Feed & Capture Button (CameraPage.xaml)

<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:Dynamsoft.CameraEnhancer.Maui;assembly=Dynamsoft.CameraEnhancer.Maui" x:Class="VINScanner.CameraPage" Title="VINScanner"> <AbsoluteLayout> <controls:CameraView x:Name="camera" AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All"/> <Button Text="Capture" AbsoluteLayout.LayoutBounds="0.5, 0.8, 0.8, 0.1" AbsoluteLayout.LayoutFlags="All" HorizontalOptions="Center" VerticalOptions="End" Clicked="OnCaptureClicked"/> </AbsoluteLayout> </ContentPage> 
Enter fullscreen mode Exit fullscreen mode

Scanning Logic & Result Handling (CameraPage.xaml.cs)

using Dynamsoft.Core.Maui; using Dynamsoft.CaptureVisionRouter.Maui; using Dynamsoft.CameraEnhancer.Maui; using Dynamsoft.CodeParser.Maui; namespace VINScanner; public partial class CameraPage : ContentPage, ICapturedResultReceiver, ICompletionListener { public CameraEnhancer enhancer = new CameraEnhancer(); CaptureVisionRouter router = new CaptureVisionRouter(); bool isCaptured = false; public CameraPage() { InitializeComponent(); router.SetInput(enhancer); router.AddResultReceiver(this); } protected override void OnHandlerChanged() { base.OnHandlerChanged(); if (this.Handler != null) { enhancer.SetCameraView(camera); var region = new DMRect(0.1f, 0.4f, 0.9f, 0.6f, true); enhancer.SetScanRegion(region); } } protected override async void OnAppearing() { isCaptured = false; base.OnAppearing(); await Permissions.RequestAsync<Permissions.Camera>(); enhancer.Open(); router.StartCapturing("ReadVIN", this); } protected override void OnDisappearing() { base.OnDisappearing(); enhancer.Close(); router.StopCapturing(); } public void OnParsedResultsReceived(ParsedResult result) { if (result?.Items?.Count > 0) { ParsedResultItem parsedResultItem = result.Items[0]; if (result.Items.Count > 1) { foreach (var item in result.Items) { if (item.TaskName == "parse-vin-barcode") { parsedResultItem = item; break; } } } var dictionary = ConvertToVINDictionary(parsedResultItem); if (dictionary != null && isCaptured) { router.StopCapturing(); enhancer.ClearBuffer(); MainThread.BeginInvokeOnMainThread(async () => { await Navigation.PushAsync(new ResultPage(dictionary)); }); } } } private void OnCaptureClicked(object sender, EventArgs e) { isCaptured = true; } public Dictionary<string, string>? ConvertToVINDictionary(ParsedResultItem item) { if (item.ParsedFields.TryGetValue("vinString", out ParsedField? value) && value != null) { Dictionary<string, string> dic = []; string[] infoLists = ["vinString", "WMI", "region", "VDS", "checkDigit", "modelYear", "plantCode", "serialNumber"]; foreach (var info in infoLists) { if (item.ParsedFields.TryGetValue(info, out ParsedField? field) && field != null) { if (item.ParsedFields[info].ValidationStatus == EnumValidationStatus.VS_FAILED) { return null; } else { dic.Add(CapitalizeFirstLetter(info), item.ParsedFields[info].Value); } } } return dic; } else { return null; } } public static string CapitalizeFirstLetter(string input) { if (string.IsNullOrWhiteSpace(input)) return input; return char.ToUpper(input[0]) + input.Substring(1); } public void OnFailure(int errorCode, string errorMessage) { MainThread.BeginInvokeOnMainThread(() => { DisplayAlert("Error", errorMessage, "OK"); }); } } 
Enter fullscreen mode Exit fullscreen mode

Scan VIN in a .NET MAUI app

2.3 Display VIN Results with Structured Data

Data Presentation UI (ResultPage.xaml)

<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="VINScanner.ResultPage" Title="VIN Result"> <ContentPage.Content> <CollectionView ItemsSource="{Binding TableItems}"> <CollectionView.ItemTemplate> <DataTemplate> <VerticalStackLayout Padding="10"> <Label Text="{Binding Key}" FontAttributes="Bold" TextColor="Black" FontSize="16"/> <BoxView HeightRequest="1" BackgroundColor="LightGray" Margin="0,5"/> <Label Text="{Binding Value}" FontAttributes="None" TextColor="Gray" FontSize="14"/> <BoxView HeightRequest="1" BackgroundColor="LightGray" Margin="0,5"/> </VerticalStackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </ContentPage.Content> </ContentPage> 
Enter fullscreen mode Exit fullscreen mode

Data Binding Logic (ResultPage.xaml.cs)

using System.Collections.ObjectModel; namespace VINScanner; public partial class ResultPage : ContentPage { public ObservableCollection<TableItem> TableItems { get; set; } public ResultPage(Dictionary<String, String> dictionary) { InitializeComponent(); TableItems = []; foreach (var item in dictionary) { TableItems.Add(new TableItem { Key = item.Key, Value = item.Value }); } BindingContext = this; } } public class TableItem { public string Key { get; set; } public string Value { get; set; } } 
Enter fullscreen mode Exit fullscreen mode

Parse the VIN result

Source Code

https://github.com/yushulx/maui-barcode-mrz-document-scanner/tree/main/examples/VINScanner

Top comments (0)