In .NET Konfigurationsdaten auslesen

Automatisches Parsen von Konfigurationsdaten aus JSON in ein DTO mithilfe der Klasse

ConfigureOptions
.

Die Verwaltung von Konfigurationseinstellungen in .NET-Anwendungen bietet zahlreiche Möglichkeiten, doch oft finde ich die vorhandene Dokumentation nicht optimal. Dies macht es schwierig, die beste™ oder zumindest eine benutzerfreundliche Methode zu identifizieren. In diesem Artikel werde ich darlegen, wie ich dieses Problem angegangen bin.

Der manuelle Weg

In zahlreichen Anleitungen, sowohl im Internet als auch auf der Microsoft-Webseite, wird erläutert, wie man mit der Klasse
IConfiguration
auf Konfigurationsdaten zugreifen kann, die über JSON, Umgebungsvariablen und Startargumente bereitgestellt werden. Oftmals erfolgt das Auslesen dieser Daten manuell.
appsettings.json
{
	"Azure":
	{
		"ApiKey": "YouNeedToPushYourSecretKeyToAllRemotesIfYouHaventDoneSoAlready"
	}
}
Startup.cs
public class Startup
{
	public Startup(IConfiguration configuration)
	{
		Configuration = configuration;
	}

	public IConfiguration Configuration { get; }

	public void ConfigureServices(IServiceCollection services)
	{
		string apiKey = Configuration["Azure:ApiKey"];
	}
}
Das Problem dieser Methode ist ihre Anfälligkeit für Fehler. Einerseits wird mit Zeichenketten hantiert, die keine Typsicherheit bieten, andererseits kann es besonders kompliziert werden, sobald komplexere Daten als einfache Zeichenketten benötigt werden.

Komplexe Datenstrukturen

Die folgende JSON-Datei ist praxisnäher gestaltet und umfasst Elemente, die beim manuellen Parsen Kopfschmerzen bereiten können: verschiedene Datentypen, einschließlich Enumerationen, POCOs und Arrays.
appsettings.json
{
	"Localization":
	{
		"DefaultLanguage": "en",
		"Languages": [ "de", "en" ],
		"Providers": [ "JsonFileLocalizationProvider" ],
		"PageLocalization":
		{
			"HomePageRedirect": true,
			"RedirectStyle": "WithoutPrompt",
			"RedirectDelay": 0
		}
	}
}
Diese Datenstruktur in Form sähe in C# beispielsweise so aus:
LocalizationOptions.cs
public record LocalizationOptions
{
	public String? DefaultLanguage { get; init; }
	public List<String>? Languages { get; init; } = new();
	public List<String>? Providers { get; init; } = new();
	public PageLocalizationOptions PageLocalization { get; init; } = new();
}
PageLocalizationOptions.cs
public record PageLocalizationOptions
{
	public Boolean HomePageRedirect { get; init; }
	public RedirectStyle RedirectStyle { get; init; }
	public Int32 RedirectDelay { get; init; }
}
RedirectStyle.cs
public enum RedirectStyle
{
	WithoutPrompt,
	PromptUser,
	TooManyRedirects
}
Das Auslesen dieser Struktur kann äußerst mühsam sein (Pain in the Ass®). Es verdient Erwähnung, daß in diesem Beispiel der
RedirectStyle.TooManyRedirects
besonders hervorsticht, mit dem die Anwendung ohne ersichtlichen Grund den HTTP-Statuscode
310
zurückgibt. Dies ist eine brillante Idee, die in jeder Anwendung an irgendeiner Stelle implementiert werden sollte.

Automatisches Mapping

Das Mapping lässt sich vereinfachen durch den Einsatz der Klasse
ConfigureOptions
aus dem Paket
Microsoft.Extensions.Options
. Bei diesem Vorgehen leitet man von der Klasse ab und definiert einen Lambda-Ausdruck im Konstruktor. Dieser Delegate erhält eine Instanz der DTO-Klasse als Parameter. Da der Ausdruck im Konstruktor spezifiziert wird, befindet sich auch eine Instanz von
IConfiguration
im Scope. Der Methode
Bind()
übergibt man den durch einen String spezifizierten (und mit
:
getrennten) Pfad im Konfigurationsbaum und die Instanz der Optionen-Klasse, wodurch die Werte in das Objekt geschrieben werden.
Startup.cs
public class LocalizationOptionsSetup(IConfiguration configuration) : ConfigureOptions<LocalizationOptions>
	(options => configuration.Bind("Localization", options));
Um das Dependency Injection-System darüber zu informieren, daß diese Optionsklasse verfügbar ist, fügt man sie zur
IServiceCollection
hinzu.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.TryAddEnumerable(
        ServiceDescriptor.Singleton<IConfigureOptions<LocalizationOptions>, LocalizationOptionsSetup>());
}
Nach diesem Schritt steht die Klasse anderen, im DI-Container registrierten, Klassen zur Verfügung.
MyService.cs
public class MyService(IOptions<LocalizationOptions> options)
{
}

Erweiterte Konfiguration

Da der Container zum Zeitpunkt des Setups bereits zur Verfügung steht, ist es möglich, andere Dienste anzufordern, falls für den korrekten Aufbau der Options-Klasse spezielle Aktionen notwendig sein sollten. Dies bietet Flexibilität und erweiterte Möglichkeiten zur Anpassung.
LocalizationOptionsSetup.cs
public class LocalizationOptionsSetup(IConfiguration configuration, ISomeService someService)
	: ConfigureOptions<LocalizationOptions>(options =>
		Configure(options, configuration, someService))
{
	private static void Configure(LocalizationOptions options,
								  IConfiguration configuration,
								  ISomeService someService)
	{
		configuration.Bind("Localization", options);
		someService.DoImportantThings(options);
	}
}
Im nächsten Artikel werde ich zeigen, wie man mit
IOptionsMonitor
auf Änderungen an der Konfiguration im Programm reagieren kann. Dies ermöglicht Echtzeit-Aktualisierungen, die sicherstellen, dass sich die Anwendung dynamisch an Konfigurationsänderungen anpassen kann.

Post teilen

Auf Konfigurations-Änderungen In .NET reagieren

Auf Konfigurations-Änderungen In .NET reagieren

IOptionsMonitor
ist ein leistungsstarkes Werkzeug, das es ermöglicht, Konfigurationsoptionen dynamisch zu überwachen und auf Änderungen zu reagieren.