Cómo implementar OCR en C# usando bibliotecas de código abierto
Esta guía está dirigida a desarrolladores .NET que hayan integrado la API REST de Klippa y estén migrando a IronOCR para el procesamiento de documentos en sus propias instalaciones. Cubre los pasos prácticos para eliminar la infraestructura del cliente HTTP, suprimir la deserialización JSON y reemplazar las cargas de documentos dependientes de la nube con llamadas OCR locales que nunca acceden a la red.
¿Por qué migrar desde Klippa OCR?
Klippa es un servicio de inteligencia documental exclusivamente en la nube que no dispone de un SDK .NET . Cada integración es un cliente REST desarrollado manualmente. Esa realidad arquitectónica tiene consecuencias posteriores que se acumulan a lo largo de la vida útil de un sistema de producción.
Si no hay paquete NuGet , usted es el propietario de la capa de integración. No hay nada que instalar. El costo de entrada es escribir un envoltorio HttpClient, configurar los encabezados de autenticación X-Auth-Key, construir los cuerpos de solicitud MultipartFormDataContent, deserializar el esquema de respuesta JSON de Klippa y conectar la lógica de reintentos para fallos transitorios. Eso supone entre 2 y 4 días de trabajo de ajuste antes de que el primer documento se procese de forma fiable en producción. Cuando Klippa actualiza el esquema de su API, su código de deserialización deja de funcionar y requiere mantenimiento manual.
Cada carga de documento implica una dependencia de red. Klippa procesa los documentos exclusivamente en servidores alojados en la UE. Las interrupciones en la producción en el lado de Klippa, la latencia elevada o cualquier interrupción en el acceso saliente a Internet desde su servidor de aplicaciones detienen por completo el procesamiento de documentos. No existe ningún mecanismo de respaldo, ni modo local, ni reintento que resuelva el problema de que un servicio en la nube no esté disponible.
Los documentos confidenciales salen de su infraestructura. Los documentos financieros (recibos con detalles de pago, facturas con números de IVA e importes, documentos de identidad con datos del pasaporte) se transmiten a un servidor de terceros en cada llamada a la API. Las disposiciones del RGPD sobre transferencia de datos abordan parte de esto en el caso del procesamiento con sede en la UE, pero el alcance de la auditoría aún se extiende a la infraestructura de Klippa, las políticas de retención de datos y los subprocesadores. Para los equipos con contratos en los sectores sanitario, jurídico, financiero o gubernamental, la denominación "alojado en la UE" no cumple con el requisito de que los datos no salgan de la organización.
Los precios por documento varían sin límite máximo. Klippa no publica sus precios. Con cualquier volumen significativo de documentos (10.000 recibos al mes en un sistema de gestión de gastos, 500 facturas al día en un flujo de trabajo de automatización de cuentas por pagar), el modelo de facturación por documento genera costes que una licencia perpetua jamás generaría. La trayectoria de los costes está directamente ligada al crecimiento empresarial, lo cual es lo contrario de lo que debería ocurrir con el gasto en infraestructuras.
El alcance del especialista se rompe cuando los requisitos se amplían. Klippa está capacitado en recibos, facturas y documentos de identidad. Una aplicación que comienza como una herramienta de gestión de gastos rara vez se queda ahí. La primera vez que aparece un tipo de documento que no pertenece a esas tres categorías (un contrato de trabajo escaneado, un formulario médico, un dibujo técnico, una orden de compra con un formato no estándar), Klippa no devuelve ningún resultado útil.IronOCR procesa cualquier documento que contenga texto, sin restricciones de categoría.
Las llamadas REST que solo utilizan operaciones asíncronas añaden latencia en contextos síncronos. Cada llamada a Klippa es una operación HTTP asíncrona. El envío de un solo documento (ida y vuelta) tarda entre 500 ms y 2000 ms en la red.IronOCR procesa el mismo documento localmente en 100-400 ms sin la sobrecarga asíncrona en escenarios donde el procesamiento síncrono se ajusta mejor a la arquitectura.
El problema fundamental
Klippa no tiene SDK. OCR significa construir y enviar una solicitud HTTP, y luego deserializar 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
IronOCR lo reemplaza todo:
// 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
IronOCR vs Klippa OCR: Comparación de características
La tabla que aparece a continuación compara las dos bibliotecas en función de las dimensiones más importantes para tomar una decisión sobre la migración a producción.
| Característica | Klippa OCR | IronOCR |
|---|---|---|
| Modelo de despliegue | Solo en la nube (servidores de la UE) | En el establecimiento, totalmente local. |
| SDK de .NET / Paquete NuGet | None | IronOcr paquete NuGet |
| Se requiere conexión a Internet | Sí, en cada llamada | Nunca |
| Los datos del documento salen de la red. | Siempre | Nunca |
| OCR de propósito general | No (solo recibos, facturas e identificaciones) | Sí (cualquier tipo de documento) |
| Configuración de autenticación | X-Auth-Key encabezado HTTP |
IronOcr.License.LicenseKey cadena |
| Se requiere un cliente HTTP | Sí | No |
| deserialización de respuesta | Análisis manual de JSON | Objeto tipado OcrResult |
| Lógica de reintento/tiempo de espera | Enrollado a mano | No es necesario (llamada local) |
| Soporte sin conexión / aislado de la red | No | Sí |
| Entrada en PDF | Sí (nube) | Sí (nativo, local) |
| Entrada TIFF de varias páginas | Desconocido | Sí |
| Formatos de entrada de imagen | JPG, PNG (nube) | JPG, PNG, BMP, TIFF, GIF y más |
| Entrada de flujo y matriz de bytes | Sin SDK | Sí |
| preprocesamiento automático de imágenes | Lado de las nubes (opaco) | Sí (Desinclinar, Reducir ruido, Contraste, Binarizar, Nitidez) |
| Salida estructurada: coordenadas de palabras | No | Sí |
| Puntuación de confianza por palabra | No | Sí |
| Salida en PDF con capacidad de búsqueda | No | Sí |
| Lectura de BarCodes durante el OCR | No | Sí |
| Soporte multilingüe | Limitado a tipos de documentos capacitados | más de 125 idiomas |
| Seguridad de los hilos | No disponible (llamadas HTTP) | Sí (uno IronTesseract por hilo) |
| Implementación multiplataforma | Independiente de REST | Windows, Linux, macOS, Docker, Azure, AWS |
| Cumplimiento de HIPAA / ITAR / aislamiento de red | No | Sí |
| Modelo de precios | Software como servicio (SaaS) por documento (tarifas no publicadas) | Licencia perpetua de $999 |
| Coste por página a escala | Sí, sin límites | None |
Inicio rápido: Migración de Klippa OCRa IronOCR
Paso 1: Sustituir el paquete NuGet
Klippa no tiene un paquete NuGet oficial. Elimine las dependencias del cliente HTTP que existen únicamente para admitir la integración con 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
Instala IronOCR desde NuGet :
dotnet add package IronOcr
Paso 2: Actualizar los espacios de nombres
Elimine los espacios de nombres HTTP y JSON que requería la integración con Klippa. Agregue el espacio de nombres único de 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;
// 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
Paso 3: Inicializar licencia
Agrega la inicialización de la licencia una vez al inicio de la aplicación — en Program.cs, Startup.cs, o antes de la primera llamada a OCR:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Ejemplos de migración de código
Sustitución de la clase de servicio de cliente HTTP
La integración de Klippa requiere una clase de servicio completa que encapsule la infraestructura HTTP. No hay forma de evitar esto porque no existe un SDK.
Enfoque de 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
Enfoque 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
La clase de servicio Klippa existe únicamente porque la API requiere infraestructura HTTP. El equivalente de IronOCR se reduce a una única llamada Read(). Los tiempos de espera, los encabezados de autenticación y los patrones de eliminación desaparecen porque no hay red. Consulte la guía de configuración de IronTesseract para conocer las opciones de inicialización y el ejemplo básico de OCR para ver el código funcional.
Eliminación de la carga de formularios multipartes
Klippa recibe los documentos como cargas de formularios multipartes. El código de carga es mecánico pero frágil: lectura de archivos, encabezados de tipo de contenido, construcción de límites y gestión del tamaño de la carga.
Enfoque de 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
Enfoque 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
La construcción MultipartFormDataContent, los encabezados de tipo de contenido y la carga en sí han desaparecido.IronOCR lee directamente desde la ruta del archivo, desde una matriz de bytes o desde un Stream — los mismos datos que Klippa estaba transmitiendo a la nube se mantienen locales. La guía de entrada de imágenes abarca todos los formatos de entrada compatibles, y la guía de entrada de flujo abarca la ruta de flujo de memoria para los documentos que llegan como matrices de bytes desde procesos anteriores.
Reemplazar la deserialización de respuesta JSON
Klippa devuelve una estructura JSON anidada. Navegar por esa estructura requiere ya sea un modelo C# coincidente o un recorrido JsonDocument en línea, ambos los cuales se rompen cuando Klippa cambia su esquema de respuesta.
Enfoque de 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
Enfoque 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
OcrResult es un objeto tipado .NET. No hay que analizar ningún JSON, ni mantener ninguna clase de modelo, ni existe riesgo de que la desviación del esquema interrumpa la deserialización en producción. La guía de resultados de lectura documenta el modelo de objeto OcrResult completo, incluidos los coordenadas de palabras, las puntuaciones de confianza y la jerarquía de página estructurada. Para patrones de extracción de campos específicos de facturas construidos sobre OcrResult, el tutorial de OCR de facturas cubre la lógica de extracción de extremo a extremo.
Eliminación de la infraestructura de manejo de errores y reintentos
La integración de Klippa a través de HTTP requiere el manejo de errores para cada modo de fallo que pueda producir una llamada de red: tiempos de espera agotados, respuestas 4xx, respuestas 5xx, límites de velocidad y JSON parcial. Los equipos que gestionan integraciones de producción añaden políticas de reintento mediante Polly o lógica personalizada. Esa infraestructura desaparece cuando desaparece la llamada de red.
Enfoque de 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
Enfoque 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)
{
// No retry loop. No CancellationTokenSource. No HTTP status checks.
// No rate limit handling. No 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)
{
// No retry loop. No CancellationTokenSource. No HTTP status checks.
// No rate limit handling. No 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
' No retry loop. No CancellationTokenSource. No HTTP status checks.
' No rate limit handling. No partial-JSON guards.
Dim result = New IronTesseract().Read(filePath)
Return result.Text
End Function
Toda la infraestructura de reintento — el bucle, el cálculo del retraso, el CancellationTokenSource, la bifurcación del código de estado HTTP, el bloque de captura TaskCanceledException — existe únicamente debido a la red. Elimina la llamada de red y todo desaparecerá. Una llamada OCR local falla rápidamente con una excepción tipificada si el archivo de entrada no existe o es ilegible, y se ejecuta correctamente en caso contrario. La guía de optimización de velocidad cubre el ajuste del rendimiento de IronOCR si el rendimiento es un factor importante después de la migración.
Procesamiento de archivos PDF de varias páginas sin carga en la nube
Klippa acepta cargas de PDF a través del mismo punto de acceso parseDocument. Los archivos PDF de varias páginas siguen saliendo de tu red.IronOCR lee archivos PDF de forma nativa, durante el proceso de procesamiento, con acceso a los resultados página por página.
Enfoque de 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
Enfoque 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
IronOCR lee archivos PDF de forma nativa sin necesidad de conversión. Cada página es accesible individualmente, conservando su jerarquía completa de líneas, palabras y caracteres. La llamada SaveAsSearchablePdf() produce un PDF con capa de texto a partir de un documento escaneado — una capacidad que Klippa no ofrece. La guía de entrada de PDF abarca las opciones de carga, y la guía de PDF con función de búsqueda abarca las opciones de salida, incluido el formato PDF/A para el cumplimiento de los requisitos de archivo.
Referencia de mapeo de la API de OCR de Klippa a IronOCR
Klippa es una API REST, no un SDK tipado. El siguiente diagrama traduce la superficie de integración de Klippa a sus equivalentes en IronOCR.
| Concepto Klippa | Equivalente a IronOCR |
|---|---|
HttpClient con encabezado X-Auth-Key |
Instancia IronTesseract — sin configuración de autenticación |
MultipartFormDataContent |
OcrInput.LoadImage(path) o OcrInput.LoadPdf(path) |
POST /api/v1/parseDocument |
IronTesseract.Read(input) |
await _client.PostAsync(...) |
ocr.Read(input) — sincronización, no se necesita await |
response.EnsureSuccessStatusCode() |
No es necesario: no hay respuesta HTTP. |
JsonSerializer.Deserialize<KlippaResponse>(json) |
Objeto tipado OcrResult — sin deserialización |
KlippaResponse.Data.ParsedDocument.Text |
OcrResult.Text |
KlippaResponse.Data.ParsedDocument.Amount |
Regex personalizado en OcrResult.Text o OcrResult.Lines |
KlippaResponse.Data.ParsedDocument.Merchant |
OcrResult.Pages[0].Lines[0].Text |
Bucle de reintento con Task.Delay |
No es necesario: no hay modo de fallo de red. |
CancellationTokenSource(TimeSpan.FromSeconds(30)) |
No es necesario: ejecución local |
| Gestión de límites de velocidad (HTTP 429) | No es necesario: no hay límites de velocidad. |
| Enrutamiento de documentos en la nube a servidores de la UE | Ejecución local en proceso |
KlippaService.Dispose() / HttpClient.Dispose() |
Eliminación OcrInput vía declaración using |
| Campos de respuesta JSON estructurados | OcrResult.Text + OcrResult.Pages + OcrResult.Words |
| Suscripción a la API SaaS | Cadena IronOcr.License.LicenseKey — perpetua |
Problemas comunes de migración y soluciones
Problema 1: Sitios de llamadas solo asíncronas después de la eliminación de HTTP
Klippa: Toda la integración con Klippa es asíncrona porque las llamadas HTTP lo requieren. Controladores, servicios y trabajadores en segundo plano en toda tu base de código llaman a await ProcessDocumentAsync(...). Eliminar la llamada HTTP significa que ya no se necesita await, pero las firmas de los métodos async permanecen.
Solución:IronOCR proporciona APIs tanto síncronas como asíncronas. Para los sitios de llamada que deben permanecer asíncronos (controladores de ASP.NET Core, servicios en segundo plano con CancellationToken), utiliza 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
La guía de OCR asíncrono cubre ReadAsync y CancellationToken integración para patrones de ASP.NET Core y servicios alojados.
Problema 2: Registro de inyección de dependencias
Klippa: La clase KlippaService se registra en DI como un servicio singleton o de ámbito y envuelve HttpClient. Eliminarlo implica actualizar el registro DI y todos los puntos de inyección.
Solución: Registra IronTesseract como un singleton (es seguro para hilos) e inyecta directamente, o crea un envoltorio delgado que refleje tu interfaz de servicio existente:
// 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
Una instancia IronTesseract registrada como un singleton maneja solicitudes concurrentes. Cada llamada a Read() es segura para hilos.
Problema 3: Extracción de campos estructurados sin JSON preanalizado
Klippa: Klippa devuelve amount, merchant, date y vat_amount como propiedades JSON tipadas. Migrar a IronOCR implica que esos campos ya no llegan preanalizados.
Solución: OcrResult de IronOCR proporciona el texto en bruto y las coordenadas a nivel de palabra para construir una extracción equivalente. Para documentos con diseños predecibles, el OCR basado en regiones se dirige directamente a campos específicos:
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
La guía de OCR basada en regiones cubre el uso de CropRectangle en detalle. Para obtener patrones de extracción completos en todos los formatos de recibos y facturas, el tutorial de escaneo de recibos proporciona un código completamente funcional.
Problema 4: Documentos que llegan como flujos desde servicios ascendentes
Klippa: Klippa recibe documentos como cargas de formularios multipart (bytes de archivo envueltos en el contenido de un formulario HTTP). Si su aplicación recibe documentos como flujos desde S3, Azure Blob Storage o API internas, estaba leyendo el flujo en bytes y luego subiendo esos bytes a Klippa.
Solución:IronOCR acepta objetos Stream directamente. El paso de conversión de bytes desaparece:
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
No ReadAllBytes, no construcción MultipartFormDataContent, no HTTP POST. El flujo va directamente a OcrInput. La guía de entrada de arroyos abarca los tipos de arroyos y los patrones de eliminación.
Problema 5: Pruebas de integración que dependen de la simulación HTTP
Klippa: Las pruebas de integración para el código de Klippa simulan HttpClient o usan interceptores HTTP (por ejemplo, WireMock, MockHttp) para simular respuestas de API. Esas pruebas simulan la capa HTTP, no la lógica del OCR.
Solución: Las pruebas de IronOCR utilizan documentos reales con un resultado esperado conocido. No se necesita infraestructura de burla. Las pruebas se ejecutan sin conexión:
[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
Las pruebas que antes requerían una conexión Klippa en tiempo real o una configuración simulada HTTP compleja ahora se ejecutan en CI sin acceso a la red.
Problema 6: Documentos de baja calidad que Klippa mejoró en el servidor.
Klippa: El procesamiento en la nube aplica mejoras a la imagen antes del reconocimiento. Los desarrolladores nunca configuran esto; sucede automáticamente en los servidores de Klippa. Al migrar, los documentos que Klippa procesó de forma silenciosa pueden presentar una menor precisión sin un preprocesamiento explícito en IronOCR.
Solución: Aplicar explícitamente los filtros de preprocesamiento de IronOCR. El conjunto de filtros refleja lo que aplican los servicios en la nube en el servidor:
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
La guía de corrección de la calidad de imagen abarca todos los filtros de preprocesamiento y el orden en que deben aplicarse para los diferentes tipos de degradación del documento.
Lista de verificación de migración de Klippa OCR
Pre-Migración
Antes de eliminar cualquier código, revise su base de código para localizar todo el código específico de 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" .
Notas del inventario:
- Registra cada clase que envoltorio
HttpClientpara llamadas a Klippa - Lista todos los modelos de clase de deserialización JSON (
KlippaResponse,KlippaParsedDocument, etc.) - Documentar todas las asignaciones de campos que consumen las propiedades JSON preanalizadas de Klippa.
- Tenga en cuenta cualquier política de reintento de Polly o bucles de reintento personalizados creados para Klippa.
Migración de código
- Instala el paquete NuGet
IronOcr(dotnet add package IronOcr) - Añade
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"al inicio de la aplicación - Elimina
System.Net.Http,System.Text.Json,Newtonsoft.Jsonimportaciones de archivos de servicio de Klippa - Elimina la clase
KlippaService(o reemplaza su cuerpo con llamadasIronTesseract, manteniendo la interfaz) - Registra
IronTesseractcomo un singleton en el contenedor DI - Reemplaza los bloques de carga
MultipartFormDataContentconOcrInput.LoadImage()oOcrInput.LoadPdf() - Elimina las clases de modelo de respuesta JSON (
KlippaResponse,KlippaData,KlippaParsedDocument) - Reemplaza las cadenas de navegación JSON anulables (
.Data?.ParsedDocument?.Text) conresult.Text - Elimina los bucles de reintento y los tiempos de espera
CancellationTokenSourcede los sitios de llamada a Klippa - Eliminar el manejo de límites de velocidad (bloqueos de captura HTTP 429)
- Reemplaza
await ProcessDocumentAsync(...)conawait ocr.ReadAsync(...)oocr.Read(...)síncrono - Añade filtros de preprocesamiento
OcrInput(Deskew,DeNoise,Contrast) para entradas de documentos de baja calidad - Reemplazar la infraestructura de pruebas simuladas HTTP con pruebas de documentación reales.
- Elimine las políticas de reintento de Polly o el middleware de reintento personalizado con ámbito limitado a las llamadas de Klippa.
Posmigración
- Verificar que el resultado de la extracción de texto coincida con el contenido esperado de los documentos de prueba conocidos.
- Confirmar que las puntuaciones de confianza superan el umbral aceptable (normalmente 70 % o más) para los tipos de documentos de producción.
- Prueba de entrada PDF: carga PDFs de varias páginas nativamente y verifica el acceso a texto por página a través de
result.Pages - Prueba de entrada de flujo: pasa
MemoryStreamy verifica queOcrInput.LoadImage(stream)produzca el resultado correcto - Verificar que los filtros de preprocesamiento mejoren la precisión en escaneos de baja calidad en comparación con la línea base sin procesar.
- Confirma que el singleton
IronTesseractinyectado por DI maneja solicitudes concurrentes sin contención - Ejecutar las pruebas de integración sin conexión (sin conexión a la red): todas las pruebas deberían pasar sin acceso a la nube.
- Verifica la salida de PDF buscable con
result.SaveAsSearchablePdf("output.pdf")para flujos de documentos escaneados - Prueba
ReadAsyncen contexto de controlador ASP.NET Core con propagaciónCancellationToken - Confirma que el patrón de eliminación
using var input = new OcrInput()no filtra memoria bajo carga sostenida
Principales ventajas de migrar a IronOCR
Soberanía de datos desde el primer día. Tras la migración, los documentos financieros confidenciales, los escaneos de identidad y las facturas confidenciales nunca salen de su infraestructura. En el alcance de la auditoría no hay ningún procesador externo, ninguna política de retención de datos que revisar y ningún acuerdo de transferencia de datos que mantener. Las restricciones de HIPAA, ITAR, CMMC y FedRAMP que anteriormente hacían que Klippa fuera problemático se cumplen por defecto. El despliegue en Docker , AWS o Azure mantiene todo dentro de los límites de su propia infraestructura.
Se eliminó la complejidad de la infraestructura. La clase de servicio, el cliente HTTP, el código de carga de formularios, los modelos JSON, la política de reintentos, la configuración de tiempo de espera: todo existía para encapsular una llamada de red. Si se elimina la llamada de red, todo lo demás desaparece. El código resultante es más pequeño, más fácil de leer y tiene menos posibilidades de fallo. Una sola instancia de IronTesseract inyectada a través de DI reemplaza toda la capa de integración HTTP.
Costo predecible sin importar el volumen. Una licencia perpetua de IronOCR en $999 (Lite), $1,499 (Professional), o $2,999 (Enterprise) cubre procesamiento ilimitado de documentos. Procesar 500 documentos al mes o 500.000 al mes cuesta lo mismo. El sistema de facturación por documento que encarecía Klippa a gran escala está estructuralmente ausente. La página de licencias de IronOCR detalla todos los niveles y lo que incluye cada uno.
Alcance del documento sin límites.IronOCR procesa cualquier documento que contenga texto. Contratos escaneados, dibujos técnicos, formularios médicos, órdenes de compra, notas manuscritas, capturas de pantalla, archivos TIFF — todo manejado por la misma llamada Read() con la misma API. La restricción de ámbito especializado que requería un segundo sistema para los documentos que quedaban fuera de las categorías para las que Klippa estaba capacitado ha desaparecido. Una biblioteca, un punto de integración, cualquier tipo de documento.
Ahora se admiten entornos de red sin conexión y con acceso restringido. Las aplicaciones implementadas en redes bancarias, sistemas gubernamentales, entornos periféricos o cualquier infraestructura con acceso de salida restringido funcionan exactamente igual que en entornos abiertos. No se realiza ninguna comprobación de conectividad, ni ninguna prueba de estado a un punto final en la nube, ni se activa un modo degradado cuando no hay conexión a Internet. Las implementaciones aisladas de la red funcionan sin modificaciones. La guía de implementación de Linux y la guía de implementación de Docker cubren las rutas de implementación en contenedores y del lado del servidor para estos entornos.
Control total sobre la mejora de la imagen. El preprocesamiento en la nube era una caja negra: Klippa lo aplicaba, usted observaba los resultados y no tenía parámetros que ajustar. El pipeline de preprocesamiento de IronOCR es explícito y componible: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Cada filtro es opcional y debe pedirse por separado. Las mejoras en la precisión son medibles, reproducibles y están bajo su control. La guía de corrección de la calidad de imagen y la página de funciones de preprocesamiento cubren el catálogo completo de filtros, con indicaciones sobre cuándo aplicar cada uno.
Preguntas Frecuentes
¿Por qué debería migrar de Klippa OCR API a IronOCR?
Los impulsores comunes incluyen la eliminación de la complejidad de la interoperabilidad COM, la sustitución de la gestión de licencias basada en archivos, la evitación de la facturación por página, la habilitación de la implementación de Docker/contenedores y la adopción de un flujo de trabajo nativo de NuGet que se integre con las herramientas .NET estándar.
¿Cuáles son los principales cambios de código al migrar de Klippa OCR API a IronOCR?
Sustituya las secuencias de inicialización de Klippa por la instanciación de IronTesseract, elimine la gestión del ciclo de vida COM (patrones explícitos Create/Load/Close) y actualice los nombres de las propiedades de los resultados. El resultado es un número significativamente menor de líneas repetitivas.
¿Cómo instalo IronOCR para comenzar la migración?
Ejecute 'Install-Package IronOcr' en la consola del gestor de paquetes o 'dotnet add package IronOcr' en la CLI. Los paquetes de idiomas son paquetes independientes: 'dotnet add package IronOcr.Languages.French' para el francés, por ejemplo.
¿IronOCR ofrece la misma precisión de OCR que la API de OCR de Klippa para documentos comerciales estándar?
IronOCR consigue una gran precisión para contenido empresarial estándar, como facturas, contratos, recibos y formularios mecanografiados. Los filtros de preprocesamiento de imágenes (eliminación de distorsiones, eliminación de ruido, mejora del contraste) mejoran aún más el reconocimiento en entradas degradadas.
¿Cómo gestiona IronOCR los datos de idioma que la API OCR de Klippa instala por separado?
Los datos de idiomas en IronOCR se distribuyen como paquetes NuGet. dotnet add package IronOcr.Languages.German' instala la compatibilidad con el alemán. No es necesario colocar manualmente los archivos ni las rutas de los directorios.
¿Requiere la migración de Klippa OCR API a IronOCR cambios en la infraestructura de despliegue?
IronOCR requiere menos cambios de infraestructura que Klippa OCR API. No hay rutas binarias del SDK, ubicaciones de archivos de licencia ni configuraciones del servidor de licencias. El paquete NuGet contiene el motor OCR completo, y la clave de licencia es una cadena establecida en el código de la aplicación.
¿Cómo configuro las licencias de IronOCR después de la migración?
Asigne IronOcr.License.LicenseKey = "YOUR-KEY" en el código de inicio de la aplicación. En Docker o Kubernetes, almacene la clave como una variable de entorno y léala en el inicio. Utilice License.IsValidLicense para validar antes de aceptar tráfico.
¿Puede IronOCR procesar archivos PDF del mismo modo que Klippa?
Sí, IronOCR lee PDF nativos y escaneados. Instancie IronTesseract, llame a ocr.Read(input) donde input es una ruta PDF u OcrPdfInput, e itere las páginas OcrResult. No es necesario un proceso de renderizado de PDF independiente.
¿Cómo gestiona IronOCR los hilos en el procesamiento de grandes volúmenes?
IronTesseract puede instanciarse de forma segura por subproceso. Gire una instancia por hilo en un Parallel.ForEach o Task pool, ejecute OCR concurrentemente, y disponga de cada instancia cuando termine. No se requiere estado global o bloqueo.
¿Qué formatos de salida admite IronOCR tras la extracción de texto?
IronOCR devuelve resultados estructurados que incluyen texto, coordenadas de palabras, puntuaciones de confianza y estructura de páginas. Las opciones de exportación incluyen texto sin formato, PDF con opción de búsqueda y objetos de resultados estructurados para su procesamiento posterior.
¿Es el precio de IronOCR más predecible que el de la API OCR de Klippa para escalar cargas de trabajo?
IronOCR utiliza licencias perpetuas de tarifa plana sin cargos por página o volumen. Tanto si procesa 10.000 como 10 millones de páginas, el coste de la licencia permanece constante. Las opciones de licencias por volumen y por equipo se encuentran en la página de precios de IronOCR.
¿Qué sucede con mis pruebas existentes después de migrar de Klippa OCR API a IronOCR?
Las pruebas que validan el contenido de texto extraído deben seguir superándose tras la migración. Las pruebas que validan patrones de llamada a API o el ciclo de vida de objetos COM deberán actualizarse para reflejar el modelo de inicialización y resultados más sencillo de IronOCR.

