Skip to content

Commit

Permalink
Add BindConfiguration extension method for OptionsBuilder (dotnet#39825)
Browse files Browse the repository at this point in the history
  • Loading branch information
fredrikhr authored Aug 5, 2020
1 parent 6e922b8 commit 5870d4f
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Option
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Options.ConfigurationExtensions", "src\Microsoft.Extensions.Options.ConfigurationExtensions.csproj", "{D4C213E7-336F-4601-9F92-1D632D082422}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{4CB60D51-6D36-49DB-A3AC-B45664F39EFC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Options.ConfigurationExtensions.Tests", "tests\Microsoft.Extensions.Options.ConfigurationExtensions.Tests.csproj", "{5F41C8C2-663D-4D57-9211-2F495D7DFACE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{3EFE3DA9-F864-40A3-8892-899D731CC027}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.DependencyInjection", "..\Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj", "{D8AC9F6D-95FA-4377-A867-FF75683393AA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Configuration", "..\Microsoft.Extensions.Configuration\src\Microsoft.Extensions.Configuration.csproj", "{1F88377A-7C91-466E-9351-1B0AE0DF78AB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Options", "..\Microsoft.Extensions.Options\src\Microsoft.Extensions.Options.csproj", "{D53344C9-58C5-4A2B-8182-74A48763A8FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -25,13 +37,38 @@ Global
{D4C213E7-336F-4601-9F92-1D632D082422}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4C213E7-336F-4601-9F92-1D632D082422}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4C213E7-336F-4601-9F92-1D632D082422}.Release|Any CPU.Build.0 = Release|Any CPU
{5F41C8C2-663D-4D57-9211-2F495D7DFACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5F41C8C2-663D-4D57-9211-2F495D7DFACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5F41C8C2-663D-4D57-9211-2F495D7DFACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5F41C8C2-663D-4D57-9211-2F495D7DFACE}.Release|Any CPU.Build.0 = Release|Any CPU
{3EFE3DA9-F864-40A3-8892-899D731CC027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3EFE3DA9-F864-40A3-8892-899D731CC027}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3EFE3DA9-F864-40A3-8892-899D731CC027}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3EFE3DA9-F864-40A3-8892-899D731CC027}.Release|Any CPU.Build.0 = Release|Any CPU
{D8AC9F6D-95FA-4377-A867-FF75683393AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8AC9F6D-95FA-4377-A867-FF75683393AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8AC9F6D-95FA-4377-A867-FF75683393AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8AC9F6D-95FA-4377-A867-FF75683393AA}.Release|Any CPU.Build.0 = Release|Any CPU
{1F88377A-7C91-466E-9351-1B0AE0DF78AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F88377A-7C91-466E-9351-1B0AE0DF78AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F88377A-7C91-466E-9351-1B0AE0DF78AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F88377A-7C91-466E-9351-1B0AE0DF78AB}.Release|Any CPU.Build.0 = Release|Any CPU
{D53344C9-58C5-4A2B-8182-74A48763A8FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D53344C9-58C5-4A2B-8182-74A48763A8FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D53344C9-58C5-4A2B-8182-74A48763A8FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D53344C9-58C5-4A2B-8182-74A48763A8FE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F0815ADD-0115-4368-886E-2135355BC439} = {9EDF28B3-F998-47FE-B23A-753205641B49}
{D4C213E7-336F-4601-9F92-1D632D082422} = {BF3EE9AA-4B66-4410-B1D2-3971169F1E40}
{5F41C8C2-663D-4D57-9211-2F495D7DFACE} = {4CB60D51-6D36-49DB-A3AC-B45664F39EFC}
{3EFE3DA9-F864-40A3-8892-899D731CC027} = {4CB60D51-6D36-49DB-A3AC-B45664F39EFC}
{D8AC9F6D-95FA-4377-A867-FF75683393AA} = {4CB60D51-6D36-49DB-A3AC-B45664F39EFC}
{1F88377A-7C91-466E-9351-1B0AE0DF78AB} = {4CB60D51-6D36-49DB-A3AC-B45664F39EFC}
{D53344C9-58C5-4A2B-8182-74A48763A8FE} = {4CB60D51-6D36-49DB-A3AC-B45664F39EFC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0414F2BB-333F-487E-92AE-530AA983499B}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Microsoft.Extensions.DependencyInjection
{
public static partial class OptionsBuilderConfigurationExtensions
{
public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> BindConfiguration<TOptions>(this Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, string configSectionPath, System.Action<Microsoft.Extensions.Configuration.BinderOptions> configureBinder = null) where TOptions : class { throw null; }
public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, Microsoft.Extensions.Configuration.IConfiguration config) where TOptions : class { throw null; }
public static Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, Microsoft.Extensions.Configuration.IConfiguration config, System.Action<Microsoft.Extensions.Configuration.BinderOptions> configureBinder) where TOptions : class { throw null; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,37 @@ public static OptionsBuilder<TOptions> Bind<TOptions>(this OptionsBuilder<TOptio
optionsBuilder.Services.Configure<TOptions>(optionsBuilder.Name, config, configureBinder);
return optionsBuilder;
}

/// <summary>
/// Registers the dependency injection container to bind <typeparamref name="TOptions"/> against
/// the <see cref="IConfiguration"/> obtained from the DI service provider.
/// </summary>
/// <typeparam name="TOptions">The options type to be configured.</typeparam>
/// <param name="optionsBuilder">The options builder to add the services to.</param>
/// <param name="configSectionPath">The name of the configuration section to bind from.</param>
/// <param name="configureBinder">Optional. Used to configure the <see cref="BinderOptions"/>.</param>
/// <returns>The <see cref="OptionsBuilder{TOptions}"/> so that additional calls can be chained.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="optionsBuilder"/> or <paramref name="configSectionPath" /> is <see langword="null"/>.
/// </exception>
/// <seealso cref="Bind{TOptions}(OptionsBuilder{TOptions}, IConfiguration, Action{BinderOptions})"/>
public static OptionsBuilder<TOptions> BindConfiguration<TOptions>(
this OptionsBuilder<TOptions> optionsBuilder,
string configSectionPath,
Action<BinderOptions> configureBinder = null)
where TOptions : class
{
_ = optionsBuilder ?? throw new ArgumentNullException(nameof(optionsBuilder));
_ = configSectionPath ?? throw new ArgumentNullException(nameof(configSectionPath));

optionsBuilder.Configure<IConfiguration>((opts, config) =>
{
IConfiguration section = string.Equals("", configSectionPath, StringComparison.OrdinalIgnoreCase)
? config
: config.GetSection(configSectionPath);
section.Bind(opts, configureBinder);
});
return optionsBuilder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Extensions.Options.ConfigurationExtensions.Tests
{
public class FakeOptions
{
public string? Message { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Nullable>enable</Nullable>
<TargetFrameworks>$(NetCoreAppCurrent);net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration\src\Microsoft.Extensions.Configuration.csproj"/>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj"/>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Options\src\Microsoft.Extensions.Options.csproj"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\Microsoft.Extensions.Options.ConfigurationExtensions.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Microsoft.Extensions.Options.ConfigurationExtensions.Tests
{
public class OptionsBuidlerConfigurationExtensionsTests
{
[Fact]
public static void BindConfiguration_ThrowsForNullBuilder()
{
OptionsBuilder<FakeOptions> optionsBuilder = null!;

Assert.Throws<ArgumentNullException>("optionsBuilder", () =>
{
optionsBuilder.BindConfiguration("test");
});
}

[Fact]
public static void BindConfiguration_ThrowsForNullConfigurationSectionPath()
{
var services = new ServiceCollection();
var optionsBuilder = new OptionsBuilder<FakeOptions>(services, Options.DefaultName);
string configSectionPath = null!;

Assert.Throws<ArgumentNullException>("configSectionPath", () =>
{
optionsBuilder.BindConfiguration(configSectionPath);
});
}

[Fact]
public static void BindConfiguration_ReturnsSameBuilderInstance()
{
var services = new ServiceCollection();
var optionsBuilder = new OptionsBuilder<FakeOptions>(services, Options.DefaultName);

var returnedBuilder = optionsBuilder.BindConfiguration("Test");

Assert.Same(optionsBuilder, returnedBuilder);
}

[Fact]
public static void BindConfiguration_OptionsMaterializationThrowsIfNoConfigurationInDI()
{
var services = new ServiceCollection();
var optionsBuilder = services.AddOptions<FakeOptions>();

_ = optionsBuilder.BindConfiguration("Test");
using ServiceProvider serviceProvider = services.BuildServiceProvider();

Assert.ThrowsAny<InvalidOperationException>(() =>
{
_ = serviceProvider.GetRequiredService<IOptions<FakeOptions>>();
});
}

[Fact]
public static void BindConfiguration_UsesConfigurationSectionPath()
{
const string configSectionName = "Test";
const string messageValue = "This is a test";
var configEntries = new Dictionary<string, string>
{
[ConfigurationPath.Combine(configSectionName, nameof(FakeOptions.Message))] = messageValue
};
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
.AddInMemoryCollection(configEntries)
.Build());
var optionsBuilder = services.AddOptions<FakeOptions>();

_ = optionsBuilder.BindConfiguration(configSectionName);

using ServiceProvider serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<FakeOptions>>().Value;

Assert.Equal(messageValue, options.Message);
}

[Fact]
public static void BindConfiguration_UsesConfigurationRootIfSectionNameIsEmptyString()
{
const string messageValue = "This is a test";
var configEntries = new Dictionary<string, string>
{
[nameof(FakeOptions.Message)] = messageValue
};
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
.AddInMemoryCollection(configEntries)
.Build());
var optionsBuilder = services.AddOptions<FakeOptions>();

_ = optionsBuilder.BindConfiguration(configSectionPath: "");

using ServiceProvider serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<FakeOptions>>().Value;

Assert.Equal(messageValue, options.Message);
}
}
}

0 comments on commit 5870d4f

Please sign in to comment.