Architektur- und Technologie-Übersicht
Dieses Projekt ist ein ASP.NET Core Backend, das in C# entwickelt wurde.
Es handelt sich um eine REST API, die sowohl klassische Endpunkte für Clients bereitstellt als auch einzelne *
dauerhaft laufende Komponenten* (sogenannte Hosted Services).
Getestet wird mit xUnit als eigenständigem Testprojekt.
Datenbank-Kommunikation
Für die Datenbank-Anbindung wird Entity Framework Core (EF Core) genutzt.
EF Core ist ein ORM (Object-Relational Mapper), das es erlaubt, Datenbanktabellen als Klassen (Entities) im Code
darzustellen.
Statt rohe SQL-Abfragen zu schreiben, wird mit LINQ-Abfragen gearbeitet, die EF Core in SQL übersetzt.
👉 Im Code suchen nach:
ApplicationDbContext(zentraler Einstiegspunkt für EF Core)DbSet<T>(eine Tabelle in Form einer Liste von Objekten)UseSqlServer(...)oderUseInMemoryDatabase(...)
Architektur: Controller – Service – Repository
Das Projekt folgt einem klassischen 3-Schichten-Modell:
Controller
- Repräsentieren die API-Endpunkte.
- Entgegennehmen von HTTP-Requests, Rückgabe von HTTP-Responses.
- Enthalten wenig bis keine Geschäfts-Logik, sondern leiten Aufrufe an Services weiter und reagiert auf Ergebnisse.
- Controller orchestrieren services und verarbeiten Web-Logik.
👉 Im Code suchen nach:
- Klassen mit
Controller-Suffix [HttpGet],[HttpPost],[HttpPut]usw.
Services
- Enthalten die Business-Logik (das „Wie“ der Prozesse).
- Überprüfen, ob Aktionen erlaubt sind, verarbeiten Daten, orchestrieren Abläufe.
- Rufen Repositories auf, um Daten zu speichern oder zu laden.
👉 Im Code suchen nach:
- Klassen mit
Service-Suffix - Interfaces wie
IExhibitorService
Repositories
- Direkter Zugriff auf die Datenbank.
- Alle SQL-/EF-Core-Operationen sind hier gekapselt.
- Controller und Services sprechen nie direkt mit EF Core, sondern immer über Repositories.
👉 Im Code suchen nach:
- Klassen mit
Repository-Suffix - Interfaces wie
IExhibitorRepository
Dependency Injection (DI)
Das Projekt nutzt Dependency Injection (DI), ein grundlegendes Muster in ASP.NET Core.
Statt Klassen direkt zu instanziieren, werden sie vom Framework automatisch mit ihren Abhängigkeiten versorgt.
Beispiel:
builder.Services.AddScoped<IExhibitorService, ExhibitorService>();
Das bedeutet: Immer wenn ein IExhibitorService gebraucht wird, bekommt man eine Instanz von ExhibitorService.
So sind Klassen entkoppelt und leichter testbar.
👉 Im Code suchen nach:
- builder.Services.AddScoped
- AddSingleton, AddTransient
Basisklassen & Vererbung
Viele Services und Repositories erben von Basisklassen, die gemeinsame Funktionalität bereitstellen.
Diese Basisklassen enthalten generische Methoden mit sogenannten Dynamics.
Dynamics sind dynamisch typisierte Objekte in C#, die zur Laufzeit geprüft werden.
Damit können universelle Low-Level-Funktionen implementiert werden, die von allen erbenden Klassen genutzt werden.
👉 Im Code suchen nach:
- BaseService, BaseRepository
- Schlüsselwort dynamic
Extension Methods
Ein weiteres genutztes Feature sind Extension Methods.
Das sind Erweiterungsmethoden für bestehende Klassen oder Typen, ohne dass man diese ändern muss.
So können auch „geschlossene“ Klassen um neue Funktionen ergänzt werden.
Beispiel:
public static class StringExtensions
{
public static bool IsValidEmail(this string input)
{
return input.Contains("@");
}
}
Hier wird dem Typ string die neue Funktion IsValidEmail() hinzugefügt. Im Code kann man dann schreiben:
var mail = "test@example.com";
if (mail.IsValidEmail()) { ... }
👉 Im Code suchen nach:
- Klassen mit Extensions im Namen
- Schlüsselwort this im Methodenparameter
Hosted Services
Zusätzlich zu den API-Endpunkten laufen im Hintergrund Hosted Services. Das sind Prozesse, die dauerhaft aktiv sind, zum Beispiel:
- Warteschlangen abarbeiten
- Cronjobs ausführen
- Synchronisationen durchführen
Sie werden beim Start der Anwendung registriert und vom Framework parallel betrieben.
👉 Im Code suchen nach:
- Klassen, die IHostedService oder BackgroundService implementieren
Tests
Das Projekt nutzt xUnit als Framework für Unit- und Integrationstests.
Dazu gibt es ein eigenes Testprojekt (messeapp.Backend.Test).
Tests überprüfen, ob Controller, Services und Repositories wie gewünscht zusammenarbeiten.
👉 Im Code suchen nach:
- [Fact] (einzelner Test)
- [Theory] (Test mit Datenvarianten)
- TestClientFactory (wird genutzt, um Test-APIs mit InMemory-Datenbanken aufzusetzen)
Konfigurationsvalidierung (IValidateOptions<T>)
Um Laufzeitfehler durch fehlerhafte Konfigurationen zu vermeiden, werden alle kritischen Konfigurationsabschnitte
bereits beim Start der Anwendung über das IValidateOptions<T>-Muster überprüft.
Beispielsweise implementiert die Klasse ChefsInspirationEventImportOptions dieses Interface, um sicherzustellen, dass
alle erforderlichen Felder (wie ApiKey, ChefsInspirationHost und CutoffInYears) korrekt gesetzt und logisch
konsistent sind.
Schlägt die Validierung fehl, startet die Anwendung nicht – falsche oder unvollständige Umgebungsvariablen werden
somit bereits beim Deployment oder lokalen Start erkannt.
Dieses Vorgehen sorgt für saubere Konfigurationen und ein vorhersehbares Laufzeitverhalten über alle Umgebungen hinweg.
Authentifizierungsschema
Die API verwendet ein hybrides Authentifizierungssystem, das JWT-Bearer-Tokens und API-Keys kombiniert.
- JWT-Tokens werden über die Endpunkte
/api/v1.0/Auth/Useroder/api/v1.0/Auth/ApiKeyausgestellt und repräsentieren entweder Benutzer oder Systemidentitäten. - API-Keys werden nicht direkt zur Authentifizierung verwendet, sondern müssen zunächst über den entsprechenden Auth-Endpunkt gegen ein JWT eingetauscht werden.
- Das Standard-Authentifizierungsschema ist
Bearerund wird durch den integrierten ASP.NET Core JWT-Handler verwaltet.
Zusätzliche prüfungs- oder scope-bezogene Logik (z. B. API-Key-Scopes) wird durch eine eigene Middleware realisiert.
Dieses einheitliche Authentifizierungskonzept sorgt dafür, dass alle Requests – unabhängig davon, ob sie von Benutzern
oder Systemen stammen – denselben standardisierten, tokenbasierten Authentifizierungsfluss durchlaufen.
Dadurch können Audit-Logs, Claims und Zugriffsprüfungen zentral und konsistent gehandhabt werden.
Hot Reloading der API-Key-Konfiguration
Die Definitionen der API-Keys werden in einer separaten Konfigurationsdatei (appsettings.ApiKey.json) gespeichert und
über IOptionsMonitor<ApiKeySchemeOptions> geladen.
Durch den Einsatz von IOptionsMonitor ist Hot Reloading möglich – sobald sich die Datei ändert, wird die neue
Konfiguration automatisch übernommen, ohne dass die Anwendung neu gestartet werden muss.
Administratoren können so API-Keys zur Laufzeit hinzufügen, anpassen oder sperren, und die
Authentifizierungsmiddleware arbeitet sofort mit dem aktualisierten Stand (optionsMonitor.CurrentValue).
Dieses Design ermöglicht es, laufende Systeme flexibel an neue betriebliche oder sicherheitsrelevante Anforderungen
anzupassen, ohne Downtime oder Redeployment.