Teilen über


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:

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;
    }
}

Im vorherigen Code führt AddMyLibraryService folgende Aktionen durch:

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:

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 dem T den Wert LibraryOptions 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:

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 dem T den Wert LibraryOptions 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();

Siehe auch