An Attendance Platform in ASP.NET Core
Badge gates, WFH timers and shift allowances - the shape of a seven-project intranet attendance platform, the onion it almost is, and the roadmap for fourteen more parts.
348 articles filed under this topic.
Badge gates, WFH timers and shift allowances - the shape of a seven-project intranet attendance platform, the onion it almost is, and the roadmap for fourteen more parts.
A 6.23 MB JavaScript bundle loaded on every page, a manifest that minifies CSS but concatenates JS, and two copies of jQuery - the first of four frontend chapters the attendance series skipped.
Hard deletes destroy the audit trail; soft deletes clutter live tables forever. Introducing ArchiveKit, a .NET library that intercepts deletions and stores immutable archive snapshots with TTL policies and cascade archiving.
Unit tests mock what you assume, end-to-end tests tell you too late - consumer-driven contract testing closes the gap, and a small open-source .NET library called Pactify is the clearest way I know to learn how it works. Part 1 of a series.
The 22-part Convey series read the packages; it never once walked the sample system that ships with them. Opening the missing chapters with the trip an order takes through Orders, Pricing and Deliveries — and what the samples quietly teach that no package README does.
Rename a property, change a type, ship the release - and every payload you stored last year stops deserialising. Introducing VersionKit, a .NET library that declares schema history as attributes and migrates old data on read.
Controllers grow sideways and Minimal APIs grow into soup. FastEndpoints bets on a third shape - one class per endpoint - and this series reads its source to see how the bet is implemented. Part 1 of FastEndpoints in Depth.
Seventeen parts of reading FastEndpoints and the series never once mentioned mappers - the entity-translation layer half the endpoint base classes exist to serve. The Missing Chapters opens with the biggest gap: where mappers live, how they are found, and why they are secretly singletons. Part 1 of FastEndpoints — The Missing Chapters.
Inflow is a modular monolith whose modules agree on their boundaries and disagree about everything inside them - a four-project onion next to a single feature-sliced assembly. Part 1 of a series reading the money-moving half of the estate.
A teaching repo that implements one airline-charter domain three ways on purpose - CRUD, CRUD-plus-rich, and Clean Architecture - and persists it three ways to match. Part 1 of a source-reading series.
Every team's fifth microservice ships with the same four hundred lines of bootstrap code as the first four. Opening a deep-dive series on Convey, the open-source .NET toolkit that turned that boilerplate into thirty composable packages.
Database-per-tenant, schema-per-tenant, or packed into a shared database? Kicking off a series on building a multi-tenant platform where one EF Core codebase supports all three isolation strategies behind a single control plane.
DShop is one e-commerce shop stamped out as nine microservices over RabbitMQ - this series reads the real source to see how the topology holds together and where it drifts. Part 1 of Nine Services and a Message Bus.
A 2018 API gateway promises that routing, auth, validation and messaging need no code at all - this series reads the source that makes the promise true, and the places where it is not.
DevMentors' ModularMonolith sample packs three conference-system modules, a mini-framework and one deliberately broken contract into eighty-four C# files - opening a series that reads all of it, defects included.
A guided tour of FeedR, DevMentors' open-source .NET microservices sample for real-time data feeds - six services, Redis pub/sub, Apache Pulsar and a gRPC stream, and the two-tier eventing model that makes it worth reading. Part 1 of a source-level deep dive.
Split one database into five services and the transaction you deleted does not disappear - it goes feral. On sagas, compensation, process managers, and Chronicle, the small open-source .NET library this series reads line by line.
Dozens of Azure Functions and Logic Apps need each other's URLs and keys, and there is no Kubernetes, no sidecar, no mesh to hand them out. How a small .NET library turned one Key Vault and a naming convention into a working discovery layer - the consumer side of a registry made of secrets. Part 1 of a new series.
Reading a repository one project at a time hides everything that lives in the edges between them - and in this estate the edges are where the interesting failures are.
Convey sells a microservice in thirty lines, and Pacco builds ten of them - this series reads the whole estate to price what the framework compresses and what every consumer pays for by hand. Part 1 of The Convey Consumer Tax.
Five executables, zero queues - how an attendance platform coordinated a web app, three Azure Functions and a console job through one SQL database, and what that choice bought and cost. Part 1 of the series.
DShop.Common is the shared library nine microservices were built on - and the rough draft of a framework its authors later shipped as Convey.
Every modular monolith claims its modules are decoupled. Inflow proves it the only way that counts - by having no project reference between any two of them - and then has to build a whole mini-framework to make that survivable. Part 1 of a source-read through the shared layer.
Nobody in Pacco approves an order - a vehicle reservation does. A tour of DevMentors' parcel-delivery estate, the domain it models, and the fifteen-part series it earns.
Inflow ships an executable request collection - five .rest files, thirty-seven requests. On a fresh clone nine of them return a 2xx, and eight of those nine are in one file. This series reads the repository itself as the artefact.
Trill's Stories service is four projects with a dependency-free domain at the bottom. Its Users service is two projects with everything in one assembly - same author, same week, same estate, opposite answers to what a layer is.
The DevMentors Trill estate is a nine-service microservices sample spread over eleven sibling repositories with no README, no clone script and no bootstrap tooling - and the only artefact that names the whole system is a Visual Studio solution file.
What .NET Aspire actually does for a real multi-service system - the AppHost model, service defaults, the dashboard, and the road from F5 to deployment.
Wire four services into a .NET Aspire AppHost and run it once - traces, logs, health and environment variables for the whole system, before you have written a single line of observability code. Part 1 of a series on Aspire in production.
SaveChanges is already a transaction, until it isn't enough. A tour of explicit transactions, TransactionScope, execution strategies and the failure modes that only show up in production.
Rename a property, change a type, ship the release - and every payload you stored last year stops deserialising. Introducing VersionKit, a .NET library that declares schema history as attributes and migrates old data on read.
Seven parts of migration machinery existed to deliver one table — a hierarchical crumb trail with three ltree columns, a jsonb payload, GiST and GIN indexes, and a Dapper read side that speaks PostgreSQL's native operators. This is why it was PostgreSQL all along.
Shipping a migration to one database is a deploy step. Shipping it to every schema in every group database is an orchestration problem — cross-database MigrateAsync, an HTTP migration surface, a per-tenant audit ledger, and the honest gap where the fan-out loop should be.
Tenants are data too. The tenant-management database is the one single-tenant database in the whole platform, and its five entities — Tenant, TenantConnection, TenantFeature, DatabaseGroup, TenantMigrationHistory — carry every decision the rest of the series depends on.
Deleting a tenant is the most dangerous button in the platform — behind it sit DROP SCHEMA CASCADE and DROP DATABASE. A two-step lifecycle with global query filters, a SaveChanges interception and an explicit purge keeps the blast radius survivable.
A signup click becomes a real PostgreSQL database or schema in seconds. Running CREATE DATABASE from application code feels illegal until you contain it — identifier validation, quoted DDL, idempotent steps, and a status machine that admits when provisioning fails.
EF Core wants one database, one model and one migrations history table. Schema-per-tenant breaks all three assumptions. Constructor-injected migrations, a per-schema history table and a replaced IMigrationsAssembly put one migration set in charge of every tenant schema.
You built schema-aware migrations, provisioned tenant A, and everything worked. Tenant B provisions without a single error — and gets no tables. EF Core's model cache is the silent culprit, and IModelCacheKeyFactory is the three-field fix.
Header, route, subdomain or query string: how the platform decides who is asking before it touches any data, how tenant identity becomes a connection string and a schema, and where the caching knives are hidden.
Database-per-tenant, schema-per-tenant, or packed into a shared database? Kicking off a series on building a multi-tenant platform where one EF Core codebase supports all three isolation strategies behind a single control plane.
SaveChanges is already a transaction, until it isn't enough. A tour of explicit transactions, TransactionScope, execution strategies and the failure modes that only show up in production.
A 40-line entry script that is entirely commented out, thirty-one views carrying inline script blocks, a 2,904-line page file, and @section as the only architecture - the addendum's closing ledger.
A sequel stylesheet four times the size of the original, a sheet per screen, six copied tour themes, and a modal built as one inline HTML string - what a naming convention and a tokens file actually earn.
A 6.23 MB JavaScript bundle loaded on every page, a manifest that minifies CSS but concatenates JS, and two copies of jQuery - the first of four frontend chapters the attendance series skipped.
The platform bundled a client-side validation framework - one file of it twice - and wired none of it, hand-stitching every real check inside modal callbacks with manual error classes and keyup character counters.
The honest ledger for the whole estate - what coordinating through tables actually cost, what genuinely earned its keep, and what I would reach for now instead of using the database as a message bus.
Four pipelines run dotnet test against projects with no tests and pass every time, the shared model ships as committed .nupkg files in a folder, one executable has no pipeline at all, and a DLL with no source hides an algorithm pasted inline elsewhere.
The same dashboard KPI is computed by three different formulas across role-variant procedures, rendered on a gauge with a sibling metric quietly deleted - and the fact table beneath it is an accidental Kimball star nobody designed.
The business month runs the 16th to the 15th, so a year-overview proc carries twelve correlated subqueries, five-day-capped weeks, time averaged through float casts and contractor filters that drift by a digit between copies.
One hierarchy, three implementations - a recursive CTE, a view that calls a scalar function on every row, and a whole family of tree procs - plus a relationship flag that is a decimal number pretending to be binary.
Thirty-eight tables, fourteen foreign keys, none on the tables that need them - plus a shipped SSMS template index, auto-tuning suggestions accepted blind, three-state booleans and a demo mode that mutates production master data.
The same database solves the same sortable-paging problem twice - once by concatenating user input straight into EXEC, once with a CASE whitelist, QUOTENAME and sp_executesql. A side-by-side of the wrong and right answer.
UTC timers, IST badge gates, DateTime.Now in one executable and UtcNow in the next, columns that record neither - how a system whose entire product is timestamps got away with never deciding what time it is.
Every five minutes a function hunts for people still virtually swiped in past ten hours and emails them - magic node numbers, an at-most-once flag with a gap in it, and a local config file pointing straight at production.
A 22-character job name, a 20-character column, and an error handler that has thrown on every attempt since the day it shipped - plus the four parallel logging systems that still could not catch it.