Skip to content

Commit f21b3ce

Browse files
authored
Lib authors options (dotnet#22433)
* Added options pattern guidance for lib authors. * Pre-commit hook, applied automatic markdownlint CLI fixes * Fixed highlighting boundaries * Updates from re-read, and fixed even more bits. * Fixed typo * Added TOC entry and cross-link from options pattern doc * Fixed another bug, misalignment of hightlighting due to using * Added suggestioned feedback from review * More fine tuning of the intro paragraph
1 parent ffa2077 commit f21b3ce

27 files changed

+469
-4
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@
158158
# I/O
159159
/docs/standard/io/ @adegeo
160160
# Library guidance
161-
/docs/standard/library-guidance/ @jamesnk
161+
/docs/standard/library-guidance/ @jamesnk @IEvangelist
162162
# LINQ
163163
/docs/standard/linq/ @dotnet/docs
164164
# Memory and spans

docs/architecture/modernize-desktop/example-migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Attributes are autogenerated on .NET projects. If the project contains an *Assem
8484

8585
```xml
8686
<Project Sdk="Microsoft.NET.Sdk">
87-
<PropertyGroup>
87+
<PropertyGroup>
8888
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
8989
</PropertyGroup>
9090
</Project>

docs/core/extensions/access-by-line.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ snippets/configuration/custom-provider/Program.cs: ~/docs/core/extensions/custom
1818
snippets/configuration/dependency-injection/Program.cs: ~/docs/core/extensions/dependency-injection.md
1919
snippets/configuration/di-anti-patterns/Foo.cs: ~/docs/core/extensions/dependency-injection-guidelines.md
2020
snippets/configuration/di-anti-patterns/Program.cs: ~/docs/core/extensions/dependency-injection-guidelines.md
21+
snippets/configuration/options-action/ServiceCollectionExtensions.cs: ~/docs/core/extensions/options-library-authors.md
22+
snippets/configuration/options-configparam/ServiceCollectionExtensions.cs: ~/docs/core/extensions/options-library-authors.md
23+
snippets/configuration/options-noparams/ServiceCollectionExtensions.cs: ~/docs/core/extensions/options-library-authors.md
24+
snippets/configuration/options-object/ServiceCollectionExtensions.cs: ~/docs/core/extensions/options-library-authors.md
25+
snippets/configuration/options-postconfig/ServiceCollectionExtensions.cs: ~/docs/core/extensions/options-library-authors.md
2126
snippets/configuration/worker-service-options/Worker.cs: ~/docs/core/extensions/high-performance-logging.md
2227
snippets/configuration/worker-service/appsettings.IncludeScopes.json: ~/docs/core/extensions/logging.md
2328
snippets/configuration/worker-service/Worker.cs: ~/docs/core/extensions/logging.md
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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)

docs/core/extensions/options.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Options pattern in .NET
33
author: IEvangelist
44
description: Learn how to use the options pattern to represent groups of related settings in .NET apps.
55
ms.author: dapine
6-
ms.date: 01/06/2021
6+
ms.date: 01/21/2021
77
---
88

99
# Options pattern in .NET
@@ -344,3 +344,4 @@ services.PostConfigureAll<CustomOptions>(customOptions =>
344344
## See also
345345

346346
- [Configuration in .NET](configuration.md)
347+
- [Options pattern guidance for .NET library authors](options-library-authors.md)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public class LibraryOptions
2+
{
3+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Threading.Tasks;
2+
using ExampleLibrary.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Hosting;
4+
5+
namespace Options.Action
6+
{
7+
class Program
8+
{
9+
static async Task Main(string[] args)
10+
{
11+
using IHost host = CreateHostBuilder(args).Build();
12+
13+
// Application code should start here.
14+
15+
await host.RunAsync();
16+
}
17+
18+
static IHostBuilder CreateHostBuilder(string[] args) =>
19+
Host.CreateDefaultBuilder(args)
20+
.ConfigureServices(services =>
21+
{
22+
services.AddMyLibraryService(options =>
23+
{
24+
// User defined option values
25+
// options.SomePropertyValue = ...
26+
});
27+
});
28+
}
29+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace ExampleLibrary.Extensions.DependencyInjection
5+
{
6+
public static class ServiceCollectionExtensions
7+
{
8+
public static IServiceCollection AddMyLibraryService(
9+
this IServiceCollection services,
10+
Action<LibraryOptions> configureOptions)
11+
{
12+
services.Configure(configureOptions);
13+
14+
// Register lib services here...
15+
// services.AddScoped<ILibraryService, DefaultLibraryService>();
16+
17+
return services;
18+
}
19+
}
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
<RootNamespace>Options.Action</RootNamespace>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
11+
</ItemGroup>
12+
13+
</Project>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public class LibraryOptions
2+
{
3+
}

0 commit comments

Comments
 (0)