This page describes how DocView Capture is built, for developers and IT architects who integrate with it, extend it, or plan its hosting.
The programs
DocView Capture has three programs that share one SQL Server database and one storage location for page images and outputs.
| Program | Runs as | What it does |
|---|---|---|
| Web app | IIS, Azure App Service or a Linux container | Every screen, the REST API, and live progress over SignalR. |
| DocView Worker | Windows service, Azure WebJob or a Linux container; one or more | OCR, separation, classification, extraction, export and import polling. |
| Scan Agent | Tray app or Windows service on each scanning PC | Connects TWAIN and WIA scanners to the browser. |
Storage is a local folder or network share, or Azure Blob Storage. Page images and full OCR results are never stored in SQL Server. SQL holds metadata only, which keeps SQL Server Express within its 10 GB limit.
Clean architecture
The source code follows clean architecture. References point inwards only:
Web / Worker → Infrastructure → Application → Domain
| Project | Contains | May reference |
|---|---|---|
DocView.Capture.Domain |
Entities, value objects and domain rules. | Nothing. No framework at all. |
DocView.Capture.Application |
Use cases as plain application services, ports and the plugin contracts. | Domain, and only the abstractions of EF Core. |
DocView.Capture.Infrastructure |
EF Core and SQL Server, the SQL queue, storage, plugin implementations, scripting, encryption. | Application, Domain. |
DocView.Capture.Web |
MVC controllers and Razor views, REST API, SignalR hub, authentication. | Infrastructure, Application, Domain. |
DocView.Capture.Worker |
Hosted services that run automatic modules and import pollers. | Infrastructure, Application, Domain. |
DocView.Capture.ScanAgent |
The Windows Scan Agent. | None of the above. |
Architecture tests (tests/DocView.Capture.Architecture.Tests) enforce these rules. They also fail the build if a forbidden package is referenced.
Other design choices:
- No mediator library. Each use-case area is a plain service, such as
BatchClassServiceorIngestionService, injected through dependency injection. - Hand-written mapping. No mapping library.
- Async throughout, structured logging, and EF Core migrations for every schema change.
- Only permissive open-source licences (MIT, Apache 2.0, BSD, MS-PL). The notices they require ship with every installation in
NOTICES.txt.
Batches, workflow and the queue
A batch holds documents, and documents hold pages. Every level carries field values and metadata.
Each Batch Setup version defines an ordered list of workflow steps. Scan is always first and PDF generation & export always last. Each step is its own queue. A module takes batches from its queue, processes them and routes them on: to the next step, to a later step, to a manual review step, or to the exception queue with a message.
The queue is the Batches table itself. DocView Workers claim work with a single atomic SQL statement using UPDLOCK, READPAST hints, so many DocView Workers can pull from the same queue without ever claiming the same batch twice. Automatic modules hold a lease they renew while working. If a DocView Worker stops, a sweeper returns its batches to the queue after the lease runs out. No message broker is needed.
Batches keep the Batch Setup version they started with. Releasing a new version never changes batches in progress.
Scaling
- DocView Workers scale horizontally. Add servers or instances; they share the queue. Worker profiles decide what each DocView Worker does. See DocView Workers.
- Web servers can run behind a load balancer with sticky sessions (for live progress).
- The recognition module processes the pages of a batch in parallel.
Recognition pipeline
The automatic recognition step runs these stages in order:
image cleanup → barcode detection → OCR → separation → classification
→ extraction → validation → confidence check → route
The same pipeline powers the administrators' Test tab, without saving anything, so tests behave exactly like production.
Plugin interfaces
Engines, rules and connectors are plugins behind interfaces in DocView.Capture.Application/Plugins. Core logic depends only on the interfaces, never on a specific OCR engine or connector.
| Interface | Purpose | Shipped implementations |
|---|---|---|
IOcrEngine |
Text, lines and words with positions and confidence; optional key-value pairs and tables. | Tesseract (tesseract), Azure AI Document Intelligence (azure), Google Cloud Vision (google). |
IImageProcessor |
One image cleanup operation. | Deskew, blank page detection, despeckle, border removal, punch-hole removal, auto-rotate. |
IDocumentSeparator |
One separation rule. Votes per page. | Fixed page count, page size and count, blank page, barcode, patch code, separator sheet, keyword, class change, new file, manual. |
IClassifier |
Scores every candidate document class. | Rules classifier, machine-learning text classifier. |
IFieldExtractor |
One extraction method. | Regex, key-value, zonal, tick boxes (OMR), anchor, barcode, table, cloud key-value. |
IExportConnector |
Staged, transactional delivery. | Folder, SFTP, SQL insert, REST, C# script. |
IImportSource |
Pull-based import, polled by DocView Workers. | Watched folder, email (IMAP/POP3). |
Common rules for plugins
Every plugin implements IPlugin:
public interface IPlugin
{
string Key { get; } // stable, lower-case, stored in configuration
string DisplayName { get; } // shown in the administration screens
}
- The Key is stored in Batch Setups, so it must never change once shipped.
- Plugins are registered in dependency injection as
IEnumerable<TContract>.IPluginCatalog<TContract>finds them by key (case-insensitive) and fails at start-up on an empty or duplicate key. - Settings are a
JsonElementowned by the plugin and stored with the rule or step that uses it. - Secrets are never inside settings. Settings hold the secret's name; the host resolves it through
ISecretStore. - Plugins must be usable from a Test action: pure functions of their inputs, with no hidden state.
Example: the OCR contract
public interface IOcrEngine : IPlugin
{
OcrEngineCapabilities Capabilities { get; }
Task<OcrPageResult> RecognizeAsync(OcrRequest request, CancellationToken cancellationToken);
}
public sealed record OcrRequest(
IPageImage Page,
IReadOnlyList<string> Languages,
BoundingBox? Region = null,
JsonElement? Settings = null);
Example: the export contract
public interface IExportConnector : IPlugin
{
Task<ConnectorTestResult> TestAsync(JsonElement settings, CancellationToken cancellationToken);
Task<IExportSession> BeginAsync(ExportContext context, CancellationToken cancellationToken);
}
public interface IExportSession : IAsyncDisposable
{
Task WriteFileAsync(string relativePath, Stream content, string contentType, CancellationToken cancellationToken);
Task WriteRecordAsync(string name, IReadOnlyDictionary<string, object?> values, CancellationToken cancellationToken);
Task CommitAsync(CancellationToken cancellationToken);
Task RollbackAsync(CancellationToken cancellationToken);
}
A session that is disposed without a commit must roll back, so no partial output is left behind. Each destination records its own delivery, so a retry skips destinations that already succeeded.
Adding a plugin
- Implement the interface in
DocView.Capture.Infrastructure, with a new, unique key. - Register it in
Infrastructure/DependencyInjection.cs, for exampleservices.AddScoped<IExportConnector, MyConnector>();. - Describe its settings in
Application/Plugins/SettingsSchemas.cs, so administrators get a form with labels, help texts and defaults. - Add unit tests, and check the licence of any new package.
Note: Most administration screens list plugins from the catalog. The OCR engine choice on the Batch Setup's General tab lists the three shipped engines, so a new OCR engine also needs that list extended.
For customer-specific exports that do not justify a new plugin, use C# export scripts.
Scripting
| Kind | Language | Where it runs |
|---|---|---|
| File and folder names, XML, CSV and JSON templates | Scriban templates | Sandboxed, with time and size limits. |
| Finders, rules, formatters, calculated fields, server event scripts | JavaScript | Sandboxed: no .NET, file or network access; time, statement and memory limits. |
| Browser event scripts | JavaScript | A Web Worker with no network access, stopped after 1.5 seconds. |
| C# export scripts | C# | On the DocView Worker with its rights. Not sandboxed; guarded by a permission and a switch. |
Security design
- ASP.NET Core Identity with cookie sign-in, and optional Windows (Negotiate) sign-in.
- Permission-based policies per module, and per Batch Setup rights.
- Secrets encrypted at rest with ASP.NET Core Data Protection; the key ring is stored in the database and protected by the machine or a certificate.
- API keys stored as salted SHA-256 hashes.
- A full audit trail, including every field value change.