Central læringsplatform til Grundforløb 2 programmering på Mercantec — C#, Git, opgaver og projekter.
Produktion:learn.mags.dk
- Blazor Web App (.NET 10) med Static SSR
- Markdown + YAML frontmatter i
content/(versioneret i Git) - Markdig til rendering med custom directives (
:::git-step,:::exercise, osv.) - Mercantec Auth (auth.mercantec.tech) — OAuth 2.0 + PKCE
- Docker + Cloudflare Tunnel til deploy
| Sektion | Indhold |
|---|---|
| Pensum | C#-teori og Git |
| Opgaver | Øvelser (begynder / mellem / avanceret) |
| Projekter | 7 forløb i tre niveauer (begynder → mellem → avanceret/AD) |
Pensum, opgaver og projekter er offentligt tilgængelige. /profile kræver login.
cd src/GF2Learn.Web
dotnet user-secrets set"MercantecAuth:ClientSecret""dit-client-secret"
dotnet run --urls http://localhost:5288Åbn http://localhost:5083 (eller den port du bruger med dotnet run --urls)
Registrér klient gf2-learn med præcis disse redirect URIs:
| Miljø | Redirect URI |
|---|---|
Lokal (dotnet run) | http://localhost:5083/signin-mercantec |
| Docker lokal | http://localhost:2020/signin-mercantec |
| Dev (Cloudflare Tunnel) | https://learn-dev.gf2.dk/signin-mercantec |
| Produktion | https://learn.mags.dk/signin-mercantec |
Miljøvariabler (se .env.example):
| Variabel | Beskrivelse |
|---|---|
MercantecAuth__ClientId | OAuth client_id (default: gf2-learn) |
MercantecAuth__ClientSecret | Client secret (confidential client) |
JWT valideres mod iss=https://auth.mercantec.tech og aud=mercantec-apps.
| Sti | Formål |
|---|---|
/auth/login | Start OAuth-flow (PKCE S256) |
/signin-mercantec | OAuth callback (automatisk) |
/auth/logout | Log ud + redirect til auth signout |
/profile | Brugerprofil (kræver login) |
Logout rydder app-cookie og sender brugeren til https://auth.mercantec.tech/signout?returnUrl=... for at nulstille auth-session (SSO).
Adminområdet giver undervisere et samlet elevgitter, individuel progression og aktiv tid på pensum- og opgavesider. Superadmins kan desuden oprette hold, tildele registrerede elever manuelt og generere invitationslinks eller holdkoder.
| Sti | Adgang | Formål |
|---|---|---|
/admin | Underviser/admin | Elevoversigt, filtre og aktivitetsnøgletal |
/admin/students/{id} | Underviser/admin | Progression og indholdsbrug for én elev |
/admin/groups | Superadmin | Hold, medlemmer, invitationer og koder |
/join | Logget ind elev | Tilmelding med holdkode |
/join/{token} | Logget ind elev | Bekræft invitation |
Adgangen styres af role-claims og kan suppleres med en eksplicit subject-
allowlist. Rolle- og subjectværdier matches case-insensitivt:
AdminAccess__EducatorRoles=teacher,underviser,admin,superadmin,super_adminAdminAccess__SuperAdminRoles=superadmin,super_adminAdminAccess__EducatorSubjects=AdminAccess__SuperAdminSubjects=Hvis Mercantec Auth endnu ikke sender rollerne, kan bruger-id'et fra /profile
indsættes i de relevante Subjects-felter. Elevens navn fra det validerede
login-token gemmes ved autentificerede besøg, så undervisere kan identificere
eleven i adminområdet; e-mail gemmes ikke. Underviserens interne kaldenavn gemmes
separat og sendes aldrig til elevsider eller elev-API'er. Hvis Auth ikke leverer
et navn, vises et pseudonymt elev-ID som fallback.
Aktiv tid registreres kun for loggede elever og kun mens en pensum- eller opgaveside er synlig, browseren har fokus, og eleven ikke har været inaktiv i mere end ét minut. Browseren sender kumulativ tid, som aggregeres pr. dag; tallet skal derfor læses som estimeret aktiv tid og ikke som præcis arbejdstid. Tekniske sessions-id'er bruges kortvarigt til deduplikering og slettes senest efter ét døgn.
content/
pensum/ # *.md — teori
opgaver/ # begynder/, mellem/, avanceret/
projekter/ # projekt-*/overview.md (ét dokument per projekt)
Reference-løsninger og afleveringskrav: /projects/{slug}/solution (Blazor). Konsol-demo-kode i src/GF2Learn.Web/ProjectReferences/.
title: "Emnetitel"order: 1topics: [csharp, git]kompetencemaal: ["Kan ..."]timer: 2difficulty: begynder # kun opgaverrelated_pensum: [02-variabler-og-datatyper]youtube_id: "..."# valgfri| Directive | Formål |
|---|---|
:::callout type="info" | Info/advarsel-boks |
:::git-step | Git commit/branch instruktion |
:::exercise | Opgavebeskrivelse |
:::solution | Fold-ud løsning |
:::code-playground | Interaktiv C#-playground (kører i browser via WASM). Valgfri refs: og expected: |
:::related-pensum | Links til pensum |
På /exercises/* loades en WebAssembly-runtime (~15–25 MB, caches i browseren). Elever kan redigere og køre top-level C# med Console.WriteLine direkte på egen maskine.
Editoren er Monaco (samme motor som VS Code) med C#-syntax og snippets — fx skriv cw + Tab for Console.WriteLine(). Ctrl+Space viser flere snippets (for, foreach, if, …).
Ekstra NuGet-assemblies whitelistes i wwwroot/playground/playground.json — se wwwroot/playground/refs/README.md.
:::code-playground
refs: System.Text.Json
```csharpvarjson=JsonSerializer.Serialize(new { navn="Ada" });
Console.WriteLine(json);expected: {"navn":"Ada"} :::
Manuel test (efter `dotnet run`):
1. Åbn `/exercises/01-variabler` — gennemgå kapitel 1 og løs opgaverne i jeres konsolprojekt.
2. Indsæt syntaksfejl — compile-fejl vises i output-panelet.
3. Uendelig løkke — timeout efter 3 sekunder.
4. JSON med `System.Text.Json` uden ekstra refs — skal virke via BCL.
## Docker
**Lokal (Windows/macOS/Linux):**
```bash
cp .env.example .env # udfyld secrets ved behov
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build
| Tjeneste | URL |
|---|---|
| Web | http://localhost:8031 |
| Health | http://localhost:8031/health |
Videnscenter-branding: sæt VidenscenterBranding__Enabled=true i .env på VC-afleveringsversionen; false (standard) internt hos Mercantec uden VAT-logoer.
Dokploy (prod/dev på server): kun docker-compose.yml — kræver dokploy-network og Traefik.
OAuth redirect for lokal Docker (tilføj i Auth Admin hvis du tester login):
http://localhost:8031/signin-mercantec
Videnstjek-progression gemmes i Postgres ved login (migrationer kører automatisk ved opstart).
Port exposure som andre apps — se deploy/dokploy-cloudflare.md.
| Cloudflare (host cloudflared) | http://localhost:8031 |
| Cloudflare (cloudflared i Docker) | http://host.docker.internal:8031 |
WEB_HOST_PORT=8031 i Dokploy skal matche.
OAuth: https://learn-dev.gf2.dk/signin-mercantec