Un lab interactif pour (re)apprendre ASP.NET Core et Angular. Chaque leçon est une page avec :
- une explication courte (le modèle mental, les pièges) ;
- une démo live branchée sur la vraie API ;
- les fichiers sources exacts qui font tourner la démo, lus depuis le disque par
GET /api/source.
Un panneau Réseau (en bas à droite) montre chaque requête HttpClient : méthode, URL, headers (dont Authorization), statut, durée.
- ⌘K / Ctrl+K partout — palette de commandes qui cherche dans les 36 leçons, les 80 questions d'entretien et les 51 compétences de la roadmap, avec navigation clavier. L'index des questions (85 kB) n'est chargé qu'à la première ouverture, pour ne pas alourdir le premier rendu.
- Roadmap en timeline — les 4 niveaux sur un fil conducteur, anneau de progression SVG par niveau.
- Thème sombre avec deux sources de lumière, typographie fluide (
clamp()), révélations décalées qui se désactivent sousprefers-reduced-motion— et qui ne conditionnent jamais la visibilité du contenu.
/parcours : The .NET Maturity Stack en 7 niveaux — fondamentaux C#, types & POO, collections & LINQ,
async & concurrence, ASP.NET Core & données, performance & runtime, systèmes en production. Chaque notion du
référentiel (61 au total) renvoie à la leçon qui l'enseigne ; les leçons .NET de la barre latérale suivent cet ordre.
La page propose la prochaine leçon à faire et suit ta progression par niveau.
/roadmap : le référentiel .NET Junior → Middle → Senior → Architect (51 compétences) confronté à ce que le lab
enseigne réellement. Trois états honnêtes — leçon dédiée (20), partiel (17), à construire (14) — chaque trou
décrivant la leçon manquante. Couverture actuelle : 56 % : Junior et Middle presque complets, Senior bien couvert, Architect encore mince.
Les items marqués ☁️ deviennent des démos réelles avec un abonnement Azure.
/interview : 80 questions (Angular, .NET, Auth & sécurité, Architecture · Junior → Senior), QCM auto-corrigés et questions ouvertes
avec détection des points-clés dans ta réponse + auto-évaluation, explication, schéma SVG et lien vers la leçon. Score et « à revoir » persistés en localStorage.
| Angular | .NET | Auth full-stack |
|---|---|---|
| Signals & state (+ NgRx SignalStore) | Minimal API vs Controllers | JWT + refresh token (rotation, expiration 60 s) |
| RxJS, HttpClient, httpResource | DI : Transient / Scoped / Singleton | Cookie auth + CSRF (antiforgery) |
| Routing, guards, resolvers, lazy | Pipeline de middlewares + ProblemDetails | API key (AuthenticationHandler custom) |
| Reactive Forms typés (+ validateur async) | EF Core + SQLite (CRUD paginé) | OAuth2/OIDC Authorization Code + PKCE (IdP embarqué) |
| Injection de dépendances | Validation (FluentValidation + endpoint filter) | Authorization : rôles, claims, policies, resource-based |
| Interceptors HTTP (refresh auto sur 401) | SignalR + BackgroundService | |
| Control flow, @defer, zoneless | Cache (IMemoryCache, OutputCache, HybridCache anti-stampede) + rate limiting | |
| Composants (input/output/model, viewChild, hostDirectives) | Options pattern & configuration | |
| Directives & pipes custom | Tests d'intégration xUnit (+ tests d'architecture NetArchTest) | |
| Setup pro : Directory.Build.props, CPM, analyzers, triage | ||
| Vertical Slice Architecture + Result (module Orders) | ||
| Modular Monolith : PublicApi, événements, frontières testées | ||
| C# pour qui vient de TypeScript (patterns, struct vs class, records, génériques) | ||
| Collections & LINQ (HashSet benchmark, deferred execution tracée) | ||
| Async & concurrence : WhenAll, SemaphoreSlim, Channels, IAsyncEnumerable, annulation, ThreadPool | ||
| Résilience : retries, timeouts, circuit breaker (Polly v8) | ||
| Versioning d'API : URL / header / query, Deprecation + Sunset, OpenAPI par version | ||
| Migrations EF Core : snapshot, historique, bundle, expand/contract | ||
| Idempotence (Idempotency-Key) & outbox transactionnelle + inbox | ||
| Observabilité : OpenTelemetry, viewer de traces, Aspire, runbooks | ||
| Health checks : liveness, readiness, startup | ||
| Performance & runtime : BenchmarkDotNet, Span/ArrayPool, GC, tiered JIT, dotnet-counters, Native AOT |
| Partie | Où | Pourquoi |
|---|---|---|
| Frontend Angular | Vercel → https://devlab-dotnet-angular.vercel.app | statique, CDN, preview par déploiement |
| API .NET | Azure Container Apps — https://devlab-api.thankfulsky-d03b7a33.italynorth.azurecontainerapps.io | Vercel n'exécute pas ASP.NET Core : il faut un conteneur long-running (SignalR, EF, SQLite). Scale-to-zero = coût nul sans trafic. |
# 1. front (à refaire après chaque modif)
cd frontend && vercel --prod
# 2. back sur Azure Container Apps (recommandé — scale-to-zero, WebSockets natifs)
az login # seule étape interactive
./deploy-azure.sh # build l'image, déploie, configure, recâble le front
# variante Railway
railway login && ./deploy-backend.shL'API tourne en Production : clé JWT générée à chaque déploiement (jamais celle du repo),
origines CORS et URIs de redirection OIDC injectées en variables d'environnement.
Contraintes rencontrées sur « Azure for Students », encodées dans le script :
- une policy limite les régions à
italynorth, norwayeast, austriaeast, belgiumcentral, polandcentral; - ACR Tasks est interdit → l'image est construite localement (
--platform linux/amd64, obligatoire depuis un Mac Apple Silicon) puis poussée, au lieu du build distant deaz containerapp up.
Base SQLite éphémère : avec le scale-to-zero, chaque réveil repart d'une base reseedée.
Comment le front trouve le back : frontend/vercel.json proxifie /api/* et /idp/* vers Railway
(le navigateur reste en same-origin → les leçons Cookie et CSRF fonctionnent réellement).
Les WebSockets ne peuvent pas être proxifiés par un edge CDN : SignalR appelle l'API en direct via
frontend/public/config.json (hubOrigin), autorisé par le CORS configuré côté API.
Sans backend déployé, le site reste utile : explications, schémas, mode entretien complet, et tout le
code source — un snapshot (npm run snapshot) est embarqué au build et sert de repli quand /api/source
ne répond pas (badge « snapshot » dans le lecteur de code). Une bannière l'annonce sur chaque démo.
Secrets : la clé JWT de dev est publique (le lab l'affiche lui-même). deploy-backend.sh génère une
nouvelle clé aléatoire par déploiement (Jwt__SigningKey) : jamais celle du repo.
- .NET SDK 10 (installé dans
~/.dotnetpardotnet-install.sh) →export PATH="$HOME/.dotnet:$PATH" - Node 24 (
nvm uselit le.nvmrc)
# Terminal 1 — API sur http://localhost:5080 (Scalar/OpenAPI : /scalar/v1)
cd backend/DevLab.Api && dotnet run
# Terminal 2 — Angular sur http://localhost:4200 (proxy /api, /hubs, /idp → :5080)
cd frontend && npm startou en une commande : ./dev.sh.
Le schéma SQLite vient de migrations EF Core, appliquées au démarrage (MigrateAsync, acceptable pour un lab
mono-instance — voir la leçon Migrations EF Core). Pour en créer une :
dotnet tool restore # dotnet-ef épinglé dans dotnet-tools.json (même version qu'EF Core)
dotnet ef migrations add <Nom> --project backend/DevLab.Api --context OrdersDbContext --output-dir Features/Orders/Migrationscd backend && dotnet test # 112 tests : intégration (WebApplicationFactory) + architecture (NetArchTest)
cd frontend && npm test # Vitest| Utilisateur | Mot de passe | Rôles | Permissions | Âge |
|---|---|---|---|---|
| alice | alice123 | Admin, User | products:write, products:delete | 34 |
| bob | bob123 | User | products:write | 17 |
| carol | carol123 | User | — | 25 |
API keys (dev) : lab-key-123 (Service), lab-admin-key (Service + Admin).
backend/
Directory.Build.props règles communes : warnings = erreurs, analyzers Meziantou + Sonar
Directory.Packages.props versions de packages centralisées
.editorconfig style + triage des analyzers (chaque exception est justifiée)
backend/DevLab.Api/
Program.cs composition root : chaque feature s'enregistre via une extension method
Infrastructure/ Result<T>, IModule/IApiEndpoint (scan), InProcessEventBus,
Messaging/ (outbox, dispatcher, inbox), Idempotency/ (IdempotencyFilter)
Features/*/Migrations/ migrations EF Core de chaque module (générées, exclues des analyzers)
Features/Orders/ module en vertical slices (PlaceOrder/, GetOrders/, CancelOrder/, PublicApi/, Shared/)
Features/Products/PublicApi/ le seul contrat que les autres modules voient (IProductCatalog)
Features/<Feature>/ un dossier = une leçon (endpoints + services + options)
Features/Auth/ Jwt/ Cookie/ ApiKey/ Oidc/ Authorization/ + LabAuthentication.cs (5 schemes)
backend/DevLab.Api.Tests/ xUnit + WebApplicationFactory
backend/DevLab.ServiceDefaults/ Aspire : OpenTelemetry, health checks par défaut, service discovery
backend/DevLab.AppHost/ Aspire : orchestre API + Angular, dashboard (./aspire.sh)
docs/runbooks/ runbooks d'incident (5xx/readiness, latence, SignalR/CORS)
frontend/src/app/
lessons/catalog.ts la liste des leçons et les fichiers qu'elles affichent
lessons/{angular,dotnet,auth}/ un fichier = une leçon
core/auth/ AuthStore (signals), authInterceptor (refresh sur 401), guards
core/http/ requestLogInterceptor → panneau Réseau
shared/ lesson-shell, code-viewer (highlight.js), network-panel, progress, diagrams (SVG)
interview/ mode entretien : questions.ts (banque), interview.ts, interview.store.ts
La clé JWT de dev est dans appsettings.Development.json (préfixée DEV-ONLY) pour que le lab démarre sans config.
En vrai projet : dotnet user-secrets set "Jwt:SigningKey" "…" ou variable d'env Jwt__SigningKey.