diff --git a/README.md b/README.md index bd5b0c9..f1aba6b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Samhammer.Options +# Samhammer.Options ## Usage @@ -93,6 +93,29 @@ appsettings.json } ``` +### How to get options from IConfiguration ## +It is possible to bind options directly from IConfiguration. \ +This can be helpful in Program.cs to use typed Options there, even before they are registered in IOC. + +```csharp +var builder = WebApplication.CreateBuilder(args); +var key = builder.Configuration.GetOptions().MyKey; +``` + +```csharp +[Option] +public class ExampleOptions{ + public string MyKey { get; set; } +} +``` + +appsettings.json +```json +"ExampleOptions": { + "MyKey": "1234" +} +``` + ## Configuration Starting with version 3.1.5 all customizations needs to be done with the options action. diff --git a/src/Samhammer.Options.Test/ConfigurationExtensionsTest.cs b/src/Samhammer.Options.Test/ConfigurationExtensionsTest.cs new file mode 100644 index 0000000..fda64b2 --- /dev/null +++ b/src/Samhammer.Options.Test/ConfigurationExtensionsTest.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Samhammer.Options.Abstractions; +using Xunit; + +namespace Samhammer.Options.Test +{ + public class ConfigurationExtensionsTest + { + private readonly ConfigurationBuilder configurationBuilder; + + private readonly List> configurationValues; + + private IConfiguration Configuration => configurationBuilder.Build(); + + public ConfigurationExtensionsTest() + { + configurationBuilder = new ConfigurationBuilder(); + configurationValues = new List>(); + configurationBuilder.AddInMemoryCollection(configurationValues); + } + + [Fact] + public void GetOptions_FromConfiguration_WithAttribute() + { + // arrange + configurationValues.Add(new KeyValuePair("TestOptions:Url", "http://localhost")); + + // act + var options = Configuration.GetOptions(); + + // assert + var expectedOptions = new TestOptionsWithAttribute { Url = "http://localhost" }; + options.Should().BeEquivalentTo(expectedOptions); + } + + [Fact] + public void GetOptions_FromConfiguration_WithRootAttribute() + { + // arrange + configurationValues.Add(new KeyValuePair("Url", "http://localhost")); + + // act + var options = Configuration.GetOptions(); + + // assert + var expectedOptions = new TestOptionsWithRootAttribute { Url = "http://localhost" }; + options.Should().BeEquivalentTo(expectedOptions); + } + + [Fact] + public void GetOption_FromConfiguration_WithoutAttribute() + { + // arrange + configurationValues.Add(new KeyValuePair("TestOptions:Url", "http://localhost")); + + // act + var options = Configuration.GetOptions(); + + // assert + var expectedOptions = new TestOptions { Url = "http://localhost" }; + options.Should().BeEquivalentTo(expectedOptions); + } + + [Option(sectionName: "TestOptions")] + private class TestOptionsWithAttribute + { + public string Url { get; set; } + } + + [Option(FromRootSection = true)] + private class TestOptionsWithRootAttribute + { + public string Url { get; set; } + } + + private class TestOptions + { + public string Url { get; set; } + } + } +} diff --git a/src/Samhammer.Options.sln.DotSettings b/src/Samhammer.Options.sln.DotSettings index af4c5dc..81a365d 100644 --- a/src/Samhammer.Options.sln.DotSettings +++ b/src/Samhammer.Options.sln.DotSettings @@ -6,8 +6,12 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> True True True True + True \ No newline at end of file diff --git a/src/Samhammer.Options/ConfigurationExtensions.cs b/src/Samhammer.Options/ConfigurationExtensions.cs new file mode 100644 index 0000000..55917f8 --- /dev/null +++ b/src/Samhammer.Options/ConfigurationExtensions.cs @@ -0,0 +1,23 @@ +using System.Linq; +using Microsoft.Extensions.Configuration; +using Samhammer.Options.Abstractions; +using Samhammer.Options.Utils; + +namespace Samhammer.Options +{ + public static class ConfigurationExtensions + { + public static T GetOptions(this IConfiguration configuration) where T : new() + { + var optionType = typeof(T); + var attributes = ReflectionUtils.GetAttributesOfType(optionType).ToList(); + + var section = attributes.Count > 0 + ? ConfigurationResolver.GetSection(configuration, optionType, attributes.First()) + : ConfigurationResolver.GetSection(configuration, optionType); + + var option = section.Get(); + return option != null ? option : new T(); + } + } +} diff --git a/src/Samhammer.Options/ConfigurationResolver.cs b/src/Samhammer.Options/ConfigurationResolver.cs new file mode 100644 index 0000000..78772ed --- /dev/null +++ b/src/Samhammer.Options/ConfigurationResolver.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.Extensions.Configuration; +using Samhammer.Options.Abstractions; + +namespace Samhammer.Options +{ + internal static class ConfigurationResolver + { + internal static IConfiguration GetSection(IConfiguration configuration, Type optionType, OptionAttribute optionAttribute) + { + var sectionName = GetSectionName(optionType, optionAttribute); + return optionAttribute.FromRootSection ? configuration : configuration.GetSection(sectionName); + } + + internal static IConfiguration GetSection(IConfiguration configuration, Type optionType) + { + var sectionName = GetSectionName(optionType); + return configuration.GetSection(sectionName); + } + + private static string GetSectionName(Type optionType, OptionAttribute attribute) + { + var key = attribute.SectionName; + + if (string.IsNullOrWhiteSpace(key)) + { + key = optionType.Name; + } + + return key; + } + + private static string GetSectionName(Type optionType) + { + return optionType.Name; + } + } +} diff --git a/src/Samhammer.Options/OptionsResolver.cs b/src/Samhammer.Options/OptionsResolver.cs index 2fc2158..aab3f17 100644 --- a/src/Samhammer.Options/OptionsResolver.cs +++ b/src/Samhammer.Options/OptionsResolver.cs @@ -51,25 +51,12 @@ public void ResolveConfigurations(IServiceCollection services) private void ResolveConfiguration(IServiceCollection services, Type optionType, OptionAttribute optionAttribute) { - var sectionName = GetSectionName(optionType, optionAttribute); - var section = optionAttribute.FromRootSection ? Configuration : Configuration.GetSection(sectionName); + var section = ConfigurationResolver.GetSection(Configuration, optionType, optionAttribute); var genericMethod = AddOptionMethod.MakeGenericMethod(optionType); genericMethod.Invoke(this, new object[] { services, section, optionAttribute.IocName }); } - private string GetSectionName(Type optionType, OptionAttribute attribute) - { - var key = attribute.SectionName; - - if (string.IsNullOrWhiteSpace(key)) - { - key = optionType.Name; - } - - return key; - } - private void AddOption(IServiceCollection services, IConfiguration section, string iocOptionName = null) where T : class { services.Configure(