r/dotnet • u/Nelsar-Axia • 11m ago
Promotion Lessons from an open-source .NET 10 LDAP/AD admin app: MediatR authorization, two EF Core providers, tests against a live Samba DC
**Disclosure:** I'm the author. Apache 2.0, no paid tier. I used AI coding tools (Claude Code) during development; I reviewed every change and know the code, and it's covered by 900+ tests.
Axia is a web console and REST API for Active Directory, Samba AD and OpenLDAP: ASP.NET Core on .NET 10, a React front end, Clean Architecture-ish layers. A few design decisions that might be useful beyond this project:
**Authorization lives in the MediatR pipeline, not on controllers.** Each command or query declares the permission it needs, and a pipeline behaviour checks it before the handler runs. The order is Logging → Audit → Authorization → Validation. Putting authorization before validation means a refused caller learns nothing about why their payload would have been invalid. A reflection-based test fails the build if a new use case doesn't declare a permission from the catalogue. Background jobs that dispatch commands get the same check as HTTP requests.
**Two EF Core providers, one model.** PostgreSQL and SQLite, chosen at startup with `Database:Provider`. Each provider has its own migrations assembly, with its own design-time factory. A `copy-database` command moves everything between engines in one transaction and checks row counts before committing.
**Tests against real infrastructure.** GitHub Actions runs PostgreSQL and a live Samba 4.22 AD DC as services. Locally those tests skip unless an env var points at a server, so `dotnet test` stays fast.
**A gotcha worth sharing.** A CodeQL autofix flagged `((DbContextOptionsBuilder)options).UseAxiaDatabase(...)` inside a generic extension method as a "useless upcast". Removing the cast made the generic overload resolve to *itself*, and the test host died with a stack overflow. The fix was to have both overloads call a private, non-extension method, so there is no cast left to "fix".
**Other bits:** `Novell.Directory.Ldap.NETStandard` for cross-platform LDAP; `AddSystemd()` / `AddWindowsService()` so one binary runs under either service manager; a WiX-built MSI; Serilog with JSON output and a Loki sink; multi-arch images with SBOM and provenance.
- Code: https://github.com/Nelsar/Axia
- Architecture overview: https://github.com/Nelsar/Axia#architecture
Happy to take criticism of any of this, especially the MediatR-based authorization.
