Passer au contenu du pied de page
VIDéOS

Comment implémenter l'OCR en C# en utilisant des bibliothèques open source

Ce guide s'adresse aux développeurs .NET qui ont intégré l'API REST de Klippa et qui migrent vers IronOCR pour le traitement de documents sur site. Il décrit les étapes pratiques pour supprimer l'infrastructure client HTTP, éliminer la désérialisation JSON et remplacer les téléchargements de documents dépendants du cloud par des appels OCR locaux qui ne passent jamais par le réseau.

Pourquoi migrer depuis Klippa OCR?

Klippa est un service d'analyse documentaire exclusivement basé sur le cloud et ne nécessitant pas de kit de développement .NET . Chaque intégration est un client REST développé sur mesure. Cette réalité architecturale a des conséquences en aval qui s'accumulent tout au long du cycle de vie d'un système de production.

L'absence de package NuGet signifie que vous êtes propriétaire de la couche d'intégration. Il n'y a rien à installer. Le coût d'entrée est l'écriture d'un HttpClient wrapper, la configuration des en-têtes d'authentification X-Auth-Key, la création des corps de requête MultipartFormDataContent, la désérialisation du schéma de réponse JSON de Klippa et la mise en place de la logique de nouvelle tentative pour les échecs transitoires. Il faut donc compter 2 à 4 jours de mise en place avant que le premier document ne soit traité de manière fiable en production. Lorsque Klippa met à jour son schéma d'API, votre code de désérialisation devient incompatible et nécessite une maintenance manuelle.

Chaque téléchargement de document nécessite une connexion réseau. Klippa traite les documents exclusivement sur des serveurs hébergés dans l'UE. Les pannes de production du côté de Klippa, une latence élevée ou toute interruption de l'accès Internet sortant de votre serveur d'application interrompent complètement le traitement des documents. Il n'existe aucune solution de repli, aucun mode local, ni aucune nouvelle tentative pour résoudre le problème d'indisponibilité d'un service cloud.

Les documents sensibles quittent votre infrastructure. Les documents financiers (reçus avec les détails de paiement, factures avec les numéros de TVA et les montants, pièces d'identité avec les données du passeport) sont transmis à un serveur tiers à chaque appel d'API. Les dispositions du RGPD relatives au transfert de données répondent en partie à ces problématiques pour les traitements effectués dans l'UE, mais le périmètre de l'audit s'étend toujours à l'infrastructure de Klippa, à ses politiques de conservation des données et à ses sous-traitants. Pour les équipes ayant des contrats dans les secteurs de la santé, des services juridiques, financiers ou gouvernementaux, l'expression " hébergé dans l'UE " ne satisfait pas à l'exigence selon laquelle les données ne doivent pas quitter l'organisation.

Tarification par document sans plafond. Klippa ne publie pas ses tarifs. Pour tout volume de documents significatif (10 000 reçus par mois dans un système de gestion des dépenses, 500 factures par jour dans un flux de travail d'automatisation des comptes fournisseurs), le modèle de facturation par document engendre des coûts qu'une licence perpétuelle n'entraînerait jamais. L'évolution des coûts est directement liée à la croissance des entreprises, ce qui est l'inverse de ce que devraient faire les dépenses d'infrastructure.

Le périmètre d'intervention du spécialiste s'élargit lorsque les besoins évoluent. Klippa est formée à la gestion des reçus, des factures et des pièces d'identité. Une application conçue initialement pour la gestion des dépenses reste rarement cantonnée à ce rôle. La première fois qu'un type de document ne faisant pas partie de ces trois catégories apparaît (un contrat de travail scanné, un formulaire médical, un dessin technique, un bon de commande avec une mise en page non standard), Klippa ne renvoie rien d'utile. IronOCR traite tout document contenant du texte, sans restriction de catégorie.

Les appels REST asynchrones ajoutent de la latence dans les contextes synchrones. Chaque appel Klippa est une opération HTTP asynchrone. L'envoi aller-retour d'un seul document prend entre 500 ms et 2000 ms sur le réseau. IronOCR traite le même document localement en 100 à 400 ms sans la surcharge asynchrone dans les scénarios où le traitement synchrone convient mieux à l'architecture.

Le problème fondamental

Klippa ne possède pas de SDK. L'OCR consiste à construire et à envoyer une requête HTTP, puis à désérialiser le JSON :

// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks

var response = await _client.PostAsync(
    "https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost

var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks

var response = await _client.PostAsync(
    "https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost

var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
Imports System.Net.Http
Imports System.IO
Imports System.Text.Json
Imports System.Threading.Tasks

' Klippa: 15+ lines of HTTP plumbing before you read a single character
Dim content As New MultipartFormDataContent()
content.Add(New ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg")
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey) ' auth header — rotates, breaks, leaks

Dim response As HttpResponseMessage = Await _client.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", content)
response.EnsureSuccessStatusCode() ' throws on 4xx/5xx — no retry, document lost

Dim json As String = Await response.Content.ReadAsStringAsync()
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json) ' your schema, your maintenance
Dim text As String = parsed?.Data?.ParsedDocument?.Text ' nullable chain — breaks when schema changes
$vbLabelText   $csharpLabel

IronOCR remplace tout cela :

// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
' IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text = New IronTesseract().Read(imagePath).Text
$vbLabelText   $csharpLabel

Comparaison des fonctionnalités IronOCR et de Klippa OCR

Le tableau ci-dessous compare les deux bibliothèques selon les dimensions les plus importantes pour une décision de migration en production.

Fonction Klippa OCR IronOCR
Modèle de déploiement Serveurs exclusivement en nuage (serveurs européens) Sur place, entièrement local
Kit de développement .NET / package NuGet None IronOcr NuGet package
Internet requis Oui, à chaque appel Jamais
Les données du document quittent le réseau Toujours Jamais
OCR à usage général Non (reçus, factures, pièces d'identité uniquement) Oui (tout type de document)
Configuration de l'authentification X-Auth-Key HTTP header IronOcr.License.LicenseKey string
Client HTTP requis Oui Non
Désérialisation de la réponse Analyse manuelle JSON Objet typé OcrResult
logique de nouvelle tentative/délai d'attente roulé à la main Pas nécessaire (appel local)
Assistance hors ligne / isolée du réseau Non Oui
Entrée PDF Oui (cloud) Oui (natif, local)
Entrée TIFF multipage Inconnu Oui
Formats d'entrée d'image JPG, PNG (nuage) JPG, PNG, BMP, TIFF, GIF et plus encore
Entrée de flux et de tableau d'octets Pas de SDK Oui
prétraitement automatique des images Côté nuageux (opaque) Oui (Correction de l'inclinaison, réduction du bruit, contraste, binarisation, netteté)
Sortie structurée : coordonnées des mots Non Oui
Scores de confiance par mot Non Oui
Sortie PDF consultable Non Oui
Lecture de codes-barres lors de la reconnaissance optique de caractères (OCR) Non Oui
Assistance multilingue Limité aux types de documents formés plus de 125 langues
Sécurité du fil N/A (appels HTTP) Oui (un IronTesseract par thread)
Déploiement multiplateforme REST-agnostique Windows, Linux, macOS, Docker, Azure, AWS
Conformité HIPAA/ITAR/environnement isolé (sans contact) Non Oui
Modèle de tarification SaaS par document (tarifs non publiés) Licence perpétuelle de $999
Coût par page à l'échelle Oui, sans limites None

Démarrage rapide : Migration de Klippa OCRvers IronOCR

Étape 1 : Remplacer le package NuGet

Klippa ne possède pas de package NuGet officiel. Supprimez les dépendances du client HTTP qui existent uniquement pour prendre en charge l'intégration de Klippa :

# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
SHELL

Installez IronOCR depuis NuGet :

dotnet add package IronOcr

Étape 2 : Mise à jour des espaces de noms

Supprimez les espaces de noms HTTP et JSON requis par l'intégration de Klippa. Ajouter l'espace de noms IronOCR unique :

// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;

// After (IronOCR)
using IronOcr;
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;

// After (IronOCR)
using IronOcr;
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Text.Json
Imports System.Text.Json.Serialization

Imports IronOcr
$vbLabelText   $csharpLabel

Étape 3 : initialisation de la licence

Ajoutez l'initialisation de la licence une fois au démarrage de l'application — dans Program.cs, Startup.cs, ou avant le premier appel OCR :

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

Exemples de migration de code

Remplacement de la classe de service client HTTP

L'intégration de Klippa nécessite une classe de service complète encapsulant l'infrastructure HTTP. Il n'y a aucun moyen d'éviter cela car il n'existe pas de SDK.

Approche Klippa :

// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";

    public KlippaOcrService(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
        _httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
    }

    public async Task<string> ReadDocumentTextAsync(string filePath)
    {
        using var form = new MultipartFormDataContent();
        var fileBytes = await File.ReadAllBytesAsync(filePath);
        form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));

        var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        // navigate Klippa's nested JSON schema
        return doc.RootElement
            .GetProperty("data")
            .GetProperty("parsed_document")
            .GetProperty("text")
            .GetString() ?? string.Empty;
    }

    public void Dispose() => _httpClient.Dispose();
}
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";

    public KlippaOcrService(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
        _httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
    }

    public async Task<string> ReadDocumentTextAsync(string filePath)
    {
        using var form = new MultipartFormDataContent();
        var fileBytes = await File.ReadAllBytesAsync(filePath);
        form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));

        var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        // navigate Klippa's nested JSON schema
        return doc.RootElement
            .GetProperty("data")
            .GetProperty("parsed_document")
            .GetProperty("text")
            .GetString() ?? string.Empty;
    }

    public void Dispose() => _httpClient.Dispose();
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Text.Json

' Klippa: entire service class just to send one HTTP request
Public Class KlippaOcrService
    Implements IDisposable

    Private ReadOnly _httpClient As HttpClient
    Private ReadOnly _baseUrl As String = "https://custom-ocr.klippa.com/api/v1"

    Public Sub New(apiKey As String)
        _httpClient = New HttpClient()
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey)
        _httpClient.Timeout = TimeSpan.FromSeconds(30) ' network timeout required
    End Sub

    Public Async Function ReadDocumentTextAsync(filePath As String) As Task(Of String)
        Using form As New MultipartFormDataContent()
            Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
            form.Add(New ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath))

            Dim response = Await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form)
            response.EnsureSuccessStatusCode()

            Dim json = Await response.Content.ReadAsStringAsync()
            Using doc = JsonDocument.Parse(json)
                ' navigate Klippa's nested JSON schema
                Return doc.RootElement _
                    .GetProperty("data") _
                    .GetProperty("parsed_document") _
                    .GetProperty("text") _
                    .GetString() OrElse String.Empty
            End Using
        End Using
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        _httpClient.Dispose()
    End Sub
End Class
$vbLabelText   $csharpLabel

Approche IronOCR :

// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ReadDocumentText(string filePath)
    {
        return _ocr.Read(filePath).Text;
    }
}

// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ReadDocumentText(string filePath)
    {
        return _ocr.Read(filePath).Text;
    }
}

// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
Imports IronOcr

Public Class OcrService
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ReadDocumentText(filePath As String) As String
        Return _ocr.Read(filePath).Text
    End Function
End Class

' At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Usage — identical call site, different internals:
Dim service As New OcrService()
Dim text As String = service.ReadDocumentText("invoice.jpg") ' local, synchronous, zero network
$vbLabelText   $csharpLabel

La classe de service Klippa existe uniquement parce que l'API nécessite une infrastructure HTTP. L'équivalent d'IronOCR se réduit à un seul appel Read(). Les délais d'attente, les en-têtes d'authentification et les modèles de suppression disparaissent tous en l'absence de réseau. Consultez le guide d'installation d'IronTesseract pour les options d'initialisation et l' exemple de code OCR de base pour un code fonctionnel.

Suppression du téléchargement de formulaires en plusieurs parties

Klippa reçoit les documents sous forme de formulaires multiparties téléchargés. Le code de téléchargement est mécanique mais fragile : lectures de fichiers, en-têtes de type de contenu, construction des limites et gestion de la taille du téléchargement.

Approche Klippa :

// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
    string filePath, string documentType = "financial")
{
    using var form = new MultipartFormDataContent();

    // read file into memory — entire document in RAM before upload
    var fileBytes = await File.ReadAllBytesAsync(filePath);
    var byteContent = new ByteArrayContent(fileBytes);
    byteContent.Headers.ContentType =
        new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");

    form.Add(byteContent, "document", Path.GetFileName(filePath));
    form.Add(new StringContent(documentType), "DocumentType");

    // document leaves your server here
    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
    }

    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<KlippaResult>(json,
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
    string filePath, string documentType = "financial")
{
    using var form = new MultipartFormDataContent();

    // read file into memory — entire document in RAM before upload
    var fileBytes = await File.ReadAllBytesAsync(filePath);
    var byteContent = new ByteArrayContent(fileBytes);
    byteContent.Headers.ContentType =
        new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");

    form.Add(byteContent, "document", Path.GetFileName(filePath));
    form.Add(new StringContent(documentType), "DocumentType");

    // document leaves your server here
    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
    }

    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<KlippaResult>(json,
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class KlippaUploader
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    Public Async Function UploadAndParseAsync(filePath As String, Optional documentType As String = "financial") As Task(Of KlippaResult)
        Using form As New MultipartFormDataContent()
            ' read file into memory — entire document in RAM before upload
            Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
            Dim byteContent = New ByteArrayContent(fileBytes)
            byteContent.Headers.ContentType = New System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg")

            form.Add(byteContent, "document", Path.GetFileName(filePath))
            form.Add(New StringContent(documentType), "DocumentType")

            ' document leaves your server here
            Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)

            If Not response.IsSuccessStatusCode Then
                Dim error = Await response.Content.ReadAsStringAsync()
                Throw New InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}")
            End If

            Dim json = Await response.Content.ReadAsStringAsync()
            Return JsonSerializer.Deserialize(Of KlippaResult)(json, New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True})
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Approche IronOCR :

// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);

// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);

Console.WriteLine(result.Text);
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);

// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);

Console.WriteLine(result.Text);
Imports IronOcr
Imports System.IO

' IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' From file path
Using input As New OcrInput()
    input.LoadImage("invoice.jpg")
    Dim result = New IronTesseract().Read(input)

    ' From byte array (same bytes Klippa was uploading)
    Dim fileBytes As Byte() = Await File.ReadAllBytesAsync("invoice.jpg")
    Using inputFromBytes As New OcrInput()
        inputFromBytes.LoadImage(fileBytes)
        Dim resultFromBytes = New IronTesseract().Read(inputFromBytes)

        Console.WriteLine(result.Text)
    End Using
End Using
$vbLabelText   $csharpLabel

La construction MultipartFormDataContent, les en-têtes de type de contenu et le téléchargement lui-même ont tous disparu. IronOCR lit directement à partir du chemin du fichier, d'un tableau d'octets ou d'un Stream — les mêmes données que Klippa transmettait au cloud restent locales. Le guide d'entrée d'images couvre tous les formats d'entrée pris en charge, et le guide d'entrée de flux couvre le chemin de flux mémoire pour les documents qui arrivent sous forme de tableaux d'octets provenant de processus en amont.

Remplacement de la désérialisation de la réponse JSON

Klippa renvoie une structure JSON imbriquée. Naviguer dans cette structure nécessite soit un modèle C# correspondant, soit un parcours inline JsonDocument — qui se cassent tous les deux lorsque Klippa modifie leur schéma de réponse.

Approche Klippa :

// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
    [JsonPropertyName("data")]
    public KlippaData Data { get; set; }
}

public class KlippaData
{
    [JsonPropertyName("parsed_document")]
    public KlippaParsedDocument ParsedDocument { get; set; }
}

public class KlippaParsedDocument
{
    [JsonPropertyName("text")]
    public string Text { get; set; }

    [JsonPropertyName("amount")]
    public decimal? Amount { get; set; }

    [JsonPropertyName("merchant")]
    public string Merchant { get; set; }

    [JsonPropertyName("date")]
    public string Date { get; set; }
}

// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
    var klippaResult = await UploadAndParseAsync(imagePath);
    // every property access is nullable — schema drift breaks this silently
    return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
    [JsonPropertyName("data")]
    public KlippaData Data { get; set; }
}

public class KlippaData
{
    [JsonPropertyName("parsed_document")]
    public KlippaParsedDocument ParsedDocument { get; set; }
}

public class KlippaParsedDocument
{
    [JsonPropertyName("text")]
    public string Text { get; set; }

    [JsonPropertyName("amount")]
    public decimal? Amount { get; set; }

    [JsonPropertyName("merchant")]
    public string Merchant { get; set; }

    [JsonPropertyName("date")]
    public string Date { get; set; }
}

// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
    var klippaResult = await UploadAndParseAsync(imagePath);
    // every property access is nullable — schema drift breaks this silently
    return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
Imports System.Text.Json.Serialization

' Klippa: deserialization model — breaks when API schema changes
Public Class KlippaResponse
    <JsonPropertyName("data")>
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    <JsonPropertyName("parsed_document")>
    Public Property ParsedDocument As KlippaParsedDocument
End Class

Public Class KlippaParsedDocument
    <JsonPropertyName("text")>
    Public Property Text As String

    <JsonPropertyName("amount")>
    Public Property Amount As Decimal?

    <JsonPropertyName("merchant")>
    Public Property Merchant As String

    <JsonPropertyName("date")>
    Public Property Date As String
End Class

' Usage: navigate the nullable chain every time
Public Async Function GetExtractedTextAsync(imagePath As String) As Task(Of String)
    Dim klippaResult = Await UploadAndParseAsync(imagePath)
    ' every property access is nullable — schema drift breaks this silently
    Return If(klippaResult?.Data?.ParsedDocument?.Text, String.Empty)
End Function
$vbLabelText   $csharpLabel

Approche IronOCR :

// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");

// Direct property access — no deserialization, no nullable navigation
string fullText   = result.Text;
double confidence = result.Confidence;
int pageCount     = result.Pages.Count();

// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
    }
}
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");

// Direct property access — no deserialization, no nullable navigation
string fullText   = result.Text;
double confidence = result.Confidence;
int pageCount     = result.Pages.Count();

// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
    }
}
Imports IronOcr

' IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Dim ocr As New IronTesseract()
Dim result = ocr.Read("invoice.jpg")

' Direct property access — no deserialization, no nullable navigation
Dim fullText As String = result.Text
Dim confidence As Double = result.Confidence
Dim pageCount As Integer = result.Pages.Count()

' Structured data: lines and words with coordinates
For Each page In result.Pages
    For Each line In page.Lines
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}")
    Next
Next
$vbLabelText   $csharpLabel

OcrResult est un objet .NET typé. Il n'y a pas de JSON à analyser, pas de classe de modèle à maintenir et aucun risque de dérive de schéma interrompant la désérialisation en production. Le guide de lecture des résultats documente le modèle d'objet OcrResult complet, y compris les coordonnées de mot, les scores de confiance et la hiérarchie de page structurée. Pour des modèles d'extraction de champs spécifiques aux factures construits sur OcrResult, le tutoriel sur la reconnaissance des factures OCR couvre la logique d'extraction de bout en bout.

Suppression de l'infrastructure de gestion des erreurs et de nouvelle tentative

L'intégration de Klippa via HTTP nécessite une gestion des erreurs pour chaque mode d'échec qu'un appel réseau peut produire : délais d'attente, réponses 4xx, réponses 5xx, limites de débit et JSON partiel. Les équipes qui gèrent les intégrations en production ajoutent des politiques de nouvelle tentative à l'aide de Polly ou d'une logique personnalisée. Cette infrastructure disparaît lorsque l'appel réseau disparaît.

Approche Klippa :

// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
    var delay = TimeSpan.FromSeconds(1);

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var form = new MultipartFormDataContent();
            form.Add(
                new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
                "document",
                Path.GetFileName(filePath));

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
            var response = await _httpClient.PostAsync(
                "https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);

            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                // rate limited — back off and retry
                await Task.Delay(delay * attempt);
                continue;
            }

            response.EnsureSuccessStatusCode();
            var json = await response.Content.ReadAsStringAsync(cts.Token);
            var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
            return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
        }
        catch (HttpRequestException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // exponential backoff
        }
        catch (TaskCanceledException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // timeout — retry
        }
    }

    throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
    var delay = TimeSpan.FromSeconds(1);

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var form = new MultipartFormDataContent();
            form.Add(
                new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
                "document",
                Path.GetFileName(filePath));

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
            var response = await _httpClient.PostAsync(
                "https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);

            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                // rate limited — back off and retry
                await Task.Delay(delay * attempt);
                continue;
            }

            response.EnsureSuccessStatusCode();
            var json = await response.Content.ReadAsStringAsync(cts.Token);
            var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
            return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
        }
        catch (HttpRequestException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // exponential backoff
        }
        catch (TaskCanceledException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // timeout — retry
        }
    }

    throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks

Public Class KlippaService
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    ' Klippa: retry policy required — cloud calls fail unpredictably
    Public Async Function ReadWithRetryAsync(filePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
        Dim delay As TimeSpan = TimeSpan.FromSeconds(1)

        For attempt As Integer = 1 To maxRetries
            Try
                Using form As New MultipartFormDataContent()
                    form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(filePath)), "document", Path.GetFileName(filePath))

                    Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
                        Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token)

                        If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
                            ' rate limited — back off and retry
                            Await Task.Delay(delay * attempt)
                            Continue For
                        End If

                        response.EnsureSuccessStatusCode()
                        Dim json As String = Await response.Content.ReadAsStringAsync(cts.Token)
                        Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json)
                        Return If(parsed?.Data?.ParsedDocument?.Text, String.Empty)
                    End Using
                End Using
            Catch ex As HttpRequestException When attempt < maxRetries
                Await Task.Delay(delay * attempt) ' exponential backoff
            Catch ex As TaskCanceledException When attempt < maxRetries
                Await Task.Delay(delay * attempt) ' timeout — retry
            End Try
        Next

        Throw New InvalidOperationException($"Klippa API failed after {maxRetries} attempts")
    End Function
End Class

Public Class KlippaResponse
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    Public Property ParsedDocument As ParsedDocument
End Class

Public Class ParsedDocument
    Public Property Text As String
End Class
$vbLabelText   $csharpLabel

Approche IronOCR :

// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

public string ReadDocument(string filePath)
{
    // Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
    // Non rate limit handling. Non partial-JSON guards.
    var result = new IronTesseract().Read(filePath);
    return result.Text;
}
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

public string ReadDocument(string filePath)
{
    // Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
    // Non rate limit handling. Non partial-JSON guards.
    var result = new IronTesseract().Read(filePath);
    return result.Text;
}
Imports IronOcr

' IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Public Function ReadDocument(filePath As String) As String
    ' Non retry loop. Non CancellationTokenSource. Non HTTP status checks.
    ' Non rate limit handling. Non partial-JSON guards.
    Dim result = New IronTesseract().Read(filePath)
    Return result.Text
End Function
$vbLabelText   $csharpLabel

L'ensemble de l'infrastructure de nouvelle tentative — la boucle, le calcul de retard, le CancellationTokenSource, le branchement du code d'état HTTP, le bloc de capture TaskCanceledException — existe uniquement à cause du réseau. Supprimez l'appel réseau et tout disparaît. Un appel OCR local échoue rapidement avec une exception typée si le fichier d'entrée est manquant ou illisible, et réussit dans le cas contraire. Le guide d'optimisation de la vitesse explique comment optimiser les performances IronOCR si le débit est un point important après la migration.

Traitement des PDF multipages sans téléchargement sur le cloud

Klippa accepte les téléchargements de PDF via le même point de terminaison parseDocument. Les fichiers PDF multipages quittent toujours votre réseau. IronOCR lit les fichiers PDF nativement, en temps réel, avec un accès aux résultats page par page.

Approche Klippa :

// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
    var pages = new List<string>();

    // Klippa parses the entire PDF server-side and returns combined results
    // You cannot control per-page processing or access raw page text
    using var form = new MultipartFormDataContent();
    form.Add(
        new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
        "document",
        Path.GetFileName(pdfPath));

    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<KlippaResponse>(json);
    // Klippa returns the combined parsed text — no per-page breakdown in basic API
    pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);

    return pages;
}
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
    var pages = new List<string>();

    // Klippa parses the entire PDF server-side and returns combined results
    // You cannot control per-page processing or access raw page text
    using var form = new MultipartFormDataContent();
    form.Add(
        new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
        "document",
        Path.GetFileName(pdfPath));

    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<KlippaResponse>(json);
    // Klippa returns the combined parsed text — no per-page breakdown in basic API
    pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);

    return pages;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class PdfExtractor
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    ' Klippa: PDF upload — entire document transmitted, results depend on cloud availability
    Public Async Function ExtractPdfPagesAsync(pdfPath As String) As Task(Of List(Of String))
        Dim pages As New List(Of String)()

        ' Klippa parses the entire PDF server-side and returns combined results
        ' You cannot control per-page processing or access raw page text
        Using form As New MultipartFormDataContent()
            form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(pdfPath)), "document", Path.GetFileName(pdfPath))

            Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
            response.EnsureSuccessStatusCode()

            Dim json = Await response.Content.ReadAsStringAsync()
            Dim result = JsonSerializer.Deserialize(Of KlippaResponse)(json)
            ' Klippa returns the combined parsed text — no per-page breakdown in basic API
            pages.Add(If(result?.Data?.ParsedDocument?.Text, String.Empty))
        End Using

        Return pages
    End Function
End Class

Public Class KlippaResponse
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    Public Property ParsedDocument As ParsedDocument
End Class

Public Class ParsedDocument
    Public Property Text As String
End Class
$vbLabelText   $csharpLabel

Approche IronOCR :

// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
    Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
    Console.WriteLine(page.Text);
}

// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
    Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
    Console.WriteLine(page.Text);
}

// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
Imports IronOcr

' IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Using input As New OcrInput()
    input.LoadPdf("multi-page-invoice.pdf") ' reads locally — no HTTP

    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(input)

    ' Per-page access — not available from Klippa's combined response
    For Each page In result.Pages
        Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines")
        Console.WriteLine(page.Text)
    Next

    ' Or produce a searchable PDF from the scanned original
    result.SaveAsSearchablePdf("searchable-output.pdf")
End Using
$vbLabelText   $csharpLabel

IronOCR lit les fichiers PDF nativement, sans aucune conversion. Chaque page est accessible individuellement, avec sa hiérarchie complète de lignes, de mots et de caractères. L'appel SaveAsSearchablePdf() produit un PDF avec couche de texte à partir d'un document numérisé — une capacité que Klippa n'offre pas. Le guide d'importation PDF décrit les options de chargement, et le guide PDF consultable décrit les options de sortie, notamment le format PDF/A pour la conformité aux exigences d'archivage.

Référence de mappage de l'API Klippa OCRvers IronOCR

Klippa est une API REST, et non un SDK typé. Le tableau ci-dessous traduit la surface d'intégration de Klippa en équivalents IronOCR .

Concept Klippa Équivalent d'IronOCR
HttpClient avec en-tête X-Auth-Key Instance IronTesseract — pas de configuration d'authentification
MultipartFormDataContent OcrInput.LoadImage(path) ou OcrInput.LoadPdf(path)
POST /api/v1/parseDocument IronTesseract.Read(input)
await _client.PostAsync(...) ocr.Read(input) — synchrone, pas de await nécessaire
response.EnsureSuccessStatusCode() Inutile — aucune réponse HTTP
JsonSerializer.Deserialize<KlippaResponse>(json) Objet typé OcrResult — pas de désérialisation
KlippaResponse.Data.ParsedDocument.Text OcrResult.Text
KlippaResponse.Data.ParsedDocument.Amount Regex personnalisé sur OcrResult.Text ou OcrResult.Lines
KlippaResponse.Data.ParsedDocument.Merchant OcrResult.Pages[0].Lines[0].Text
Boucle de nouvelle tentative avec Task.Delay Inutile — aucun mode de défaillance réseau
CancellationTokenSource(TimeSpan.FromSeconds(30)) Inutile — exécution locale
Gestion des limites de débit (HTTP 429) Inutile — aucune limite de débit
Routage de documents cloud vers les serveurs de l'UE Exécution locale en cours
KlippaService.Dispose() / HttpClient.Dispose() Élimination de OcrInput via l'instruction using
Champs de réponse JSON structurés OcrResult.Text + OcrResult.Pages + OcrResult.Words
Abonnement à l'API SaaS Objet string IronOcr.License.LicenseKey — perpétuel

Problèmes de migration courants et solutions

Problème 1 : Sites d'appels asynchrones uniquement après la suppression du protocole HTTP

Klippa : Toute l'intégration de Klippa est asynchrone car les appels HTTP l'exigent. Les contrôleurs, services et travailleurs en arrière-plan dans l'ensemble de votre base de code appellent await ProcessDocumentAsync(...). Supprimer l'appel HTTP signifie que le await n'est plus nécessaire, mais les signatures de méthode async restent.

Solution : IronOCR fournit des API synchrones et asynchrones. Pour les sites d'appel qui doivent rester asynchrones (contrôleurs ASP.NET Core, services en arrière-plan avec CancellationToken), utilisez ReadAsync :

// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
    string filePath, CancellationToken cancellationToken = default)
{
    // Previously: await _httpClient.PostAsync(...)
    // Now: local call, same awaitable pattern
    var ocr = new IronTesseract();
    var result = await ocr.ReadAsync(filePath);
    return result.Text;
}
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
    string filePath, CancellationToken cancellationToken = default)
{
    // Previously: await _httpClient.PostAsync(...)
    // Now: local call, same awaitable pattern
    var ocr = new IronTesseract();
    var result = await ocr.ReadAsync(filePath);
    return result.Text;
}
Imports System.Threading
Imports System.Threading.Tasks

Public Class DocumentProcessor
    Public Async Function ProcessDocumentAsync(filePath As String, Optional cancellationToken As CancellationToken = Nothing) As Task(Of String)
        Dim ocr As New IronTesseract()
        Dim result = Await ocr.ReadAsync(filePath)
        Return result.Text
    End Function
End Class
$vbLabelText   $csharpLabel

Le guide OCR asynchrone couvre l'intégration ReadAsync et CancellationToken pour ASP.NET Core et les modèles de service hébergés.

Problème 2 : Enregistrement de l'injection de dépendances

Klippa : La classe KlippaService est enregistrée dans DI comme service singleton ou scoped et encapsule HttpClient. Sa suppression implique la mise à jour de l'enregistrement DI et de tous les points d'injection.

Solution : Enregistrez IronTesseract comme singleton (il est sûr pour le thread) et injectez-le directement, ou créez un wrapper mince qui reflète votre interface de service existante :

// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();

// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();

public class IronOcrService : IOcrService
{
    private readonly IronTesseract _ocr;
    public IronOcrService(IronTesseract ocr) => _ocr = ocr;

    public string ReadDocument(string path) => _ocr.Read(path).Text;
}
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();

// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();

public class IronOcrService : IOcrService
{
    private readonly IronTesseract _ocr;
    public IronOcrService(IronTesseract ocr) => _ocr = ocr;

    public string ReadDocument(string path) => _ocr.Read(path).Text;
}
Imports Microsoft.Extensions.DependencyInjection

' In Program.vb or Startup.vb
builder.Services.AddSingleton(Of IronTesseract)()

' Or wrap for interface compatibility
builder.Services.AddSingleton(Of IOcrService, IronOcrService)()

Public Class IronOcrService
    Implements IOcrService

    Private ReadOnly _ocr As IronTesseract

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

    Public Function ReadDocument(path As String) As String Implements IOcrService.ReadDocument
        Return _ocr.Read(path).Text
    End Function
End Class
$vbLabelText   $csharpLabel

Une instance IronTesseract enregistrée comme singleton gère les requêtes concurrentes. Chaque appel à Read() est sûr pour le thread.

Problème 3 : Extraction de champs structurés sans JSON pré-analysé

Klippa : Klippa renvoie amount, merchant, date, et vat_amount comme propriétés JSON typées. La migration vers IronOCR signifie que ces champs ne sont plus pré-analysés.

Solution : OcrResult d'IronOCR fournit le texte brut et les coordonnées au niveau des mots pour construire une extraction équivalente. Pour les documents dont la mise en page est prévisible, la reconnaissance optique de caractères (OCR) basée sur les régions cible directement des champs spécifiques :

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion   = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60);  // top header area

using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();

using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion   = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60);  // top header area

using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();

using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Target specific layout regions instead of relying on pre-parsed cloud fields
Dim totalRegion As New CropRectangle(350, 580, 250, 50) ' bottom-right total area
Dim merchantRegion As New CropRectangle(50, 30, 400, 60) ' top header area

Using merchantInput As New OcrInput()
    merchantInput.LoadImage("receipt.jpg", merchantRegion)
    Dim merchantName As String = New IronTesseract().Read(merchantInput).Text.Trim()
End Using

Using totalInput As New OcrInput()
    totalInput.LoadImage("receipt.jpg", totalRegion)
    Dim totalText As String = New IronTesseract().Read(totalInput).Text.Trim()
End Using
$vbLabelText   $csharpLabel

Le guide OCR basé sur la région couvre l'utilisation CropRectangle en détail. Pour obtenir des modèles d'extraction complets pour les différents formats de reçus et de factures, le tutoriel sur la numérisation des reçus fournit un code fonctionnel complet.

Problème 4 : Documents arrivant sous forme de flux depuis les services en amont

Klippa : Klippa reçoit les documents sous forme de téléchargements de formulaires multipart — des octets de fichier enveloppés dans le contenu d'un formulaire HTTP. Si votre application reçoit des documents sous forme de flux provenant de S3, d'Azure Blob Storage ou d'API internes, vous lisiez le flux en octets, puis vous téléversiez ces octets sur Klippa.

Solution : IronOCR accepte directement les objets Stream. L'étape de conversion d'octets disparaît :

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
    using var input = new OcrInput();
    input.LoadImage(documentStream); // accepts Stream directly

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
}
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
    using var input = new OcrInput();
    input.LoadImage(documentStream); // accepts Stream directly

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
}
Imports System.IO
Imports System.Threading.Tasks
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Public Async Function ProcessDocumentStreamAsync(documentStream As Stream) As Task(Of String)
    Using input As New OcrInput()
        input.LoadImage(documentStream) ' accepts Stream directly

        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(input)
        Return result.Text
    End Using
End Function
$vbLabelText   $csharpLabel

Pas de ReadAllBytes, pas de construction MultipartFormDataContent, pas de POST HTTP. Le flux va directement dans OcrInput. Le guide des entrées de flux couvre les types de flux et les modèles d'évacuation.

Problème 5 : Tests d'intégration dépendant de la simulation HTTP

Klippa : Les tests d'intégration pour le code Klippa simulent HttpClient ou utilisent des intercepteurs HTTP (par exemple, WireMock, MockHttp) pour simuler les réponses de l'API. Ces tests simulent la couche HTTP, et non la logique OCR.

Solution : Les tests IronOCR utilisent de vrais documents avec un résultat attendu connu. Aucune infrastructure de simulation n'est nécessaire. Les tests s'exécutent hors ligne :

[Fact]
public void ReadDocument_ReturnsExpectedText()
{
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
    var ocr = new IronTesseract();

    // Use a real test fixture — no HTTP mocking, runs fully offline
    var result = ocr.Read("test-fixtures/sample-invoice.jpg");

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
    Assert.True(result.Confidence > 70);
}
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
    var ocr = new IronTesseract();

    // Use a real test fixture — no HTTP mocking, runs fully offline
    var result = ocr.Read("test-fixtures/sample-invoice.jpg");

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
    Assert.True(result.Confidence > 70);
}
<Fact>
Public Sub ReadDocument_ReturnsExpectedText()
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
    Dim ocr = New IronTesseract()

    ' Use a real test fixture — no HTTP mocking, runs fully offline
    Dim result = ocr.Read("test-fixtures/sample-invoice.jpg")

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase)
    Assert.True(result.Confidence > 70)
End Sub
$vbLabelText   $csharpLabel

Les tests qui nécessitaient auparavant une connexion Klippa en direct ou une configuration HTTP simulée complexe s'exécutent désormais en intégration continue sans accès réseau.

Problème n° 6 : Documents de faible qualité améliorés côté serveur par Klippa

Klippa : Le traitement dans le cloud applique une amélioration de l'image avant la reconnaissance. Les développeurs n'ont jamais besoin de configurer cela — cela se fait automatiquement sur les serveurs de Klippa. Lors de la migration, les documents traités silencieusement par Klippa peuvent présenter une précision moindre sans prétraitement explicite dans IronOCR.

Solution : Appliquer explicitement les filtres de prétraitement d'IronOCR. L'ensemble de filtres reflète celui appliqué côté serveur par les services cloud :

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();    // fix rotation from camera or scanner
input.DeNoise();   // remove compression noise
input.Contrast();  // boost faded ink
input.Binarize();  // clean background for clearer character edges

var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();    // fix rotation from camera or scanner
input.DeNoise();   // remove compression noise
input.Contrast();  // boost faded ink
input.Binarize();  // clean background for clearer character edges

var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Using input As New OcrInput()
    input.LoadImage("low-quality-scan.jpg")
    input.Deskew()    ' fix rotation from camera or scanner
    input.DeNoise()   ' remove compression noise
    input.Contrast()  ' boost faded ink
    input.Binarize()  ' clean background for clearer character edges

    Dim result = New IronTesseract().Read(input)
    Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
$vbLabelText   $csharpLabel

Le guide de correction de la qualité d'image couvre tous les filtres de prétraitement et l'ordre dans lequel les appliquer pour différents types de dégradation de documents.

Liste de contrôle de migration Klippa OCR

Pré-migration

Avant de supprimer quoi que ce soit, vérifiez votre code source afin de localiser tout le code spécifique à Klippa :

# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .

# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .

# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .

# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .

# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .

# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .

# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .

# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .

# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
SHELL

Notes d'inventaire :

  • Enregistrez chaque classe qui encapsule HttpClient pour les appels Klippa
  • Listez toutes les classes de modèle de désérialisation JSON (KlippaResponse, KlippaParsedDocument, etc.)
  • Documentez tous les mappages de champs qui utilisent les propriétés JSON pré-analysées de Klippa
  • Veuillez noter les politiques de nouvelle tentative Polly ou les boucles de nouvelle tentative personnalisées conçues pour Klippa.

Migration de code

  1. Installez le IronOcr package NuGet (dotnet add package IronOcr)
  2. Ajoutez IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" au démarrage de l'application
  3. Supprimez les importations System.Net.Http, System.Text.Json, Newtonsoft.Json des fichiers de service Klippa
  4. Supprimez la classe KlippaService (ou remplacez son corps par des appels IronTesseract, en conservant l'interface)
  5. Enregistrez IronTesseract comme singleton dans le conteneur DI
  6. Remplacez les blocs de téléchargement MultipartFormDataContent par OcrInput.LoadImage() ou OcrInput.LoadPdf()
  7. Supprimez les classes de modèle de réponse JSON (KlippaResponse, KlippaData, KlippaParsedDocument)
  8. Remplacez les chaînes de navigation JSON nullable (.Data?.ParsedDocument?.Text) par result.Text
  9. Supprimez les boucles de nouvelle tentative et CancellationTokenSource timeouts des sites d'appel Klippa
  10. Supprimer la gestion des limites de débit (blocs catch HTTP 429)
  11. Remplacez await ProcessDocumentAsync(...) par await ocr.ReadAsync(...) ou ocr.Read(...) synchrones
  12. Ajoutez des filtres de prétraitement OcrInput (Deskew, DeNoise, Contrast) pour les entrées de documents de mauvaise qualité
  13. Remplacer l'infrastructure de test HTTP simulée par des tests de données réelles
  14. Supprimez les stratégies de nouvelle tentative Polly ou le middleware de nouvelle tentative personnalisé limité aux appels Klippa

Après la migration

  • Vérifier que le texte extrait correspond au contenu attendu des documents de test connus.
  • Confirmer que les scores de confiance dépassent le seuil acceptable (généralement 70 % et plus) pour les types de documents de production
  • Testez les entrées PDF : chargez des PDF multi-pages nativement et vérifiez l'accès au texte par page via result.Pages
  • Testez les entrées de flux : passez MemoryStream et vérifiez que OcrInput.LoadImage(stream) produit le bon résultat
  • Vérifier que les filtres de prétraitement améliorent la précision des numérisations de faible qualité par rapport à la ligne de base non traitée
  • Confirmez que le IronTesseract singleton injecté par DI gère les requêtes concurrentes sans contention
  • Exécuter les tests d'intégration hors ligne (sans connexion réseau) — tous les tests doivent réussir sans accès au cloud
  • Vérifiez la sortie du PDF consultable avec result.SaveAsSearchablePdf("output.pdf") pour les flux de documents numérisés
  • Testez ReadAsync dans le contexte du contrôleur ASP.NET Core avec la propagation CancellationToken
  • Confirmez que le modèle de disposition using var input = new OcrInput() ne fuit pas de mémoire sous une charge soutenue

Principaux avantages de la migration vers IronOCR

Souveraineté des données dès le premier jour. Après la migration, vos documents financiers sensibles, les scans d'identité et les factures confidentielles restent à jamais dans votre infrastructure. Il n'y a pas de sous-traitant tiers dans le périmètre de l'audit, pas de politique de conservation des données à examiner et pas d'accord de transfert de données à maintenir. Les contraintes HIPAA, ITAR, CMMC et FedRAMP qui posaient auparavant problème à Klippa sont satisfaites par défaut. Le déploiement sur Docker , AWS ou Azure permet de conserver l'ensemble de vos ressources à l'intérieur des limites de votre propre infrastructure.

Complexité de l'infrastructure éliminée. La classe de service, le client HTTP, le code de téléchargement de formulaire, les modèles JSON, la politique de nouvelle tentative, la configuration du délai d'expiration — tout cela servait à encapsuler un appel réseau. Supprimez l'appel réseau et tout disparaît avec lui. Le code source qui en résulte est plus petit, plus facile à lire et comporte moins de sources de défaillance. Une instance unique IronTesseract injectée par DI remplace l'ensemble de la couche d'intégration HTTP.

Coût prévisible quel que soit le volume. Une licence IronOCR perpétuelle à $999 (Lite), 1 499 $ (Professional) ou 2 999 $ (Enterprise) couvre un traitement illimité des documents. Le traitement de 500 documents par mois ou de 500 000 documents par mois coûte la même chose. Le modèle de facturation par document qui rendait Klippa coûteux à grande échelle est structurellement absent. La page de licences IronOCR détaille tous les niveaux et ce que chacun inclut.

Portée des documents sans limites. IronOCR traite tout document contenant du texte. Les contrats numérisés, les dessins techniques, les formulaires médicaux, les bons de commande, les notes manuscrites, les captures d'écran, les archives TIFF — tous gérés par le même appel Read() avec la même API. La restriction de champ d'application spécialisée qui exigeait un deuxième système pour les documents ne relevant pas des catégories de formation de Klippa a disparu. Une seule bibliothèque, un seul point d'intégration, tout type de document.

Prise en charge désormais des environnements hors ligne et à accès restreint. Les applications déployées sur les réseaux bancaires, les systèmes gouvernementaux, les environnements périphériques ou toute infrastructure avec accès sortant restreint fonctionnent exactement comme dans les environnements ouverts. Il n'y a pas de vérification de connectivité, pas de test de santé vers un point de terminaison cloud, et pas de mode dégradé lorsque la connexion Internet est indisponible. Les déploiements isolés du réseau fonctionnent sans modification. Le guide de déploiement Linux et le guide de déploiement Docker couvrent les voies de déploiement conteneurisées et côté serveur pour ces environnements.

Contrôle total de l'amélioration d'image. Le prétraitement dans le cloud était une boîte noire : Klippa l'appliquait, vous observiez les résultats, vous n'aviez aucun paramètre à régler. Le pipeline de prétraitement d'IronOCR est explicite et composable : Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Chaque filtre est optionnel et doit être commandé. Les améliorations en matière de précision sont mesurables, reproductibles et sous votre contrôle. Le guide de correction de la qualité d'image et la page relative aux fonctionnalités de prétraitement couvrent l'intégralité du catalogue de filtres et fournissent des indications sur le moment opportun pour appliquer chacun d'eux.

Veuillez noterKlippa et Tesseract sont des marques déposées de leurs propriétaires respectifs. Ce site n'est pas affilié à, approuvé par ou sponsorisé par Google ou Klippa. Tous les noms de produits, logos et marques sont la propriété de leurs propriétaires respectifs. Les comparaisons sont à titre informatif uniquement et reflètent les informations publiquement disponibles au moment de l'écriture.

Questions Fréquemment Posées

Pourquoi devrais-je migrer de Klippa OCR API vers IronOCR ?

Parmi les motivations communes, citons l'élimination de la complexité de l'interopérabilité COM, le remplacement de la gestion des licences basée sur les fichiers, l'absence de facturation à la page, l'activation du déploiement Docker/conteneur et l'adoption d'un flux de travail NuGet-natif qui s'intègre à l'outillage .NET standard.

Quels sont les principaux changements de code lors de la migration de l'API OCR de Klippa vers IronOcr ?

Remplacer les séquences d'initialisation de Klippa par l'instanciation d'IronTesseract, supprimer la gestion du cycle de vie de COM (modèles explicites Create/Load/Close) et mettre à jour les noms des propriétés des résultats. Le résultat est une réduction significative du nombre de lignes de code.

Comment installer IronOCR pour commencer la migration ?

Exécutez "Install-Package IronOcr" dans la console du Package Manager ou "dotnet add package IronOcr" dans le CLI. Les packs de langues sont des paquets distincts : 'dotnet add package IronOcr.Languages.French' pour le français, par exemple.

IronOCR offre-t-il la même précision d'OCR que Klippa OCR API pour les documents commerciaux standard ?

IronOcr atteint un niveau de précision élevé pour les contenus commerciaux standard, notamment les factures, les contrats, les reçus et les formulaires dactylographiés. Les filtres de prétraitement d'image (désalignement, suppression du bruit, amélioration du contraste) améliorent encore la reconnaissance sur des données dégradées.

Comment IronOCR gère-t-il les données linguistiques que Klippa OCR API installe séparément ?

Les données linguistiques de l'IronOcr sont distribuées sous forme de packages NuGet. 'dotnet add package IronOcr.Languages.German' installe la prise en charge de l'allemand. Il n'y a pas de placement manuel de fichiers ou de chemins d'accès aux répertoires.

La migration de l'API OCR de Klippa vers IronOCR nécessite-t-elle des modifications de l'infrastructure de déploiement ?

IronOCR nécessite moins de changements d'infrastructure que l'API OCR de Klippa. Il n'y a pas de chemins binaires SDK, de placements de fichiers de licence ou de configurations de serveurs de licence. Le package NuGet contient le moteur OCR complet, et la clé de licence est une chaîne de caractères définie dans le code de l'application.

Comment configurer les licences IronOCR après la migration ?

Attribuer IronOcr.License.LicenseKey = "YOUR-KEY" dans le code de démarrage de l'application. Dans Docker ou Kubernetes, stockez la clé dans une variable d'environnement et lisez-la au démarrage. Utilisez License.IsValidLicense pour valider avant d'accepter le trafic.

IronOcr peut-il traiter les PDF de la même manière que Klippa ?

Oui, IronOCR lit aussi bien les PDF natifs que les PDF numérisés. Instanciez IronTesseract, appelez ocr.Read(input) où l'input est un chemin PDF ou OcrPdfInput, et itérez les pages OcrResult. Aucun pipeline de rendu PDF séparé n'est nécessaire.

Comment IronOcr gère-t-il le threading dans le cadre d'un traitement à haut volume ?

IronTesseract est sûr pour l'instanciation par thread. Créez une instance par thread dans un Parallel.ForEach ou un Task pool, exécutez l'OCR simultanément et disposez de chaque instance lorsque vous avez terminé. Aucun état global ou verrouillage n'est nécessaire.

Quels formats de sortie IronOCR prend-il en charge après l'extraction du texte ?

IronOCR renvoie des résultats structurés comprenant le texte, les coordonnées des mots, les scores de confiance et la structure des pages. Les options d'exportation comprennent le texte brut, le PDF interrogeable et les objets de résultats structurés pour le traitement en aval.

La tarification d'IronOCR est-elle plus prévisible que celle de l'API OCR de Klippa pour la mise à l'échelle des charges de travail ?

IronOCR utilise des licences perpétuelles forfaitaires sans frais par page ou par volume. Que vous traitiez 10 000 ou 10 millions de pages, le coût de la licence reste constant. Les options de licence en volume et en équipe sont disponibles sur la page de tarification d'IronOcr.

Qu'advient-il de mes tests existants après la migration de l'API Klippa OCR vers IronOcr ?

Les tests qui vérifient le contenu du texte extrait doivent continuer à passer après la migration. Les tests qui valident les modèles d'appel d'API ou le cycle de vie des objets COM devront être mis à jour pour refléter le modèle d'initialisation et de résultat plus simple d'IronOcr.

Kannaopat Udonpant
Ingénieur logiciel
Avant de devenir ingénieur logiciel, Kannapat a obtenu un doctorat en ressources environnementales à l'université d'Hokkaido au Japon. Pendant qu'il poursuivait son diplôme, Kannapat est également devenu membre du laboratoire de robotique de véhicules, qui fait partie du département de bioproduction. En 2022, il a utilisé ses compé...
Lire la suite

Équipe de soutien Iron

Nous sommes en ligne 24 heures sur 24, 5 jours sur 7.
Chat
Email
Appelez-moi