Jeder Programmierer stellte sich vor - nun ja oder möchte es sich vorstellen - als Flugzeugpilot, wenn Sie ein großes Projekt, eine riesige Auswahl an Sensoren, Metriken und Schaltern haben, mit denen Sie einfach alles so konfigurieren können, wie es sollte. Nun, zumindest nicht laufen, um das Chassis selbst manuell anzuheben. Sowohl Metriken als auch Grafiken sind alle gut, aber heute möchte ich Ihnen die gleichen Kippschalter und Schaltflächen vorstellen, mit denen Sie die Parameter des Flugzeugverhaltens ändern und konfigurieren können.
Die Bedeutung von Konfigurationen ist schwer zu unterschätzen. Jeder verwendet den einen oder anderen Ansatz bei der Konfiguration seiner Anwendungen, und im Prinzip ist daran nichts Kompliziertes, aber ist es so einfach? Ich schlage vor, das "Vorher" und "Nachher" in der Konfiguration zu betrachten und die Details zu verstehen: wie was funktioniert, welche neuen Funktionen wir haben und wie man sie in vollem Umfang nutzt. Diejenigen, die mit der Konfiguration in .NET Core nicht vertraut sind, erhalten die Grundlagen, und diejenigen, die mit der Konfiguration in .NET Core vertraut sind, erhalten Informationen zum Nachdenken und zur Verwendung neuer Ansätze in ihrer täglichen Arbeit.
Pre-.NET Core-Konfiguration
Im Jahr 2002 wurde das .NET Framework eingeführt, und da es die Zeit des XML-Hype war, entschieden die Entwickler von Microsoft, dass wir es überall haben sollten. Als Ergebnis haben wir XML-Konfigurationen erhalten, die noch am Leben sind. Am Anfang der Tabelle steht eine statische ConfigurationManager-Klasse, über die wir Zeichenfolgendarstellungen von Parameterwerten erhalten. Die Konfiguration selbst sah ungefähr so aus:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="Title" value=".NET Configuration evo" />
<add key="MaxPage" value="10" />
</appSettings>
</configuration>
Das Problem wurde gelöst, die Entwickler erhielten eine Anpassungsoption, die besser als INI-Dateien ist, aber ihre eigenen Besonderheiten aufweist. So wird beispielsweise die Unterstützung für unterschiedliche Einstellungswerte für verschiedene Arten von Anwendungsumgebungen mithilfe von XSLT-Transformationen der Konfigurationsdatei implementiert. Wir können unsere eigenen XML-Schemas für Elemente und Attribute definieren, wenn wir etwas Komplexeres in Bezug auf die Gruppierung von Daten wünschen. Schlüssel-Wert-Paare haben einen strengen String-Typ. Wenn wir eine Zahl oder ein Datum benötigen, dann "machen wir es irgendwie selbst":
string title = ConfigurationManager.AppSettings["Title"];
int maxPage = int.Parse(ConfigurationManager.AppSettings["MaxPage"]);
Im Jahr 2005 haben wir Konfigurationsabschnitte hinzugefügt , in denen Parameter gruppiert, eigene Schemata erstellt und Namenskonflikte vermieden wurden. Wir haben auch * .settings-Dateien und einen speziellen Designer für sie vorgestellt.
Jetzt können Sie eine generierte, stark typisierte Klasse erhalten, die Konfigurationsdaten darstellt. Mit dem Designer können Sie die Werte bequem bearbeiten. Eine Sortierung nach Editor-Spalten ist möglich. Die Daten werden mithilfe der Default-Eigenschaft der generierten Klasse abgerufen, die das Singleton-Konfigurationsobjekt bereitstellt.
DateTime date = Properties.Settings.Default.CustomDate;
int displayItems = Properties.Settings.Default.MaxDisplayItems;
string name = Properties.Settings.Default.ApplicationName;
Wir haben auch Bereiche für Konfigurationsparameterwerte hinzugefügt. Der Benutzerbereich ist verantwortlich für Benutzerdaten, die von ihm geändert und während der Programmausführung gespeichert werden können. Das Speichern erfolgt in einer separaten Datei entlang des Pfads% AppData% \ * Anwendungsname *. Mit dem Anwendungsbereich können Sie Parameterwerte abrufen, ohne dass die Möglichkeit einer Benutzerüberschreibung besteht.
Trotz der guten Absichten wurde das Ganze komplizierter.
- Tatsächlich handelt es sich um dieselben XML-Dateien, deren Größe schneller zunahm und deren Lesen infolgedessen unpraktisch wurde.
- Die Konfiguration wird einmal aus der XML-Datei gelesen, und wir müssen die Anwendung neu laden, um die Änderungen auf die Konfigurationsdaten anzuwenden.
- Aus * .settings-Dateien generierte Klassen wurden mit dem versiegelten Modifikator markiert, sodass diese Klasse nicht vererbt werden konnte. Außerdem könnte diese Datei geändert werden, aber wenn eine Regeneration stattfindet, verlieren wir alles, was wir selbst geschrieben haben.
- Arbeiten nur mit Daten nach dem Schlüsselwertschema. Um einen strukturierten Ansatz für die Arbeit mit Konfigurationen zu erhalten, müssen wir diesen zusätzlich selbst implementieren.
- Die Datenquelle kann nur eine Datei sein, externe Anbieter werden nicht unterstützt.
- Außerdem haben wir einen menschlichen Faktor: Private Parameter gelangen in das Versionskontrollsystem und werden offengelegt.
Alle diese Probleme bleiben bis heute in .NET Framework bestehen.
.NET Core-Konfiguration
In .NET Core haben sie die Konfiguration neu definiert und alles von Grund auf neu erstellt, die statische ConfigurationManager-Klasse entfernt und viele der "vorherigen" Probleme gelöst. Was haben wir neu bekommen? Nach wie vor - die Phase der Generierung von Konfigurationsdaten und die Phase der Verwendung dieser Daten, jedoch mit einem flexibleren und längeren Lebenszyklus.
Einrichten und Befüllen mit Konfigurationsdaten
Für die Phase der Datengenerierung können wir also viele Quellen verwenden und uns nicht nur auf Dateien beschränken. Die Konfiguration erfolgt über den IConfgurationBuilder - die Basis, auf der wir Datenquellen hinzufügen können. NuGet-Pakete sind für verschiedene Arten von Quellen verfügbar:
| Format | Erweiterungsmethode zum Hinzufügen einer Quelle zu IConfigurationBuilder | NuGet-Paket |
| Json | AddJsonFile | Microsoft.Extensions.Configuration.Json |
| XML | AddXmlFile | Microsoft.Extensions.Configuration.Xml |
| INI | AddIniFile | Microsoft.Extensions.Configuration.Ini |
| Kommandozeilenargumente | AddCommandLine | Microsoft.Extensions.Configuration.CommandLine |
| Umgebungsvariablen | AddEnvironmentVariables | Microsoft.Extensions.Configuration.EnvironmentVariables |
| Benutzergeheimnisse | AddUserSecrets | Microsoft.Extensions.Configuration.UserSecrets |
| KeyPerFile | AddKeyPerFile | Microsoft.Extensions.Configuration.KeyPerFile |
| Azure KeyVault | AddAzureKeyVault | Microsoft.Extensions.Configuration.AzureKeyVault |
Jede Quelle wird als neue Ebene hinzugefügt und überschreibt die Parameter mit übereinstimmenden Schlüsseln. Hier ist das Program.cs-Beispiel, das standardmäßig in der ASP.NET Core-App-Vorlage (Version 3.1) enthalten ist.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{ webBuilder.UseStartup<Startup>(); });
Ich möchte den Hauptfokus auf CreateDefaultBuilder ziehen . Innerhalb der Methode werden wir sehen, wie die anfängliche Konfiguration von Quellen erfolgt.
public static IWebHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new WebHostBuilder();
...
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostingEnvironment env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
Assembly appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly, optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
...
return builder;
}
Wir erhalten also, dass die Basis für die gesamte Konfiguration die Datei appsettings.json ist. Wenn es eine Datei für eine bestimmte Umgebung gibt, hat sie eine höhere Priorität und überschreibt dadurch die übereinstimmenden Werte der Basis. Und so bei jeder nachfolgenden Quelle. Die Reihenfolge der Addition beeinflusst den Endwert. Optisch sieht alles so aus:
Wenn Sie Ihre Bestellung verwenden möchten, können Sie sie einfach löschen und definieren, wie Sie sie benötigen.
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.ConfigureAppConfiguration((context,
builder) =>
{
builder.Sources.Clear();
//
});
Jede Konfigurationsquelle besteht aus zwei Teilen:
- Implementierung von IConfigurationSource. Bietet eine Quelle für Konfigurationswerte.
- Implementierung von IConfigurationProvider. Konvertiert die Originaldaten in den resultierenden Schlüsselwert.
Durch die Implementierung dieser Komponenten können wir unsere eigene Datenquelle für die Konfiguration erhalten. Hier ist ein Beispiel dafür, wie Sie das Abrufen von Parametern aus einer Datenbank über das Entity Framework implementieren können.
Verwenden und Abrufen von Daten
Nachdem mit der Einstellung und dem Befüllen mit Konfigurationsdaten alles klar ist, möchte ich einen Blick darauf werfen, wie wir diese Daten verwenden und wie wir sie bequemer erhalten können. Der neue Ansatz zur Konfiguration von Projekten tendiert stark zum beliebten JSON-Format, und dies ist nicht überraschend, da wir mit seiner Hilfe alle Datenstrukturen erstellen, Daten gruppieren und gleichzeitig eine lesbare Datei haben können. Nehmen wir zum Beispiel die folgende Konfigurationsdatei:
{
"Features" : {
"Dashboard" : {
"Title" : "Default dashboard",
"EnableCurrencyRates" : true
},
"Monitoring" : {
"EnableRPSLog" : false,
"EnableStorageStatistic" : true,
"StartTime": "09:00"
}
}
}
Alle Daten bilden ein flaches Schlüsselwertwörterbuch. Der Konfigurationsschlüssel wird aus der gesamten Dateischlüsselhierarchie für jeden Wert gebildet. Eine ähnliche Struktur hätte den folgenden Datensatz:
| Features: Dashboard: Titel | Standard-Dashboard |
| Funktionen: Dashboard: EnableCurrencyRates | wahr |
| Features: Überwachung: EnableRPSLog | falsch |
| Funktionen: Überwachung: EnableStorageStatistic | wahr |
| Features: Überwachung: StartTime | 09:00 |
Wir können den Wert mit dem IConfiguration- Objekt erhalten . Hier ist zum Beispiel, wie wir die Parameter erhalten können:
string title = Configuration["Features:Dashboard:Title"];
string title1 = Configuration.GetValue<string>("Features:Dashboard:Title");
bool currencyRates = Configuration.GetValue<bool>("Features:Dashboard:EnableCurrencyRates");
bool enableRPSLog = Configuration.GetValue<bool>("Features:Monitoring:EnableRPSLog");
bool enableStorageStatistic = Configuration.GetValue<bool>("Features:Monitoring:EnableStorageStatistic");
TimeSpan startTime = Configuration.GetValue<TimeSpan>("Features:Monitoring:StartTime");
Und das ist schon nicht schlecht, wir haben eine gute Möglichkeit, Daten zu erhalten, die in den gewünschten Datentyp umgewandelt werden, aber irgendwie nicht so cool, wie wir möchten. Wenn wir Daten wie oben angegeben erhalten, erhalten wir einen doppelten Code und machen Fehler in den Namen der Schlüssel. Anstelle einzelner Werte können Sie ein vollständiges Konfigurationsobjekt zusammenstellen. Das Binden von Daten an ein Objekt über die Bind-Methode hilft uns dabei. Beispiel für das Abrufen von Klassen und Daten:
public class MonitoringConfig
{
public bool EnableRPSLog { get; set; }
public bool EnableStorageStatistic { get; set; }
public TimeSpan StartTime { get; set; }
}
var monitorConfiguration = new MonitoringConfig();
Configuration.Bind("Features:Monitoring", monitorConfiguration);
var monitorConfiguration1 = new MonitoringConfig();
IConfigurationSection configurationSection = Configuration.GetSection("Features:Monitoring");
configurationSection.Bind(monitorConfiguration1);
Im ersten Fall binden wir nach dem Abschnittsnamen, und im zweiten Fall erhalten wir einen Abschnitt und binden daraus. In diesem Abschnitt können Sie mit einer Teilansicht der Konfiguration arbeiten. Auf diese Weise können Sie den Datensatz steuern, mit dem wir arbeiten. Abschnitte werden auch in Standarderweiterungsmethoden verwendet. Wenn Sie beispielsweise eine Verbindungszeichenfolge abrufen, wird der Abschnitt "ConnectionStrings" verwendet.
string connectionString = Configuration.GetConnectionString("Default");
public static string GetConnectionString(this IConfiguration configuration, string name)
{
return configuration?.GetSection("ConnectionStrings")?[name];
}
Optionen - eingegebene Konfigurationsansicht
Das manuelle Erstellen eines Konfigurationsobjekts und das Binden an Daten ist nicht praktikabel, es gibt jedoch eine Lösung in Form der Verwendung von Optionen . Optionen werden verwendet, um eine stark typisierte Ansicht einer Konfiguration zu erhalten. Die Ansichtsklasse muss mit einem Konstruktor ohne Parameter und öffentliche Eigenschaften zum Zuweisen eines Werts öffentlich sein. Das Objekt wird durch Reflektion gefüllt. Weitere Details finden Sie in der Quelle .
Um Optionen verwenden zu können, müssen Sie den Konfigurationstyp mithilfe der Configure-Erweiterungsmethode für IServiceCollection registrieren und den Abschnitt angeben, den wir auf unsere Klasse projizieren möchten.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<MonitoringConfig>(Configuration.GetSection("Features:Monitoring"));
}
Danach können wir Konfigurationen empfangen, indem wir eine Abhängigkeit von den Schnittstellen IOptions, IOptionsMonitor und IOptionsSnapshot einfügen. Wir können das MonitoringConfig-Objekt über die Value-Eigenschaft von der IOptions-Schnittstelle abrufen.
public class ExampleService
{
private IOptions<MonitoringConfig> _configuration;
public ExampleService(IOptions<MonitoringConfig> configuration)
{
_configuration = configuration;
}
public void Run()
{
TimeSpan timeSpan = _configuration.Value.StartTime; // 09:00
}
}
Ein Merkmal der IOptions-Schnittstelle besteht darin, dass im Abhängigkeitsinjektionscontainer die Konfiguration als Objekt mit dem Singleton-Lebenszyklus registriert wird. Wenn ein Wert zum ersten Mal von der Value-Eigenschaft angefordert wird, wird ein Objekt mit Daten initialisiert, die vorhanden sind, solange dieses Objekt vorhanden ist. IOptions unterstützt keine Datenaktualisierung. Es gibt IOptionsSnapshot- und IOptionsMonitor-Schnittstellen zur Unterstützung von Updates.
Der IOptionsSnapshot im DI-Container wird im Scoped-Lebenszyklus registriert, sodass auf Anfrage ein neues Konfigurationsobjekt mit einem neuen Containerbereich abgerufen werden kann. Während einer Webanforderung erhalten wir beispielsweise dasselbe Objekt, bei einer neuen Anforderung erhalten wir jedoch ein neues Objekt mit aktualisierten Daten.
IOptionsMonitor ist als Singleton registriert, mit dem einzigen Unterschied, dass jede Konfiguration mit den tatsächlichen Daten zum Zeitpunkt der Anforderung empfangen wird. Darüber hinaus können Sie mit IOptionsMonitor einen Handler für Konfigurationsänderungsereignisse registrieren, wenn Sie auf das Datenänderungsereignis selbst reagieren müssen.
public class ExampleService
{
private IOptionsMonitor<MonitoringConfig> _configuration;
public ExampleService(IOptionsMonitor<MonitoringConfig> configuration)
{
_configuration = configuration;
configuration.OnChange(config =>
{
Console.WriteLine(" ");
});
}
public void Run()
{
TimeSpan timeSpan = _configuration.CurrentValue.StartTime; // 09:00
}
}
Es ist auch möglich, IOptionsSnapshot und IOptionsMontitor nach Namen abzurufen. Dies ist erforderlich, wenn Sie mehrere Konfigurationsabschnitte haben, die einer Klasse entsprechen, und wenn Sie einen bestimmten abrufen möchten. Zum Beispiel haben wir die folgenden Daten:
{
"Cache": {
"Main": {
"Type": "global",
"Interval": "10:00"
},
"Partial": {
"Type": "personal",
"Interval": "01:30"
}
}
}
Der für die Projektion zu verwendende Typ:
public class CachePolicy
{
public string Type { get; set; }
public TimeSpan Interval { get; set; }
}
Wir registrieren Konfigurationen mit einem bestimmten Namen:
services.Configure<CachePolicy>("Main", Configuration.GetSection("Cache:Main"));
services.Configure<CachePolicy>("Partial", Configuration.GetSection("Cache:Partial"));
Wir können Werte wie folgt erhalten:
public class ExampleService
{
public ExampleService(IOptionsSnapshot<CachePolicy> configuration)
{
CachePolicy main = configuration.Get("Main");
TimeSpan mainInterval = main.Interval; // 10:00
CachePolicy partial = configuration.Get("Partial");
TimeSpan partialInterval = partial.Interval; // 01:30
}
}
Wenn Sie sich den Quellcode der Erweiterungsmethode ansehen, mit der wir den Konfigurationstyp registrieren, sehen Sie, dass der Standardname Options.Default ist, eine leere Zeichenfolge. Wir geben also implizit immer einen Namen für die Konfigurationen ein.
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config);
Da die Konfiguration durch eine Klasse dargestellt werden kann, können wir auch die Validierung von Parameterwerten hinzufügen, indem wir die Eigenschaften mithilfe von Validierungsattributen aus dem System.ComponentModel.DataAnnotations-Namespace markieren. Beispielsweise geben wir an, dass der Wert für die Type-Eigenschaft erforderlich sein muss. Bei der Registrierung der Konfiguration müssen wir jedoch auch angeben, dass die Validierung grundsätzlich erfolgen soll. Hierfür gibt es eine Erweiterungsmethode ValidateDataAnnotations.
public class CachePolicy
{
[Required]
public string Type { get; set; }
public TimeSpan Interval { get; set; }
}
services.AddOptions<CachePolicy>()
.Bind(Configuration.GetSection("Cache:Main"))
.ValidateDataAnnotations();
Die Besonderheit einer solchen Validierung besteht darin, dass sie erst zum Zeitpunkt des Empfangs des Konfigurationsobjekts erfolgt. Dies macht es schwierig zu verstehen, dass die Konfiguration beim Starten der Anwendung nicht gültig ist. Auf GitHub gibt es ein Problem mit diesem Problem . Eine Lösung für dieses Problem kann der im Artikel Hinzufügen einer Validierung zu stark typisierten Konfigurationsobjekten in ASP.NET Core vorgestellte Ansatz sein .
Nachteile von Optionen und wie man sie umgeht
Das Konfigurieren über Optionen hat auch seine Nachteile. Zur Verwendung müssen wir eine Abhängigkeit hinzufügen und jedes Mal, wenn wir auf die Value / CurrentValue-Eigenschaft zugreifen müssen, um ein Wertobjekt abzurufen. Sie können saubereren Code erzielen, indem Sie ein sauberes Konfigurationsobjekt ohne den Options-Wrapper abrufen. Die einfachste Lösung für das Problem kann die zusätzliche Registrierung einer reinen Konfigurationstypabhängigkeit im Container sein.
services.Configure<MonitoringConfig>(Configuration.GetSection("Features:Monitoring"));
services.AddScoped<MonitoringConfig>(provider => provider.GetRequiredService<IOptionsSnapshot<MonitoringConfig>>().Value);
Die Lösung ist unkompliziert. Wir erzwingen nicht, dass der endgültige Code über IOptions informiert wird, verlieren jedoch die Flexibilität für zusätzliche Konfigurationsaktionen, wenn wir diese benötigen. Um dieses Problem zu lösen, können wir das "Bridge" -Muster verwenden, mit dem wir eine zusätzliche Ebene erhalten, in der wir zusätzliche Aktionen ausführen können, bevor wir das Objekt empfangen.
Um dieses Ziel zu erreichen, müssen wir den aktuellen Beispielcode umgestalten. Da die Konfigurationsklasse eine Einschränkung in Form eines Konstruktors ohne Parameter aufweist, können wir das Objekt IOptions / IOptionsSnapshot / IOptionsMontitor nicht an den Konstruktor übergeben. Dazu trennen wir den Konfigurationsleser von der endgültigen Ansicht.
Angenommen, wir möchten die StartTime-Eigenschaft der MonitoringConfig-Klasse mit einer Zeichenfolgendarstellung von Minuten mit dem Wert "09" angeben, die nicht zum Standardformat passt.
public class MonitoringConfigReader
{
public bool EnableRPSLog { get; set; }
public bool EnableStorageStatistic { get; set; }
public string StartTime { get; set; }
}
public interface IMonitoringConfig
{
bool EnableRPSLog { get; }
bool EnableStorageStatistic { get; }
TimeSpan StartTime { get; }
}
public class MonitoringConfig : IMonitoringConfig
{
public MonitoringConfig(IOptionsMonitor<MonitoringConfigReader> option)
{
MonitoringConfigReader reader = option.Value;
EnableRPSLog = reader.EnableRPSLog;
EnableStorageStatistic = reader.EnableStorageStatistic;
StartTime = GetTimeSpanValue(reader.StartTime);
}
public bool EnableRPSLog { get; }
public bool EnableStorageStatistic { get; }
public TimeSpan StartTime { get; }
private static TimeSpan GetTimeSpanValue(string value) => TimeSpan.ParseExact(value, "mm", CultureInfo.InvariantCulture);
}
Um eine saubere Konfiguration zu erhalten, müssen wir sie im Abhängigkeitsinjektionscontainer registrieren.
services.Configure<MonitoringConfigReader>(Configuration.GetSection("Features:Monitoring"));
services.AddTransient<IMonitoringConfig, MonitoringConfig>();
Mit diesem Ansatz können Sie einen völlig separaten Lebenszyklus für die Bildung eines Konfigurationsobjekts erstellen. Es ist möglich, eine eigene Datenvalidierung hinzuzufügen oder die Stufe der Datenentschlüsselung zusätzlich zu implementieren, wenn Sie diese in verschlüsselter Form erhalten.
Gewährleistung der Datensicherheit
Eine wichtige Konfigurationsaufgabe ist die Datensicherheit. Dateikonfigurationen sind unsicher, da die Daten im Klartext gespeichert sind, der leicht zu lesen ist. Oft befinden sich die Dateien im selben Verzeichnis wie die Anwendung. Aus Versehen können Sie die Werte an das Versionskontrollsystem übergeben, das die Daten deklassieren kann. Stellen Sie sich jedoch vor, es handelt sich um öffentlichen Code! Die Situation ist so häufig, dass es sogar ein vorgefertigtes Tool zum Auffinden solcher Lecks gibt - Gitleaks . Es gibt einen separaten Artikel , der Statistiken und die Vielfalt der offengelegten Daten enthält.
Oft muss ein Projekt separate Parameter für verschiedene Umgebungen haben (Release / Debug usw.). Als eine der Lösungen können Sie beispielsweise die Substitution von Endwerten mithilfe der Tools für die kontinuierliche Integration und Bereitstellung verwenden. Diese Option schützt die Daten jedoch nicht während der Entwicklung. Das User Secrets- Tool soll den Entwickler schützen . Es ist im .NET Core SDK (3.0.100 und höher) enthalten. Was ist das Hauptprinzip dieses Tools? Zuerst müssen wir unser Projekt initialisieren, um mit dem Befehl init zu arbeiten.
dotnet user-secrets init
Der Befehl fügt der .csproj-Projektdatei ein UserSecretsId-Element hinzu. Mit diesem Parameter erhalten wir einen privaten Speicher, in dem eine reguläre JSON-Datei gespeichert wird. Der Unterschied besteht darin, dass es sich nicht in Ihrem Projektverzeichnis befindet und daher nur auf dem aktuellen Computer verfügbar ist. Der Pfad für Windows lautet% APPDATA% \ Microsoft \ UserSecrets \ <Benutzer-Sekret_ID> \ Secrets.json und für Linux und MacOS ~ / .microsoft / usersecrets / <Benutzer_Sekret_ID> /Sekret.json. Wir können den Wert aus dem obigen Beispiel mit dem Befehl set hinzufügen:
dotnet user-secrets set "Features:Monitoring:StartTime" "09:00"
Eine vollständige Liste der verfügbaren Befehle finden Sie in der Dokumentation.
Die Datensicherheit in der Produktion wird am besten durch speziellen Speicher gewährleistet, z. B.: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, Consul, ZooKeeper. Um einige zu verbinden, gibt es bereits vorgefertigte NuGet-Pakete, und für einige ist es einfach, sie selbst zu implementieren, da Zugriff auf die REST-API besteht.
Fazit
Moderne Probleme erfordern moderne Lösungen. Mit der Abkehr von Monolithen zu dynamischen Infrastrukturen haben sich auch die Konfigurationsansätze geändert. Unabhängig von der Position und Art der Quellen der Konfigurationsdaten war eine sofortige Reaktion auf Datenänderungen erforderlich. Zusammen mit .NET Core haben wir ein gutes Tool zum Implementieren aller Arten von Anwendungskonfigurationsszenarien erhalten.