Optionsmusterleitfaden für .NET-Bibliotheksautoren
Durch die Unterstützung der Abhängigkeitsinjektion kann beim Registrieren Ihrer Dienste und bei den entsprechenden Konfigurationen das Optionsmuster genutzt werden. Das Optionsmuster ermöglicht den Consumern Ihrer Bibliothek (und Ihrer Dienste) das Anfordern von Instanzen von Optionsschnittstellen, wobei die TOptions
die Optionsklasse darstellt. Durch die Verwendung von Konfigurationsoptionen über stark typisierte Objekte wird eine konsistente Wertdarstellung sichergestellt, die Überprüfung mit Datenanmerkungen ermöglicht und der Aufwand der manuellen Analyse von Zeichenfolgenwerten entfällt. Es gibt zahlreiche Konfigurationsanbieter, die von Consumern Ihrer Bibliothek verwendet werden können. Mit diesen Anbietern können Consumer Ihre Bibliothek in vielerlei Hinsicht konfigurieren.
Als Autor der .NET-Bibliothek erhalten Sie eine allgemeine Anleitung, wie Sie das Optionsmuster für Consumer Ihrer Bibliothek ordnungsgemäß verfügbar machen. Es gibt verschiedene Möglichkeiten, dasselbe Ergebnis zu erzielen, und es sind einige Überlegungen anzustellen.
Benennungskonventionen
Gemäß der Konvention werden für die Registrierung von Diensten verantwortliche Erweiterungsmethoden mit Add{Service}
benannt, wobei {Service}
für einen aussagekräftigen und beschreibenden Namen steht. Die Add{Service}
-Erweiterungsmethoden sind in ASP.NET Core und .NET identisch.
✔️ Sie SOLLTEN Namen in Betracht ziehen, die Ihren Dienst von anderen Angeboten eindeutig unterscheiden.
❌ Verwenden Sie KEINE Namen, die bereits Teil des .NET-Ökosystems aus offiziellen Microsoft-Paketen sind.
✔️ Sie SOLLTEN statische Klassen, die Erweiterungsmethoden verfügbar machen, mit {Type}Extensions
. Hierbei steht {Type}
für den zu erweiternden Typ.
Leitfaden für Namespaces
Microsoft-Pakete verwenden den Namespace Microsoft.Extensions.DependencyInjection
, um die Registrierung verschiedener Dienstangebote zu vereinheitlichen.
✔️ Sie SOLLTEN einen Namespace in Erwägung ziehen, der das Paketangebot eindeutig identifiziert.
❌ Verwenden Sie den Namespace Microsoft.Extensions.DependencyInjection
NICHT für nicht offizielle Microsoft-Pakete.
Parameterlos
Wenn Ihr Dienst mit minimaler oder ohne explizite Konfiguration funktioniert, sollten Sie eine parameterlose Erweiterungsmethode in Betracht ziehen.
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services)
{
services.AddOptions<LibraryOptions>()
.Configure(options =>
{
// Specify default option values
});
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Ruft OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) mit dem Typparameter
LibraryOptions
auf. - Verkettet einen Aufruf an Configure, bei dem die Standardoptionswerte angegeben werden.
IConfiguration
-Parameter
Wenn Sie eine Bibliothek erstellen, die viele Optionen für Consumer verfügbar macht, sollten Sie eine IConfiguration
-Parametererweiterungsmethode anfordern. Die erwartete IConfiguration
-Instanz sollte mithilfe der IConfiguration.GetSection-Funktion auf einen benannten Abschnitt der Konfiguration festgelegt werden.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
IConfiguration namedConfigurationSection)
{
// Default library options are overridden
// by bound configuration values.
services.Configure<LibraryOptions>(namedConfigurationSection);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Tipp
Die Methode Configure<TOptions>(IServiceCollection, IConfiguration) ist Teil des NuGet-Pakets Microsoft.Extensions.Options.ConfigurationExtensions
.
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Definiert einen IConfiguration-Parameter
namedConfigurationSection
. - Aufruf von Configure<TOptions>(IServiceCollection, IConfiguration) mit Übergabe des generischen Typparameters von
LibraryOptions
und dernamedConfigurationSection
-Instanz, die konfiguriert werden soll
Consumer in diesem Muster stellen die bereichsbezogene IConfiguration
-Instanz des benannten Abschnitts bereit:
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(
builder.Configuration.GetSection("LibraryOptions"));
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
Der Aufruf von .AddMyLibraryService
erfolgt für den Typ IServiceCollection.
Als Bibliotheksautor sind Sie für die Angabe von Standardwerten zuständig.
Hinweis
Es ist möglich, die Konfiguration an eine Optionsinstanz zu binden. Dabei besteht jedoch das Risiko von Namenskonflikten, und diese führen zu Fehlern. Wenn Sie auf diese Weise eine manuelle Bindung vornehmen, beschränken Sie außerdem die Nutzung Ihres Optionsmusters auf einmaliges Lesen. Änderungen an den Einstellungen werden nicht erneut gebunden, da solche Consumer die IOptionsMonitor-Schnittstelle nicht verwenden können.
services.AddOptions<LibraryOptions>()
.Configure<IConfiguration>(
(options, configuration) =>
configuration.GetSection("LibraryOptions").Bind(options));
Verwenden Sie stattdessen die Erweiterungsmethode BindConfiguration. Diese Erweiterungsmethode bindet die Konfiguration an die Optionsinstanz und registriert auch eine Änderungstokenquelle für den Konfigurationsabschnitt. Auf diese Weise können Consumer die IOptionsMonitor-Schnittstelle verwenden.
Konfigurationsabschnittspfad-Parameter
Consumer Ihrer Bibliothek möchten möglicherweise den Konfigurationsabschnittspfad angeben, um den zugrunde liegenden TOptions
-Typ zu binden. In diesem Szenario definieren Sie einen string
-Parameter in Ihrer Erweiterungsmethode.
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
string configSectionPath)
{
services.AddOptions<SupportOptions>()
.BindConfiguration(configSectionPath)
.ValidateDataAnnotations()
.ValidateOnStart();
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Definiert den
string
-ParameterconfigSectionPath
- Ruft auf:
- AddOptions mit dem generischen Typparameter von
SupportOptions
- BindConfiguration mit dem angegebenen
configSectionPath
-Parameter - ValidateDataAnnotations zum Aktivieren der Datenanmerkungsüberprüfung
- ValidateOnStart zum Erzwingen der Überprüfung beim Start statt zur Laufzeit
- AddOptions mit dem generischen Typparameter von
Im nächsten Beispiel wird das Microsoft.Extensions.Options.DataAnnotations-NuGet-Paket verwendet, um die Datenanmerkungsüberprüfung zu aktivieren. Die SupportOptions
-Klasse wird wie folgt definiert:
using System.ComponentModel.DataAnnotations;
public sealed class SupportOptions
{
[Url]
public string? Url { get; set; }
[Required, EmailAddress]
public required string Email { get; set; }
[Required, DataType(DataType.PhoneNumber)]
public required string PhoneNumber { get; set; }
}
Stellen Sie sich vor, dass die JSON-Datei appsettings.json verwendet wird:
{
"Support": {
"Url": "https://support.example.com",
"Email": "help@support.example.com",
"PhoneNumber": "+1(888)-SUPPORT"
}
}
Action<TOptions>
-Parameter
Consumer Ihrer Bibliothek sind möglicherweise an der Bereitstellung eines Lambda-Ausdrucks interessiert, der eine Instanz der Optionsklasse ergibt. In diesem Szenario definieren Sie einen Action<LibraryOptions>
-Parameter in Ihrer Erweiterungsmethode.
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
Action<LibraryOptions> configureOptions)
{
services.Configure(configureOptions);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Definiert einen Action<T>-Parameter
configureOptions
, bei demT
den WertLibraryOptions
aufweist. - Ruft Configure anhand der
configureOptions
-Aktion auf.
Consumer in diesem Muster stellen einen Lambda-Ausdruck (oder einen Delegaten, der den Action<LibraryOptions>
-Parameter erfüllt) bereit:
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(options =>
{
// User defined option values
// options.SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
Optionsinstanzparameter
Consumer Ihrer Bibliothek bevorzugen möglicherweise die Bereitstellung einer Inlineoptionsinstanz. In diesem Szenario machen Sie eine Erweiterungsmethode verfügbar, die eine Instanz Ihres Optionsobjekts LibraryOptions
verwendet.
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
LibraryOptions userOptions)
{
services.AddOptions<LibraryOptions>()
.Configure(options =>
{
// Overwrite default option values
// with the user provided options.
// options.SomeValue = userOptions.SomeValue;
});
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Ruft OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) mit dem Typparameter
LibraryOptions
auf. - Verkettet einen Aufruf von Configure, der Standardoptionswerte angibt, die von der angegebenen
userOptions
-Instanz überschrieben werden können.
Consumer in diesem Muster stellen eine Instanz der LibraryOptions
-Klasse bereit, wobei die gewünschten Eigenschaftswerte inline definiert werden:
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(new LibraryOptions
{
// Specify option values
// SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
Nach der Konfiguration
Nachdem alle Konfigurationsoptionswerte gebunden oder angegeben wurden, ist die Funktionalität der Postkonfiguration verfügbar. Wenn Sie den oben beschriebenen Action<TOptions>
-Parameter verfügbar machen, können Sie PostConfigure aufrufen. Die Postkonfiguration wird nach allen .Configure
-Aufrufen ausgeführt. Es gibt verschiedene Gründe, warum Sie die Verwendung von PostConfigure
Betracht ziehen sollten:
- Ausführungsreihenfolge: Sie können alle Konfigurationswerte überschreiben, die in den
.Configure
-Aufrufen festgelegt wurden. - Validierung: Sie können überprüfen, ob die Standardwerte festgelegt wurden, nachdem alle anderen Konfigurationen angewandt wurden.
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
Action<LibraryOptions> configureOptions)
{
services.PostConfigure(configureOptions);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
Im vorherigen Code führt AddMyLibraryService
folgende Aktionen durch:
- Erweitert eine Instanz von IServiceCollection.
- Definiert einen Action<T>-Parameter
configureOptions
, bei demT
den WertLibraryOptions
aufweist. - Ruft PostConfigure anhand der
configureOptions
-Aktion auf.
Consumer in diesem Muster stellen einen Lambda-Ausdruck (oder einen Delegaten, der den Action<LibraryOptions>
-Parameter erfüllt) bereit, genau wie dies beim Action<TOptions>
-Parameter in einem Szenario der Fall wäre, das nicht nach der Konfiguration stattfindet:
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(options =>
{
// Specify option values
// options.SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();