Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Samhammer.Options
# Samhammer.Options

## Usage

Expand Down Expand Up @@ -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<ExampleOptions>().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.

Expand Down
83 changes: 83 additions & 0 deletions src/Samhammer.Options.Test/ConfigurationExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -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<KeyValuePair<string, string>> configurationValues;

private IConfiguration Configuration => configurationBuilder.Build();

public ConfigurationExtensionsTest()
{
configurationBuilder = new ConfigurationBuilder();
configurationValues = new List<KeyValuePair<string, string>>();
configurationBuilder.AddInMemoryCollection(configurationValues);
}

[Fact]
public void GetOptions_FromConfiguration_WithAttribute()
{
// arrange
configurationValues.Add(new KeyValuePair<string, string>("TestOptions:Url", "http://localhost"));

// act
var options = Configuration.GetOptions<TestOptionsWithAttribute>();

// assert
var expectedOptions = new TestOptionsWithAttribute { Url = "http://localhost" };
options.Should().BeEquivalentTo(expectedOptions);
}

[Fact]
public void GetOptions_FromConfiguration_WithRootAttribute()
{
// arrange
configurationValues.Add(new KeyValuePair<string, string>("Url", "http://localhost"));

// act
var options = Configuration.GetOptions<TestOptionsWithRootAttribute>();

// assert
var expectedOptions = new TestOptionsWithRootAttribute { Url = "http://localhost" };
options.Should().BeEquivalentTo(expectedOptions);
}

[Fact]
public void GetOption_FromConfiguration_WithoutAttribute()
{
// arrange
configurationValues.Add(new KeyValuePair<string, string>("TestOptions:Url", "http://localhost"));

// act
var options = Configuration.GetOptions<TestOptions>();

// 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; }
}
}
}
4 changes: 4 additions & 0 deletions src/Samhammer.Options.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a4f433b8_002Dabcd_002D4e55_002Da08f_002D82e78cef0f0c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"&gt;&lt;ElementKinds&gt;&lt;Kind Name="LOCAL_CONSTANT" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary>
23 changes: 23 additions & 0 deletions src/Samhammer.Options/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<T>(this IConfiguration configuration) where T : new()
{
var optionType = typeof(T);
var attributes = ReflectionUtils.GetAttributesOfType<OptionAttribute>(optionType).ToList();

var section = attributes.Count > 0
? ConfigurationResolver.GetSection(configuration, optionType, attributes.First())
: ConfigurationResolver.GetSection(configuration, optionType);

var option = section.Get<T>();
return option != null ? option : new T();
}
}
}
38 changes: 38 additions & 0 deletions src/Samhammer.Options/ConfigurationResolver.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
15 changes: 1 addition & 14 deletions src/Samhammer.Options/OptionsResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(IServiceCollection services, IConfiguration section, string iocOptionName = null) where T : class
{
services.Configure<T>(
Expand Down