Ein .env-File in .NET laden

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.