Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration! How!?

Scanning

public interface IDocumentScanner
{
bool IsSupported { get; }
Task<DocumentScanResult> ScanAsync(DocumentScanRequest? request = null, CancellationToken cancellationToken = default);
}

ScanAsync presents the platform’s own scanner and completes when the user finishes or cancels. It is a modal flow — it takes over the screen — which is why it composes as a service rather than as another camera frame analyzer.

var scan = await scanner.ScanAsync(new DocumentScanRequest
{
PageLimit = 3,
AllowGalleryImport = true,
Formats = DocumentScanFormats.Image | DocumentScanFormats.Pdf
});
if (scan.IsCancelled)
return;
foreach (var page in scan.Pages)
{
// page.ImageData (byte[]), page.MimeType
}
if (scan.Pdf is { } pdfBytes)
await File.WriteAllBytesAsync(path, pdfBytes);
OptionDefaultNotes
PageLimit10Maximum pages the user may capture. Enforced natively on Android.
AllowGalleryImporttrueOffer importing from the photo library where the platform supports it (Android).
FormatsImageImage is always produced. Pdf is honoured on Android and ignored elsewhere.

All platforms honour what they can and ignore the rest, so a request never fails for asking too much.

public class DocumentScanResult
{
public bool IsCancelled { get; }
public IReadOnlyList<DocumentScannedPage> Pages { get; } // (byte[] ImageData, string MimeType)
public byte[]? Pdf { get; }
}

Cancellation is a normal result (IsCancelled == true), not an exception. Only an unsupported platform throws — PlatformNotSupportedException.

VNDocumentCameraViewController: automatic edge detection, perspective correction, multi-page capture, retake/reorder in the native UI. Presented from the top view controller, found natively via connected scenes — no MAUI page reference needed. IsSupported gates on iOS 13+.

GmsDocumentScanning: edge detect, crop and enhance, with optional gallery import and PDF output. The scan intent is launched from a transparent proxy activity, which sidesteps the AndroidX rule that an ActivityResultLauncher must be registered before RESUMED. Activity tracking is bootstrapped automatically by a content provider, so there is nothing to initialize. It also needs no CAMERA permission — the scanner UI runs in the Google Play services process under that process’ own permission (see Getting Started).

AppKit has no document camera. Instead the user picks one or more images through an NSOpenPanel, and each is deskewed with VNDetectDocumentSegmentationRequest plus a Core Image perspective correction. Same contract, different acquisition step.

Note that MAUI on Mac targets Catalyst, which uses the VisionKit path — the AppKit implementation is for native macOS apps.

IsSupported is false and ScanAsync throws PlatformNotSupportedException. The registration still succeeds, so DI resolves and your UI can degrade gracefully.

Pass the whole result to the extractor rather than pulling pages apart yourself — multi-page handling differs by document type:

var doc = await extractor.ExtractAsync(scan, DocumentType.Invoice);

See Extraction.