← Projects

Document management system

I built a web application in C# that registers official correspondence for a government office, stamps barcodes on scanned letters and follows each one through approval, entirely on the office network.

Year
2026
Context
For the Egyptian Commercial Service
Built with
C#, .NET 9, Blazor Server, SQLite

Overview

The Egyptian Commercial Service handles its official correspondence on paper: letters that arrive, letters that go out, and internal memos. Each one needs a reference number, a record of who handled it, and the signature of an administrator. I built a web application that registers this correspondence, follows each document through its steps and keeps every version of the scanned file.

The application is written in C# on .NET 9. The interface uses Blazor Server, so clerks only need a browser, and the data lives in SQLite on one Windows machine in the office. It runs on the office network and does not need the internet for its core work.

The current version is a rebuild. The first version, written in January and February 2026, was an Avalonia desktop client that talked to an ASP.NET Core API backed by SQL Server, with Arabic text recognition, full text search and drawn signatures. In March 2026 I replaced it with the web version and dropped the features the office did not use in its daily work. I tagged version 1.0.0 on 26 March 2026.

How it works

There are three kinds of documents.

  • An incoming letter is registered, stamped with a barcode on its first page, printed so an administrator can sign it, scanned again, given a recorded action and marked as delivered.
  • An outgoing letter starts as a draft. An administrator approves or rejects it, a rejected draft can be submitted again, and an approved letter is signed, uploaded and marked as sent.
  • An internal memo follows the outgoing path.

When a clerk registers a document, the server gives it a barcode in the form ECS.YYYYMMDD.NNNN and a reference number made of a yearly counter for its direction, an Arabic letter for that direction and the year. It also sets a deadline from the urgency: 7 days for normal, 5 days for urgent, 48 hours for very urgent, or a custom number of days.

The label is a Code 128 image drawn with ZXing.Net and SkiaSharp. The clerk drags it over the first page in a PDF.js viewer, which reports its position as fractions of the page size. PDFsharp multiplies those fractions by the page size in points and draws the label there, so the zoom level does not matter.

Every change to a file is saved as a new version, and every change of status goes into a timeline with the old and the new value.

Design decisions

  • A browser application instead of a desktop one. With Blazor Server, nothing has to be installed on the clerks' computers, and an update means replacing the files on one machine.
  • SQLite instead of SQL Server. The office has few users, and SQLite in WAL mode lets them read while one of them writes, without a separate database server to install and maintain.
  • Paper signatures instead of electronic ones. The first version had a pad where an administrator drew a signature. The office signs on paper, so the web version prints the first page, and the scan of the signed page replaces page 1 as a new version of the PDF.
  • No text recognition for now. Arabic recognition on real scans was not reliable enough to depend on, so the system stores the PDF as it is, and search covers the reference number, subject, barcode and organization names.
  • Confidentiality in one place. There are five levels, from Public to Top Secret. Every query for documents goes through one filter, which shows administrators everything and shows clerks only Public and Internal documents.
  • PDFsharp for editing PDFs, because it can replace a page and draw an image on it without a commercial license.

Problems I ran into

  • Arabic text recognition. The first version used Tesseract with its Arabic model, then switched to EasyOCR, called from C# as a Python process, because it read Arabic better. An earlier Python prototype used Qari, a vision model for Arabic text with 2 billion parameters, but through a hosted service, which does not fit an office without internet. None of them was reliable enough on real scans, so the web version has no recognition.
  • Arabic search in SQL Server. Full text search needs the Arabic word breaker, language 1025, and the SQL Server image I used in Docker for development did not support it. I made that migration do nothing there and kept a plain LIKE search as a fallback.
  • Right to left text. In the desktop client, Arabic letters did not appear until I set a list of fallback fonts, and chart legends stayed in English because they came straight from enum names.
  • File streams in Blazor. Replacing page 1 failed with "Synchronous reads are not supported", because PDFsharp reads synchronously and the upload stream in Blazor does not allow it. The upload is now copied into a MemoryStream first.
  • Schema changes. The server creates the database with EnsureCreated, which does not add new columns to an existing table, so at startup it runs a short list of ALTER TABLE statements and ignores the error when a column already exists.

Results

Version 1.0.0 covers the three document paths, barcode labels and stamping, replacement of page 1, topics that group the letters of one conversation, links between a reply and the letter it answers, user management with two roles, five confidentiality levels, deadlines and a timeline for every document. The repository has 408 commits from January to March 2026, across both versions.

The server can be published as a Windows service that listens on the office network. It limits uploads to 50 MB, and each time it starts it copies the database to a backup folder and keeps the last 7 copies. A /health endpoint reports that it is running, and a GitHub Actions workflow builds the server and shared projects on every push.

The web version has no automated tests. The 21 tests in the repository belong to the first desktop version and cover its text recognition and search services.

What I'd do next

  • Make the web version fully offline. The page still loads its fonts from Google Fonts and its icons from a CDN, so on a network without internet the text falls back to system fonts and the icons are missing. The application should serve these files itself.
  • Take backups with the SQLite backup API. The server copies the database file directly, and in WAL mode the latest changes can still sit in the separate WAL file, so a plain copy can miss them.
  • Make deletion match the design. Documents have an IsDeleted flag and a query filter that hides deleted rows, but the delete action still removes the row and its files.
  • Add tests for the workflow rules, such as which status can follow which, because those rules are spread across several methods.
  • Try Arabic text recognition again with a model that runs on the office machine.

← Projets

Système de gestion documentaire

J'ai construit en C# une application web qui enregistre la correspondance officielle d'une administration, appose des codes-barres sur les lettres numérisées et suit chacune jusqu'à son approbation, entièrement sur le réseau du bureau.

Année
2026
Contexte
Pour le Service commercial égyptien
Outils
C#, .NET 9, Blazor Server, SQLite

Aperçu

Le Service commercial égyptien traite sa correspondance officielle sur papier : les lettres qui arrivent, les lettres qui partent et les notes internes. Chacune a besoin d'un numéro de référence, d'une trace de qui l'a traitée et de la signature d'un administrateur. J'ai construit une application web qui enregistre cette correspondance, suit chaque document à travers ses étapes et conserve chaque version du fichier numérisé.

L'application est écrite en C# sur .NET 9. L'interface utilise Blazor Server, donc les commis n'ont besoin que d'un navigateur, et les données sont stockées dans SQLite sur un seul poste Windows du bureau. Elle fonctionne sur le réseau du bureau et n'a pas besoin d'Internet pour l'essentiel de son travail.

La version actuelle est une reconstruction. La première version, écrite en janvier et février 2026, était un client de bureau Avalonia relié à une API ASP.NET Core et à SQL Server, avec reconnaissance de texte arabe, recherche plein texte et signatures dessinées. En mars 2026, je l'ai remplacée par la version web et j'ai retiré les fonctions que le bureau n'utilisait pas au quotidien. J'ai publié la version 1.0.0 le 26 mars 2026.

Fonctionnement

Il existe trois types de documents.

  • Une lettre entrante est enregistrée, reçoit un code-barres sur sa première page, est imprimée pour qu'un administrateur la signe, est numérisée de nouveau, reçoit une action consignée et est marquée comme remise.
  • Une lettre sortante commence comme brouillon. Un administrateur l'approuve ou la rejette, un brouillon rejeté peut être soumis de nouveau, et une lettre approuvée est signée, téléversée et marquée comme envoyée.
  • Une note interne suit le parcours des lettres sortantes.

Quand un commis enregistre un document, le serveur lui attribue un code-barres de la forme ECS.YYYYMMDD.NNNN et un numéro de référence composé d'un compteur annuel propre à sa direction, d'une lettre arabe qui indique cette direction et de l'année. Il fixe aussi une échéance selon l'urgence : 7 jours pour normal, 5 jours pour urgent, 48 heures pour très urgent, ou un nombre de jours choisi.

L'étiquette est une image Code 128 dessinée avec ZXing.Net et SkiaSharp. Le commis la fait glisser sur la première page dans une visionneuse PDF.js, qui transmet sa position en fractions de la taille de la page. PDFsharp multiplie ces fractions par la taille de la page en points et y dessine l'étiquette, donc le niveau de zoom n'a pas d'importance.

Chaque modification d'un fichier est enregistrée comme une nouvelle version, et chaque changement de statut entre dans un historique avec l'ancienne et la nouvelle valeur.

Choix de conception

  • Une application web plutôt qu'une application de bureau. Avec Blazor Server, rien n'est à installer sur les postes des commis, et une mise à jour consiste à remplacer les fichiers sur une seule machine.
  • SQLite plutôt que SQL Server. Le bureau compte peu d'utilisateurs, et SQLite en mode WAL leur permet de lire pendant que l'un d'eux écrit, sans serveur de base de données distinct à installer et à entretenir.
  • Des signatures sur papier plutôt qu'électroniques. La première version avait une zone où un administrateur dessinait sa signature. Le bureau signe sur papier, donc la version web imprime la première page, et la numérisation de la page signée remplace la page 1 dans une nouvelle version du PDF.
  • Pas de reconnaissance de texte pour l'instant. La reconnaissance de l'arabe sur de vraies numérisations n'était pas assez fiable pour qu'on s'y fie, donc le système conserve le PDF tel quel, et la recherche porte sur le numéro de référence, l'objet, le code-barres et le nom des organismes.
  • La confidentialité à un seul endroit. Il y a cinq niveaux, de Public à Très secret. Toute requête sur les documents passe par un seul filtre, qui montre tout aux administrateurs et seulement les documents Public et Interne aux commis.
  • PDFsharp pour modifier les PDF, parce qu'il peut remplacer une page et y dessiner une image sans licence commerciale.

Problèmes rencontrés

  • La reconnaissance du texte arabe. La première version utilisait Tesseract avec son modèle arabe, puis est passée à EasyOCR, appelé depuis C# comme processus Python, parce qu'il lisait mieux l'arabe. Un prototype Python antérieur utilisait Qari, un modèle de vision pour le texte arabe de 2 milliards de paramètres, mais par un service hébergé, ce qui ne convient pas à un bureau sans Internet. Aucun n'était assez fiable sur de vraies numérisations, donc la version web n'a pas de reconnaissance.
  • La recherche en arabe dans SQL Server. La recherche plein texte a besoin du découpeur de mots arabe, la langue 1025, et l'image Docker de SQL Server que j'utilisais en développement ne le prenait pas en charge. J'ai rendu cette migration inactive dans ce cas et gardé une recherche LIKE simple en solution de repli.
  • Le texte de droite à gauche. Dans le client de bureau, les lettres arabes ne s'affichaient pas avant que je définisse une liste de polices de repli, et les légendes des graphiques restaient en anglais parce qu'elles venaient directement des noms d'énumération.
  • Les flux de fichiers dans Blazor. Le remplacement de la page 1 échouait avec « Synchronous reads are not supported », parce que PDFsharp lit de façon synchrone et que le flux de téléversement de Blazor ne le permet pas. Le fichier est maintenant copié d'abord dans un MemoryStream.
  • Les changements de schéma. Le serveur crée la base avec EnsureCreated, qui n'ajoute pas de nouvelles colonnes à une table existante, donc au démarrage il exécute une courte liste d'instructions ALTER TABLE et ignore l'erreur quand une colonne existe déjà.

Résultats

La version 1.0.0 couvre les trois parcours de documents, les étiquettes et l'apposition des codes-barres, le remplacement de la page 1, les sujets qui regroupent les lettres d'un même échange, les liens entre une réponse et la lettre à laquelle elle répond, la gestion des utilisateurs avec deux rôles, cinq niveaux de confidentialité, les échéances et un historique pour chaque document. Le dépôt compte 408 commits de janvier à mars 2026, pour les deux versions.

Le serveur peut être publié comme service Windows qui écoute sur le réseau du bureau. Il limite les téléversements à 50 Mo et, à chaque démarrage, il copie la base dans un dossier de sauvegarde et garde les 7 dernières copies. Un point d'accès /health indique qu'il fonctionne, et un workflow GitHub Actions compile le serveur et le projet partagé à chaque push.

La version web n'a pas de tests automatisés. Les 21 tests du dépôt appartiennent à la première version de bureau et portent sur ses services de reconnaissance de texte et de recherche.

Pour la suite

  • Rendre la version web entièrement hors ligne. La page charge encore ses polices depuis Google Fonts et ses icônes depuis un CDN, donc sur un réseau sans Internet le texte passe aux polices du système et les icônes manquent. L'application devrait servir ces fichiers elle-même.
  • Faire les sauvegardes avec l'API de sauvegarde de SQLite. Le serveur copie directement le fichier de la base, et en mode WAL les dernières modifications peuvent encore se trouver dans le fichier WAL séparé, donc une simple copie peut les manquer.
  • Accorder la suppression avec la conception. Les documents ont un indicateur IsDeleted et un filtre de requête qui masque les lignes supprimées, mais l'action de suppression efface encore la ligne et ses fichiers.
  • Ajouter des tests pour les règles du flux de travail, par exemple quel statut peut suivre lequel, parce que ces règles sont réparties dans plusieurs méthodes.
  • Essayer de nouveau la reconnaissance du texte arabe avec un modèle qui tourne sur la machine du bureau.