Ein .env-File in .NET laden
- Matt
Ein Kollege schickt mir ein kleines Node-Werkzeug, das ich kurz laufen lassen
soll. Im Repository liegt eine .env.example, ich kopiere sie nach .env,
trage zwei Werte ein, fertig. Diesen Ablauf haben Leute aus der JavaScript- und
Python-Welt im Kopf, und wenn sie dann an einem .NET-Dienst mitarbeiten, suchen
sie als Erstes die .env. In .NET gibt es sie von Haus aus nicht. Die
Konfiguration kommt aus appsettings.json, Umgebungsvariablen und den User
Secrets, nicht aus einer Datei mit Zeilen der Form SCHLUESSEL=WERT.
Manchmal will ich den vertrauten Weg trotzdem, etwa wenn ich ein Tutorial
nachbaue, das eine .env voraussetzt, oder wenn dasselbe Set an Variablen von
einem Container und von der lokalen Entwicklung gelesen werden soll.
Der einfache Weg
Dafür gibt es DotNetEnv. Ein Paket, das die Datei einliest und die Werte in
die Prozess-Umgebungsvariablen schreibt:
DotNetEnv.Env.Load();
Das sucht .env im aktuellen Verzeichnis. Danach stehen die Werte in
Environment.GetEnvironmentVariable(...). Wer sie lieber direkt in die
IConfiguration schiebt, nutzt die eingebaute Erweiterung aus dem Namensraum
DotNetEnv.Configuration:
var config = new ConfigurationBuilder()
.AddDotNetEnv(".env", LoadOptions.TraversePath())
.Build();
TraversePath klettert dabei die Elternverzeichnisse hoch, bis eine .env
auftaucht, praktisch, wenn das Arbeitsverzeichnis nicht das Projektverzeichnis
ist. Verschachtelte Abschnitte gehen über doppelte Unterstriche: aus
Database__ConnectionString in der Datei wird der Schlüssel
Database:ConnectionString in der Konfiguration, genau wie bei den normalen
Umgebungsvariablen.
Der Haken
Was in keiner Kurzanleitung steht: Load schreibt echte
Prozess-Umgebungsvariablen, und standardmäßig überschreibt es dabei bereits
gesetzte Werte. Wer die Reihenfolge vertauscht und die .env nach dem Aufbau
der Konfiguration lädt, wundert sich, warum ein von außen gesetzter Wert aus
der Pipeline plötzlich von einem alten Eintrag aus der Datei verdrängt wird.
Dagegen hilft NoClobber, das vorhandene Variablen stehen lässt:
DotNetEnv.Env.NoClobber().TraversePath().Load();
Der zweite Haken ist keiner der Bibliothek, sondern des Musters: eine .env
gehört in die .gitignore und sonst nirgendwo hin. Sie liegt im Klartext im
Projekt, und genau deshalb wandert sie erfahrungsgemäß früher oder später
versehentlich in einen Commit.
Wann ich es lieber lasse
Für reine lokale Entwicklung nehme ich weiterhin die User Secrets, die .NET
schon mitbringt und die außerhalb des Projektordners liegen. Darüber habe ich
in Zugangsdaten aus dem Repository halten
geschrieben. DotNetEnv hole ich nur dazu, wenn wirklich eine .env im Spiel
ist, weil ein Tutorial, ein Docker-Compose-Setup oder ein Werkzeug aus einer
anderen Sprachwelt sie voraussetzt. Eine zusätzliche Abhängigkeit, nur um das
Format nachzustellen, das man von woanders kennt, ist es sonst nicht wert.