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)