Build a Passport MRZ Scanner for Android and iOS in C# with .NET MAUI

Creating a mobile app that scans and decodes Machine Readable Zones (MRZs) is essential for many industries, including travel, security, and identification verification. With .NET MAUI (Multi-platform App UI), developers can build cross-platform applications for Android and iOS using a single codebase, streamlining development and maintenance. In this tutorial, we’ll demonstrate how to leverage .NET MAUI alongside the Dynamsoft Capture Vision MAUI Bundle to create an MRZ scanner app with ease.

What you’ll build: A cross-platform .NET MAUI app that uses the device camera to scan passport and ID card MRZ zones in real time and displays parsed identity fields (name, nationality, document number, expiry date) on Android and iOS.

Key Takeaways

  • .NET MAUI lets you build a single-codebase MRZ scanner that deploys to both Android and iOS without platform-specific UI code.
  • The Dynamsoft Capture Vision MAUI SDK provides CameraView, CameraEnhancer, and CaptureVisionRouter to handle camera input, frame filtering, and MRZ text-line recognition.
  • MultiFrameResultCrossFilter with cross-verification enabled reduces false positives by confirming MRZ strings across consecutive frames.
  • This approach works for ICAO 9303–compliant passports, ID cards, and travel documents with TD1, TD2, and TD3 MRZ formats.

Common Developer Questions

  • How do I build a passport MRZ scanner in .NET MAUI for Android and iOS?
  • How do I capture and parse MRZ text from a live camera stream in C#?
  • What SDK can I use for real-time MRZ recognition in a cross-platform .NET mobile app?

.NET MAUI MRZ Scanner Demo Video

Prerequisites

To get started, ensure you have the following tools installed:

  • Get a 30-day free trial license for Dynamsoft Capture Vision
  • Xcode
  • Android Studio
  • Visual Studio for Windows or Visual Studio Code for macOS with the .NET MAUI extension
  • Provisioning Profile for iOS (required if building a .NET MAUI app with Visual Studio Code on macOS):
    1. Log into your Apple Developer account.
    2. Navigate to Certificates, Identifiers & Profiles to create an App ID and Provisioning Profile.
    3. Download the *.mobileprovision file and copy it to the ~/Library/MobileDevice/Provisioning Profiles folder.

Clone and Configure the Official MRZ Scanner Sample

Dynamsoft’s GitHub page offers several .NET MAUI samples, including those for barcode reading, document normalization, and MRZ recognition.

Dynamsoft MAUI samples

To get started, clone the MRZ MAUI sample project from the GitHub repository:

git clone https://github.com/Dynamsoft/mrz-scanner-mobile-maui.git

This project is configured for .NET 7.0. To update the target framework to .NET 8.0:

  1. Open the .csproj file and change the TargetFramework value from net7.0 to net8.0.
  2. Add <SupportedOSPlatformVersion> to the PropertyGroup element to ensure compatibility:
     <PropertyGroup Condition="$(TargetFramework.Contains('-ios'))">
         <SupportedOSPlatformVersion>11.0</SupportedOSPlatformVersion>
     </PropertyGroup>
    

The sample project includes basic MRZ scanning functionality. You can run the app on Android and iOS devices to test the MRZ recognition feature. In the following sections, we will examine the MRZ scanner app in detail and explain how it works.

Understand the .NET MAUI MRZ Scanner Architecture

The MRZ scanner app consists of three pages: MainPage, ScanPage, and ResultPage. The MainPage is the entry point of the app, where users can initiate the MRZ scanning process. The ScanPage performs real-time MRZ scanning using the camera stream, while the ResultPage displays the MRZ information extracted from the recognized MRZ strings.

The MainPage contains a button that navigates to the ScanPage.

MainPage.xml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage NavigationPage.HasNavigationBar="False"
             NavigationPage.HasBackButton="False"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title=""
             BackgroundColor="#323234"
             x:Class="mrz_scanner_mobile_maui.MainPage">
    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            VerticalOptions="Start"
            Padding="30,130">

            <Button
                x:Name="NavigationBtn"
                Text="Scan an MRZ"
                FontSize="16"
                WidthRequest="180"
                BackgroundColor="#FE8E14"
                Clicked="OnNavigationBtnClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>


    </ScrollView>

</ContentPage>

MainPage.xaml.cs

namespace mrz_scanner_mobile_maui;

public partial class MainPage : ContentPage
{
    
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnNavigationBtnClicked(object sender, EventArgs e)
    {
        await Navigation.PushAsync(new ScanPage());

    }

}

Capture the Camera Stream on ScanPage

The ScanPage starts a camera preview with the CameraView control provided by the Dynamsoft Capture Vision SDK. Here’s the ScanPage.xml file:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
    Shell.NavBarIsVisible="False"
    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.CaptureVisionBundle.Maui"
    Title=""
    x:Class="mrz_scanner_mobile_maui.ScanPage">
    <AbsoluteLayout>
        <controls:CameraView x:Name="camera"
                             AbsoluteLayout.LayoutBounds="0,0,1,1"
                             AbsoluteLayout.LayoutFlags="All">
        </controls:CameraView>

        ...
    </AbsoluteLayout>
</ContentPage>

The CameraView is connected to a CameraEnhancer object, which serves as the input source for the CaptureVisionRouter object. The CaptureVisionRouter processes the camera stream and recognizes MRZ strings, returning results through callback events.

Here’s the code snippet from the ScanPage.xaml.cs file:

  • Set the license key for the Dynamsoft Capture Vision SDK and instantiate CameraEnhancer and CaptureVisionRouter in the constructor.

      public ScanPage()
      {
          InitializeComponent();
          LicenseManager.InitLicense("LICENSE-KEY", this);
          currentTemplate = "ReadPassportAndId";
          enhancer = new CameraEnhancer();
          router = new CaptureVisionRouter();
      }
    
  • Bind the CameraView to the CameraEnhancer object:

      protected override void OnHandlerChanged()
      {
          base.OnHandlerChanged();
          if (this.Handler != null)
          {
              enhancer.SetCameraView(camera);
              enhancer.EnableEnhancedFeatures(EnumEnhancedFeatures.EF_FRAME_FILTER);
          }
      }
    
  • Register callback events for MRZ recognition and start camera capturing in the OnAppearing event handler:

      void OnParsedResultReceived(ParsedResult result)
      {
          if (result.Items == null)
          {
              return;
          }
          ImageData data = router.GetIntermediateResultManager().GetOriginalImage(result.OriginalImageHashId);
    
          Dictionary<String, String> labelMap = AssembleMap(result.Items[0]);
          if (labelMap != null && labelMap.Count != 0)
          {
              MainThread.BeginInvokeOnMainThread(() =>
              {
                  Navigation.PushAsync(new ResultPage(labelMap, data));
                  ClearText();
              });
    
          }
      }
    
      protected override async void OnAppearing()
      {
          base.OnAppearing();
          beepStatus = Preferences.Default.Get("status", true);
          UpdateBackground();
          await Permissions.RequestAsync<Permissions.Camera>();
          MultiFrameResultCrossFilter filter = new MultiFrameResultCrossFilter();
          filter.EnableResultCrossVerification(EnumCapturedResultItemType.TextLine, true);
          router?.AddResultFilter(filter);
          try
          {
              router.SetInput(enhancer);
          }
          catch (Exception e)
          {
              e.GetBaseException();
          }
          router.AddResultReceiver(this);
          restartCapture();
          enhancer?.SetColourChannelUsageType(EnumColourChannelUsageType.CCUT_FULL_CHANNEL);
          enhancer?.Open();
      }
    

    When a recognized result is returned, a corresponding image is stored in the CaptureVisionRouter object. By default, the image is converted to grayscale. To retain the original color, call SetColourChannelUsageType method with CCUT_FULL_CHANNEL before starting camera capture.

Display Parsed MRZ Results on ResultPage

The ResultPage displays the MRZ information extracted from the recognized MRZ strings in a ScrollView.

The ResultPage.xaml file is as follows:

<?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="mrz_scanner_mobile_maui.ResultPage"
             BackgroundColor="#3B3B3B"
             Title="ResultPage">


    <ScrollView x:Name="ScrollView">
        <VerticalStackLayout x:Name="VerticalLayout"
                             Padding="30">
        </VerticalStackLayout>

    </ScrollView>

</ContentPage>

The ResultPage.xaml.cs file populates the VerticalStackLayout with MRZ information extracted from recognized MRZ strings. It also displays an image of the scanned document.

namespace mrz_scanner_mobile_maui;

using Dynamsoft.Core.Maui;

public partial class ResultPage : ContentPage
{
    public ResultPage(Dictionary<String, String> labelMap, ImageData imageData)
    {
        InitializeComponent();
        if (labelMap.Count > 0)
        {
            VerticalLayout.Add(ChildView("Document Type:", labelMap["Document Type"]));
            VerticalLayout.Add(ChildView("Document Number:", labelMap["Document Number"]));
            VerticalLayout.Add(ChildView("Full Name:", labelMap["Name"]));
            VerticalLayout.Add(ChildView("Sex:", labelMap["Sex"].First().ToString().ToUpper()));
            VerticalLayout.Add(ChildView("Age:", labelMap["Age"]));
            VerticalLayout.Add(ChildView("Issuing State:", labelMap["Issuing State"]));
            VerticalLayout.Add(ChildView("Nationality:", labelMap["Nationality"]));
            VerticalLayout.Add(ChildView("Date of Birth(YYYY-MM-DD):", labelMap["Date of Birth(YY-MM-DD)"]));
            VerticalLayout.Add(ChildView("Date of Expiry(YYYY-MM-DD):", labelMap["Date of Expiry(YY-MM-DD)"]));

            var imageControl = new Image
            {
                HeightRequest = 400,
                HorizontalOptions = LayoutOptions.Center,
                VerticalOptions = LayoutOptions.Center,
            };
            imageControl.Source = imageData.ToImageSource();
            VerticalLayout.Add(imageControl);
        }
    }

    IView ChildView(string label, string text)
    {
        return new VerticalStackLayout
            {
                new Label
                {
                    Text = label,
                    TextColor = Color.FromArgb("AAAAAA"),
                    FontSize = 16,
                    Padding = new Thickness(0, 20, 0, 0),
                },
                new Entry
                {
                    Text = text,
                    TextColor = Colors.White,
                    FontSize = 16,
                    BackgroundColor = Colors.Transparent, 
                    IsReadOnly = false, 
                }
            };

    }
}

Run and Test the MRZ Scanner on Android and iOS

  1. Scan the machine-readable zone (MRZ) on a passport or ID card.

    .NET MAUI MRZ recognition

  2. Extract MRZ information from the scanned document.

    .NET MAUI MRZ scanner

Common Issues and Edge Cases

  • Camera permission denied on first launch: On both Android and iOS, the app requests camera permission at runtime via Permissions.RequestAsync<Permissions.Camera>(). If the user denies permission, the camera will not open. Handle the denied case by showing an alert that directs users to the app settings.
  • MRZ not detected under low light: The EF_FRAME_FILTER enhanced feature in CameraEnhancer filters out blurry frames, but extremely low-light conditions can still prevent detection. Advise users to scan under adequate lighting or enable the device flashlight.
  • iOS provisioning profile errors: If you see a signing or provisioning error when deploying to a physical iOS device, ensure your App ID and provisioning profile are correctly configured in your Apple Developer account and that the *.mobileprovision file is placed in ~/Library/MobileDevice/Provisioning Profiles.

Source Code

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