De Dynamische Developer #2 – Houtsneden en gravures veiling met Angular & C#/.NET & PostgreSQL
Auteur: Danny Holstein
Om mijn eigen Github repositories aan te vullen en op te leuken, liep ik al langere tijd met het idee om een veiling applicatie te maken. Tijdens mijn vrije dagen had ik hier tijd voor om een begin mee te maken. Op Github zijn al andere veilingapplicaties te vinden met uiteenlopende onderwerpen. Om iets origineels te maken heb ik ‘houtsneden en gravures’ als onderwerp gekozen voor de veiling applicatie. Dit nieuwe blogbericht zal gaan over de ontwikkeling van deze applicatie en de technische uitdagingen die zich voordeden.
Tekst en beeldmateriaal voor de houtsneden en gravures
Als beeldmateriaal wordt er gebruik gemaakt van door AI-gegenereerde afbeeldingen waar geen auteursrechten op zitten en tegelijkertijd vrij in gebruik zijn. Wanneer deze afbeeldingen eenmaal gepubliceerd zijn op internet dan zijn er geen rechten aan te ontlenen. Op Fotor.com is er een Woodcut (houtsnede) en Copper Engraving (kopergravure) effect waarvan ik dankbaar gebruik heb gemaakt. Uit de vele gegenereerde afbeeldingen is een selectie gemaakt voor de veiling applicatie.
Om de veilingapplicatie visueel aantrekkelijk en esthetisch verantwoord te maken, is er in het bijzonder gekozen voor zeer originele afbeeldingen met een 18e of 19e-eeuwse sfeer. Op verschillende afbeeldingen is het fictieve personage Olivia des Beauchamps te zien in verschillende settings en in 18e-eeuwse kledij. De titels en korte beschrijvingen zijn allemaal door mijzelf verzonnen. Ook tijdens de ontwikkeling van de applicatie ben ik lekker creatief bezig geweest.
PostgreSQL database en C#/.NET10 WebApi
De techstack bestaat uit technologieën en programmeertalen die normaliter bij BEE-2B worden gebruikt. Dit is gewoonlijk een PostgreSQL (of MSSQL Server) database, een C#/.NET WebApi en een Angular frontend applicatie. De PostgreSQL database wordt niet lokaal geïnstalleerd, maar wordt uitgevoerd in een Docker container. In de repository op GitHub is een docker-compose bestand opgenomen om deze database snel up-and-running te krijgen. De afbeeldingen worden in dit project opgeslagen als byte array in plaats als base-64-string. Het opslaan van afbeeldingen als byte array is effectiever maar technisch gezien iets lastiger. Het voordeel van base64-strings is dat dit makkelijker is om op te slaan, maar het nadeel is dat de grootte van de afbeeldingen in ieder geval met 33% toeneemt. Een ander nadeel van base64-strings is dat zowel de backend- als de frontend-applicatie dit moeten encoderen en decoderen.
In de afbeelding hier boven is te zien dat de database 6 tabellen heeft waarvan er 5 betrekking hebben op de veilingen (Auction). Deze tabellen bevatten informatie over de veilingen, zoals: de veilingdetails, de afbeelding, het aantal biedingen en welke gebruiker de veiling heeft gewonnen. De gebruikers zijn ‘dummy users’ en de veilingapplicatie heeft geen ‘echte gebruikers’. Voor deze applicatie wilde ik niet zo ver gaan om een authenticatie- en autorisatie systeem toe te passen. Aan de frontend zijde is er al een ‘ingelogde’ gebruiker met de naam ‘Logged in User’ die een uniek id met het getal 5 heeft. Met dit unieke id is te onderscheiden welke gebruiker op welke veiling (en wanneer) een bod heeft geplaatst. De tabel AuctionItems heeft relaties met de andere tabellen in de database (waaronder een one-to-many relatie met de AuctionBids tabel).
Een veiling eindigt op een bepaalde dag en tijdstip. In de wereld van C#/.NET zijn er twee populaire libraries om events ‘af te vuren’ (zoals het einde van een veiling) op een bepaald tijdstip door middel van Jobs/taken die op de achtergrond worden uitgevoerd. Dit zijn Quartz.NET en Hangfire, waarbij de laatst genoemde meer geschikt bleek te zijn voor deze applicatie. Na het instellen van Hangfire worden er in de database tabellen gemaakt voor de Jobs/taken. Wanneer de WebApi wordt stopgezet (en dat is het geval tijdens lokale ontwikkeling), dan worden de openstaande Jobs/taken vanuit de database opnieuw geladen door middel van een ‘Catch Up Service’ die uitgevoerd wordt tijdens het opstarten van de WebApi. Wanneer er tijdens de downtime veilingen zijn verlopen, dan worden deze gesloten.
Een andere belangrijke schakel tussen de WebApi en de Angular applicatie is het gebruik van SignalR. SignalR is een ASP.NET Core web-technologie dat een makkelijke abstractie biedt met betrekking tot WebSockets (voor live verbindingen – WebSockets worden ondersteund door alle moderne browers). Of met andere woorden: wanneer een gebruiker de detailpagina van een open veiling bezoekt, dan wordt er een live verbinding gemaakt met de server (in dit geval de WebApi). De WebApi fungeert als hub en zal meldingen versturen aan alle (frontend) clients die zich hebben aangemeld bij deze hub. Concreet houdt dit in dat alle clients de meest recentste informatie ontvangen over de huidige prijs van een veiling of wanneer een veiling wordt gesloten.
Een andere technische uitdaging is wanneer 2 of meerdere gebruikers tegelijkertijd een bod doen op een veiling voor hetzelfde bedrag. In zulke situaties kan er maar 1 gebruiker de veiling winnen. Het plaatsen van biedingen is een race condition waarbij zowel de huidige (en hoogste) prijs als het aantal biedingen op een veilige manier moet worden geüpdatet. De technische oplossing is om met EntityFramework een transaction te gebruiken. Dit houdt in wanneer de data van de update juist is er een commit plaatsvindt en de data wordt dan vervolgens opgeslagen. Maar wanneer er onjuiste data wordt geüpdatet of er iets fout gaat, dan vind er een rollback plaats en zal er niets worden opgeslagen. Met een extra TimeStamp / RowVersion is er een extra controle voor zulke gebeurtenissen die tegelijkertijd kunnen plaatsvinden en dit voorkomt het verlies van data. De uiteindelijke winnaar van een veiling wordt vastgesteld in de volgorde: bedrag van de hoogste bieding, gevolgd door de tijd van de bieding en tenslotte het eerste Id uit de AuctionBids tabel.
De WebApi maakt gebruik van een zeer recente versie van .NET, waarmee ik nog niet bekend was. Voor mij was Scalar, dat gebruikt wordt in .NET10 om de endpoints en REST API uit te testen, helemaal nieuw en hier moest ik even aan wennen. Normaliter wordt er gebruik gemaakt van Swagger (dat overigens hetzelfde doet als Scalar) voor verschillende WebApi’s. Voor deze WebApi is ook gebruik gemaakt van de Clean Architecture waarbij de lagen strikt van elkaar zijn gescheiden. De Clean Architecture gaat van buiten naar binnen toe en volgt de lagen:
1. Domain (Entities voor de database en interfaces)
2. Infrastructure (de repository laag, implementatie van EntityFramework, transacties)
3. Application (Services, Mappers en Data Transfer Objecten)
4. WebApi (Controllers/endpoints, Dependency Injection).
Door de Clean Architecture moest ik een aantal zaken (zoals het sorteren van data, de plaats van Hangfire en SignalR) op een andere manier toepassen waarbij zaken gescheiden blijven. Als laatste zijn er 2 endpoints gemaakt: de eerste kan biedingen simuleren van (dummy)gebruikers en de tweede kan een veiling binnen 3 minuten beëindigen. Met deze 2 endpoints is de werking van de Angular applicatie verder uit te testen.
Frontend Angular applicatie
De Angular applicatie maakt gebruik van de meest recente versie van Angular (dat is versie 22). Welke libraries er verder gebruikt kunnen worden, dat hangt vaak af van aard van de applicatie. Over het algemeen is Angular Material beter geschikt voor mobiele applicaties en waar toegankelijkheid belangrijk is. Voor zakelijke applicaties biedt de library ng-zorro-antd een uitweg. Voor deze veilingapplicatie heb ik gekozen voor daisyUI dat gebruik maakt van TailwindCSS, want deze library is flexibeler in gebruik en componenten zijn makkelijker aan te passen. Over daisyUI heb ik eerder in januari 2026 een blogbericht geschreven en deze library kan goed samenwerken met Angular. De iconen zijn afkomstig uit een andere library, namelijk heroicons dat een goede aanvulling is voor daisyUI.
Een andere vraag die ik moest beantwoorden had betrekking op welke HttpClient er voor deze applicatie gebruikt moest worden. Op dit moment biedt Angular drie mogelijkheden: de reguliere HttpClient in combinatie met RxJS Observables, de Resource API en de HttpResource API. Ik heb voor de laatste optie gekozen en heb in deze applicatie het pakket RxJS verwijderd. Al eerder heb ik generieke classes gemaakt voor de HttpResource API die voor dit project verder zijn verfijnd. De veiling applicatie maakt alleen gebruik van Angular signals. In de praktijk bleek het relatief eenvoudig te zijn om de RxJS Subjects en BehaviorSubjects te vervangen door computed en readonly signals. Ook bleek in de praktijk dat SignalR goed kan samenwerken met signals.
Over het algemeen had ik al een goed idee welke onderdelen en pagina’s nodig zijn voor deze applicatie, zoals: een overzichtspagina met filters waar alle veilingen worden getoond, een detailpagina waar biedingen gedaan kunnen worden op een veiling, een pagina waar nieuwe veilingen gemaakt en geüpdatet worden, een overzichtspagina met alle openstaande en gesloten veilingen, en tenslotte een pagina met alle gewonnen veilingen. In de onderstaande afbeelding wordt de overzichtspagina getoond.
Het uittesten van de detailpagina bleek wel een uitdaging te zijn. Ten eerste moet een gebruiker zich via de detailpagina aan kunnen melden voor een open veiling met SignalR. Veel voorbeelden op internet maken gebruik van RxJS, maar deze library heb ik verwijderd en ik zou gebruik maken van readonly signals. Na de nodige debugging en uittesten werkte de live verbinding naar behoren. Via dezelfde live verbinding ontvangen alle frontend clients een notificatie wanneer de prijs van een veiling toeneemt of een veiling wordt gesloten. De library daisyUI heeft een mooi component genaamd ‘Countdown’ dat een aftelklok is waarmee de resterende minuten en secondes worden aangegeven. Het voordeel van dit component is dat er geen extra JavaScript-library nodig is. Maar het addertje onder het gras was wel dat dit Countdown-component aangedreven moest worden door Angular met signals en de JavaScript functie interval. In de laatste minuut geeft het Countdown-component met rode getallen de resterende secondes aan. Het resultaat is een visueel aantrekkelijke detailpagina met veel functionaliteit die hieronder is te zien:
Goede ideeën en andere gebruikersvriendelijke zaken heb ik overgenomen van al bestaande veiling-platformen (zoals bijvoorbeeld Ebay). Vanuit de Angular applicatie is het niet de bedoeling dat de server wordt ‘ge-spammed’ of overladen met POST-requests. Als tussenstap moet een gebruiker het ingevoerde bedrag voor een veiling bevestigen door middel van een pop-up. Wanneer deze pop-up open is, dan zijn alle andere knoppen grijs en uitgeschakeld zodat een request niet meerdere keren kan worden verzonden. Het kan altijd voorkomen dat een gebruiker per ongeluk een getal te veel invoert voor een veiling. Door middel van de pop-up waar het bedrag nog een keer dikgedrukt wordt getoond, worden mogelijk zulke typefouten voorkomen.
Wat betreft CRUD-functionaliteit (CRUD = Create, update en delete) heeft de Angular applicatie een pagina die veilingen kan maken en updaten. Hierbij wordt gebruik gemaakt van Angular’s meest recente signal forms dat voordelen heeft zoals: minder code, compactere code en type safety. Het verzenden van de data (voor POST- en PUT-requests) gaat in deze applicatie op een iets andere manier dan gebruikelijk. De Angular applicatie verstuurt alles als FormData (de afbeelding en alle data van de veiling) naar een endpoint dat zowel een IFormFile voor de afbeelding als een FromForm attribuut voor de veilingdata heeft als parameters. Het voordeel is dat afbeeldingen op een effectieve manier (als byte array) worden geüpload. Een probleem deed zich echter voor met de interpretatie van punten en komma’s in de prijzen van een veiling. De WebApi vermenigvuldigde de prijzen aanvankelijk met 100 en dit was niet de bedoeling. Dit probleem is opgelost door de CultureInfo expliciet op InvariantCulture te zetten in de WebApi. De Angular applicatie kan de afbeeldingen ook weer opvragen via een endpoint die dit als een datastream van ruwe bytes zal aanleveren.
Alles Dockerizeren
Voor lokale ontwikkeling zit er in de Github repository een docker-compose bestand dat de PostgreSQL database snel up-and-running maakt in een Docker container. Als developer is gewoonlijk alles wat nodig is bij de ontwikkeling al lokaal geïnstalleerd (.NET10, .NET8, Node.JS, Angular CLI, Visual Studio 2022 en VS Code, etc.). Maar voor iedere developer kunnen de tools en programmeertalen weer anders zijn. Een andere essentiële tool voor deze applicatie is de dotnet ef tool waarmee database migraties zijn te maken en te importeren in de database. Omdat er zoveel nodig is voor lokale ontwikkeling, leek het me voor deze applicatie een goed idee om alle onderdelen te ‘dockerizeren’. Dit wil zeggen dat er door middel van een enkel docker-compose bestand de gehele veiling applicatie (met database inclusief voorbeelddata) in een Docker container kan worden uitgevoerd.
Globaal bestaat het docker-compose bestand uit 4 services voor: de PostgreSQL database, de C#/.NET10 WebApi, de frontend Angular applicatie en de database migraties. Wat betreft de database migraties (waarmee de voorbeelddata in de database wordt toegevoegd) bleek het maken van een extra service uitkomst te bieden. Mijn eerdere pogingen om de database migraties toe te voegen in de Dockerfile van de WebApi leidde niet alleen tot een mislukking, maar ook tot allerlei andere foutmeldingen. Het docker-compose bestand maakt gebruik van andere Dockerfiles waarmee de juiste Docker image (voor .NET en Node.js) worden opgehaald en de applicaties worden geïnstalleerd. De vierde service – voor de database migratie – heeft ook een healthcheck die controleert of de PostgreSQL database toegankelijk is.
Na de nodige trail-and-error en het verbeteren van bepaalde code, is de hele applicatie in een Docker container uit te voeren. Het grote voordeel is dat andere (GitHub) gebruikers en of geïnteresseerden niet alle benodigde tools en of programmeertalen lokaal hoeven te installeren. Met behulp van Docker Desktop en het commando docker compose up --build -d is de hele veiling applicatie up-and-running te krijgen in een Docker container. Dit laatste is in de afbeelding hier onder te zien, waarbij de Angular applicatie toegankelijk is op localhost en poort 80 –de WebApi is ook toegankelijk op localhost maar op een andere poort, namelijk: 5001. Hetzelfde geldt voor de PostgreSQL database die op poort 5432 wordt uitgevoerd.
Tot slot
En hierbij is een einde gekomen aan dit nieuwe blogbericht waarin de ontwikkeling van de veiling applicatie is besproken. Verschillende technische uitdagingen en oplossingen zijn besproken, zoals Jobs/taken met Hangfire, SignalR in zowel de frontend als backend applicatie, het gebruik van een transaction omdat biedingen race conditions zijn, het gebruik van Angular signals in plaats van het RxJS pakket, het uploaden van afbeeldingen als byte array en de hele applicatie ‘dockerizeren’.
Het resultaat is een interessante applicatie die gebruik maakt van: een PostgreSQL database, een WebApi (C#/.NET10) en een Angular applicatie. Ook tijdens mijn vrije dagen ben ik lekker creatief bezig geweest met het ontwikkelen van deze veiling applicatie. Mijn plan is om de applicatie in de toekomst te updaten met nieuwe ‘houtsnedes en gravures’ en de meest gangbare programmeerpraktijken toe te passen.
Geïnteresseerden kunnen de code en of extra afbeeldingen bekijken via de link hieronder:
woodcuts-and-engravings-auction-angular-dotnet