Skip to footer content
MIGRATION GUIDES

Migrating from TesseractOcrMaui to IronOCR

This guide walks through a complete migration from TesseractOcrMaui to IronOCR, with practical before-and-after code for every step. It targets developers who have already decided to move beyond the MAUI platform constraint and need a systematic path to a library that runs identically in mobile apps, server-side APIs, background workers, and cloud functions. No prior reading of the comparison article is required.

Why Migrate from TesseractOcrMaui

TesseractOcrMaui was written to solve a real gap: existing .NET Tesseract wrappers could not resolve mobile platform interop on their own. For a pure MAUI prototype with no server footprint, it fills that gap. The problems surface the moment the product grows past that narrow scope.

MAUI-Only Target Frameworks Prevent Code Sharing. TesseractOcrMaui ships targets for net8.0-ios, net8.0-android, and net8.0-windows — all MAUI platform monikers. The package contains no net8.0, no netstandard2.1, no server-compatible target. Referencing it from a class library, an ASP.NET Core project, or an Azure Function produces a compile error. There is no workaround: the package is architecturally incapable of running outside a MAUI host. Every time the OCR requirement appears in a non-MAUI context, a second library must be introduced and maintained in parallel.

Mandatory MAUI Dependency Injection Coupling. The AddTesseractOcr() call in MauiProgram.cs wires ITesseract to the MAUI service provider. There is no factory method, no static entry point, and no constructor outside that DI graph. This means OCR logic cannot be extracted into a portable class library — every class that takes ITesseract in its constructor is locked to the MAUI application host for its entire lifetime.

No PDF Input at Any Level. PDF documents are the most common format for scanned contracts, invoices, and identity documents. TesseractOcrMaui throws NotSupportedException on any PDF input. Processing a PDF requires adding a separate PDF rendering library, writing page-by-page image extraction, managing temporary files in the device cache, and cleaning them up after each call. That is 100+ lines of infrastructure code before a single OCR call executes — and it still only works on MAUI.

No Built-In Preprocessing for Real-World Images. Mobile cameras produce images with rotation, sensor noise, and inconsistent DPI across device models. TesseractOcrMaui passes images directly to the Tesseract engine with zero preprocessing. Teams that need better accuracy must add SkiaSharp or ImageSharp, implement deskewing and denoising algorithms manually, write temporary file management, and test it all across iOS and Android device variants. Most skip it. Accuracy on real mobile captures suffers as a result.

Single-Developer Maintenance Risk on a Production Dependency. TesseractOcrMaui is maintained by one developer. There is no company behind it, no SLA, no security patch commitment, and no escalation path beyond a GitHub issue. For production applications in regulated industries — finance, healthcare, legal — a volunteer-maintained library with roughly 33,900 total NuGet downloads is not an acceptable dependency.

The Fundamental Problem

TesseractOcrMaui compiles only inside a MAUI project. The moment another project type needs OCR, the architecture breaks:

// TesseractOcrMaui: wired to MAUI host — cannot escape to a shared library
// This code compiles only inside a .NET MAUI application
public class OcrService
{
    private readonly ITesseract _tesseract; // resolved from MAUI DI — no other source exists

    public OcrService(ITesseract tesseract) { _tesseract = tesseract; }

    public async Task<string> ReadAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng"); // traineddata must be bundled as MauiAsset
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.Success ? result.RecognizedText : string.Empty;
    }
    // Cannot reference this class from ASP.NET Core, Azure Functions, or Docker
}
// TesseractOcrMaui: wired to MAUI host — cannot escape to a shared library
// This code compiles only inside a .NET MAUI application
public class OcrService
{
    private readonly ITesseract _tesseract; // resolved from MAUI DI — no other source exists

    public OcrService(ITesseract tesseract) { _tesseract = tesseract; }

    public async Task<string> ReadAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng"); // traineddata must be bundled as MauiAsset
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.Success ? result.RecognizedText : string.Empty;
    }
    // Cannot reference this class from ASP.NET Core, Azure Functions, or Docker
}
Imports System.Threading.Tasks

' TesseractOcrMaui: wired to MAUI host — cannot escape to a shared library
' This code compiles only inside a .NET MAUI application
Public Class OcrService
    Private ReadOnly _tesseract As ITesseract ' resolved from MAUI DI — no other source exists

    Public Sub New(tesseract As ITesseract)
        _tesseract = tesseract
    End Sub

    Public Async Function ReadAsync(imagePath As String) As Task(Of String)
        Await _tesseract.InitAsync("eng") ' traineddata must be bundled as MauiAsset
        Dim result = Await _tesseract.RecognizeTextAsync(imagePath)
        Return If(result.Success, result.RecognizedText, String.Empty)
    End Function
    ' Cannot reference this class from ASP.NET Core, Azure Functions, or Docker
End Class
$vbLabelText   $csharpLabel
// IronOCR: plain instantiable class — compiles in any .NET project type
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract(); // no DI, no MAUI host

    public string Read(string imagePath)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return _ocr.Read(input).Text;
    }
    // Place this in a netstandard2.1 library — reference from MAUI, API, and Functions together
}
// IronOCR: plain instantiable class — compiles in any .NET project type
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract(); // no DI, no MAUI host

    public string Read(string imagePath)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return _ocr.Read(input).Text;
    }
    // Place this in a netstandard2.1 library — reference from MAUI, API, and Functions together
}
Imports IronOcr

Public Class OcrService
    Private ReadOnly _ocr As New IronTesseract() ' no DI, no MAUI host

    Public Function Read(imagePath As String) As String
        Using input As New OcrInput()
            input.LoadImage(imagePath)
            Return _ocr.Read(input).Text
        End Using
    End Function
    ' Place this in a netstandard2.1 library — reference from MAUI, API, and Functions together
End Class
$vbLabelText   $csharpLabel

IronOCR vs TesseractOcrMaui: Feature Comparison

The table below covers capability differences relevant to teams evaluating this migration.

Feature TesseractOcrMaui IronOCR
.NET MAUI (iOS) Yes Yes (IronOcr.iOS)
.NET MAUI (Android) Yes Yes (IronOcr.Android)
.NET MAUI (Windows) Yes Yes
ASP.NET Core No Yes
Azure Functions No Yes
AWS Lambda No Yes
Docker / Linux containers No Yes
Console applications No Yes
WPF / WinForms No Yes
Shared .NET class library No Yes
PDF input (native) No Yes
Password-protected PDF input No Yes
Stream input No Yes
Byte array input No Yes
Multi-page TIFF input No Yes
Searchable PDF output No Yes
hOCR export No Yes
Automatic deskew No Yes
Automatic denoising No Yes
Contrast enhancement No Yes
Binarization No Yes
Region-based OCR No Yes
Barcode reading during OCR No Yes
Word-level coordinates No Yes
Multi-language simultaneous No Yes
Languages supported Manually bundled traineddata 125+ via NuGet packages
Thread safety Manual Built-in
Commercial support None (single developer) Yes (Iron Software)
Licensing Apache 2.0 (free) Perpetual from $999
NuGet downloads ~33,900 5.3M+

Quick Start: TesseractOcrMaui to IronOCR Migration

Step 1: Replace NuGet Package

Remove TesseractOcrMaui from the MAUI project:

dotnet remove package TesseractOcrMaui
dotnet remove package TesseractOcrMaui
SHELL

Install IronOCR. For MAUI projects, add the platform-specific packages alongside the core package:

dotnet add package IronOcr, IronOcr.Android, IronOcr.iOS

For server-side projects (ASP.NET Core, Azure Functions, console):

dotnet add package IronOcr

The IronOCR NuGet package page lists all available platform packages.

Step 2: Update Namespaces

Replace TesseractOcrMaui namespaces with the IronOCR namespace:

// Before (TesseractOcrMaui)
using TesseractOcrMaui;
using TesseractOcrMaui.Results;
using Microsoft.Maui.Storage;

// After (IronOCR)
using IronOcr;
// Before (TesseractOcrMaui)
using TesseractOcrMaui;
using TesseractOcrMaui.Results;
using Microsoft.Maui.Storage;

// After (IronOCR)
using IronOcr;
Imports IronOcr
$vbLabelText   $csharpLabel

Step 3: Initialize License

Add license initialization at application startup. In a MAUI app this goes in MauiProgram.cs; in ASP.NET Core it goes in Program.cs:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

Code Migration Examples

Replacing MAUI Dependency Injection Registration

TesseractOcrMaui requires registering the OCR engine through the MAUI service provider. Removing that registration is the first architectural step, because it is what locks all subsequent OCR code to the MAUI host.

TesseractOcrMaui Approach:

// MauiProgram.cs — OCR engine registered here; nowhere else resolves it
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

        // Binds OCR to MAUI DI — no standalone path exists after this
        builder.Services.AddTesseractOcr();

        return builder.Build();
    }
}

// Any class that needs OCR must receive ITesseract from the MAUI container
public class InvoicePageViewModel
{
    private readonly ITesseract _tesseract;

    public InvoicePageViewModel(ITesseract tesseract)
    {
        _tesseract = tesseract; // fails to construct outside MAUI host
    }

    public async Task<string> ScanInvoiceAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.RecognizedText ?? string.Empty;
    }
}
// MauiProgram.cs — OCR engine registered here; nowhere else resolves it
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

        // Binds OCR to MAUI DI — no standalone path exists after this
        builder.Services.AddTesseractOcr();

        return builder.Build();
    }
}

// Any class that needs OCR must receive ITesseract from the MAUI container
public class InvoicePageViewModel
{
    private readonly ITesseract _tesseract;

    public InvoicePageViewModel(ITesseract tesseract)
    {
        _tesseract = tesseract; // fails to construct outside MAUI host
    }

    public async Task<string> ScanInvoiceAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.RecognizedText ?? string.Empty;
    }
}
Imports Microsoft.Maui
Imports Microsoft.Extensions.DependencyInjection

' MauiProgram.vb — OCR engine registered here; nowhere else resolves it
Public Module MauiProgram
    Public Function CreateMauiApp() As MauiApp
        Dim builder = MauiApp.CreateBuilder()
        builder.UseMauiApp(Of App)()

        ' Binds OCR to MAUI DI — no standalone path exists after this
        builder.Services.AddTesseractOcr()

        Return builder.Build()
    End Function
End Module

' Any class that needs OCR must receive ITesseract from the MAUI container
Public Class InvoicePageViewModel
    Private ReadOnly _tesseract As ITesseract

    Public Sub New(tesseract As ITesseract)
        _tesseract = tesseract ' fails to construct outside MAUI host
    End Sub

    Public Async Function ScanInvoiceAsync(imagePath As String) As Task(Of String)
        Await _tesseract.InitAsync("eng")
        Dim result = Await _tesseract.RecognizeTextAsync(imagePath)
        Return If(result.RecognizedText, String.Empty)
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

// MauiProgram.cs — license only; no DI registration needed
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

        // One-line initialization — works for all project types
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

        return builder.Build();
    }
}

// No constructor injection needed — IronTesseract instantiates directly
public class InvoicePageViewModel
{
    public string ScanInvoice(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return ocr.Read(input).Text;
    }
}
// MauiProgram.cs — license only; no DI registration needed
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder.UseMauiApp<App>();

        // One-line initialization — works for all project types
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

        return builder.Build();
    }
}

// No constructor injection needed — IronTesseract instantiates directly
public class InvoicePageViewModel
{
    public string ScanInvoice(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return ocr.Read(input).Text;
    }
}
Imports Microsoft.Maui
Imports IronOcr

Public Module MauiProgram
    Public Function CreateMauiApp() As MauiApp
        Dim builder = MauiApp.CreateBuilder()
        builder.UseMauiApp(Of App)()

        ' One-line initialization — works for all project types
        IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

        Return builder.Build()
    End Function
End Module

Public Class InvoicePageViewModel
    Public Function ScanInvoice(imagePath As String) As String
        Dim ocr = New IronTesseract()
        Using input As New OcrInput()
            input.LoadImage(imagePath)
            Return ocr.Read(input).Text
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Removing AddTesseractOcr() eliminates the MAUI DI coupling. The IronTesseract class has a public parameterless constructor and carries no platform dependency — it can be instantiated anywhere. See the IronTesseract setup guide for initialization options including engine mode and language configuration.

Moving OCR Logic to a Shared Class Library

With TesseractOcrMaui, sharing OCR logic across project types is structurally impossible. With IronOCR, the migration path is straightforward: extract the service into a .NET Standard 2.1 or net8.0 class library and reference it from every project in the solution.

TesseractOcrMaui Approach:

// This service CANNOT be extracted to a shared library.
// It compiles only in a project that references TesseractOcrMaui,
// which only has MAUI platform targets.
//
// Result: every non-MAUI project must use a different OCR library,
// duplicating language config, error handling, and accuracy tuning.

public class DocumentOcrService
{
    private readonly ITesseract _tesseract; // MAUI DI only

    public DocumentOcrService(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<string> ProcessDocumentAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.Success ? result.RecognizedText : string.Empty;
    }
    // Server team writes their own version using a different library
    // Two codebases, two accuracy profiles, two maintenance tracks
}
// This service CANNOT be extracted to a shared library.
// It compiles only in a project that references TesseractOcrMaui,
// which only has MAUI platform targets.
//
// Result: every non-MAUI project must use a different OCR library,
// duplicating language config, error handling, and accuracy tuning.

public class DocumentOcrService
{
    private readonly ITesseract _tesseract; // MAUI DI only

    public DocumentOcrService(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<string> ProcessDocumentAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);
        return result.Success ? result.RecognizedText : string.Empty;
    }
    // Server team writes their own version using a different library
    // Two codebases, two accuracy profiles, two maintenance tracks
}
Imports System.Threading.Tasks

' This service CANNOT be extracted to a shared library.
' It compiles only in a project that references TesseractOcrMaui,
' which only has MAUI platform targets.
'
' Result: every non-MAUI project must use a different OCR library,
' duplicating language config, error handling, and accuracy tuning.

Public Class DocumentOcrService
    Private ReadOnly _tesseract As ITesseract ' MAUI DI only

    Public Sub New(tesseract As ITesseract)
        _tesseract = tesseract
    End Sub

    Public Async Function ProcessDocumentAsync(imagePath As String) As Task(Of String)
        Await _tesseract.InitAsync("eng")
        Dim result = Await _tesseract.RecognizeTextAsync(imagePath)
        Return If(result.Success, result.RecognizedText, String.Empty)
    End Function
    ' Server team writes their own version using a different library
    ' Two codebases, two accuracy profiles, two maintenance tracks
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

// Place this in: MyCompany.OcrCore (net8.0 or netstandard2.1 class library)
// Reference from: MyCompany.MauiApp, MyCompany.Api, MyCompany.BatchWorker

using IronOcr;

namespace MyCompany.OcrCore
{
    public class DocumentOcrService
    {
        private readonly IronTesseract _ocr;

        public DocumentOcrService()
        {
            _ocr = new IronTesseract();
        }

        public string ProcessDocument(string imagePath)
        {
            using var input = new OcrInput();
            input.LoadImage(imagePath);
            input.Deskew();
            input.DeNoise();
            return _ocr.Read(input).Text;
        }

        public string ProcessDocumentFromBytes(byte[] imageData)
        {
            using var input = new OcrInput();
            input.LoadImage(imageData);
            input.Deskew();
            input.DeNoise();
            return _ocr.Read(input).Text;
        }

        public string ProcessDocumentFromStream(Stream imageStream)
        {
            using var input = new OcrInput();
            input.LoadImage(imageStream);
            return _ocr.Read(input).Text;
        }
    }
}
// Place this in: MyCompany.OcrCore (net8.0 or netstandard2.1 class library)
// Reference from: MyCompany.MauiApp, MyCompany.Api, MyCompany.BatchWorker

using IronOcr;

namespace MyCompany.OcrCore
{
    public class DocumentOcrService
    {
        private readonly IronTesseract _ocr;

        public DocumentOcrService()
        {
            _ocr = new IronTesseract();
        }

        public string ProcessDocument(string imagePath)
        {
            using var input = new OcrInput();
            input.LoadImage(imagePath);
            input.Deskew();
            input.DeNoise();
            return _ocr.Read(input).Text;
        }

        public string ProcessDocumentFromBytes(byte[] imageData)
        {
            using var input = new OcrInput();
            input.LoadImage(imageData);
            input.Deskew();
            input.DeNoise();
            return _ocr.Read(input).Text;
        }

        public string ProcessDocumentFromStream(Stream imageStream)
        {
            using var input = new OcrInput();
            input.LoadImage(imageStream);
            return _ocr.Read(input).Text;
        }
    }
}
Imports IronOcr

Namespace MyCompany.OcrCore

    Public Class DocumentOcrService
        Private ReadOnly _ocr As IronTesseract

        Public Sub New()
            _ocr = New IronTesseract()
        End Sub

        Public Function ProcessDocument(imagePath As String) As String
            Using input As New OcrInput()
                input.LoadImage(imagePath)
                input.Deskew()
                input.DeNoise()
                Return _ocr.Read(input).Text
            End Using
        End Function

        Public Function ProcessDocumentFromBytes(imageData As Byte()) As String
            Using input As New OcrInput()
                input.LoadImage(imageData)
                input.Deskew()
                input.DeNoise()
                Return _ocr.Read(input).Text
            End Using
        End Function

        Public Function ProcessDocumentFromStream(imageStream As Stream) As String
            Using input As New OcrInput()
                input.LoadImage(imageStream)
                Return _ocr.Read(input).Text
            End Using
        End Function
    End Class

End Namespace
$vbLabelText   $csharpLabel

One class library, one set of tests, one accuracy profile. The MAUI app calls ProcessDocument(photoPath), the ASP.NET Core API calls ProcessDocumentFromBytes(uploadedBytes), and the Azure Function calls ProcessDocumentFromStream(blobStream) — all backed by the same implementation. The stream input guide and image input guide document all OcrInput loading variants.

Enabling Server-Side OCR in ASP.NET Core

TesseractOcrMaui cannot be referenced from an ASP.NET Core project. Teams that add a document upload endpoint are forced to reach for a different library entirely. IronOCR runs in ASP.NET Core without any configuration changes beyond the license key.

TesseractOcrMaui Approach:

// ASP.NET Core Web API — TesseractOcrMaui CANNOT be used here.
// The package has no net8.0 or netstandard target.
// Referencing it produces: "The given project does not support targeting net8.0-ios/android/windows."
//
// Team is forced to add a second OCR library — Tesseract charlesw wrapper,
// a cloud API, or another solution — creating a split codebase.

[ApiController]
[Route("api/[controller]")]
public class DocumentsController : ControllerBase
{
    // Cannot inject ITesseract here — no MAUI host, no MAUI DI container
    // Must use a completely different OCR library for server-side processing
}
// ASP.NET Core Web API — TesseractOcrMaui CANNOT be used here.
// The package has no net8.0 or netstandard target.
// Referencing it produces: "The given project does not support targeting net8.0-ios/android/windows."
//
// Team is forced to add a second OCR library — Tesseract charlesw wrapper,
// a cloud API, or another solution — creating a split codebase.

[ApiController]
[Route("api/[controller]")]
public class DocumentsController : ControllerBase
{
    // Cannot inject ITesseract here — no MAUI host, no MAUI DI container
    // Must use a completely different OCR library for server-side processing
}
Imports Microsoft.AspNetCore.Mvc

' ASP.NET Core Web API — TesseractOcrMaui CANNOT be used here.
' The package has no net8.0 or netstandard target.
' Referencing it produces: "The given project does not support targeting net8.0-ios/android/windows."
'
' Team is forced to add a second OCR library — Tesseract charlesw wrapper,
' a cloud API, or another solution — creating a split codebase.

<ApiController>
<Route("api/[controller]")>
Public Class DocumentsController
    Inherits ControllerBase

    ' Cannot inject ITesseract here — no MAUI host, no MAUI DI container
    ' Must use a completely different OCR library for server-side processing
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

// ASP.NET Core — IronOCR works without modification
using IronOcr;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class DocumentsController : ControllerBase
{
    [HttpPost("extract-text")]
    public async Task<IActionResult> ExtractText(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("No file uploaded.");

        var ocr = new IronTesseract();
        using var input = new OcrInput();

        // Load directly from the upload stream — no temp files
        using var stream = file.OpenReadStream();

        if (file.ContentType == "application/pdf")
            input.LoadPdf(stream);
        else
            input.LoadImage(stream);

        input.Deskew();
        input.DeNoise();

        var result = ocr.Read(input);

        return Ok(new
        {
            text = result.Text,
            confidence = result.Confidence,
            pageCount = result.Pages.Count()
        });
    }

    [HttpPost("extract-text-batch")]
    public async Task<IActionResult> ExtractTextBatch(List<IFormFile> files)
    {
        var results = new List<object>();

        // Thread-safe: create one IronTesseract per thread
        await Parallel.ForEachAsync(files, async (file, ct) =>
        {
            var ocr = new IronTesseract();
            using var input = new OcrInput();
            using var stream = file.OpenReadStream();
            input.LoadImage(stream);
            var result = ocr.Read(input);

            lock (results)
            {
                results.Add(new { file = file.FileName, text = result.Text });
            }
        });

        return Ok(results);
    }
}
// ASP.NET Core — IronOCR works without modification
using IronOcr;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class DocumentsController : ControllerBase
{
    [HttpPost("extract-text")]
    public async Task<IActionResult> ExtractText(IFormFile file)
    {
        if (file == null || file.Length == 0)
            return BadRequest("No file uploaded.");

        var ocr = new IronTesseract();
        using var input = new OcrInput();

        // Load directly from the upload stream — no temp files
        using var stream = file.OpenReadStream();

        if (file.ContentType == "application/pdf")
            input.LoadPdf(stream);
        else
            input.LoadImage(stream);

        input.Deskew();
        input.DeNoise();

        var result = ocr.Read(input);

        return Ok(new
        {
            text = result.Text,
            confidence = result.Confidence,
            pageCount = result.Pages.Count()
        });
    }

    [HttpPost("extract-text-batch")]
    public async Task<IActionResult> ExtractTextBatch(List<IFormFile> files)
    {
        var results = new List<object>();

        // Thread-safe: create one IronTesseract per thread
        await Parallel.ForEachAsync(files, async (file, ct) =>
        {
            var ocr = new IronTesseract();
            using var input = new OcrInput();
            using var stream = file.OpenReadStream();
            input.LoadImage(stream);
            var result = ocr.Read(input);

            lock (results)
            {
                results.Add(new { file = file.FileName, text = result.Text });
            }
        });

        return Ok(results);
    }
}
Imports IronOcr
Imports Microsoft.AspNetCore.Mvc

<ApiController>
<Route("api/[controller]")>
Public Class DocumentsController
    Inherits ControllerBase

    <HttpPost("extract-text")>
    Public Async Function ExtractText(file As IFormFile) As Task(Of IActionResult)
        If file Is Nothing OrElse file.Length = 0 Then
            Return BadRequest("No file uploaded.")
        End If

        Dim ocr As New IronTesseract()
        Using input As New OcrInput()
            ' Load directly from the upload stream — no temp files
            Using stream = file.OpenReadStream()
                If file.ContentType = "application/pdf" Then
                    input.LoadPdf(stream)
                Else
                    input.LoadImage(stream)
                End If

                input.Deskew()
                input.DeNoise()

                Dim result = ocr.Read(input)

                Return Ok(New With {
                    .text = result.Text,
                    .confidence = result.Confidence,
                    .pageCount = result.Pages.Count()
                })
            End Using
        End Using
    End Function

    <HttpPost("extract-text-batch")>
    Public Async Function ExtractTextBatch(files As List(Of IFormFile)) As Task(Of IActionResult)
        Dim results As New List(Of Object)()

        ' Thread-safe: create one IronTesseract per thread
        Await Task.Run(Sub()
                           Parallel.ForEach(files, Sub(file)
                                                       Dim ocr As New IronTesseract()
                                                       Using input As New OcrInput()
                                                           Using stream = file.OpenReadStream()
                                                               input.LoadImage(stream)
                                                               Dim result = ocr.Read(input)

                                                               SyncLock results
                                                                   results.Add(New With {
                                                                       .file = file.FileName,
                                                                       .text = result.Text
                                                                   })
                                                               End SyncLock
                                                           End Using
                                                       End Using
                                                   End Sub)
                       End Sub)

        Return Ok(results)
    End Function
End Class
$vbLabelText   $csharpLabel

The same code deploys to IIS, Kestrel, or a Linux Docker container unchanged. The ASP.NET OCR guide covers middleware configuration and the Docker deployment guide documents the Linux container setup.

Eliminating Platform-Specific Handler Code

TesseractOcrMaui's MAUI-target-only architecture forces developers to write platform-conditional code when trying to integrate OCR into multi-target solutions. IronOCR removes the need for platform conditionals because the same package resolves correctly on every target.

TesseractOcrMaui Approach:

// Attempting to share OCR logic across MAUI and non-MAUI targets
// requires platform-conditional compilation — a maintenance hazard

#if ANDROID || IOS || WINDOWS
// Only compile this block in MAUI targets
// Non-MAUI targets cannot reference TesseractOcrMaui at all
using TesseractOcrMaui;

public class PlatformOcrHandler
{
    private readonly ITesseract _tesseract;

    public PlatformOcrHandler(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<string> ProcessAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var r = await _tesseract.RecognizeTextAsync(imagePath);
        return r.RecognizedText ?? string.Empty;
    }
}
#else
// Server targets need a completely different implementation
public class PlatformOcrHandler
{
    public string ProcessAsync(string imagePath)
    {
        // Duplicate logic using a different library
        throw new PlatformNotSupportedException("Use server OCR library here");
    }
}
#endif
// Attempting to share OCR logic across MAUI and non-MAUI targets
// requires platform-conditional compilation — a maintenance hazard

#if ANDROID || IOS || WINDOWS
// Only compile this block in MAUI targets
// Non-MAUI targets cannot reference TesseractOcrMaui at all
using TesseractOcrMaui;

public class PlatformOcrHandler
{
    private readonly ITesseract _tesseract;

    public PlatformOcrHandler(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<string> ProcessAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var r = await _tesseract.RecognizeTextAsync(imagePath);
        return r.RecognizedText ?? string.Empty;
    }
}
#else
// Server targets need a completely different implementation
public class PlatformOcrHandler
{
    public string ProcessAsync(string imagePath)
    {
        // Duplicate logic using a different library
        throw new PlatformNotSupportedException("Use server OCR library here");
    }
}
#endif
#If ANDROID Or IOS Or WINDOWS Then
Imports TesseractOcrMaui

Public Class PlatformOcrHandler
    Private ReadOnly _tesseract As ITesseract

    Public Sub New(tesseract As ITesseract)
        _tesseract = tesseract
    End Sub

    Public Async Function ProcessAsync(imagePath As String) As Task(Of String)
        Await _tesseract.InitAsync("eng")
        Dim r = Await _tesseract.RecognizeTextAsync(imagePath)
        Return If(r.RecognizedText, String.Empty)
    End Function
End Class
#Else
Public Class PlatformOcrHandler
    Public Function ProcessAsync(imagePath As String) As String
        Throw New PlatformNotSupportedException("Use server OCR library here")
    End Function
End Class
#End If
$vbLabelText   $csharpLabel

IronOCR Approach:

// One implementation — no conditional compilation, no duplicate logic
using IronOcr;

public class PlatformOcrHandler
{
    // This class compiles identically for:
    // net8.0-android, net8.0-ios, net8.0-windows (MAUI targets)
    // net8.0 (server targets)
    // netstandard2.1 (shared library targets)

    public string Process(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        input.Deskew();
        return ocr.Read(input).Text;
    }
}

// Multi-target .csproj — no conditional package references needed
// <TargetFrameworks>net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
// IronOcr resolves correctly for all three targets from one package reference
// One implementation — no conditional compilation, no duplicate logic
using IronOcr;

public class PlatformOcrHandler
{
    // This class compiles identically for:
    // net8.0-android, net8.0-ios, net8.0-windows (MAUI targets)
    // net8.0 (server targets)
    // netstandard2.1 (shared library targets)

    public string Process(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        input.Deskew();
        return ocr.Read(input).Text;
    }
}

// Multi-target .csproj — no conditional package references needed
// <TargetFrameworks>net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
// IronOcr resolves correctly for all three targets from one package reference
Imports IronOcr

Public Class PlatformOcrHandler
    ' This class compiles identically for:
    ' net8.0-android, net8.0-ios, net8.0-windows (MAUI targets)
    ' net8.0 (server targets)
    ' netstandard2.1 (shared library targets)

    Public Function Process(imagePath As String) As String
        Dim ocr As New IronTesseract()
        Using input As New OcrInput()
            input.LoadImage(imagePath)
            input.Deskew()
            Return ocr.Read(input).Text
        End Using
    End Function
End Class

' Multi-target .csproj — no conditional package references needed
' <TargetFrameworks>net8.0;net8.0-android;net8.0-ios</TargetFrameworks>
' IronOcr resolves correctly for all three targets from one package reference
$vbLabelText   $csharpLabel

Platform conditionals in OCR handling code indicate an architectural split that compounds over time. Every language configuration change, every preprocessing adjustment, every confidence threshold tweak must be applied in both branches. IronOCR makes the split unnecessary. The .NET OCR library overview covers multi-target project structure in detail.

Structured Data Extraction with Word Coordinates

TesseractOcrMaui exposes only result.RecognizedText and a top-level confidence score. Extracting individual words with their bounding boxes — required for form field validation, document parsing, or highlight overlays — is not possible. IronOCR exposes a full document object model: pages, paragraphs, lines, words, and characters, each with pixel coordinates.

TesseractOcrMaui Approach:

// TesseractOcrMaui: flat text string only — no structure, no coordinates
public class TesseractMauiFormParser
{
    private readonly ITesseract _tesseract;

    public TesseractMauiFormParser(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<Dictionary<string, string>> ParseFormAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);

        // result.RecognizedText is one flat string — no field positions
        // Parsing requires fragile line-splitting and regex heuristics
        var fields = new Dictionary<string, string>();
        var lines = result.RecognizedText?.Split('\n') ?? Array.Empty<string>();

        foreach (var line in lines)
        {
            // Hope the layout stays consistent enough to parse
            var parts = line.Split(':');
            if (parts.Length == 2)
                fields[parts[0].Trim()] = parts[1].Trim();
        }

        return fields;
        // No way to validate against expected field positions
        // No confidence per word — only document-level confidence
    }
}
// TesseractOcrMaui: flat text string only — no structure, no coordinates
public class TesseractMauiFormParser
{
    private readonly ITesseract _tesseract;

    public TesseractMauiFormParser(ITesseract tesseract)
    {
        _tesseract = tesseract;
    }

    public async Task<Dictionary<string, string>> ParseFormAsync(string imagePath)
    {
        await _tesseract.InitAsync("eng");
        var result = await _tesseract.RecognizeTextAsync(imagePath);

        // result.RecognizedText is one flat string — no field positions
        // Parsing requires fragile line-splitting and regex heuristics
        var fields = new Dictionary<string, string>();
        var lines = result.RecognizedText?.Split('\n') ?? Array.Empty<string>();

        foreach (var line in lines)
        {
            // Hope the layout stays consistent enough to parse
            var parts = line.Split(':');
            if (parts.Length == 2)
                fields[parts[0].Trim()] = parts[1].Trim();
        }

        return fields;
        // No way to validate against expected field positions
        // No confidence per word — only document-level confidence
    }
}
Imports System.Threading.Tasks

' TesseractOcrMaui: flat text string only — no structure, no coordinates
Public Class TesseractMauiFormParser
    Private ReadOnly _tesseract As ITesseract

    Public Sub New(tesseract As ITesseract)
        _tesseract = tesseract
    End Sub

    Public Async Function ParseFormAsync(imagePath As String) As Task(Of Dictionary(Of String, String))
        Await _tesseract.InitAsync("eng")
        Dim result = Await _tesseract.RecognizeTextAsync(imagePath)

        ' result.RecognizedText is one flat string — no field positions
        ' Parsing requires fragile line-splitting and regex heuristics
        Dim fields As New Dictionary(Of String, String)()
        Dim lines = If(result.RecognizedText?.Split(ControlChars.Lf), Array.Empty(Of String)())

        For Each line In lines
            ' Hope the layout stays consistent enough to parse
            Dim parts = line.Split(":"c)
            If parts.Length = 2 Then
                fields(parts(0).Trim()) = parts(1).Trim()
            End If
        Next

        Return fields
        ' No way to validate against expected field positions
        ' No confidence per word — only document-level confidence
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

// IronOCR: full document structure with bounding boxes per word
using IronOcr;

public class IronOcrFormParser
{
    public List<WordLocation> ExtractWordsWithPositions(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);

        var result = ocr.Read(input);
        var wordLocations = new List<WordLocation>();

        foreach (var page in result.Pages)
        {
            foreach (var word in page.Words)
            {
                wordLocations.Add(new WordLocation
                {
                    Text = word.Text,
                    Confidence = word.Confidence,
                    X = word.X,
                    Y = word.Y,
                    Width = word.Width,
                    Height = word.Height
                });
            }
        }

        return wordLocations;
    }

    public FormData ParseStructuredForm(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        input.Deskew();

        var result = ocr.Read(input);
        var form = new FormData();

        foreach (var page in result.Pages)
        {
            foreach (var paragraph in page.Paragraphs)
            {
                // Use Y coordinate to identify form regions
                if (paragraph.Y < 200)
                    form.HeaderText += paragraph.Text + " ";
                else if (paragraph.Y > 800)
                    form.FooterText += paragraph.Text + " ";
                else
                    form.BodyLines.Add(paragraph.Text);
            }
        }

        form.OverallConfidence = result.Confidence;
        return form;
    }
}

public class WordLocation
{
    public string Text { get; set; }
    public float Confidence { get; set; }
    public int X { get; set; }
    public int Y { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
}

public class FormData
{
    public string HeaderText { get; set; } = string.Empty;
    public string FooterText { get; set; } = string.Empty;
    public List<string> BodyLines { get; set; } = new();
    public float OverallConfidence { get; set; }
}
// IronOCR: full document structure with bounding boxes per word
using IronOcr;

public class IronOcrFormParser
{
    public List<WordLocation> ExtractWordsWithPositions(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);

        var result = ocr.Read(input);
        var wordLocations = new List<WordLocation>();

        foreach (var page in result.Pages)
        {
            foreach (var word in page.Words)
            {
                wordLocations.Add(new WordLocation
                {
                    Text = word.Text,
                    Confidence = word.Confidence,
                    X = word.X,
                    Y = word.Y,
                    Width = word.Width,
                    Height = word.Height
                });
            }
        }

        return wordLocations;
    }

    public FormData ParseStructuredForm(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        input.Deskew();

        var result = ocr.Read(input);
        var form = new FormData();

        foreach (var page in result.Pages)
        {
            foreach (var paragraph in page.Paragraphs)
            {
                // Use Y coordinate to identify form regions
                if (paragraph.Y < 200)
                    form.HeaderText += paragraph.Text + " ";
                else if (paragraph.Y > 800)
                    form.FooterText += paragraph.Text + " ";
                else
                    form.BodyLines.Add(paragraph.Text);
            }
        }

        form.OverallConfidence = result.Confidence;
        return form;
    }
}

public class WordLocation
{
    public string Text { get; set; }
    public float Confidence { get; set; }
    public int X { get; set; }
    public int Y { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }
}

public class FormData
{
    public string HeaderText { get; set; } = string.Empty;
    public string FooterText { get; set; } = string.Empty;
    public List<string> BodyLines { get; set; } = new();
    public float OverallConfidence { get; set; }
}
Imports IronOcr

Public Class IronOcrFormParser
    Public Function ExtractWordsWithPositions(imagePath As String) As List(Of WordLocation)
        Dim ocr = New IronTesseract()
        Using input = New OcrInput()
            input.LoadImage(imagePath)

            Dim result = ocr.Read(input)
            Dim wordLocations = New List(Of WordLocation)()

            For Each page In result.Pages
                For Each word In page.Words
                    wordLocations.Add(New WordLocation With {
                        .Text = word.Text,
                        .Confidence = word.Confidence,
                        .X = word.X,
                        .Y = word.Y,
                        .Width = word.Width,
                        .Height = word.Height
                    })
                Next
            Next

            Return wordLocations
        End Using
    End Function

    Public Function ParseStructuredForm(imagePath As String) As FormData
        Dim ocr = New IronTesseract()
        Using input = New OcrInput()
            input.LoadImage(imagePath)
            input.Deskew()

            Dim result = ocr.Read(input)
            Dim form = New FormData()

            For Each page In result.Pages
                For Each paragraph In page.Paragraphs
                    ' Use Y coordinate to identify form regions
                    If paragraph.Y < 200 Then
                        form.HeaderText &= paragraph.Text & " "
                    ElseIf paragraph.Y > 800 Then
                        form.FooterText &= paragraph.Text & " "
                    Else
                        form.BodyLines.Add(paragraph.Text)
                    End If
                Next
            Next

            form.OverallConfidence = result.Confidence
            Return form
        End Using
    End Function
End Class

Public Class WordLocation
    Public Property Text As String
    Public Property Confidence As Single
    Public Property X As Integer
    Public Property Y As Integer
    Public Property Width As Integer
    Public Property Height As Integer
End Class

Public Class FormData
    Public Property HeaderText As String = String.Empty
    Public Property FooterText As String = String.Empty
    Public Property BodyLines As List(Of String) = New List(Of String)()
    Public Property OverallConfidence As Single
End Class
$vbLabelText   $csharpLabel

Word coordinates enable validation against known form templates, confidence-based flagging for human review, and highlight overlays in document viewer UIs. The structured results guide documents the full OcrResult object model including character-level access and the confidence scores guide covers per-word confidence filtering patterns.

Background Processing with Native Async and Progress Tracking

TesseractOcrMaui exposes an async API (RecognizeTextAsync) but only within the MAUI application context. Long-running batch jobs must run in a background service, Azure Function, or worker process — none of which TesseractOcrMaui can target. IronOCR provides native async support that works in any hosted service.

TesseractOcrMaui Approach:

// Background processing is impossible with TesseractOcrMaui.
// IHostedService runs in a server context — TesseractOcrMaui has no server target.
// The MAUI async API exists, but there is nowhere to run it outside the MAUI app host.

public class DocumentBatchWorker : BackgroundService
{
    // ITesseract cannot be injected here — no MAUI DI in a hosted service
    // Attempting to reference TesseractOcrMaui will fail to compile:
    // error: Package TesseractOcrMaui does not support target net8.0
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        throw new PlatformNotSupportedException(
            "TesseractOcrMaui has no server target. Use a different OCR library.");
    }
}
// Background processing is impossible with TesseractOcrMaui.
// IHostedService runs in a server context — TesseractOcrMaui has no server target.
// The MAUI async API exists, but there is nowhere to run it outside the MAUI app host.

public class DocumentBatchWorker : BackgroundService
{
    // ITesseract cannot be injected here — no MAUI DI in a hosted service
    // Attempting to reference TesseractOcrMaui will fail to compile:
    // error: Package TesseractOcrMaui does not support target net8.0
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        throw new PlatformNotSupportedException(
            "TesseractOcrMaui has no server target. Use a different OCR library.");
    }
}
' Background processing is impossible with TesseractOcrMaui.
' IHostedService runs in a server context — TesseractOcrMaui has no server target.
' The MAUI async API exists, but there is nowhere to run it outside the MAUI app host.

Public Class DocumentBatchWorker
    Inherits BackgroundService

    ' ITesseract cannot be injected here — no MAUI DI in a hosted service
    ' Attempting to reference TesseractOcrMaui will fail to compile:
    ' error: Package TesseractOcrMaui does not support target net8.0
    Protected Overrides Function ExecuteAsync(stoppingToken As CancellationToken) As Task
        Throw New PlatformNotSupportedException(
            "TesseractOcrMaui has no server target. Use a different OCR library.")
    End Function
End Class
$vbLabelText   $csharpLabel

IronOCR Approach:

// IronOCR: hosted service background batch processor
using IronOcr;
using Microsoft.Extensions.Hosting;

public class DocumentBatchWorker : BackgroundService
{
    private readonly ILogger<DocumentBatchWorker> _logger;
    private readonly string _inputFolder;
    private readonly string _outputFolder;

    public DocumentBatchWorker(ILogger<DocumentBatchWorker> logger, IConfiguration config)
    {
        _logger = logger;
        _inputFolder = config["Ocr:InputFolder"];
        _outputFolder = config["Ocr:OutputFolder"];
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var pendingFiles = Directory.GetFiles(_inputFolder, "*.pdf")
                .Concat(Directory.GetFiles(_inputFolder, "*.jpg"))
                .ToList();

            if (pendingFiles.Count > 0)
            {
                _logger.LogInformation("Processing {Count} documents.", pendingFiles.Count);

                // Thread-safe parallel processing — one IronTesseract per thread
                await Parallel.ForEachAsync(pendingFiles,
                    new ParallelOptions { MaxDegreeOfParallelism = 4, CancellationToken = stoppingToken },
                    async (filePath, ct) =>
                    {
                        await ProcessDocumentAsync(filePath, ct);
                    });
            }

            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }

    private async Task ProcessDocumentAsync(string filePath, CancellationToken ct)
    {
        try
        {
            var ocr = new IronTesseract();
            using var input = new OcrInput();

            if (Path.GetExtension(filePath).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
                input.LoadPdf(filePath);
            else
                input.LoadImage(filePath);

            input.Deskew();
            input.DeNoise();

            var result = await Task.Run(() => ocr.Read(input), ct);

            // Produce searchable PDF from the same OCR pass
            var outputPath = Path.Combine(_outputFolder,
                Path.GetFileNameWithoutExtension(filePath) + "_searchable.pdf");
            result.SaveAsSearchablePdf(outputPath);

            File.Delete(filePath); // move from input queue
            _logger.LogInformation("Processed {File}: {Confidence:F1}% confidence.", filePath, result.Confidence);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process {File}.", filePath);
        }
    }
}
// IronOCR: hosted service background batch processor
using IronOcr;
using Microsoft.Extensions.Hosting;

public class DocumentBatchWorker : BackgroundService
{
    private readonly ILogger<DocumentBatchWorker> _logger;
    private readonly string _inputFolder;
    private readonly string _outputFolder;

    public DocumentBatchWorker(ILogger<DocumentBatchWorker> logger, IConfiguration config)
    {
        _logger = logger;
        _inputFolder = config["Ocr:InputFolder"];
        _outputFolder = config["Ocr:OutputFolder"];
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var pendingFiles = Directory.GetFiles(_inputFolder, "*.pdf")
                .Concat(Directory.GetFiles(_inputFolder, "*.jpg"))
                .ToList();

            if (pendingFiles.Count > 0)
            {
                _logger.LogInformation("Processing {Count} documents.", pendingFiles.Count);

                // Thread-safe parallel processing — one IronTesseract per thread
                await Parallel.ForEachAsync(pendingFiles,
                    new ParallelOptions { MaxDegreeOfParallelism = 4, CancellationToken = stoppingToken },
                    async (filePath, ct) =>
                    {
                        await ProcessDocumentAsync(filePath, ct);
                    });
            }

            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
        }
    }

    private async Task ProcessDocumentAsync(string filePath, CancellationToken ct)
    {
        try
        {
            var ocr = new IronTesseract();
            using var input = new OcrInput();

            if (Path.GetExtension(filePath).Equals(".pdf", StringComparison.OrdinalIgnoreCase))
                input.LoadPdf(filePath);
            else
                input.LoadImage(filePath);

            input.Deskew();
            input.DeNoise();

            var result = await Task.Run(() => ocr.Read(input), ct);

            // Produce searchable PDF from the same OCR pass
            var outputPath = Path.Combine(_outputFolder,
                Path.GetFileNameWithoutExtension(filePath) + "_searchable.pdf");
            result.SaveAsSearchablePdf(outputPath);

            File.Delete(filePath); // move from input queue
            _logger.LogInformation("Processed {File}: {Confidence:F1}% confidence.", filePath, result.Confidence);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process {File}.", filePath);
        }
    }
}
Imports IronOcr
Imports Microsoft.Extensions.Hosting
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.Extensions.Logging
Imports Microsoft.Extensions.Configuration

Public Class DocumentBatchWorker
    Inherits BackgroundService

    Private ReadOnly _logger As ILogger(Of DocumentBatchWorker)
    Private ReadOnly _inputFolder As String
    Private ReadOnly _outputFolder As String

    Public Sub New(logger As ILogger(Of DocumentBatchWorker), config As IConfiguration)
        _logger = logger
        _inputFolder = config("Ocr:InputFolder")
        _outputFolder = config("Ocr:OutputFolder")
    End Sub

    Protected Overrides Async Function ExecuteAsync(stoppingToken As CancellationToken) As Task
        While Not stoppingToken.IsCancellationRequested
            Dim pendingFiles = Directory.GetFiles(_inputFolder, "*.pdf").Concat(Directory.GetFiles(_inputFolder, "*.jpg")).ToList()

            If pendingFiles.Count > 0 Then
                _logger.LogInformation("Processing {Count} documents.", pendingFiles.Count)

                ' Thread-safe parallel processing — one IronTesseract per thread
                Await Parallel.ForEachAsync(pendingFiles,
                    New ParallelOptions With {.MaxDegreeOfParallelism = 4, .CancellationToken = stoppingToken},
                    Async Function(filePath, ct)
                        Await ProcessDocumentAsync(filePath, ct)
                    End Function)
            End If

            Await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken)
        End While
    End Function

    Private Async Function ProcessDocumentAsync(filePath As String, ct As CancellationToken) As Task
        Try
            Dim ocr = New IronTesseract()
            Using input = New OcrInput()

                If Path.GetExtension(filePath).Equals(".pdf", StringComparison.OrdinalIgnoreCase) Then
                    input.LoadPdf(filePath)
                Else
                    input.LoadImage(filePath)
                End If

                input.Deskew()
                input.DeNoise()

                Dim result = Await Task.Run(Function() ocr.Read(input), ct)

                ' Produce searchable PDF from the same OCR pass
                Dim outputPath = Path.Combine(_outputFolder, Path.GetFileNameWithoutExtension(filePath) & "_searchable.pdf")
                result.SaveAsSearchablePdf(outputPath)

                File.Delete(filePath) ' move from input queue
                _logger.LogInformation("Processed {File}: {Confidence:F1}% confidence.", filePath, result.Confidence)
            End Using
        Catch ex As Exception
            _logger.LogError(ex, "Failed to process {File}.", filePath)
        End Try
    End Function
End Class
$vbLabelText   $csharpLabel

The worker registers in Program.cs with builder.Services.AddHostedService<DocumentBatchWorker>() and runs in any .NET 8 host — Windows Service, Linux systemd unit, Docker container, or Azure Container App. The async OCR guide covers async patterns and the searchable PDF guide documents the SaveAsSearchablePdf output options.

TesseractOcrMaui API to IronOCR Mapping Reference

TesseractOcrMaui IronOCR Equivalent
dotnet add package TesseractOcrMaui dotnet add package IronOcr
builder.Services.AddTesseractOcr() Remove entirely — no registration required
ITesseract (injected) new IronTesseract() (direct instantiation)
_tesseract.InitAsync("eng") ocr.Language = OcrLanguage.English; (or omit for default English)
_tesseract.RecognizeTextAsync(imagePath) ocr.Read(input)
result.RecognizedText result.Text
result.Success Exception-based; no boolean flag
result.Status catch (Exception ex) message
result.Confidence result.Confidence (also per-word)
TesseractOcrMaui.Results.RecognitionResult IronOcr.OcrResult
<MauiAsset> traineddata bundle dotnet add package IronOcr.Languages.French
Resources/Raw/tessdata/eng.traineddata Remove — language data is inside NuGet package
FileSystem.OpenAppPackageFileAsync() (for traineddata) Remove — not needed
No PDF support input.LoadPdf(path) or input.LoadPdf(stream)
No preprocessing input.Deskew(), input.DeNoise(), input.Binarize(), input.Contrast()
No searchable PDF output result.SaveAsSearchablePdf(outputPath)
No word coordinates result.Pages[0].Words[i].X, .Y, .Width, .Height
No per-word confidence result.Pages[0].Words[i].Confidence
net8.0-ios target only net8.0 + IronOcr.iOS package
net8.0-android target only net8.0 + IronOcr.Android package

Common Migration Issues and Solutions

Issue 1: AddTesseractOcr Cannot Be Removed Without Breaking Dependent Classes

TesseractOcrMaui: Every class that performs OCR receives ITesseract through constructor injection. Removing AddTesseractOcr() immediately breaks those constructors with a DI resolution exception.

Solution: Remove the constructor parameter and replace it with direct IronTesseract instantiation. If the project uses a DI container and you want to keep the injectable pattern, register IronTesseract manually:

// Option A: Direct instantiation (recommended for most cases)
public class ScanPageViewModel
{
    public string ScanDocument(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return ocr.Read(input).Text;
    }
}

// Option B: Register IronTesseract in DI if your architecture requires it
// In MauiProgram.cs or Program.cs:
builder.Services.AddSingleton<IronTesseract>();

// Then inject normally:
public class ScanPageViewModel
{
    private readonly IronTesseract _ocr;
    public ScanPageViewModel(IronTesseract ocr) { _ocr = ocr; }

    public string ScanDocument(string imagePath)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return _ocr.Read(input).Text;
    }
}
// Option A: Direct instantiation (recommended for most cases)
public class ScanPageViewModel
{
    public string ScanDocument(string imagePath)
    {
        var ocr = new IronTesseract();
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return ocr.Read(input).Text;
    }
}

// Option B: Register IronTesseract in DI if your architecture requires it
// In MauiProgram.cs or Program.cs:
builder.Services.AddSingleton<IronTesseract>();

// Then inject normally:
public class ScanPageViewModel
{
    private readonly IronTesseract _ocr;
    public ScanPageViewModel(IronTesseract ocr) { _ocr = ocr; }

    public string ScanDocument(string imagePath)
    {
        using var input = new OcrInput();
        input.LoadImage(imagePath);
        return _ocr.Read(input).Text;
    }
}
Imports IronTesseract

' Option A: Direct instantiation (recommended for most cases)
Public Class ScanPageViewModel
    Public Function ScanDocument(imagePath As String) As String
        Dim ocr As New IronTesseract()
        Using input As New OcrInput()
            input.LoadImage(imagePath)
            Return ocr.Read(input).Text
        End Using
    End Function
End Class

' Option B: Register IronTesseract in DI if your architecture requires it
' In MauiProgram.vb or Program.vb:
' builder.Services.AddSingleton(Of IronTesseract)()

' Then inject normally:
Public Class ScanPageViewModel
    Private ReadOnly _ocr As IronTesseract

    Public Sub New(ocr As IronTesseract)
        _ocr = ocr
    End Sub

    Public Function ScanDocument(imagePath As String) As String
        Using input As New OcrInput()
            input.LoadImage(imagePath)
            Return _ocr.Read(input).Text
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Issue 2: Traineddata Files Missing After Package Removal

TesseractOcrMaui: The Resources/Raw/tessdata/ folder, the .traineddata files inside it, and the <MauiAsset> declarations in the .csproj all need to be removed. Leaving them causes build warnings and inflates the app bundle with unused files.

Solution: Delete the tessdata folder, remove the <MauiAsset> entries, and uninstall any language that was manually downloaded. Install the equivalent IronOCR language pack instead:

# Delete traineddata assets
rm -rf Resources/Raw/tessdata

# Remove from .csproj (delete the MauiAsset ItemGroup):
# <ItemGroup>
#   <MauiAsset Include="Resources\Raw\tessdata\*.traineddata" />
# </ItemGroup>

# Install IronOCR language pack (if non-English language was needed)
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
# Delete traineddata assets
rm -rf Resources/Raw/tessdata

# Remove from .csproj (delete the MauiAsset ItemGroup):
# <ItemGroup>
#   <MauiAsset Include="Resources\Raw\tessdata\*.traineddata" />
# </ItemGroup>

# Install IronOCR language pack (if non-English language was needed)
dotnet add package IronOcr.Languages.French
dotnet add package IronOcr.Languages.German
SHELL

Language packages from IronOCR are resolved at build time and bundled without any manual file management. The multiple languages guide documents all available packages and simultaneous multi-language configuration.

Issue 3: InitAsync Must Be Called Before Every RecognizeTextAsync

TesseractOcrMaui: The ITesseract.InitAsync(language) call must precede every RecognizeTextAsync call. Teams often add _isInitialized guard flags, double-checked locking, or semaphores to avoid repeated initialization. All of that code becomes dead code after migration.

Solution: IronTesseract has no initialization step. Language is set once on the instance. Remove all InitAsync calls, all _isInitialized flags, and all initialization guard logic:

// Before: initialization guard required before every OCR call
private bool _isInitialized = false;
private readonly SemaphoreSlim _initLock = new SemaphoreSlim(1, 1);

public async Task<string> GetTextAsync(string imagePath)
{
    await _initLock.WaitAsync();
    try
    {
        if (!_isInitialized)
        {
            await _tesseract.InitAsync("eng");
            _isInitialized = true;
        }
    }
    finally { _initLock.Release(); }

    var result = await _tesseract.RecognizeTextAsync(imagePath);
    return result.RecognizedText ?? string.Empty;
}

// After: no initialization, no guard, no semaphore
public string GetText(string imagePath)
{
    var ocr = new IronTesseract();
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    return ocr.Read(input).Text;
}
// Before: initialization guard required before every OCR call
private bool _isInitialized = false;
private readonly SemaphoreSlim _initLock = new SemaphoreSlim(1, 1);

public async Task<string> GetTextAsync(string imagePath)
{
    await _initLock.WaitAsync();
    try
    {
        if (!_isInitialized)
        {
            await _tesseract.InitAsync("eng");
            _isInitialized = true;
        }
    }
    finally { _initLock.Release(); }

    var result = await _tesseract.RecognizeTextAsync(imagePath);
    return result.RecognizedText ?? string.Empty;
}

// After: no initialization, no guard, no semaphore
public string GetText(string imagePath)
{
    var ocr = new IronTesseract();
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    return ocr.Read(input).Text;
}
Imports System.Threading
Imports System.Threading.Tasks

' Before: initialization guard required before every OCR call
Private _isInitialized As Boolean = False
Private ReadOnly _initLock As New SemaphoreSlim(1, 1)

Public Async Function GetTextAsync(imagePath As String) As Task(Of String)
    Await _initLock.WaitAsync()
    Try
        If Not _isInitialized Then
            Await _tesseract.InitAsync("eng")
            _isInitialized = True
        End If
    Finally
        _initLock.Release()
    End Try

    Dim result = Await _tesseract.RecognizeTextAsync(imagePath)
    Return If(result.RecognizedText, String.Empty)
End Function

' After: no initialization, no guard, no semaphore
Public Function GetText(imagePath As String) As String
    Dim ocr As New IronTesseract()
    Using input As New OcrInput()
        input.LoadImage(imagePath)
        Return ocr.Read(input).Text
    End Using
End Function
$vbLabelText   $csharpLabel

Issue 4: result.Success Check Pattern Must Be Replaced

TesseractOcrMaui: The RecognizeTextAsync return value carries a Success boolean and a Status string. Code that checks if (!result.Success) and reads result.Status for error information needs to be rewritten.

Solution: IronOCR uses standard .NET exception semantics. Replace success-flag checks with try/catch. On success, .Text is always populated (empty string if no text was found):

// Before: success-flag pattern
var result = await _tesseract.RecognizeTextAsync(imagePath);
if (!result.Success)
{
    logger.LogError("OCR failed: {Status}", result.Status);
    return string.Empty;
}
return result.RecognizedText ?? string.Empty;

// After: exception pattern
try
{
    var ocr = new IronTesseract();
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    var result = ocr.Read(input);
    return result.Text; // empty string if no text found — never null
}
catch (Exception ex)
{
    logger.LogError(ex, "OCR failed for {Path}.", imagePath);
    return string.Empty;
}
// Before: success-flag pattern
var result = await _tesseract.RecognizeTextAsync(imagePath);
if (!result.Success)
{
    logger.LogError("OCR failed: {Status}", result.Status);
    return string.Empty;
}
return result.RecognizedText ?? string.Empty;

// After: exception pattern
try
{
    var ocr = new IronTesseract();
    using var input = new OcrInput();
    input.LoadImage(imagePath);
    var result = ocr.Read(input);
    return result.Text; // empty string if no text found — never null
}
catch (Exception ex)
{
    logger.LogError(ex, "OCR failed for {Path}.", imagePath);
    return string.Empty;
}
Imports IronOcr

' Before: success-flag pattern
Dim result = Await _tesseract.RecognizeTextAsync(imagePath)
If Not result.Success Then
    logger.LogError("OCR failed: {Status}", result.Status)
    Return String.Empty
End If
Return If(result.RecognizedText, String.Empty)

' After: exception pattern
Try
    Dim ocr = New IronTesseract()
    Using input As New OcrInput()
        input.LoadImage(imagePath)
        Dim result = ocr.Read(input)
        Return result.Text ' empty string if no text found — never null
    End Using
Catch ex As Exception
    logger.LogError(ex, "OCR failed for {Path}.", imagePath)
    Return String.Empty
End Try
$vbLabelText   $csharpLabel

Issue 5: MAUI-Only Target Framework in Shared Library Projects

TesseractOcrMaui: A class library that references TesseractOcrMaui automatically inherits its platform constraint. The library's <TargetFramework> must be set to a MAUI moniker (net8.0-android, net8.0-ios, or net8.0-windows), which prevents it from being referenced by server projects.

Solution: Change the class library target to net8.0 or netstandard2.1 and reference IronOcr instead. The library now resolves correctly from any consuming project:


<TargetFramework>net8.0-android</TargetFramework>
<PackageReference Include="TesseractOcrMaui" Version="*" />

<TargetFramework>net8.0</TargetFramework>
<PackageReference Include="IronOcr" Version="*" />

<TargetFramework>net8.0-android</TargetFramework>
<PackageReference Include="TesseractOcrMaui" Version="*" />

<TargetFramework>net8.0</TargetFramework>
<PackageReference Include="IronOcr" Version="*" />
XML

Issue 6: PDF Processing Requires a Second Library to Be Removed

TesseractOcrMaui: Teams that implemented PDF support added a second library (PDFium, PdfPig, or a cloud renderer) to convert PDF pages to images before passing them to RecognizeTextAsync. After migrating to IronOCR, that second library and all its page-rendering code can be deleted.

Solution: Remove the PDF rendering library and replace the entire page-extraction pipeline with input.LoadPdf():

// Before: PDF library + manual temp file management (50+ lines)
using var pdfDoc = PdfDocument.Open(pdfPath);
var results = new List<string>();
foreach (var page in pdfDoc.GetPages())
{
    var tempImagePath = Path.Combine(FileSystem.CacheDirectory, $"page_{page.Number}.png");
    RenderPageToImage(page, tempImagePath, dpi: 300);
    await _tesseract.InitAsync("eng");
    var r = await _tesseract.RecognizeTextAsync(tempImagePath);
    results.Add(r.RecognizedText ?? string.Empty);
    File.Delete(tempImagePath);
}
return string.Join("\n", results);

// After: native PDF support — 5 lines
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadPdf(pdfPath);
var result = ocr.Read(input);
return result.Text;
// Before: PDF library + manual temp file management (50+ lines)
using var pdfDoc = PdfDocument.Open(pdfPath);
var results = new List<string>();
foreach (var page in pdfDoc.GetPages())
{
    var tempImagePath = Path.Combine(FileSystem.CacheDirectory, $"page_{page.Number}.png");
    RenderPageToImage(page, tempImagePath, dpi: 300);
    await _tesseract.InitAsync("eng");
    var r = await _tesseract.RecognizeTextAsync(tempImagePath);
    results.Add(r.RecognizedText ?? string.Empty);
    File.Delete(tempImagePath);
}
return string.Join("\n", results);

// After: native PDF support — 5 lines
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadPdf(pdfPath);
var result = ocr.Read(input);
return result.Text;
Imports System.IO

' Before: PDF library + manual temp file management (50+ lines)
Using pdfDoc = PdfDocument.Open(pdfPath)
    Dim results As New List(Of String)()
    For Each page In pdfDoc.GetPages()
        Dim tempImagePath = Path.Combine(FileSystem.CacheDirectory, $"page_{page.Number}.png")
        RenderPageToImage(page, tempImagePath, dpi:=300)
        Await _tesseract.InitAsync("eng")
        Dim r = Await _tesseract.RecognizeTextAsync(tempImagePath)
        results.Add(If(r.RecognizedText, String.Empty))
        File.Delete(tempImagePath)
    Next
    Return String.Join(vbLf, results)
End Using

' After: native PDF support — 5 lines
Dim ocr As New IronTesseract()
Using input As New OcrInput()
    input.LoadPdf(pdfPath)
    Dim result = ocr.Read(input)
    Return result.Text
End Using
$vbLabelText   $csharpLabel

The PDF input guide covers page range selection, password-protected PDFs, and stream-based loading.

TesseractOcrMaui Migration Checklist

Pre-Migration

Audit the codebase to inventory all TesseractOcrMaui usage before changing any code:

# Find all files that reference TesseractOcrMaui namespaces
grep -r "TesseractOcrMaui" --include="*.cs" .

# Find all ITesseract injection points
grep -r "ITesseract" --include="*.cs" .

# Find all AddTesseractOcr registrations
grep -r "AddTesseractOcr" --include="*.cs" .

# Find all InitAsync calls
grep -r "InitAsync" --include="*.cs" .

# Find all RecognizeTextAsync calls
grep -r "RecognizeTextAsync" --include="*.cs" .

# Find traineddata asset declarations in project files
grep -r "tessdata" --include="*.csproj" .

# Find MauiAsset traineddata declarations
grep -r "MauiAsset" --include="*.csproj" .

# Identify projects with MAUI-only target frameworks that hold OCR logic
grep -r "net8.0-android\|net8.0-ios\|net8.0-windows" --include="*.csproj" .
# Find all files that reference TesseractOcrMaui namespaces
grep -r "TesseractOcrMaui" --include="*.cs" .

# Find all ITesseract injection points
grep -r "ITesseract" --include="*.cs" .

# Find all AddTesseractOcr registrations
grep -r "AddTesseractOcr" --include="*.cs" .

# Find all InitAsync calls
grep -r "InitAsync" --include="*.cs" .

# Find all RecognizeTextAsync calls
grep -r "RecognizeTextAsync" --include="*.cs" .

# Find traineddata asset declarations in project files
grep -r "tessdata" --include="*.csproj" .

# Find MauiAsset traineddata declarations
grep -r "MauiAsset" --include="*.csproj" .

# Identify projects with MAUI-only target frameworks that hold OCR logic
grep -r "net8.0-android\|net8.0-ios\|net8.0-windows" --include="*.csproj" .
SHELL

Note every class that takes ITesseract in a constructor — those constructors will change. Note every project file that declares <MauiAsset> for traineddata — those declarations will be deleted. Identify whether a PDF rendering library is present and whether it is used exclusively for OCR preprocessing.

Code Migration

  1. Run dotnet remove package TesseractOcrMaui in every project that references it
  2. Run dotnet add package IronOcr in every project that will perform OCR
  3. Run dotnet add package IronOcr.Android in MAUI projects targeting Android
  4. Run dotnet add package IronOcr.iOS in MAUI projects targeting iOS
  5. Run dotnet add package IronOcr.Languages.* for any non-English language previously bundled as traineddata
  6. Add IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"; at application startup in each entry-point project
  7. Delete Resources/Raw/tessdata/ and all .traineddata files from MAUI projects
  8. Remove all <MauiAsset Include="Resources\Raw\tessdata\*.traineddata" /> lines from .csproj files
  9. Remove builder.Services.AddTesseractOcr() from all MauiProgram.cs files
  10. Replace all using TesseractOcrMaui; and using TesseractOcrMaui.Results; with using IronOcr;
  11. Remove ITesseract constructor parameters from all service and view-model classes
  12. Replace await _tesseract.InitAsync("eng") calls with ocr.Language = OcrLanguage.English; if needed (default is English)
  13. Replace await _tesseract.RecognizeTextAsync(imagePath) with ocr.Read(input) using an OcrInput instance
  14. Replace result.RecognizedText with result.Text
  15. Replace if (!result.Success) checks with try/catch blocks
  16. If a PDF rendering library was added solely to support TesseractOcrMaui, remove it and replace page-extraction code with input.LoadPdf()
  17. Change any MAUI-only target framework in class libraries that held OCR logic to net8.0 or netstandard2.1

Post-Migration

  • Verify that OCR produces text from a JPEG image captured by the device camera on both iOS and Android targets
  • Verify that OCR produces text from the same image loaded via byte[] in the server-side API endpoint
  • Confirm the shared class library compiles and runs identically when referenced from both MAUI and ASP.NET Core projects
  • Test that PDF input works end-to-end without any temporary file creation
  • Verify that SaveAsSearchablePdf output is indexable in a PDF viewer
  • Confirm confidence scores are present on result.Confidence and on page.Words[i].Confidence
  • Test that the MAUI app produces an error-free startup log with no traineddata file-not-found exceptions
  • Verify that the Resources/Raw/tessdata/ folder is absent from the MAUI app bundle in release builds
  • Run a parallel batch job with 10 or more documents to confirm thread safety
  • Confirm that InitAsync removal has not left orphaned semaphore or _isInitialized state variables in any service class

Key Benefits of Migrating to IronOCR

One Codebase Across the Entire Product. After migration, every project in the solution — MAUI mobile app, ASP.NET Core API, Azure Function, background worker — calls the same DocumentOcrService class from the same shared library. Language configuration, preprocessing settings, and accuracy tuning happen in one place. When a new document type requires a new preprocessing filter, the change is made once and takes effect everywhere.

Server-Side Deployment Without Rewriting. IronOCR deploys to Linux containers, Windows Server, Azure App Service, AWS Lambda, and any other .NET 8 runtime target without modifications. The same IronTesseract instance that processes mobile camera captures processes server-side PDF uploads. The Azure deployment guide and AWS deployment guide document the platform-specific configuration steps.

PDF Processing Without a Second Library. Native PDF input through input.LoadPdf() eliminates the PDF rendering library, the page-by-page image extraction loop, the temporary file management, and the cleanup code that TesseractOcrMaui's architecture required. Scanned PDF contracts, invoices, and identity documents load in one line. The same OCR pass that extracts text can produce a searchable PDF with result.SaveAsSearchablePdf() — a capability that TesseractOcrMaui cannot provide at any level.

Preprocessing That Handles Real Mobile Images. input.Deskew(), input.DeNoise(), input.Binarize(), and input.Sharpen() are single-method calls that apply calibrated image corrections before the Tesseract engine sees the data. Teams that were accepting 40–60% accuracy on low-light mobile captures without preprocessing commonly see 85–90%+ after adding a three-filter pipeline. No SkiaSharp, no ImageSharp, no algorithm implementation required. The image quality correction guide documents every available filter and when to apply it.

Commercial Support With a Defined Escalation Path. Iron Software provides email support for all IronOCR license tiers and priority phone and chat support at the Professional and Enterprise levels. When a platform update breaks native library resolution on a specific Android API level — the kind of failure that TesseractOcrMaui's GitHub issue queue handles on volunteer time — there is an actual engineering team with a response obligation. Perpetual licensing starts at $999 for the Lite tier; the licensing page lists all tiers and their included support levels.

125+ Languages Through NuGet Without App Bundle Inflation. TesseractOcrMaui bundles traineddata files inside the MAUI app — each language adds 10–50 MB to the app download size. IronOCR language packs install through NuGet and are included only in server-side builds or in platform builds where they are explicitly referenced. Mobile app bundles stay lean; server-side builds get the full language set. Adding a new language is one dotnet add package command with no project file changes and no file management. The full languages catalog lists all 125+ available packs.

Please notePDFium, PdfPig, and Tesseract are registered trademarks of their respective owners. This site is not affiliated with, endorsed by, or sponsored by Chromium Project, Google, or UglyToad. All product names, logos, and brands are property of their respective owners. Comparisons are for informational purposes only and reflect publicly available information at the time of writing.

Frequently Asked Questions

Why should I migrate from Tesseract.MAUI to IronOCR?

Common drivers include eliminating COM interop complexity, replacing file-based license management, avoiding per-page billing, enabling Docker/container deployment, and adopting a NuGet-native workflow that integrates with standard .NET tooling.

What are the main code changes when migrating from Tesseract.MAUI to IronOCR?

Replace Tesseract.MAUI initialization sequences with IronTesseract instantiation, remove COM lifecycle management (explicit Create/Load/Close patterns), and update result property names. The result is significantly fewer boilerplate lines.

How do I install IronOCR to begin the migration?

Run 'Install-Package IronOcr' in Package Manager Console or 'dotnet add package IronOcr' in the CLI. Language packs are separate packages: 'dotnet add package IronOcr.Languages.French' for French, for example.

Does IronOCR match the OCR accuracy of Tesseract.MAUI for standard business documents?

IronOCR achieves high accuracy for standard business content including invoices, contracts, receipts, and typed forms. Image preprocessing filters (deskew, noise removal, contrast enhancement) further improve recognition on degraded input.

How does IronOCR handle the language data that Tesseract.MAUI installs separately?

Language data in IronOCR is distributed as NuGet packages. 'dotnet add package IronOcr.Languages.German' installs German support. No manual file placement or directory paths are involved.

Does migrating from Tesseract.MAUI to IronOCR require changes to deployment infrastructure?

IronOCR requires fewer infrastructure changes than Tesseract.MAUI. There are no SDK binary paths, license file placements, or license server configurations. The NuGet package contains the complete OCR engine, and the license key is a string set in application code.

How do I configure IronOCR licensing after migration?

Assign IronOcr.License.LicenseKey = "YOUR-KEY" in application startup code. In Docker or Kubernetes, store the key as an environment variable and read it in startup. Use License.IsValidLicense to validate before accepting traffic.

Can IronOCR process PDFs the same way Tesseract.MAUI does?

Yes. IronOCR reads both native and scanned PDFs. Instantiate IronTesseract, call ocr.Read(input) where input is a PDF path or OcrPdfInput, and iterate the OcrResult pages. No separate PDF rendering pipeline is required.

How does IronOCR handle threading in high-volume processing?

IronTesseract is safe to instantiate per-thread. Spin up one instance per thread in a Parallel.ForEach or Task pool, run OCR concurrently, and dispose each instance when done. No global state or locking is required.

What output formats does IronOCR support after text extraction?

IronOCR returns structured results including text, word coordinates, confidence scores, and page structure. Export options include plain text, searchable PDF, and structured result objects for downstream processing.

Is IronOCR pricing more predictable than Tesseract.MAUI for scaling workloads?

IronOCR uses flat-rate perpetual licensing with no per-page or volume charges. Whether you process 10,000 or 10 million pages, the license cost remains constant. Volume and team licensing options are on the IronOCR pricing page.

What happens to my existing tests after migrating from Tesseract.MAUI to IronOCR?

Tests that assert on extracted text content should continue to pass after migration. Tests that validate API call patterns or COM object lifecycle will need updating to reflect IronOCR's simpler initialization and result model.

Kannaopat Udonpant
Software Engineer
Before becoming a Software Engineer, Kannapat completed a Environmental Resources PhD from Hokkaido University in Japan. While pursuing his degree, Kannapat also became a member of the Vehicle Robotics Laboratory, which is part of the Department of Bioproduction Engineering. In 2022, he leveraged his C# skills to join Iron Software's engineering ...
Read More

Iron Support Team

We're online 24 hours, 5 days a week.
Chat
Email
Call Me