.NET
Starte im Assistenten mit einer .NET-Vorlage oder wähl .NET-Anwendung für ein leeres Projekt oder
ein Repository, oder nimm .NET im Bereich Laufzeit zu einem bestehenden Projekt dazu. Der Container
envoryx-<projekt>-dotnet läuft mit ghcr.io/envoryx/envoryx-dotnet:<version>. Das ist das SDK-Image
von Microsoft (mcr.microsoft.com/dotnet/sdk:<version>.0-noble) plus dotnet-ef, der Debugger
netcoredbg, git und socat. Er läuft als PUID:PGID, mit deinem Projekt unter /var/www/html und einem
Home-Verzeichnis unter /home/envoryx.
Der Paketordner und der HTTP-Cache von NuGet liegen im geteilten Paket-Cache. Ein Paket wird also für alle Projekte nur einmal geladen.
Versionen
Abschnitt betitelt „Versionen“Nur die LTS-Releases: Standard ist 10, außerdem gibt es 8. Der Support für .NET 8 endet am 10. November 2026, neue Projekte startest du deshalb besser auf 10.
Braucht das Target Framework in der Projektdatei oder das SDK in der global.json ein neueres .NET,
als das Projekt hat, warnen Projektliste und Projektseite und nennen beide Versionen. Anders als bei
Go oder Java gibt es auch umgekehrt eine Warnung: Jedes .NET-Image bringt nur seine eigene Runtime
und sein eigenes SDK mit, ein net8.0-Projekt startet also nicht unter .NET 10. Wähl dann entweder die
Version, auf die es zielt, oder heb <TargetFramework> (oder das SDK in der global.json) an und
starte das Projekt neu; dotnet watch bemerkt eine geänderte Projektdatei nicht, solange die App nie
gestartet ist. Ein Projekt mit <RollForward> auf Major oder LatestMajor, oder eine global.json
mit rollForward major oder latestMajor, läuft auf der neueren Version und bekommt keine
Warnung. Werte, die Envoryx nicht sicher lesen kann, etwa mehrere Target Frameworks oder eine
Directory.Build.props, ergeben keine Warnung.
Welches Projekt läuft
Abschnitt betitelt „Welches Projekt läuft“In den Server-Einstellungen kannst du die Projektdatei angeben, als Pfad relativ zum Projekt, etwa
src/Shop/Shop.csproj. Lässt du das Feld leer, nimmt Envoryx die eine .csproj, .fsproj oder
.vbproj oben im Projekt, sonst das eine ASP.NET-Core- oder Worker-Projekt, das es bis zu vier Ebenen
tiefer findet. Gibt es mehrere, hält es an und schreibt das ins Log, statt zu raten.
Üblich ist ein Aufbau mit src/ und tests/ und einer Solution oben. Leg Testprojekte nicht in den
Ordner der Anwendung: .NET kompiliert alles unterhalb eines Projekts mit hinein, Tests eingeschlossen.
Müssen sie dort liegen, schließ sie mit <Compile Remove="tests/**" /> aus.
Der Server
Abschnitt betitelt „Der Server“Schalte .NET-Server ausführen ein und wähl eine Vorlage. Der Server wird zum Hauptprozess des Containers, startet automatisch neu und bekommt einen eigenen Host-Port. Bevor er startet, wartet er auf eine Projektdatei. Schaltest du den Server aus und wieder ein, behält er diesen Port, ein Lesezeichen darauf bleibt also gültig.


| Vorlage | Entwicklung | Produktion | Port |
|---|---|---|---|
| ASP.NET Core (Web API, MVC, Razor Pages, Blazor) | dotnet watch mit Hot Reload |
dotnet publish -c Release, dann die DLL starten |
8080 |
| Andere (veröffentlichen, dann dotnet <dll>) | - | veröffentlichen, dann die DLL starten | 8080 |
In der Entwicklung übernimmt dotnet watch Codeänderungen per Hot Reload, und wo eine Änderung so nicht
geht, startet es die Anwendung neu. Envoryx übergibt --urls http://0.0.0.0:<port>, das gewinnt gegen
die applicationUrl deines Launch-Profils - die lauscht nur auf localhost, wo der Proxy nicht hinkommt.
Den Browser-Refresh von dotnet watch schaltet Envoryx ab (DOTNET_WATCH_SUPPRESS_BROWSER_REFRESH=1):
Das Skript, das er in jede Seite einbaut, verbindet sich mit einem WebSocket auf dem localhost des
Containers, und den erreicht kein Browser. Hot Reload übernimmt deine Änderungen trotzdem, nur lädst
du die Seite selbst neu (eine Blazor-Komponente aktualisiert sich über ihre eigene Verbindung).
Der Produktionsmodus veröffentlicht nach bin/envoryx-publish und startet die DLL von dort. wwwroot
und die appsettings-Dateien kommen also aus der veröffentlichten Ausgabe, wie bei einem echten
Deployment. Die zweite Vorlage für Worker Services und Konsolen-Hosts hat keinen Entwicklungsmodus: Sie
veröffentlicht und startet die DLL, die du angibst, oder die Anwendung, die das Publish erzeugt hat.
ASPNETCORE_ENVIRONMENT und DOTNET_ENVIRONMENT stehen passend zum Modus auf Development oder
Production. ASPNETCORE_FORWARDEDHEADERS_ENABLED=true sorgt dafür, dass die Anwendung den
X-Forwarded-*-Headern des Proxys traut. Weiterleitungen und erzeugte Links behalten so Schema und Host
deiner Projekt-URL.
Datenbank
Abschnitt betitelt „Datenbank“Für die primäre PostgreSQL-, MySQL- oder MariaDB-Datenbank setzt Envoryx
ConnectionStrings__DefaultConnection, das GetConnectionString("DefaultConnection") liest. Die
Verbindung steht in der ADO.NET-Form, die Npgsql und die MySQL-Provider verstehen, zum Beispiel
Host=database;Port=5432;Database=…;Username=…;Password=…. Eine zusätzliche Datenbank namens
analytics kommt als ConnectionStrings__analytics an, MongoDB als ConnectionStrings__MongoDB und
Redis als ConnectionStrings__Redis.
Wo die Anwendung antwortet
Abschnitt betitelt „Wo die Anwendung antwortet“Ohne PHP und ohne Python-, Go-, Ruby- oder Java-Server ist der .NET-Server die Anwendung:
https://<projekt>.<basis> und jede weitere Domain gehen direkt zu ihm, und Direktzugriff ist der
.NET-Host-Port. Die interaktiven Komponenten von Blazor sprechen über einen WebSocket mit dem Server,
den der Proxy durchreicht.
Templates
Abschnitt betitelt „Templates“ASP.NET Core Web API, ASP.NET Core MVC, Blazor Web App und ASP.NET Core Razor Pages
kommen aus dotnet new (webapi, mvc, blazor, webapp), tragen den Namen deines Projekts und
werden einmal gebaut. Sie entstehen mit --no-https, weil sich der Envoryx-Proxy schon um TLS kümmert.
Die Web API ist eine Minimal API mit OpenAPI, und seit .NET 9 heißt das: ein Dokument, keine Oberfläche.
Im Entwicklungsmodus findest du es unter /openapi/v1.json. Eine Swagger-Seite gibt es erst, wenn du
selbst eine einbaust.
Mit einer PostgreSQL-, MySQL- oder MariaDB-Datenbank bringt die Web-API-Vorlage EF Core mit: den
Provider (Npgsql, oder MySql.EntityFrameworkCore von Oracle für MySQL und MariaDB),
Microsoft.EntityFrameworkCore.Design und eine AppDatabase.cs mit einer Beispiel-Entität Todo
hinter GET und POST /todos. Die Tabellen legt sie beim ersten Start mit EnsureCreated an. Steht
dein Modell, steig auf Migrationen um. Eine Migration kann keine Tabellen anlegen, die es schon gibt,
und EnsureCreated hat sie ohne Migrationshistorie angelegt, deshalb sind es vier Schritte: Ersetz in
AppDatabase.cs EnsureCreated durch Migrate, führ dotnet ef migrations add Initial im Terminal
aus, lösch die Tabellen, die EnsureCreated angelegt hat (oder fang mit einer leeren Datenbank an),
und starte dann die Aktion dotnet ef database update.
Sobald es ein Testprojekt gibt (xUnit, NUnit, MSTest, TUnit oder das Test-SDK), führt der Bereich
Tests dotnet test aus - auf der Solution oben oder auf dem einen Testprojekt. Mehrere Testprojekte
ohne Solution brauchen eine: dotnet new sln, dann dotnet sln add. Filtern kannst du mit
--filter, und die Ergebnisse je Test, mit Datei und Zeile, kommen aus dem TRX-Report.
Der Lauf richtet ConnectionStrings__DefaultConnection auf <datenbank>_test, die Envoryx vorher
anlegt. Integrationstests mit WebApplicationFactory lassen deine Entwicklungsdaten deshalb in Ruhe.
Ein Projekt auf Microsoft.Testing.Platform (in global.json gewählt) läuft ohne Report und Filter; dort
zählt nur der Exit-Code.
Debuggen
Abschnitt betitelt „Debuggen“Einen Debug-Port gibt es bei .NET nicht. Deine IDE startet über SSH einen Debugger im Container und hängt
sich an die laufende Anwendung - unter dotnet watch ist das der Prozess mit dem Namen deines Projekts,
nicht dotnet watch selbst. Der SSH-Benutzer <projekt> landet im Anwendungscontainer;
<projekt>.dotnet wählt immer den .NET-Container, und den brauchst du, wenn das Projekt mehr als eine
Runtime hat. Der Bereich IDE zeigt den richtigen.
VS Code mit der C#-Erweiterung debuggt mit Microsofts vsdbg über pipeTransport. vsdbg darf man nur
mit den Werkzeugen von Microsoft nutzen, deshalb liegt er nicht im Image. vsdbg installieren im Bereich
IDE des Projekts holt ihn für dich: Es führt Microsofts Installationsskript im .NET-Container aus, das ihn
bei Microsoft ins Projekt-Home lädt (rund 200 MB), wo er bleibt. Danach zeigt der Bereich die Version,
und derselbe Knopf aktualisiert ihn später. Als Aktion heißt das dotnet:vsdbg. Von Hand, im
.NET-Terminal:
curl -sSL https://aka.ms/getvsdbgsh | sh /dev/stdin -v latest -l ~/.vsdbgDie C#-Erweiterung braucht außerdem ein .NET SDK auf deinem Rechner, sonst debuggt sie gar nicht. Und hinterleg vorher deinen öffentlichen Schlüssel unter Einstellungen → SSH-Schlüssel, denn die Pipe kann keine Passwortabfrage beantworten.
{ "version": "0.2.0", "configurations": [ { "name": "Attach to Envoryx", "type": "coreclr", "request": "attach", "processId": "${command:pickRemoteProcess}", "pipeTransport": { "pipeProgram": "ssh", "pipeArgs": ["-T", "-p", "2222", "<user>@<host>"], "pipeCwd": "${workspaceFolder}", "debuggerPath": "/home/envoryx/.vsdbg/vsdbg" }, "sourceFileMap": { "/var/www/html": "${workspaceFolder}" }, "requireExactSource": false } ]}-T sagt ssh, dass es kein Terminal anfordern soll, denn der Debugger spricht über eine schlichte Pipe. Ersetz 2222 durch deinen SSH-Port; der Bereich IDE zeigt den ganzen Eintrag mit den passenden
Werten. requireExactSource ist aus, weil vsdbg sonst jeden Haltepunkt in einer Datei von deinem
Rechner ablehnt.


netcoredbg (/usr/local/bin/netcoredbg) steckt weiter im Image, für andere Editoren wie nvim-dap oder
Emacs. Pfade zuordnen kann er allerdings nicht, seine Haltepunkte greifen also nur, wenn dein Editor die
Dateien unter /var/www/html öffnet, zum Beispiel über Remote-SSH.
Denk beim Debuggen an Hot Reload. Sobald dotnet watch eine Änderung per Hot Reload eingespielt hat,
weist die .NET-Runtime jeden Debugger ab (CORDBG_E_ASSEMBLY_UPDATES_APPLIED, Fehler 0x80131c69), bis die Anwendung neu
startet. Starte das Projekt also vorher neu und häng dich an, bevor du etwas änderst, oder mach eine
Änderung, die dotnet watch nicht per Hot Reload einspielen kann, dann startet es die App neu. Das ist
eine Regel von .NET und gilt für Rider und Visual Studio genauso.
Rider und Visual Studio hängen sich über Attach to Remote Process mit einer SSH-Verbindung als derselbe Benutzer an. Riders Debugger spricht über SSH-Portweiterleitung mit der IDE, schalte deshalb zuerst JetBrains Gateway für dieses Projekt erlauben im Bereich IDE ein. Ohne das meldet Rider sich als verbunden, hält aber nie an, und deine Anwendung hängt, bis du die Sitzung beendest. Rider kann das ganze Projekt außerdem über JetBrains Gateway im Container öffnen, siehe IDE-Integration.
Aktionen und Worker
Abschnitt betitelt „Aktionen und Worker“Im Bereich Aktionen gibt es dotnet --info, restore, build, clean, format und
list package --outdated (die brauchen eine Solution oder Projektdatei oben im Projekt), außerdem
dotnet ef database update und dotnet ef migrations list für ein Projekt, das
Microsoft.EntityFrameworkCore.Design einbindet.
Für Arbeit im Hintergrund gibt es zwei Worker-Vorlagen: Projekt veröffentlicht ein Projekt im
Container des Workers und startet es, damit das Stoppsignal bei deiner Anwendung ankommt, und DLL
startet dotnet <datei>. Der Bereich Worker & Cron zeigt, was ein Projekt-Worker wirklich
ausführt: dotnet publish, danach dotnet <dll>. Cron-Jobs laufen im .NET-Container wie in jedem anderen. Mehr dazu unter
Worker und Cron-Jobs.
.NET später dazunehmen oder entfernen
Abschnitt betitelt „.NET später dazunehmen oder entfernen“Die .NET-Karte im Bereich Laufzeit hat einen Schalter .NET aktivieren. Schaltest du ihn aus,
verschwinden der .NET-Container und die Container der .NET-Worker; deine Dateien und die
Worker-Definitionen bleiben. Auf der Kommandozeile kennt envoryx dafür --dotnet <version>,
--dotnet-server und --dotnet-preset aspnetcore|dll.