Zum Inhalt springen

.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.

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.

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.

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.

Die Karte .NET im Bereich Laufzeit mit der Version, dem eingeschalteten ASP.NET-Core-Server, dem Modus, der Projektdatei und dem PortDie Karte .NET im Bereich Laufzeit mit der Version, dem eingeschalteten ASP.NET-Core-Server, dem Modus, der Projektdatei und dem Port
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.

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.

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.

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.

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:

Terminal-Fenster
curl -sSL https://aka.ms/getvsdbgsh | sh /dev/stdin -v latest -l ~/.vsdbg

Die 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.

.vscode/launch.json
{
"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.

Die Karte .NET debugging im Bereich IDE mit SSH-Benutzer, Pfadzuordnung, dem Knopf vsdbg installieren samt Befehl für das Terminal, der launch.json für VS Code und den Schritten für RiderDie Karte .NET debugging im Bereich IDE mit SSH-Benutzer, Pfadzuordnung, dem Knopf vsdbg installieren samt Befehl für das Terminal, der launch.json für VS Code und den Schritten für Rider

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.

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.

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.