|
| 1 | +--- |
| 2 | +title: Options pattern guidance for .NET library authors |
| 3 | +author: IEvangelist |
| 4 | +description: Learn how to expose the options pattern as a library author in .NET. |
| 5 | +ms.author: dapine |
| 6 | +ms.date: 01/28/2021 |
| 7 | +--- |
| 8 | + |
| 9 | +# Options pattern guidance for .NET library authors |
| 10 | + |
| 11 | +With the help of dependency injection, registering your services and their corresponding configurations can make use of the *options pattern*. The options pattern enables consumers of your library (and your services) to require instances of [options interfaces](options.md#options-interfaces) where `TOptions` is your options class. Consuming configuration options through strongly-typed objects helps to ensure consistent value representation, and removes the burden of manually parsing string values. There are many [configuration providers](configuration-providers.md) for consumers of your library to use. With these providers, consumers can configure your library in many ways. |
| 12 | + |
| 13 | +As a .NET library author, you'll learn general guidance on how to correctly expose the options pattern to consumers of your library. There are various ways to achieve the same thing, and several considerations to make. |
| 14 | + |
| 15 | +## Naming conventions |
| 16 | + |
| 17 | +By convention, extension methods responsible for registering services are named `Add{Service}`, where `{Service}` is a meaningful and descriptive name. Depending on the package, the registration of services may be accompanied by `Use{Service}` extension methods. The `Use{Service}` extension methods are commonplace in [ASP.NET Core](/aspnet). |
| 18 | + |
| 19 | +✔️ CONSIDER names that disambiguate your service from other offerings. |
| 20 | + |
| 21 | +❌ DO NOT use names that are already part of the .NET ecosystem from official Microsoft packages. |
| 22 | + |
| 23 | +✔️ CONSIDER naming static classes that expose extension methods as `{Type}Extensions`, where `{Type}` is the type that you're extending. |
| 24 | + |
| 25 | +### Namespace guidance |
| 26 | + |
| 27 | +Microsoft packages make use of the `Microsoft.Extensions.DependencyInjection` namespace to unify the registration of various service offerings. |
| 28 | + |
| 29 | +✔️ CONSIDER a namespace that clearly identifies your package offering. |
| 30 | + |
| 31 | +❌ DO NOT use the `Microsoft.Extensions.DependencyInjection` namespace for non-official Microsoft packages. |
| 32 | + |
| 33 | +## Parameterless |
| 34 | + |
| 35 | +If your service can work with minimal or no explicit configuration, consider a parameterless extension method. |
| 36 | + |
| 37 | +:::code language="csharp" source="snippets/configuration/options-noparams/ServiceCollectionExtensions.cs" highlight="10-14"::: |
| 38 | + |
| 39 | +In the preceding code, the `AddMyLibraryService`: |
| 40 | + |
| 41 | +- Extends an instance of <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> |
| 42 | +- Calls <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions%60%601(Microsoft.Extensions.DependencyInjection.IServiceCollection)?displayProperty=nameWithType> with the type parameter of `LibraryOptions` |
| 43 | +- Chains a call to <xref:Microsoft.Extensions.Options.OptionsBuilder%601.Configure%2A>, which specifies the default option values |
| 44 | + |
| 45 | +## `IConfiguration` parameter |
| 46 | + |
| 47 | +When you author a library that exposes many options to consumers, you may want to consider requiring an `IConfiguration` parameter extension method. The expected `IConfiguration` instance should be scoped to a named section of the configuration by using the <xref:Microsoft.Extensions.Configuration.IConfiguration.GetSection%2A?displayProperty=nameWithType> function. |
| 48 | + |
| 49 | +:::code language="csharp" source="snippets/configuration/options-configparam/ServiceCollectionExtensions.cs" highlight="10,12-16"::: |
| 50 | + |
| 51 | +In the preceding code, the `AddMyLibraryService`: |
| 52 | + |
| 53 | +- Extends an instance of <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> |
| 54 | +- Defines an <xref:Microsoft.Extensions.Configuration.IConfiguration> parameter `namedConfigurationSection` |
| 55 | +- Calls <xref:Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(Microsoft.Extensions.Configuration.IConfiguration,System.Object)> passing an options instance that the configuration binds to |
| 56 | + |
| 57 | +Consumers in this pattern provide the scoped `IConfiguration` instance of the named section: |
| 58 | + |
| 59 | +:::code language="csharp" source="snippets/configuration/options-configparam/Program.cs" highlight="22-23"::: |
| 60 | + |
| 61 | +The call to `.AddMyLibraryService` is made in the <xref:Microsoft.Extensions.Hosting.IHostBuilder.ConfigureServices%2A> method. The same is true when using a `Startup` class, the addition of services being registered occurs in `ConfigureServices`. |
| 62 | + |
| 63 | +As the library author, specifying default values is up to you. |
| 64 | + |
| 65 | +> [!NOTE] |
| 66 | +> It is possible to bind configuration to an options instance. However, there is a risk of name collisions - which will cause errors. Additionally, when manually binding in this way, you limit the consumption of your options pattern to read-once. Changes to settings will not be re-bound, as such consumers will not be able to use the [IOptionsMonitor](options.md#ioptionsmonitor) interface. |
| 67 | +> |
| 68 | +> ```csharp |
| 69 | +> services.AddOptions<LibraryOptions>() |
| 70 | +> .Configure<IConfiguration>( |
| 71 | +> (options, configuration) => |
| 72 | +> configuration.GetSection("LibraryOptions").Bind(options)); |
| 73 | +> ``` |
| 74 | +
|
| 75 | +## `Action<TOptions>` parameter |
| 76 | +
|
| 77 | +Consumers of your library may be interested in providing a lambda expression that yields an instance of your options class. In this scenario, you define an `Action<LibraryOptions>` parameter in your extension method. |
| 78 | +
|
| 79 | +:::code language="csharp" source="snippets/configuration/options-action/ServiceCollectionExtensions.cs" highlight="10,12"::: |
| 80 | +
|
| 81 | +In the preceding code, the `AddMyLibraryService`: |
| 82 | +
|
| 83 | +- Extends an instance of <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> |
| 84 | +- Defines an <xref:System.Action%601> where `T` is `LibraryOptions` parameter `configureOptions` |
| 85 | +- Calls <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.Configure%2A> given the `configureOptions` action |
| 86 | +
|
| 87 | +Consumers in this pattern provide a lambda expression (or a delegate that satisfies the `Action<LibraryOptions>` parameter): |
| 88 | +
|
| 89 | +:::code language="csharp" source="snippets/configuration/options-action/Program.cs" highlight="22-26"::: |
| 90 | +
|
| 91 | +## Options instance parameter |
| 92 | +
|
| 93 | +Consumers of your library might prefer to provide an inlined options instance. In this scenario, you expose an extension method that takes an instance of your options object, `LibraryOptions`. |
| 94 | +
|
| 95 | +:::code language="csharp" source="snippets/configuration/options-object/ServiceCollectionExtensions.cs" highlight="9,11-17"::: |
| 96 | +
|
| 97 | +In the preceding code, the `AddMyLibraryService`: |
| 98 | +
|
| 99 | +- Extends an instance of <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> |
| 100 | +- Calls <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions%60%601(Microsoft.Extensions.DependencyInjection.IServiceCollection)?displayProperty=nameWithType> with the type parameter of `LibraryOptions` |
| 101 | +- Chains a call to <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.Configure%2A>, which specifies default option values that can be overridden from the given `userOptions` instance |
| 102 | +
|
| 103 | +Consumers in this pattern provide an instance of the `LibraryOptions` class, defining desired property values inline: |
| 104 | +
|
| 105 | +:::code language="csharp" source="snippets/configuration/options-object/Program.cs" highlight="22-26"::: |
| 106 | +
|
| 107 | +## Post configuration |
| 108 | +
|
| 109 | +After all configuration option values are bound or specified, post configuration functionality is available. Exposing the same [`Action<TOptions>` parameter](#actiontoptions-parameter) detailed earlier, you could choose to call <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.PostConfigure%2A>. Post configure runs after all `.Configure` calls. |
| 110 | +
|
| 111 | +:::code language="csharp" source="snippets/configuration/options-postconfig/ServiceCollectionExtensions.cs" highlight="10,12"::: |
| 112 | +
|
| 113 | +In the preceding code, the `AddMyLibraryService`: |
| 114 | +
|
| 115 | +- Extends an instance of <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> |
| 116 | +- Defines an <xref:System.Action%601> where `T` is `LibraryOptions` parameter `configureOptions` |
| 117 | +- Calls <xref:Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.PostConfigure%2A> given the `configureOptions` action |
| 118 | +
|
| 119 | +Consumers in this pattern provide a lambda expression (or a delegate that satisfies the `Action<LibraryOptions>` parameter), just as they would with the [`Action<TOptions>` parameter] in a non-post configuration scenario: |
| 120 | +
|
| 121 | +:::code language="csharp" source="snippets/configuration/options-postconfig/Program.cs" highlight="22-26"::: |
| 122 | +
|
| 123 | +## See also |
| 124 | +
|
| 125 | +- [Options pattern in .NET](options.md) |
| 126 | +- [Dependency injection in .NET](dependency-injection.md) |
| 127 | +- [Dependency injection guidelines](dependency-injection-guidelines.md) |
0 commit comments