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
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'" />
- Dynamsoft.CaptureVisionBundle.Maui: Core image processing and camera handling.
- Dynamsoft.VIN.Maui: Pre-trained VIN recognition model for accurate character extraction.
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>
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();
    }
}
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>
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;
            });
        }
    }
}
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>
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");
        });
    }
}
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>
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; }
}
Source Code
https://github.com/yushulx/maui-barcode-mrz-document-scanner/tree/main/examples/VINScanner
 


 
    
Top comments (0)