├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-and-publish.yml │ ├── build-and-test.yml │ └── snyk-test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Examples ├── Example.AppConfig.DotNetFramework │ ├── App.config │ ├── Example.AppConfig.DotNetFramework.csproj │ └── Program.cs ├── Example.EnvironmentVariableConfig.DotNetCore │ ├── Example.EnvironmentVariableConfig.DotNetCore.csproj │ ├── Program.cs │ └── appsettings.json ├── Example.FileConfig.DotNetCore │ ├── Example.FileConfig.DotNetCore.csproj │ ├── Program.cs │ └── appsettings.json └── Example.MemoryLeakTest │ ├── Example.MemoryLeakTest.csproj │ └── Program.cs ├── LICENSE.md ├── README.md ├── RockLib.Configuration.All.sln ├── RockLib.Configuration.AspNetCore ├── .editorconfig ├── AspNetExtensions.cs ├── CHANGELOG.md ├── Directory.Build.props ├── RockLib.Configuration.AspNetCore.csproj └── RockLib.Configuration.AspNetCore.sln ├── RockLib.Configuration.MessagingProvider ├── .editorconfig ├── AssemblyInformation.cs ├── BlocklistSettingFilter.cs ├── CHANGELOG.md ├── Directory.Build.props ├── ISettingFilter.cs ├── ListSettingFilterExtensions.cs ├── MessagingConfigurationProvider.cs ├── MessagingConfigurationSource.cs ├── NullSettingFilter.cs ├── README.md ├── RockLib.Configuration.MessagingProvider.csproj ├── RockLib.Configuration.MessagingProvider.sln ├── RockLibMessagingProviderExtensions.cs └── SafelistSettingFilter.cs ├── RockLib.Configuration.ObjectFactory ├── .editorconfig ├── AlternateNameAttribute.cs ├── AssemblyInformation.cs ├── CHANGELOG.md ├── ConfigReloadingProxy.cs ├── ConfigReloadingProxyFactory.cs ├── ConfigReloadingProxyReferenceModel.cs ├── ConfigSectionAttribute.cs ├── ConfigurationObjectFactory.cs ├── ConstructorOrderInfo.cs ├── ConvertMethodAttribute.cs ├── DefaultTypeAttribute.cs ├── DefaultTypes.cs ├── Directory.Build.props ├── Exceptions.cs ├── IResolver.cs ├── InternalsVisibleTo.cs ├── Members.cs ├── NullableAttributes.cs ├── README.md ├── Resolver.cs ├── RockLib.Configuration.ObjectFactory.csproj ├── RockLib.Configuration.ObjectFactory.sln └── ValueConverters.cs ├── RockLib.Configuration.ProxyFactory ├── .editorconfig ├── CHANGELOG.md ├── ConfigurationProxyFactory.cs ├── Directory.Build.props ├── Exceptions.cs ├── InternalsVisibleTo.cs ├── README.md ├── RockLib.Configuration.ProxyFactory.csproj └── RockLib.Configuration.ProxyFactory.sln ├── RockLib.Configuration ├── .editorconfig ├── AppSettings.cs ├── CHANGELOG.md ├── CompositeConfigurationExtensions.cs ├── CompositeConfigurationSection.cs ├── Config.cs ├── Directory.Build.props ├── README.md ├── RockLib.Configuration.csproj ├── RockLib.Configuration.sln ├── RockLibConfigurationBuilderExtensions.cs ├── Semimutable.cs └── SoftLock.cs ├── Tests ├── RockLib.Configuration.AppSettingsConfigTests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── ConfigTests.cs │ ├── Directory.Build.props │ ├── RockLib.Configuration.AppSettingsConfigTests.csproj │ ├── appsettings.development.json │ ├── appsettings.json │ └── appsettings.staging.json ├── RockLib.Configuration.AspNetCore.Tests │ ├── .editorconfig │ ├── AspNetExtensionsTests.cs │ ├── Directory.Build.props │ └── RockLib.Configuration.AspNetCore.Tests.csproj ├── RockLib.Configuration.CustomConfigTests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── ConfigTests.cs │ ├── Directory.Build.props │ └── RockLib.Configuration.CustomConfigTests.csproj ├── RockLib.Configuration.MessagingProvider.Tests │ ├── .editorconfig │ ├── BlocklistSettingFilterTests.cs │ ├── Directory.Build.props │ ├── FakeReceiver.cs │ ├── MessagingConfigurationProviderTests.cs │ ├── MessagingConfigurationSourceTests.cs │ ├── NullSettingFilterTests.cs │ ├── RockLib.Configuration.MessagingProvider.Tests.csproj │ ├── RockLibMessagingProviderExtensionsTests.cs │ ├── SafelistSettingFilterTests.cs │ └── appsettings.json ├── RockLib.Configuration.ObjectFactory.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── ConfigReloadingProxyFactoryTests.cs │ ├── ConfigReloadingProxyReferenceModelTests.cs │ ├── ConfigurationObjectFactorySadPathTests.cs │ ├── ConfigurationObjectFactoryTests.cs │ ├── ConstructorOrderInfoTests.cs │ ├── DefaultTypesTests.cs │ ├── Directory.Build.props │ ├── ResolverTests.cs │ ├── RockLib.Configuration.ObjectFactory.Tests.csproj │ └── ValueConvertersTests.cs ├── RockLib.Configuration.ProxyFactory.Tests │ ├── .editorconfig │ ├── Directory.Build.props │ ├── ProxyFactoryTests.cs │ └── RockLib.Configuration.ProxyFactory.Tests.csproj └── RockLib.Configuration.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── CompositeSectionTests.cs │ ├── Directory.Build.props │ └── RockLib.Configuration.Tests.csproj └── icon.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of change: 14 | 15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc) 16 | 2. Bug fix (non-breaking change that fixes an issue) 17 | 3. New feature (non-breaking change that adds functionality) 18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected) 19 | 20 | ## Checklist: 21 | 22 | - Have you reviewed your own code? Do you understand every change? 23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)? 24 | - Have you added tests that prove your fix is effective or that this feature works? 25 | - New and existing unit tests pass locally with these changes? 26 | - Have you made corresponding changes to the documentation? 27 | - Will this change require an update to an example project? (if so, create an issue and link to it) 28 | 29 | --- 30 | 31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_ 32 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run when a release is published. 8 | release: 9 | types: [ released, prereleased ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared build-and-publish workflow. 16 | call_build_and_publish: 17 | name: Call build-and-publish workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/build-and-publish.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run on pull requests to the main branch. 8 | pull_request: 9 | branches: [ main ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared unit-test workflow. 16 | call_unit_test: 17 | name: Call unit-test workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/unit-test.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/snyk-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Snyk Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run after unit-test is completed. 8 | workflow_run: 9 | workflows: [ Run Unit Test ] 10 | types: [ completed ] 11 | 12 | #################################################################################################### 13 | ## WORKFLOW JOBS 14 | #################################################################################################### 15 | jobs: 16 | # Calls the shared snyk-test workflow. 17 | call_snyk_test: 18 | name: Call snyk-test workflow 19 | uses: RockLib/RockLib.Workflows/.github/workflows/snyk-test.yml@main 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at RockLibSupport@quickenloans.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Examples/Example.AppConfig.DotNetFramework/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Examples/Example.AppConfig.DotNetFramework/Example.AppConfig.DotNetFramework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Examples/Example.AppConfig.DotNetFramework/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RockLib.Configuration; 3 | 4 | namespace Example.AppConfig.DotNetFramework 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | Console.WriteLine("App.Config File Example Harness"); 11 | 12 | var key100 = Config.AppSettings["Key100"]; 13 | 14 | Console.WriteLine($"Key100: {key100}"); 15 | 16 | Console.ReadLine(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Example.EnvironmentVariableConfig.DotNetCore/Example.EnvironmentVariableConfig.DotNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Always 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Examples/Example.EnvironmentVariableConfig.DotNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using RockLib.Configuration; 3 | using System; 4 | using Microsoft.Extensions.Configuration; 5 | using RockLib.Configuration.ObjectFactory; 6 | 7 | namespace Example.EnvironmentVariableConfig.DotNetCore 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | Console.WriteLine("RockLib.Configuration Environment Variable Configuration Example"); 14 | 15 | // Note that the current version (0.0.1-alpha02) requires that the rocklib.config.json file exists. 16 | // In order to successfully load, this file must a valid JSON object. A config file with an empty 17 | // JSON object (open & close curly braces) is the current work-around for this. 18 | 19 | SetEnvironmentVariables(); 20 | 21 | var testKey1 = Config.AppSettings["test_key1"]; 22 | var testKey2 = Config.AppSettings["test_key2"]; 23 | 24 | IConfigurationSection fooConfigSection = Config.Root.GetSection("foo_section"); 25 | Foo foo = fooConfigSection.Create(); 26 | 27 | IConfigurationSection quxConfigSection = Config.Root.GetSection("qux_section"); 28 | Qux qux = quxConfigSection.Create(); 29 | 30 | Console.WriteLine($"testKey1: {testKey1}"); 31 | Console.WriteLine($"testKey2: {testKey2}"); 32 | Console.WriteLine($"foo: {JsonConvert.SerializeObject(foo)}"); 33 | Console.WriteLine($"qux: {JsonConvert.SerializeObject(qux)}"); 34 | 35 | Console.ReadLine(); 36 | } 37 | 38 | private static void SetEnvironmentVariables() 39 | { 40 | // Note: these environment variables could be set up on any level (machine, user, process) with 41 | // the same values, and this example would still work. Setting environment variables programmatically 42 | // means that they are set on the process level. 43 | 44 | Environment.SetEnvironmentVariable("AppSettings:test_key1", "123"); 45 | Environment.SetEnvironmentVariable("AppSettings:test_key2", "789"); 46 | 47 | Environment.SetEnvironmentVariable("foo_section:Bar", "123.45"); 48 | Environment.SetEnvironmentVariable("foo_section:Baz", "abc"); 49 | 50 | Environment.SetEnvironmentVariable("qux_section:Garply", "Fred"); 51 | Environment.SetEnvironmentVariable("qux_section:Foos:0:Bar", "1.1"); 52 | Environment.SetEnvironmentVariable("qux_section:Foos:0:Baz", "aaa"); 53 | Environment.SetEnvironmentVariable("qux_section:Foos:1:Bar", "2.2"); 54 | Environment.SetEnvironmentVariable("qux_section:Foos:1:Baz", "bbb"); 55 | Environment.SetEnvironmentVariable("qux_section:Foos:2:Bar", "3.3"); 56 | Environment.SetEnvironmentVariable("qux_section:Foos:2:Baz", "ccc"); 57 | } 58 | } 59 | 60 | public class Foo 61 | { 62 | public double Bar { get; set; } 63 | public string Baz { get; set; } 64 | } 65 | 66 | public class Qux 67 | { 68 | public Garply Garply { get; set; } 69 | 70 | public Foo[] Foos { get; set; } 71 | } 72 | 73 | public enum Garply 74 | { 75 | Grault, 76 | Thud, 77 | Fred 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Examples/Example.EnvironmentVariableConfig.DotNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Examples/Example.FileConfig.DotNetCore/Example.FileConfig.DotNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Always 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Examples/Example.FileConfig.DotNetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Newtonsoft.Json; 3 | using RockLib.Configuration; 4 | using System; 5 | using RockLib.Configuration.ObjectFactory; 6 | 7 | namespace Example.FileConfig.DotNetCore 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | Console.WriteLine("Configuration Manager Example Harness"); 14 | 15 | try 16 | { 17 | var key1 = Config.AppSettings["Key1"]; 18 | string defaultConnectionString = Config.Root.GetConnectionString("Default"); 19 | 20 | IConfigurationSection fooConfigSection = Config.Root.GetSection("foo_section"); 21 | Foo foo = fooConfigSection.Create(); 22 | 23 | IConfigurationSection foo2ConfigSection = Config.Root.GetSection("foo_section"); 24 | Foo foo2 = foo2ConfigSection.Create(); 25 | 26 | Console.WriteLine($"key1: {key1}"); 27 | Console.WriteLine($"defaultConnectionString: {defaultConnectionString}"); 28 | Console.WriteLine($"foo: {JsonConvert.SerializeObject(foo)}"); 29 | Console.WriteLine($"foo is same instance as foo2: {ReferenceEquals(foo, foo2)}"); 30 | 31 | var notFound = Config.AppSettings["notFound"]; 32 | } 33 | catch (Exception e) 34 | { 35 | Console.WriteLine(e); 36 | } 37 | 38 | Console.ReadLine(); 39 | 40 | } 41 | 42 | public class Foo 43 | { 44 | public int Bar { get; set; } 45 | public string Baz { get; set; } 46 | public bool Qux { get; set; } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Examples/Example.FileConfig.DotNetCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "Key1": "Key1", 4 | "Key2": "Key2" 5 | }, 6 | "ConnectionStrings": { 7 | "Default": "schema://path" 8 | }, 9 | "Foo": { 10 | "Bar": 123, 11 | "Baz": "abc", 12 | "Qux": true 13 | } 14 | } -------------------------------------------------------------------------------- /Examples/Example.MemoryLeakTest/Example.MemoryLeakTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Examples/Example.MemoryLeakTest/Program.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Configuration; 2 | using RockLib.Configuration.ObjectFactory; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | var baseWorkingSet = Process.GetCurrentProcess().WorkingSet64; 10 | 11 | while (true) 12 | { 13 | await new Tester().Test(); 14 | 15 | var tasks = new List(); 16 | 17 | for (var i = 0; i < 8; i++) 18 | { 19 | tasks.Add(Task.Run(async () => await new Tester().Test())); 20 | } 21 | 22 | await Task.WhenAll(tasks); 23 | 24 | var currentWorkingSet = Process.GetCurrentProcess().WorkingSet64; 25 | //Console.WriteLine($"Current count: {Tester._total}"); 26 | Console.WriteLine($"Current working set: {currentWorkingSet} - diff is {((currentWorkingSet - baseWorkingSet) / (double)baseWorkingSet) * 100}"); 27 | Console.ReadLine(); 28 | } 29 | 30 | public class Tester 31 | { 32 | private static int _counter = 0; 33 | public static int _total = 0; 34 | 35 | public async Task Test() 36 | { 37 | for (int i = 0; i < 10000; i++) 38 | { 39 | var count = i; 40 | Log(count); 41 | } 42 | 43 | await Task.CompletedTask; 44 | } 45 | 46 | public interface ILogger : IDisposable { } 47 | 48 | public class Logger : ILogger 49 | { 50 | public Logger() { } 51 | 52 | public void Dispose() { } 53 | } 54 | 55 | public void Log(int i) 56 | { 57 | Interlocked.Increment(ref _counter); 58 | 59 | var config = Config.Root.GetCompositeSection("RockLib_Logging", "RockLib.Logging"); 60 | var defaultTypes = new DefaultTypes(); 61 | if (!defaultTypes.TryGet(typeof(ILogger), out var dummy)) 62 | defaultTypes.Add(typeof(ILogger), typeof(Logger)); 63 | 64 | ILogger logger = null; 65 | try 66 | { 67 | logger = config.CreateReloadingProxy(defaultTypes, null, null); 68 | } 69 | finally 70 | { 71 | //logger.Dispose(); 72 | } 73 | Interlocked.Decrement(ref _counter); 74 | Interlocked.Increment(ref _total); 75 | 76 | int count = _total; 77 | 78 | //if (count % 1000 == 0) 79 | // Console.WriteLine(count); 80 | } 81 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017-2021 Rocket Mortgage 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository contains the source code for four nuget packages: 2 | 3 | ### [RockLib.Configuration](RockLib.Configuration/README.md) 4 | 5 | Defines a static `Config` class as a general replacement for the old .NET Framework `ConfigurationManager` class. 6 | 7 | ### [RockLib.Configuration.ObjectFactory](RockLib.Configuration.ObjectFactory/README.md) 8 | 9 | An alternative to `Microsoft.Extensions.Configuration.Binder` that supports non-default constructors and other features commonly found in JSON and XML serializers. 10 | 11 | ### [RockLib.Configuration.ProxyFactory](RockLib.Configuration.ProxyFactory/README.md) 12 | 13 | A factory that creates instances of property-only interfaces, defined at run-time, and populated with values defined in an instance of `IConfiguration`. 14 | 15 | ### [RockLib.Configuration.MessagingProvider](RockLib.Configuration.MessagingProvider/README.md) 16 | 17 | A configuration provider that reloads when it receives a message containing configuration changes from a `RockLib.Messaging.IReceiver`. 18 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/AspNetExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | 5 | namespace RockLib.Configuration.AspNetCore 6 | { 7 | /// 8 | /// Extension methods for ASP.NET Core. 9 | /// 10 | public static class AspNetExtensions 11 | { 12 | /// 13 | /// Sets the value of the property to the 14 | /// containing the merged configuration of the application and the . 15 | /// 16 | /// The to configure. 17 | /// The . 18 | public static IWebHostBuilder SetConfigRoot(this IWebHostBuilder builder) 19 | { 20 | #if NET6_0_OR_GREATER 21 | ArgumentNullException.ThrowIfNull(builder); 22 | #else 23 | if (builder is null) 24 | { 25 | throw new ArgumentNullException(nameof(builder)); 26 | } 27 | #endif 28 | builder.ConfigureServices((context, services) => 29 | { 30 | if (!Config.IsLocked && Config.IsDefault) 31 | Config.SetRoot(context.Configuration); 32 | }); 33 | return builder; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Configuration.AspNetCore Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 4.0.0 - 2025-01-29 9 | 10 | #### Changed 11 | - Finalized the 4.0.0 version 12 | 13 | ## 4.0.0-alpha.1 - 2025-01-29 14 | 15 | #### Changed 16 | - Removed .NET 6 as a target framework 17 | - Updated RockLib.Configuration to 5.0.0 18 | 19 | ## 3.0.1 - 2024-07-15 20 | 21 | #### Changed 22 | - Updated RockLib.Configuration to 4.0.1 for vulnerability fix. 23 | 24 | ## 3.0.0 - 2024-02-14 25 | 26 | #### Changed 27 | - Finalized the 3.0.0 version 28 | 29 | ## 3.0.0-alpha.1 - 2023-12-14 30 | 31 | #### Changed 32 | - Removed .NET Core 3.1 as a TFM and added .NET 8. Supported targets are now .NET 4.8, .NET 6, and .NET 8. 33 | 34 | ## 2.0.0 - 2022-02-15 35 | 36 | #### Added 37 | 38 | - Added .editorconfig and Directory.Build.props files to ensure consistency. 39 | 40 | #### Changed 41 | 42 | - Supported targets: net6.0, netcoreapp3.1, and net48. 43 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 44 | 45 | ## 1.0.5 - 2021-08-11 46 | 47 | #### Changed 48 | 49 | - Changes "Quicken Loans" to "Rocket Mortgage". 50 | - Updates RockLib.Configuration to latest version, [2.5.3](https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration/CHANGELOG.md#253---2021-08-11). 51 | 52 | ## 1.0.4 - 2021-05-06 53 | 54 | #### Added 55 | 56 | - Adds SourceLink to nuget package. 57 | 58 | #### Changed 59 | 60 | - Updates RockLib.Configuration package to latest version, which includes SourceLink. 61 | - Updates Microsoft.AspNetCore.Hosting.Abstractions package to latest version. 62 | - For net5.0, use framework reference instead of package reference. 63 | 64 | ---- 65 | 66 | **Note:** Release notes in the above format are not available for earlier versions of 67 | RockLib.Configuration.AspNetCore. What follows below are the original release notes. 68 | 69 | ---- 70 | 71 | ## 1.0.3 72 | 73 | Adds net5.0 target. 74 | 75 | ## 1.0.2 76 | 77 | Adds icon to project and nuget package. 78 | 79 | ## 1.0.1 80 | 81 | Updates to align with Nuget conventions. 82 | 83 | ## 1.0.0 84 | 85 | Adds `SetConfigRoot` extension method for `IWebHostBuilder`. 86 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/RockLib.Configuration.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | RockLib.Configuration.AspNetCore 4 | 4.0.0 5 | Extension methods for RockLib.Configuration and ASP.NET Core. 6 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 7 | false 8 | A changelog is available at https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.AspNetCore/CHANGELOG.md. 9 | https://github.com/RockLib/RockLib.Configuration/tree/master/RockLib.Configuration.AspNetCore 10 | LICENSE.md 11 | icon.png 12 | RockLib Configuration AspNetCore Extensions 13 | 4.0.0 14 | True 15 | True 16 | True 17 | Embedded 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RockLib.Configuration.AspNetCore/RockLib.Configuration.AspNetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.AspNetCore", "RockLib.Configuration.AspNetCore.csproj", "{CFD500EF-CB3D-4381-8F0F-D62B4FE36080}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.AspNetCore.Tests", "..\Tests\RockLib.Configuration.AspNetCore.Tests\RockLib.Configuration.AspNetCore.Tests.csproj", "{E94CEE70-6820-46C5-B2A7-9FBF8D406000}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {CFD500EF-CB3D-4381-8F0F-D62B4FE36080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {CFD500EF-CB3D-4381-8F0F-D62B4FE36080}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {CFD500EF-CB3D-4381-8F0F-D62B4FE36080}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {CFD500EF-CB3D-4381-8F0F-D62B4FE36080}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {E94CEE70-6820-46C5-B2A7-9FBF8D406000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {E94CEE70-6820-46C5-B2A7-9FBF8D406000}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {E94CEE70-6820-46C5-B2A7-9FBF8D406000}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {E94CEE70-6820-46C5-B2A7-9FBF8D406000}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {DF36CA15-3280-487B-AA81-A9F8C00A4CBB} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/AssemblyInformation.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("RockLib.Configuration.MessagingProvider.Tests")] -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/BlocklistSettingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Configuration.MessagingProvider 5 | { 6 | /// 7 | /// An implementation of that does not allow changes to 8 | /// settings that are members of a blocklist. 9 | /// 10 | public sealed class BlocklistSettingFilter : ISettingFilter 11 | { 12 | private readonly HashSet _blockedSettings; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The collection of settings (and their child settings) that will be blocked. 19 | /// 20 | /// 21 | /// An optional setting filter that is applied if a setting is not a 22 | /// member of . 23 | /// 24 | public BlocklistSettingFilter(IEnumerable blockedSettings, ISettingFilter? innerFilter = null) 25 | { 26 | #if NET6_0_OR_GREATER 27 | ArgumentNullException.ThrowIfNull(blockedSettings); 28 | #else 29 | if (blockedSettings is null) 30 | { 31 | throw new ArgumentNullException(nameof(blockedSettings)); 32 | } 33 | #endif 34 | _blockedSettings = new HashSet(blockedSettings, StringComparer.OrdinalIgnoreCase); 35 | InnerFilter = innerFilter ?? NullSettingFilter.Instance; 36 | } 37 | 38 | /// 39 | /// Gets the collection of settings (and their child settings) that will be blocked. 40 | /// 41 | public IEnumerable BlockedSettings => _blockedSettings; 42 | 43 | /// 44 | /// Gets the inner setting filter that is applied if a setting is not a 45 | /// member of . 46 | /// 47 | public ISettingFilter InnerFilter { get; } 48 | 49 | /// 50 | /// Returns whether the specified setting should be allowed to be changed. 51 | /// 52 | /// The setting to potentially change. 53 | /// 54 | /// The headers of the message that was received that has initiated the change. 55 | /// 56 | /// 57 | /// if the setting is allowed to be changed; otherwise 58 | /// if the setting is not allowed to be changed. 59 | /// 60 | public bool ShouldProcessSettingChange(string setting, IReadOnlyDictionary receivedMessageHeaders) => 61 | !_blockedSettings.HasSetting(setting) && InnerFilter.ShouldProcessSettingChange(setting, receivedMessageHeaders); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Configuration.MessagingProvider Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 4.0.0 - 2025-01-29 9 | 10 | #### Removed 11 | - Removed support for .NET 6. 12 | 13 | ## 3.1.1 - 2024-07-19 14 | 15 | #### Changed 16 | - RockLib.Messaging.4.0.0 -> RockLib.Messaging.4.0.1 for vulnerability fix. 17 | 18 | ## 3.1.0 - 2024-03-12 19 | 20 | #### Changed 21 | - Finalized 3.1.0 version. 22 | 23 | ## 3.1.0-alpha.1 - 2024-03-11 24 | 25 | #### Changed 26 | - Updated RockLib.Messaging package reference to 4.0.0. 27 | 28 | ## 3.0.0 - 2024-02-14 29 | 30 | #### Changed 31 | - Finalized 3.0.0 version. 32 | 33 | ## 3.0.0-alpha.1 - 2023-12-14 34 | 35 | #### Changed 36 | - Removed .NET Core 3.1 as a TFM and added .NET 8. Supported targets are now .NET 4.8, .NET 6, and .NET 8. 37 | 38 | ## 2.0.0 - 2022-03-14 39 | 40 | #### Added 41 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 42 | 43 | #### Changed 44 | - Supported targets: net6.0, netcoreapp3.1, and net48. 45 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 46 | - Changes "Quicken Loans" to "Rocket Mortgage". 47 | 48 | ## 1.0.5 - 2021-05-06 49 | 50 | #### Added 51 | 52 | - Adds SourceLink to nuget package. 53 | 54 | #### Changed 55 | 56 | - Updates RockLib.Messaging package to latest version, which includes SourceLink. 57 | - Updates Newtonsoft.Json package to latest version. 58 | 59 | ---- 60 | 61 | **Note:** Release notes in the above format are not available for earlier versions of 62 | RockLib.Configuration.MessagingProvider. What follows below are the original release notes. 63 | 64 | ---- 65 | 66 | ## 1.0.4 67 | 68 | Adds net5.0 target. 69 | 70 | ## 1.0.3 71 | 72 | Adds icon to project and nuget package. 73 | 74 | ## 1.0.2 75 | 76 | Update dependency package. 77 | 78 | ## 1.0.1 79 | 80 | Updates to align with Nuget conventions. 81 | 82 | ## 1.0.0 83 | 84 | Initial release. 85 | 86 | ## 1.0.0-alpha01 87 | 88 | Initial prerelease. 89 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/ISettingFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RockLib.Configuration.MessagingProvider 4 | { 5 | /// 6 | /// Defines an object that can determine if a specific setting from a received message 7 | /// is allowed to be changed. 8 | /// 9 | public interface ISettingFilter 10 | { 11 | /// 12 | /// Returns whether the specified setting should be allowed to be changed. 13 | /// 14 | /// The setting to potentially change. 15 | /// 16 | /// The headers of the message that was received that has initiated the change. 17 | /// 18 | /// 19 | /// if the setting is allowed to be changed; otherwise 20 | /// if the setting is not allowed to be changed. 21 | /// 22 | bool ShouldProcessSettingChange(string setting, IReadOnlyDictionary receivedMessageHeaders); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/ListSettingFilterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RockLib.Configuration.MessagingProvider 4 | { 5 | internal static class ListSettingFilterExtensions 6 | { 7 | internal static bool HasSetting(this HashSet settings, string setting) 8 | { 9 | foreach (var key in SelfAndAncestors(setting)) 10 | { 11 | if (settings.Contains(key)) 12 | { 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | 19 | private static IEnumerable SelfAndAncestors(string setting) 20 | { 21 | yield return setting; 22 | var index = setting.LastIndexOf(':'); 23 | if (index != -1) 24 | { 25 | #if NET48 26 | foreach (var ancestor in SelfAndAncestors(setting.Substring(0, index))) 27 | #else 28 | foreach (var ancestor in SelfAndAncestors(setting[..index])) 29 | #endif 30 | { 31 | yield return ancestor; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/MessagingConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Newtonsoft.Json; 3 | using RockLib.Messaging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace RockLib.Configuration.MessagingProvider 9 | { 10 | /// 11 | /// An that reloads when it receives 12 | /// a message containing configuration changes. 13 | /// 14 | public sealed class MessagingConfigurationProvider : ConfigurationProvider 15 | { 16 | internal MessagingConfigurationProvider(IReceiver receiver, ISettingFilter? settingFilter) 17 | { 18 | Receiver = receiver; 19 | SettingFilter = settingFilter ?? NullSettingFilter.Instance; 20 | Receiver.Start(OnMessageReceivedAsync); 21 | } 22 | 23 | /// 24 | /// Gets the that receives messages for changing 25 | /// configuration values. 26 | /// 27 | public IReceiver Receiver { get; } 28 | 29 | /// 30 | /// Gets the that is applied to each setting of each 31 | /// received message. 32 | /// 33 | public ISettingFilter SettingFilter { get; } 34 | 35 | private async Task OnMessageReceivedAsync(IReceiverMessage message) 36 | { 37 | var newSettings = new Dictionary(StringComparer.OrdinalIgnoreCase); 38 | 39 | try 40 | { 41 | JsonConvert.PopulateObject(message.StringPayload, newSettings); 42 | } 43 | catch (Exception ex) when (ex is JsonReaderException || ex is JsonSerializationException) 44 | { 45 | await message.RejectAsync().ConfigureAwait(false); 46 | return; 47 | } 48 | 49 | if (IsChanged(newSettings, message.Headers)) 50 | { 51 | Data = newSettings; 52 | OnReload(); 53 | } 54 | 55 | await message.AcknowledgeAsync().ConfigureAwait(false); 56 | } 57 | 58 | private bool IsChanged(Dictionary newSettings, HeaderDictionary headers) 59 | { 60 | foreach (var newSetting in newSettings) 61 | { 62 | #if NET8_0_OR_GREATER 63 | if (Data.TryGetValue(newSetting.Key, out var value)) 64 | { 65 | if (value != newSetting.Value) 66 | { 67 | return true; 68 | } 69 | } 70 | #else 71 | if (Data.ContainsKey(newSetting.Key)) 72 | { 73 | if (Data[newSetting.Key] != newSetting.Value) 74 | { 75 | return true; 76 | } 77 | } 78 | #endif 79 | else if (SettingFilter.ShouldProcessSettingChange(newSetting.Key, headers)) 80 | { 81 | return true; 82 | } 83 | } 84 | 85 | foreach (var oldSetting in Data) 86 | { 87 | if (!newSettings.ContainsKey(oldSetting.Key)) 88 | { 89 | return true; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/MessagingConfigurationSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using RockLib.Messaging; 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace RockLib.Configuration.MessagingProvider 7 | { 8 | /// 9 | /// An that creates an 10 | /// that reloads when it receives a message containing configuration changes. 11 | /// 12 | public sealed class MessagingConfigurationSource : IConfigurationSource 13 | { 14 | private static readonly ConditionalWeakTable _validationCache = new ConditionalWeakTable(); 15 | 16 | private readonly Lazy _cachedProvider; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// 22 | /// The that will receive messages in order to change 23 | /// configuration values. 24 | /// 25 | /// 26 | /// The that is applied to each setting of each 27 | /// received message. 28 | /// 29 | public MessagingConfigurationSource(IReceiver receiver, ISettingFilter? settingFilter = null) 30 | { 31 | #if NET6_0_OR_GREATER 32 | ArgumentNullException.ThrowIfNull(receiver); 33 | #else 34 | if (receiver is null) 35 | { 36 | throw new ArgumentNullException(nameof(receiver)); 37 | } 38 | #endif 39 | if (!ReferenceEquals(this, _validationCache.GetValue(receiver, r => this))) 40 | { 41 | throw new ArgumentException("The same instance of IReceiver cannot be used to create multiple instances of MessagingConfigurationSource.", nameof(receiver)); 42 | } 43 | if (receiver.MessageHandler is not null) 44 | { 45 | throw new ArgumentException("The receiver is already started.", nameof(receiver)); 46 | } 47 | 48 | Receiver = receiver; 49 | SettingFilter = settingFilter; 50 | _cachedProvider = new Lazy(() => new MessagingConfigurationProvider(Receiver, SettingFilter)); 51 | } 52 | 53 | /// 54 | /// Gets the that will receive messages in order to change 55 | /// configuration values. 56 | /// 57 | public IReceiver Receiver { get; } 58 | 59 | /// 60 | /// Gets the that is applied to each setting of each 61 | /// received message. 62 | /// 63 | public ISettingFilter? SettingFilter { get; } 64 | 65 | /// 66 | /// Builds the for this source. 67 | /// 68 | /// The . 69 | /// A . 70 | public IConfigurationProvider Build(IConfigurationBuilder builder) => _cachedProvider.Value; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/NullSettingFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RockLib.Configuration.MessagingProvider 4 | { 5 | /// 6 | /// Singleton implementation of the interface 7 | /// that always returns . 8 | /// 9 | public sealed class NullSettingFilter : ISettingFilter 10 | { 11 | private NullSettingFilter() {} 12 | 13 | /// 14 | /// Gets the instance of . 15 | /// 16 | public static NullSettingFilter Instance { get; } = new NullSettingFilter(); 17 | 18 | /// 19 | /// Always returns true. 20 | /// 21 | /// Ignored. 22 | /// Ignored. 23 | /// . 24 | public bool ShouldProcessSettingChange(string setting, IReadOnlyDictionary messageHeaders) => true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/README.md: -------------------------------------------------------------------------------- 1 | # :warning: Deprecation Warning :warning: 2 | 3 | This library has been deprecated and will no longer receive updates. 4 | 5 | --- 6 | 7 | RockLib has been a cornerstone of our open source efforts here at Rocket Mortgage, and it's played a significant role in our journey to drive innovation and collaboration within our organization and the open source community. It's been amazing to witness the collective creativity and hard work that you all have poured into this project. 8 | 9 | However, as technology relentlessly evolves, so must we. The decision to deprecate this library is rooted in our commitment to staying at the cutting edge of technological advancements. While this chapter is ending, it opens the door to exciting new opportunities on the horizon. 10 | 11 | We want to express our heartfelt thanks to all the contributors and users who have been a part of this incredible journey. Your contributions, feedback, and enthusiasm have been invaluable, and we are genuinely grateful for your dedication. 🚀 12 | 13 | --- 14 | 15 | # RockLib.Configuration.MessagingProvider 16 | 17 | A configuration provider that reloads when it receives a message containing configuration changes from a `RockLib.Messaging.IReceiver`. 18 | 19 | > [!WARNING] 20 | > The 4.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. We strongly encourage developers to use standard .NET configuration libraries directly like `Microsoft.Extensions.Configuration` in place of `RockLib.Configuration`. 21 | 22 | ## Setup 23 | 24 | Add the messaging provider to an existing configuration builder with the `AddRockLibMessagingProvider` extension (in the `RockLib.Configuration.MessagingProvider` namespace). Note that the messaging configuration provider does not add any values to the configuration initially. 25 | 26 | There are two overloads for the extension method. The first one uses an existing instance of `IReceiver`: 27 | 28 | ```csharp 29 | IReceiver receiver = new NamedPipeReceiver(name: "example_receiver", pipeName: "example-pipe-name"); 30 | 31 | ConfigurationBuilder builder = new ConfigurationBuilder() 32 | .AddJsonFile("appsettings.json") 33 | .AddRockLibMessagingProvider(receiver); // Add the messaging provider last 34 | ``` 35 | 36 | The other overload creates a new instance of `IReceiver` by name from the configuration under construction: 37 | 38 | ```json 39 | { 40 | "RockLib.Messaging": { 41 | "type": "RockLib.Messaging.NamedPipes.NamedPipeReceiver, RockLib.Messaging.NamedPipes", 42 | "value": { 43 | "name": "example_receiver", 44 | "pipeName": "example-pipe-name" 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ```csharp 51 | ConfigurationBuilder builder = new ConfigurationBuilder() 52 | .AddJsonFile("appsettings.json") // Assume appsettings.json contains the above JSON 53 | .AddRockLibMessagingProvider("example_receiver"); // Add the messaging provider last 54 | ``` 55 | 56 | Each of these example adds an equivalent messaging provider to the configuration builder. All `IConfigurationRoot` objects built by the configuration builder will listen to the same `IReceiver` for configuration changes. 57 | 58 | ## Making a configuration change 59 | 60 | This section assumes there is an `appsettings.json` file and a configuration built as follows: 61 | 62 | ```json 63 | { 64 | "AppSettings": { 65 | "Foo": "abc", 66 | "Bar": 123 67 | } 68 | } 69 | ``` 70 | 71 | ```csharp 72 | IReceiver receiver = new NamedPipeReceiver(name: "example_receiver", pipeName: "example-pipe-name"); 73 | 74 | ConfigurationBuilder builder = new ConfigurationBuilder() 75 | .AddJsonFile("appsettings.json") 76 | .AddRockLibMessagingProvider(receiver); 77 | 78 | IConfigurationRoot configuration = builder.Build(); 79 | ``` 80 | 81 | To make a configuration change, send a message to the receiver with a JSON payload that describes the configuration changes. In this example, we want to change the values of `AppSettings:Foo` and `AppSettings:Bar` to `"xyz"` and `456` respectively. Note that the JSON is flattened into key/value pairs. 82 | 83 | ```csharp 84 | string configChange = @"{ 85 | ""AppSettings:Foo"": ""xyz"", 86 | ""AppSettings:Bar"": 456 87 | }"; 88 | 89 | using (ISender sender = new NamedPipeSender(name: "example_receiver", pipeName: "example-pipe-name")) 90 | { 91 | await sender.SendAsync(configChange); 92 | } 93 | ``` 94 | 95 | After the message is received, the `configuration` object will have the new values for "AppSettings:Foo" and "AppSettings:Bar". 96 | 97 | ## Filters 98 | 99 | The messaging configuration provider can be protected by passing a instance of the `ISettingFilter` instance to the extension methods. For reference, this is the definition of that interface: 100 | 101 | ```csharp 102 | public interface ISettingFilter 103 | { 104 | bool ShouldProcessSettingChange(string setting, IReadOnlyDictionary receivedMessageHeaders); 105 | } 106 | ``` 107 | 108 | When a message is received, each setting is passed to the `ShouldProcessSettingChange` method along with the `Headers` property of the received message. If the method returns true, the setting is changed. Otherwise, the setting is not changed. 109 | 110 | RockLib.Configuration.MessagingProvider has three implementations of the `ISettingFilter` interface: 111 | 112 | - `BlocklistSettingFilter` 113 | - Blocks specified settings, including child settings. 114 | - Has an optional inner filter. 115 | - `SafelistSettingFilter` 116 | - Blocks any settings, including child settings, that are *not* specified. 117 | - Has an optional inner filter. 118 | - `NullSettingFilter` 119 | - Doesn't block any settings. 120 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/RockLib.Configuration.MessagingProvider.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | A configuration provider that reloads when it receives a message containing configuration changes from a RockLib.Messaging IReceiver. 5 | True 6 | True 7 | icon.png 8 | RockLib.Configuration.MessagingProvider 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Configuration/tree/master/RockLib.Configuration.MessagingProvider 11 | A changelog is available at https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.MessagingProvider/CHANGELOG.md. 12 | false 13 | Configuration Messaging 14 | 4.0.0 15 | True 16 | 4.0.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/RockLib.Configuration.MessagingProvider.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.MessagingProvider", "RockLib.Configuration.MessagingProvider.csproj", "{592A0564-0266-4601-ACF2-73EA0265F971}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.MessagingProvider.Tests", "..\Tests\RockLib.Configuration.MessagingProvider.Tests\RockLib.Configuration.MessagingProvider.Tests.csproj", "{96A46744-4FE4-4E77-A3E5-FAB104BD5B66}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {592A0564-0266-4601-ACF2-73EA0265F971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {592A0564-0266-4601-ACF2-73EA0265F971}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {592A0564-0266-4601-ACF2-73EA0265F971}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {592A0564-0266-4601-ACF2-73EA0265F971}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {96A46744-4FE4-4E77-A3E5-FAB104BD5B66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {96A46744-4FE4-4E77-A3E5-FAB104BD5B66}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {96A46744-4FE4-4E77-A3E5-FAB104BD5B66}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {96A46744-4FE4-4E77-A3E5-FAB104BD5B66}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {16E62408-B5B7-4BD0-A503-C730909CAE3A} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/RockLibMessagingProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using RockLib.Messaging; 3 | using System; 4 | 5 | namespace RockLib.Configuration.MessagingProvider 6 | { 7 | /// 8 | /// Extension methods for registering 9 | /// with . 10 | /// 11 | public static class RockLibMessagingProviderExtensions 12 | { 13 | /// 14 | /// Adds an that reloads with changes 15 | /// specified in messages received from a new with the 16 | /// specified name created from the built builder. 17 | /// 18 | /// The to add to. 19 | /// The name of the receiver. 20 | /// 21 | /// The that is applied to each setting of each 22 | /// received message. 23 | /// 24 | /// The . 25 | /// 26 | /// This is how the is created: 27 | /// 28 | /// builder.Build().GetSection("RockLib.Messaging").CreateReceiver(receiverName) 29 | /// 30 | /// 31 | public static IConfigurationBuilder AddRockLibMessagingProvider(this IConfigurationBuilder builder, string receiverName, ISettingFilter? settingFilter = null) 32 | { 33 | #if NET6_0_OR_GREATER 34 | ArgumentNullException.ThrowIfNull(builder); 35 | #else 36 | if (builder is null) 37 | { 38 | throw new ArgumentNullException(nameof(builder)); 39 | } 40 | #endif 41 | return builder.AddRockLibMessagingProvider(builder.Build().GetSection("RockLib.Messaging").CreateReceiver(receiverName), settingFilter); 42 | } 43 | 44 | /// 45 | /// Adds an that reloads with changes 46 | /// specified in messages received from the . 47 | /// 48 | /// The to add to. 49 | /// 50 | /// The object that listens for messages that update configuration values. 51 | /// 52 | /// 53 | /// The that is applied to each setting of each 54 | /// received message. 55 | /// 56 | /// The . 57 | public static IConfigurationBuilder AddRockLibMessagingProvider(this IConfigurationBuilder builder, IReceiver receiver, ISettingFilter? settingFilter = null) 58 | { 59 | #if NET6_0_OR_GREATER 60 | ArgumentNullException.ThrowIfNull(builder); 61 | #else 62 | if (builder is null) 63 | { 64 | throw new ArgumentNullException(nameof(builder)); 65 | } 66 | #endif 67 | return builder.Add(new MessagingConfigurationSource(receiver, settingFilter)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RockLib.Configuration.MessagingProvider/SafelistSettingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Configuration.MessagingProvider 5 | { 6 | /// 7 | /// An implementation of that only allows changes to 8 | /// settings that are members of a safelist - all other settings are blocked. 9 | /// 10 | public sealed class SafelistSettingFilter : ISettingFilter 11 | { 12 | private readonly HashSet _safeSettings; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The collection of settings (and their child settings) that are safe - all 19 | /// other settings will be blocked. 20 | /// 21 | /// 22 | /// An optional setting filter that is applied if a setting is a 23 | /// member of . 24 | /// 25 | public SafelistSettingFilter(IEnumerable safeSettings, ISettingFilter? innerFilter = null) 26 | { 27 | #if NET6_0_OR_GREATER 28 | ArgumentNullException.ThrowIfNull(safeSettings); 29 | #else 30 | if (safeSettings is null) 31 | { 32 | throw new ArgumentNullException(nameof(safeSettings)); 33 | } 34 | #endif 35 | _safeSettings = new HashSet(safeSettings, StringComparer.OrdinalIgnoreCase); 36 | InnerFilter = innerFilter ?? NullSettingFilter.Instance; 37 | } 38 | 39 | /// 40 | /// Gets the collection of settings (and their child settings) that are safe - all 41 | /// other settings will be blocked. 42 | /// 43 | public IEnumerable SafeSettings => _safeSettings; 44 | 45 | /// 46 | /// Gets the inner setting filter that is applied if a setting is a 47 | /// member of . 48 | /// 49 | public ISettingFilter InnerFilter { get; } 50 | 51 | /// 52 | /// Returns whether the specified setting should be allowed to be changed. 53 | /// 54 | /// The setting to potentially change. 55 | /// 56 | /// The headers of the message that was received that has initiated the change. 57 | /// 58 | /// 59 | /// if the setting is allowed to be changed; otherwise 60 | /// if the setting is not allowed to be changed. 61 | /// 62 | public bool ShouldProcessSettingChange(string setting, IReadOnlyDictionary receivedMessageHeaders) => 63 | _safeSettings.HasSetting(setting) && InnerFilter.ShouldProcessSettingChange(setting, receivedMessageHeaders); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | csharp_indent_case_contents = true 10 | csharp_indent_switch_labels = true 11 | csharp_new_line_before_catch = true 12 | csharp_new_line_before_else = true 13 | csharp_new_line_before_finally = true 14 | csharp_new_line_before_members_in_anonymous_types = false 15 | csharp_new_line_before_members_in_object_initializers = false 16 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 17 | csharp_new_line_between_query_expression_clauses = true 18 | csharp_prefer_braces = false:suggestion 19 | csharp_prefer_simple_default_expression = true:suggestion 20 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 21 | csharp_preserve_single_line_blocks = true 22 | csharp_preserve_single_line_statements = true 23 | csharp_space_after_cast = false 24 | csharp_space_after_colon_in_inheritance_clause = true 25 | csharp_space_after_keywords_in_control_flow_statements = true 26 | csharp_space_before_colon_in_inheritance_clause = true 27 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 28 | csharp_space_between_method_call_name_and_opening_parenthesis = false 29 | csharp_space_between_method_call_parameter_list_parentheses = false 30 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_parameter_list_parentheses = false 32 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 33 | csharp_style_expression_bodied_accessors = true:suggestion 34 | csharp_style_expression_bodied_constructors = false:suggestion 35 | csharp_style_expression_bodied_methods = false:suggestion 36 | csharp_style_expression_bodied_properties = true:suggestion 37 | csharp_style_inlined_variable_declaration = true:suggestion 38 | csharp_style_var_elsewhere = true:suggestion 39 | csharp_style_var_for_built_in_types = true:suggestion 40 | csharp_style_var_when_type_is_apparent = true:suggestion 41 | dotnet_sort_system_directives_first = false 42 | dotnet_style_explicit_tuple_names = true:suggestion 43 | dotnet_style_object_initializer = true:suggestion 44 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 47 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 49 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 50 | dotnet_style_qualification_for_field = false:suggestion 51 | dotnet_style_qualification_for_method = false:suggestion 52 | dotnet_style_qualification_for_property = false:suggestion 53 | 54 | 55 | # Analyzer Configuration 56 | # These are rules we want to either ignore or have set as suggestion or info 57 | 58 | # CA1014: Mark assemblies with CLSCompliant 59 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 60 | dotnet_diagnostic.CA1014.severity = none 61 | 62 | # CA1725: Parameter names should match base declaration 63 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 64 | dotnet_diagnostic.CA1725.severity = suggestion 65 | 66 | # CA2227: Collection properties should be read only 67 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 68 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/AlternateNameAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Configuration.ObjectFactory 4 | { 5 | /// 6 | /// Defines an alternate name for a constructor parameter or property. 7 | /// 8 | [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = true)] 9 | public sealed class AlternateNameAttribute : Attribute 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The alternate name. 15 | public AlternateNameAttribute(string name) => Name = name ?? throw new ArgumentNullException(nameof(name)); 16 | 17 | /// 18 | /// Gets the alternate name. 19 | /// 20 | public string Name { get; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/AssemblyInformation.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("RockLib.Configuration.ObjectFactory.Tests")] -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Configuration.ObjectFactory Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 4.0.0 - 2025-01-29 9 | 10 | #### Changed 11 | - Removed .NET 6 as a target framework 12 | 13 | ## 3.0.0 - 2024-02-14 14 | 15 | #### Changed 16 | - Finalized 3.0.0 version. 17 | 18 | ## 3.0.0-alpha.1 - 2023-12-14 19 | 20 | #### Changed 21 | - Removed .NET Core 3.1 as a TFM and added .NET 8. Supported targets are now .NET 4.8, .NET 6, and .NET 8. 22 | 23 | ## 2.0.2 - 2022-06-06 24 | 25 | #### Changed 26 | 27 | **BREAKING CHANGES** 28 | 29 | - This is a bug fix that can be a breaking change. This fix eliminates a memory leak that can occur with `ConfigReloadingProxy`. The type passed to `CreateReloadingProxy()` must now implement `IDisposable`. Note that this is a runtime check, so you should check any `Type` objects passed to this method and make the appropriate updates in your code. Otherwise, your code will fail with `ArgumentException`-based exceptions. Review your code to ensure: 30 | - The interface passed to `CreateReloadingProxy()` must now implement `IDisposable` 31 | - This also means that you must call `Dispose()` on the object reference returned by `CreateReloadingProxy()` 32 | 33 | ## 2.0.1 - 2022-04-25 34 | 35 | #### Changed 36 | 37 | - Updated `CompareTo()` method in `ConstructorOrderInfo` to also compare the parameter types during comparison. 38 | 39 | ## 2.0.0 - 2022-02-07 40 | 41 | #### Added 42 | 43 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 44 | 45 | #### Changed 46 | 47 | - Supported targets: net6.0, netcoreapp3.1, and net48. 48 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 49 | - Updated attributes to be `sealed`. 50 | - Fixes ambiguous constructor issue between injectable and named parameters counts. 51 | - `Resolver` no longer catches all exceptions for invocations of provided `Func<>` objects on construction. 52 | 53 | ## 1.6.9 - 2021-08-11 54 | 55 | #### Changed 56 | 57 | - Changes "Quicken Loans" to "Rocket Mortgage". 58 | 59 | ## 1.6.8 - 2021-05-06 60 | 61 | #### Added 62 | 63 | - Adds SourceLink to nuget package. 64 | 65 | #### Changed 66 | 67 | - Updates System.Reflection.Emit package to latest version. 68 | 69 | ---- 70 | 71 | **Note:** Release notes in the above format are not available for earlier versions of 72 | RockLib.Configuration.ObjectFactory. What follows below are the original release notes. 73 | 74 | ---- 75 | 76 | ## 1.6.7 77 | 78 | Relaxes restrictions around types that implement IEnumerable. Only implementations of IEnumerable from the System.Collections namespace are disallowed. 79 | 80 | ## 1.6.6 81 | 82 | Adds net5.0 target. 83 | 84 | ## 1.6.5 85 | 86 | Fixes bug when a constructor has an optional nullable enum parameter with default value of null and the parameter was not supplied. 87 | 88 | ## 1.6.4 89 | 90 | Adds support for optional nullable enumerations in contructors. 91 | 92 | ## 1.6.3 93 | 94 | Adds icon to project and nuget package. 95 | 96 | ## 1.6.2 97 | 98 | Updates to align with Nuget conventions. 99 | 100 | ## 1.6.1 101 | 102 | Adds the [ConfigSection] attribute, for use by configuration editing tools. 103 | 104 | ## 1.6.0 105 | 106 | Adds the [AlternateName] attribute, allowing users to specify one or more alternate names for a constructor parameter / writable property. 107 | 108 | ## 1.5.0 109 | 110 | Adds support for members of type `Func`. 111 | 112 | ## 1.4.0 113 | 114 | Adds support for arbitrary DI containers, such as Ninject or Unity. 115 | 116 | ## 1.3.1 117 | 118 | Improves the debugging experience of config reloading proxy objects. 119 | 120 | ## 1.3.0 121 | 122 | Adds the ability to handle identifiers with different casing strategies - PascalCase, camelCase, snake_case, and kebab-case. 123 | 124 | ## 1.2.3 125 | 126 | Fixes two bugs related to members of type `object`: 127 | 128 | - If the target type is `object`, and it is being created by a configuration that has a value, then just use the configuration value directly. Previously, a conversion error would occur. 129 | - If the target type is `object`, and it has a default type of string-to-anything dictionary, and if the configuration has an "object" shape, then create the string dictionary from configuration. Previously, an nonsensical error would occur stating that it couldn't convert the dictionary type to the dictionary type. 130 | 131 | ## 1.2.2 132 | 133 | Improvements around when not to reload: 134 | 135 | - A reloading proxy doesn't reload if its configuration section hasn't changed. 136 | - If a section has `reloadOnChange` explicitly configured to be `false`, then the `CreateReloadingProxy` extension method returns a regular object, not a config reloading proxy object. 137 | - A small expansion of the definition of what a "type-specified object" is to allow a `reloadOnChange` of `false`. 138 | 139 | ## 1.2.1 140 | 141 | Fixes a constructor selection bug. 142 | 143 | ## 1.2.0 144 | 145 | Adds the ability to create a "config reloading proxy" - an object that reloads itself when its configuration changes. 146 | 147 | ## 1.1.4 148 | 149 | Adds support for IReadOnlyDictionary. 150 | 151 | ## 1.1.3 152 | 153 | Fixes a constructor selection bug. 154 | 155 | ## 1.1.2 156 | 157 | Fixes bug where top-level object wouldn't get its default type applied. 158 | 159 | ## 1.1.1 160 | 161 | This version enables read-only properties with a type that implements non-generic IList and does not have a public parameterless constructor. 162 | 163 | ## 1.1.0 164 | 165 | Adds support for non-generic lists. 166 | 167 | ## 1.0.0 168 | 169 | Initial release. 170 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/ConfigReloadingProxyReferenceModel.cs: -------------------------------------------------------------------------------- 1 | #if REFERENCE_MODEL 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | 5 | // These pragmas are here just to quiet the compiler. 6 | // This code is just reference code, it's already been published, 7 | // and it's not worth updating to resolve these CA issues. 8 | #pragma warning disable CA1033 // Interface methods should be callable by child types 9 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 10 | namespace RockLib.Configuration.ObjectFactory.ReferenceModel 11 | { 12 | public interface IFoo 13 | { 14 | int Bar { get; } 15 | int Baz(int factor); 16 | string? Qux { get; set; } // Special case - read/write reference-type property 17 | event EventHandler? Garply; 18 | } 19 | 20 | public class ProxyFoo : ConfigReloadingProxy, IFoo 21 | { 22 | public ProxyFoo(IConfiguration section, DefaultTypes defaultTypes, ValueConverters valueConverters, Type declaringType, string memberName) 23 | : base(section, defaultTypes, valueConverters, declaringType, memberName, null) 24 | { 25 | } 26 | 27 | // Properties are strictly pass-through. 28 | int IFoo.Bar => Object.Bar; 29 | 30 | // Methods are strictly pass-through. 31 | int IFoo.Baz(int qux) => Object.Baz(qux); 32 | 33 | // Properties are strictly pass-through. 34 | string? IFoo.Qux { get => Object.Qux; set => Object.Qux = value; } 35 | 36 | // Events are pass-through, but also need to capture handlers in a private field. 37 | private EventHandler? _garply; 38 | event EventHandler? IFoo.Garply 39 | { 40 | add 41 | { 42 | Object.Garply += value; 43 | _garply += value; 44 | } 45 | remove 46 | { 47 | Object.Garply -= value; 48 | _garply -= value; 49 | } 50 | } 51 | 52 | protected override void TransferState(IFoo oldObject, IFoo newObject) 53 | { 54 | if(oldObject is not null && newObject is not null) 55 | { 56 | // Event handlers from the old object need to be copied to the new one. 57 | newObject.Garply += _garply; 58 | 59 | // Special case for when the interface has a read/write property: if the new 60 | // property value is null and the old property value is not null, then copy 61 | // the value from old to new. 62 | if (oldObject.Qux is not null && newObject.Qux is null) 63 | { 64 | newObject.Qux = oldObject.Qux; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 71 | #pragma warning restore CA1033 // Interface methods should be callable by child types 72 | #endif 73 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/ConfigSectionAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Configuration.ObjectFactory 4 | { 5 | /// 6 | /// Associates the path to a configuration section with a target type. The contents 7 | /// of such a configuration section should declare an object of the target type. 8 | /// 9 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] 10 | public sealed class ConfigSectionAttribute : Attribute 11 | { 12 | /// 13 | /// Initializes a new instance of the class, 14 | /// associating the specified path to a configuration section with the specified type. 15 | /// The contents of such a configuration section should declare an object of the target type. 16 | /// 17 | /// 18 | /// The path to a target configuration section. The contents of such a configuration 19 | /// section should declare an object of the type of the parameter. 20 | /// 21 | /// 22 | /// The type of object that should be able to be created using a configuration section 23 | /// specified by the parameter. 24 | /// 25 | public ConfigSectionAttribute(string path, Type type) 26 | { 27 | Path = path ?? throw new ArgumentNullException(nameof(path)); 28 | Type = type ?? throw new ArgumentNullException(nameof(type)); 29 | } 30 | 31 | /// 32 | /// Gets the path to a target configuration section. The contents of such a configuration 33 | /// section should declare an object of the type of the property. 34 | /// 35 | public string Path { get; } 36 | 37 | /// 38 | /// Gets the type of object that should be able to be created using a configuration section 39 | /// specified by the property. 40 | /// 41 | public Type Type { get; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/ConstructorOrderInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace RockLib.Configuration.ObjectFactory 8 | { 9 | internal sealed class ConstructorOrderInfo : IComparable 10 | { 11 | public ConstructorOrderInfo(ConstructorInfo constructor, Dictionary availableMembers, IResolver? resolver) 12 | { 13 | Constructor = constructor; 14 | var parameters = constructor.GetParameters(); 15 | TotalParameters = parameters.Length; 16 | if (TotalParameters == 0) 17 | { 18 | IsInvokableWithoutDefaultParameters = true; 19 | IsInvokableWithDefaultParameters = true; 20 | MissingParameterNames = new List(); 21 | MatchedParameters = 0; 22 | ParameterTypes = new List(); 23 | } 24 | else 25 | { 26 | bool HasAvailableValue(ParameterInfo p) => 27 | p.GetNames().Any(name => availableMembers.ContainsKey(name)) || (resolver?.CanResolve(p) ?? false); 28 | bool HasAvailableNamedValue(ParameterInfo p) => 29 | p.GetNames().Any(name => availableMembers.ContainsKey(name)); 30 | 31 | IsInvokableWithoutDefaultParameters = parameters.Count(HasAvailableValue) == TotalParameters; 32 | IsInvokableWithDefaultParameters = parameters.Count(p => HasAvailableValue(p) || p.HasDefaultValue) == TotalParameters; 33 | MissingParameterNames = parameters.Where(p => !HasAvailableValue(p) && !p.HasDefaultValue).Select(p => p.Name!).ToList(); 34 | MatchedParameters = parameters.Count(HasAvailableValue); 35 | MatchedNamedParameters = parameters.Count(HasAvailableNamedValue); 36 | ParameterTypes = parameters.Select(p => p.ParameterType).ToList(); 37 | } 38 | } 39 | 40 | public ConstructorInfo Constructor { get; } 41 | public bool IsInvokableWithoutDefaultParameters { get; } 42 | public bool IsInvokableWithDefaultParameters { get; } 43 | public int MatchedParameters { get; } 44 | public int MatchedNamedParameters { get; } 45 | public List ParameterTypes { get; } 46 | public int TotalParameters { get; } 47 | public List MissingParameterNames { get; } 48 | 49 | public int CompareTo(ConstructorOrderInfo? other) 50 | { 51 | if (other is null) return 1; 52 | if (IsInvokableWithoutDefaultParameters && !other.IsInvokableWithoutDefaultParameters) return -1; 53 | if (!IsInvokableWithoutDefaultParameters && other.IsInvokableWithoutDefaultParameters) return 1; 54 | if (IsInvokableWithDefaultParameters && !other.IsInvokableWithDefaultParameters) return -1; 55 | if (!IsInvokableWithDefaultParameters && other.IsInvokableWithDefaultParameters) return 1; 56 | if (MatchedParameters > other.MatchedParameters) return -1; 57 | if (MatchedParameters < other.MatchedParameters) return 1; 58 | if (TotalParameters > other.TotalParameters) return -1; 59 | if (TotalParameters < other.TotalParameters) return 1; 60 | if (MatchedNamedParameters > other.MatchedNamedParameters) return -1; 61 | if (MatchedNamedParameters < other.MatchedNamedParameters) return 1; 62 | // Finds parameter types in ParameterTypes that are not in other.ParameterTypes 63 | if (ParameterTypes.Except(other.ParameterTypes).Any()) return -1; 64 | // Finds parameter types in other.ParameterTypes that are not in ParameterTypes 65 | if (other.ParameterTypes.Except(ParameterTypes).Any()) return 1; 66 | return 0; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/ConvertMethodAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Configuration.ObjectFactory 4 | { 5 | /// 6 | /// Defines which method should be used to convert a configuration string value to a target type. The method 7 | /// must be static, have a single parameter of type string, and return a type (other than ) 8 | /// that is assignable to the member that this attribute decorates. 9 | /// 10 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 11 | public sealed class ConvertMethodAttribute : Attribute 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// 17 | /// The name of the method that does the conversion for the member that this attribute decorates. 18 | /// 19 | public ConvertMethodAttribute(string convertMethodName) => ConvertMethodName = convertMethodName ?? throw new ArgumentNullException(nameof(convertMethodName)); 20 | 21 | /// 22 | /// Get the name of the method that does the conversion for the member that this attribute decorates. 23 | /// 24 | public string ConvertMethodName { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/DefaultTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Configuration.ObjectFactory 4 | { 5 | /// 6 | /// Defines the default type of a property, constructor parameter, class, or interface. The value of this attribute 7 | /// must be assignable to the member it decorates. 8 | /// 9 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] 10 | public sealed class DefaultTypeAttribute : Attribute 11 | { 12 | /// 13 | /// Initializes a new instance of the type. 14 | /// 15 | /// The default type of the member that this attribute decorates. 16 | public DefaultTypeAttribute(Type value) => Value = value ?? throw new ArgumentNullException(nameof(value)); 17 | 18 | /// 19 | /// Gets the default type of the member that this attribute decorates. 20 | /// 21 | public Type Value { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/IResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Reflection; 3 | 4 | namespace RockLib.Configuration.ObjectFactory 5 | { 6 | /// 7 | /// Represents a dependency injection container, such as Ninject, Unity, 8 | /// Autofac, or StructureMap. 9 | /// 10 | public interface IResolver 11 | { 12 | /// 13 | /// Returns a value indicating whether a dependency can be retrieved 14 | /// that is suitable for the specified parameter. 15 | /// 16 | /// 17 | /// A constructor parameter that needs a value. 18 | /// 19 | /// 20 | /// true if a dependency can be retrieved for the specified parameter; 21 | /// otherwise, false. 22 | /// 23 | bool CanResolve(ParameterInfo parameter); 24 | 25 | /// 26 | /// Retrieves a dependency suitable for the specified parameter. 27 | /// 28 | /// 29 | /// A constructor parameter that needs a value. 30 | /// 31 | /// 32 | /// When this method returns, contains the object that was resolved 33 | /// for the specified parameter, if it is resolvable; otherwise, null. 34 | /// This parameter is passed uninitialized. 35 | /// 36 | /// 37 | /// true if a dependency was successfully retrieved; otherwise, false. 38 | /// 39 | bool TryResolve(ParameterInfo parameter, [MaybeNullWhen(false)] out object value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: InternalsVisibleTo("RockLib.Configuration.ObjectFactory.Tests")] 5 | #endif -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/Members.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace RockLib.Configuration.ObjectFactory 7 | { 8 | internal static class Members 9 | { 10 | public static IEnumerable Find(Type? declaringType, string? memberName) 11 | { 12 | if (declaringType is null || memberName is null) return Enumerable.Empty(); 13 | var constructorParameters = FindConstructorParameters(declaringType, memberName).ToList(); 14 | return FindProperties(declaringType, memberName, constructorParameters).Concat(constructorParameters); 15 | } 16 | 17 | private static IEnumerable FindConstructorParameters(Type declaringType, string memberName) => 18 | declaringType.GetConstructors(BindingFlags.Public | BindingFlags.Instance) 19 | .SelectMany(c => c.GetParameters()) 20 | .Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Name, memberName)) 21 | .Select(p => new Member(p.Name!, p.ParameterType, MemberType.ConstructorParameter)); 22 | 23 | private static IEnumerable FindProperties(Type declaringType, string memberName, List constructorParameters) => 24 | declaringType.GetProperties(BindingFlags.Public | BindingFlags.Instance) 25 | .Where(p => StringComparer.OrdinalIgnoreCase.Equals(p.Name, memberName) 26 | && (p.CanWrite 27 | || ((p.IsReadonlyList() || p.IsReadonlyDictionary()) 28 | && !constructorParameters.Any(c => StringComparer.OrdinalIgnoreCase.Equals(c.Name, memberName))))) 29 | .Select(p => new Member(p.Name, p.PropertyType, MemberType.Property)); 30 | 31 | public static bool IsReadonlyList(this PropertyInfo p) => 32 | p.CanRead 33 | && !p.CanWrite 34 | && ((p.PropertyType.IsGenericType 35 | && (p.PropertyType.GetGenericTypeDefinition() == typeof(List<>) 36 | || p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) 37 | || p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))) 38 | || p.PropertyType.IsNonGenericList()); 39 | 40 | public static bool IsReadonlyDictionary(this PropertyInfo p) => 41 | p.CanRead 42 | && !p.CanWrite 43 | && p.PropertyType.IsGenericType 44 | && (p.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>) 45 | || p.PropertyType.GetGenericTypeDefinition() == typeof(IDictionary<,>)); 46 | } 47 | 48 | internal sealed class Member 49 | { 50 | public Member(string name, Type type, MemberType memberType) 51 | { 52 | Name = name; 53 | Type = type; 54 | MemberType = memberType; 55 | } 56 | 57 | public string Name { get; } 58 | public Type Type { get; } 59 | public MemberType MemberType { get; } 60 | 61 | public override string ToString() => 62 | $"{(MemberType == MemberType.Property ? "Property" : "Constructor parameter")}: {Type} {Name}"; 63 | } 64 | 65 | internal enum MemberType 66 | { 67 | Property, 68 | ConstructorParameter 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/NullableAttributes.cs: -------------------------------------------------------------------------------- 1 | // The point of the types in this file are to "patch" in the nullable attributes 2 | // (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis) 3 | // for .NET 4.8. This code came from https://source.dot.net/#System.Private.CoreLib/NullableAttributes.cs 4 | 5 | #if NET48 6 | namespace System.Diagnostics.CodeAnalysis 7 | { 8 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 9 | internal sealed class MaybeNullWhenAttribute 10 | : Attribute 11 | { 12 | /// Initializes the attribute with the specified return value condition. 13 | /// 14 | /// The return value condition. If the method returns this value, the associated parameter may be null. 15 | /// 16 | public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; 17 | 18 | /// Gets the return value condition. 19 | public bool ReturnValue { get; } 20 | } 21 | } 22 | #endif -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/RockLib.Configuration.ObjectFactory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Debug;Release;ReferenceModel 4 | 5 | 6 | RockLib.Configuration.ObjectFactory 7 | 4.0.0 8 | RockLib 9 | Creates objects from IConfiguration and IConfigurationSection objects. A replacement for some of the functionality of Microsoft.Extensions.Configuration.Binder. 10 | false 11 | A changelog is available at https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.ObjectFactory/CHANGELOG.md. 12 | https://github.com/RockLib/RockLib.Configuration/tree/master/RockLib.Configuration.ObjectFactory 13 | LICENSE.md 14 | icon.png 15 | Copyright 2017-2023 (c) Rocket Mortgage. All rights reserved. 16 | Configuration Factory Binder IConfiguration IConfigurationSection 17 | 4.0.0 18 | True 19 | True 20 | True 21 | Embedded 22 | 23 | 24 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 25 | 26 | 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | TRACE;REFERENCE_MODEL 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RockLib.Configuration.ObjectFactory/RockLib.Configuration.ObjectFactory.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32516.85 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.ObjectFactory", "RockLib.Configuration.ObjectFactory.csproj", "{1A866445-42C8-4A9B-82C5-D3B34EBF9147}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.ObjectFactory.Tests", "..\Tests\RockLib.Configuration.ObjectFactory.Tests\RockLib.Configuration.ObjectFactory.Tests.csproj", "{9DA180C2-E6BD-42E3-B157-D3164F6F269F}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | ReferenceModel|Any CPU = ReferenceModel|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.ReferenceModel|Any CPU.ActiveCfg = ReferenceModel|Any CPU 20 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.ReferenceModel|Any CPU.Build.0 = ReferenceModel|Any CPU 21 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {1A866445-42C8-4A9B-82C5-D3B34EBF9147}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.ReferenceModel|Any CPU.ActiveCfg = ReferenceModel|Any CPU 26 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.ReferenceModel|Any CPU.Build.0 = ReferenceModel|Any CPU 27 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {9DA180C2-E6BD-42E3-B157-D3164F6F269F}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {DF36CA15-3280-487B-AA81-A9F8C00A4CBB} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Configuration.ProxyFactory Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 4.0.0 - 2025-01-29 9 | 10 | #### Changed 11 | - Final version of 4.0.0. 12 | 13 | ## 4.0.0-alpha.1 - 2025-01-29 14 | 15 | #### Changed 16 | - Removed .NET 6 as a target framework 17 | - Updated RockLib.Configuration.ObjectFactory to 4.0.0 18 | 19 | ## 3.0.0 - 2024-02-14 20 | 21 | #### Changed 22 | - Final version of 3.0.0. 23 | 24 | ## 3.0.0-alpha.1 - 2023-12-14 25 | 26 | #### Changed 27 | - Removed .NET Core 3.1 as a TFM and added .NET 8. Supported targets are now .NET 4.8, .NET 6, and .NET 8. 28 | 29 | ## 2.0.1 - 2023-02-14 30 | 31 | #### Added 32 | - Added update for RockLib.Configuration.ObjectFactory reference version `2.0.2` 33 | 34 | ## 2.0.0 - 2022-02-09 35 | 36 | #### Added 37 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 38 | 39 | #### Changed 40 | - Supported targets: net6.0, netcoreapp3.1, and net48. 41 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 42 | 43 | ## 1.0.7 - 2021-08-11 44 | 45 | #### Changed 46 | 47 | - Changes "Quicken Loans" to "Rocket Mortgage". 48 | - Updates RockLib.Configuration.ObjectFactory to latest version, [1.6.9](https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.ObjectFactory/CHANGELOG.md#169---2021-08-11). 49 | 50 | ## 1.0.6 - 2021-05-06 51 | 52 | #### Added 53 | 54 | - Adds SourceLink to nuget package. 55 | 56 | #### Changed 57 | 58 | - Updates RockLib.Configuration.ObjectFactory package to latest version, which includes SourceLink. 59 | - Updates System.Reflection.Emit and System.ValueTuple packages to latest versions. 60 | 61 | ---- 62 | 63 | **Note:** Release notes in the above format are not available for earlier versions of 64 | RockLib.Configuration.ProxyFactory. What follows below are the original release notes. 65 | 66 | ---- 67 | 68 | ## 1.0.5 69 | 70 | Adds net5.0 target. 71 | 72 | ## 1.0.4 73 | 74 | Adds icon to project and nuget package. 75 | 76 | ## 1.0.2 77 | 78 | Updates RockLib.Configuration.ObjectFactory package to latest version. 79 | 80 | ## 1.0.1 81 | 82 | Updates RockLib.Configuration.ObjectFactory package to latest version. 83 | 84 | ## 1.0.0 85 | 86 | Initial release. 87 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/Exceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace RockLib.Configuration.ProxyFactory 6 | { 7 | internal static class Exceptions 8 | { 9 | internal static ArgumentException CannotCreateProxyOfNonInterfaceType(Type type) => 10 | new ArgumentException($"Cannot create proxy instance of non-interface type {type}.", nameof(type)); 11 | 12 | internal static ArgumentException TargetInterfaceCannotHaveAnyMethods(Type type, MethodInfo m) => 13 | new ArgumentException($"Cannot create proxy {type} implementation: target interface cannot contain any methods. `{m}`", nameof(type)); 14 | 15 | internal static ArgumentException TargetInterfaceCannotHaveAnyEvents(Type type, EventInfo e) => 16 | new ArgumentException($"Cannot create proxy {type} implementation: target interface cannot contain any events. `{e}`", nameof(type)); 17 | 18 | internal static ArgumentException TargetInterfaceCannotHaveAnyIndexerProperties(Type type, PropertyInfo p) => 19 | new ArgumentException($"Cannot create proxy {type} implementation: target interface cannot contain any indexer properties. `{p.PropertyType.Name} this[{string.Join(", ", p.GetIndexParameters().Select(i => i.ParameterType.Name))}] {{ {(p.CanRead ? "get; " : "")} {(p.CanWrite ? "set; " : "")}}}`", nameof(type)); 20 | 21 | internal static ArgumentException TargetInterfaceCannotHaveAnyWriteOnlyProperties(Type type, PropertyInfo p) => 22 | new ArgumentException($"Cannot create proxy {type} implementation: target interface cannot contain write-only methods. `{p} {{ set; }}`", nameof(type)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: InternalsVisibleTo("RockLib.Configuration.ProxyFactory.Tests")] 5 | #endif -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/README.md: -------------------------------------------------------------------------------- 1 | # :warning: Deprecation Warning :warning: 2 | 3 | This library has been deprecated and will no longer receive updates. 4 | 5 | --- 6 | 7 | RockLib has been a cornerstone of our open source efforts here at Rocket Mortgage, and it's played a significant role in our journey to drive innovation and collaboration within our organization and the open source community. It's been amazing to witness the collective creativity and hard work that you all have poured into this project. 8 | 9 | However, as technology relentlessly evolves, so must we. The decision to deprecate this library is rooted in our commitment to staying at the cutting edge of technological advancements. While this chapter is ending, it opens the door to exciting new opportunities on the horizon. 10 | 11 | We want to express our heartfelt thanks to all the contributors and users who have been a part of this incredible journey. Your contributions, feedback, and enthusiasm have been invaluable, and we are genuinely grateful for your dedication. 🚀 12 | 13 | --- 14 | 15 | # RockLib.Configuration.ProxyFactory 16 | 17 | A factory that creates instances of property-only interfaces, defined at run-time, and populated with values defined in an instance of `IConfiguration`. 18 | 19 | > [!WARNING] 20 | > The 4.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. We strongly encourage developers to use standard .NET configuration libraries directly like `Microsoft.Extensions.Configuration` in place of `RockLib.Configuration`. 21 | 22 | ## Supported Targets 23 | 24 | This library supports the following targets: 25 | - .NET 6 26 | - .NET Core 3.1 27 | - .NET Framework 4.8 28 | 29 | ## Overview 30 | 31 | Let's say we have a class, `Foo`, that has various settings that it requires. 32 | 33 | ```csharp 34 | public class Foo 35 | { 36 | private readonly IFooSettings _settings; 37 | public Foo(IFooSettings settings) => _settings = settings; 38 | // TODO: Define methods that use the settings 39 | } 40 | 41 | public interface IFooSettings 42 | { 43 | bool IsBar { get; } 44 | int BazCount { get; } 45 | string QuxCapacitor { get; } 46 | } 47 | ``` 48 | 49 | When unit testing our `Foo` class, it is easy to use any mocking framework to define each of the settings as needed for each test. But when our class is being used in production, we'll want these values to come from configuration. In order to do this, we could use the `Create` extension method from `RockLib.Configuration.ObjectFactory` or the `Get` extension method from `Microsoft.Extensions.Configuration.Binder`. But those extension methods require a concrete target, so we would need to first define an implementation class for the settings interface. That implementation class is low-value, copy/paste code that we would like to avoid having to write. 50 | 51 | The `CreateProxy` extension method fills the gap by creating a concrete implementation type at run-time - a "proxy" class. It then uses that proxy class as the target type for the `Create` extension method from `RockLib.Configuration.ObjectFactory` and returns the result of that call. 52 | 53 | ```csharp 54 | IConfiguration configuration; // TODO: get from somewhere 55 | IFooSettings settings = configuration.CreateProxy(); 56 | Foo foo = new Foo(settings); 57 | ``` 58 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/RockLib.Configuration.ProxyFactory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | RockLib.Configuration.ProxyFactory 4 | 4.0.0 5 | Creates proxy objects from interfaces using IConfiguration and IConfigurationSection objects. 6 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 7 | false 8 | A changelog is available at https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration.ProxyFactory/CHANGELOG.md. 9 | https://github.com/RockLib/RockLib.Configuration/tree/master/RockLib.Configuration.ProxyFactory 10 | LICENSE.md 11 | icon.png 12 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 13 | Configuration Factory Proxy IConfiguration IConfigurationSection 14 | 4.0.0 15 | True 16 | True 17 | True 18 | Embedded 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RockLib.Configuration.ProxyFactory/RockLib.Configuration.ProxyFactory.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.ProxyFactory", "RockLib.Configuration.ProxyFactory.csproj", "{668E5809-C181-4FFD-AF2B-29D342FD6909}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.ProxyFactory.Tests", "..\Tests\RockLib.Configuration.ProxyFactory.Tests\RockLib.Configuration.ProxyFactory.Tests.csproj", "{0AF2FE77-91AA-43C5-AC7E-7E3E31627283}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {668E5809-C181-4FFD-AF2B-29D342FD6909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {668E5809-C181-4FFD-AF2B-29D342FD6909}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {668E5809-C181-4FFD-AF2B-29D342FD6909}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {668E5809-C181-4FFD-AF2B-29D342FD6909}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {0AF2FE77-91AA-43C5-AC7E-7E3E31627283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {0AF2FE77-91AA-43C5-AC7E-7E3E31627283}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {0AF2FE77-91AA-43C5-AC7E-7E3E31627283}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {0AF2FE77-91AA-43C5-AC7E-7E3E31627283}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {DF36CA15-3280-487B-AA81-A9F8C00A4CBB} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Configuration/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /RockLib.Configuration/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RockLib.Configuration 4 | { 5 | /// 6 | /// Defines an indexer property that retrieves settings from the "AppSettings" section from 7 | /// . 8 | /// 9 | public sealed class AppSettings 10 | { 11 | private AppSettings() { } 12 | 13 | internal static AppSettings Instance { get; } = new AppSettings(); 14 | 15 | /// 16 | /// Gets the setting associated with the parameter. 17 | /// 18 | /// The key of the setting to look up. 19 | /// The setting associated with the parameter. 20 | /// 21 | /// If the given key is not found in the "AppSettings" section of . 22 | /// 23 | public string this[string key] => Config.Root?[$"AppSettings:{key}"] ?? 24 | throw new KeyNotFoundException($"Unable to locate {nameof(Config.AppSettings)} key '{key}' in {typeof(Config).FullName}.{nameof(Config.Root)}."); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RockLib.Configuration/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # RockLib.Configuration Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 5.0.0 - 2025-01-29 9 | 10 | #### Changed 11 | - Updated `CompositeConfigurationSection`, `MemoryConfigurationBuilderExtensions`, and `Config` members for nullability. 12 | - Updated Microsoft NuGet package references to latest 8.* versions. 13 | 14 | #### Removed 15 | - Removed support for .NET 6. 16 | 17 | ## 4.0.3 - 2024-07-12 18 | 19 | #### Added 20 | - Direct dependency on System.Text.Json to .NET 6 package to resolve vulnerability. 21 | - Updated multiple dependencies in the test projects. 22 | 23 | ## 4.0.2 - 2024-10-10 24 | 25 | #### Changed 26 | - Re-release to pull and build with the latest System.Text.Json to fix vulnerability. 27 | 28 | ## 4.0.1 - 2024-07-12 29 | 30 | #### Added 31 | - Direct dependency on System.Text.Json to .NET 8 package to resolve vulnerability. 32 | 33 | ## 4.0.1-alpha.1 - 2024-07-12 34 | 35 | #### Added 36 | - Direct dependency on System.Text.Json to .NET 8 package to resolve vulnerability. 37 | 38 | ## 4.0.0 - 2024-02-14 39 | 40 | #### Changed 41 | - Finalized 4.0.0 version. 42 | 43 | ## 4.0.0-alpha.1 - 2023-12-14 44 | 45 | #### Changed 46 | - Removed .NET Core 3.1 as a TFM and added .NET 8. Supported targets are now .NET 4.8, .NET 6, and .NET 8. 47 | - Removed RockLib.Immutable as a dependency, and added `Semimutable<>` and `SoftLock<>` internally. 48 | 49 | ## 3.1.0 - 2023-03-24 50 | 51 | #### Added 52 | - Added support for generic host projects to allow loading of correct environment appsettings files. 53 | 54 | ## 3.1.0-alpha.1 - 2023-03-21 55 | 56 | #### Added 57 | - Added support for generic host projects to allow loading of correct environment appsettings files. 58 | 59 | ## 3.0.0 - 2022-02-10 60 | 61 | #### Added 62 | - Added `.editorconfig` and `Directory.Build.props` files to ensure consistency. 63 | 64 | #### Changed 65 | - Supported targets: net6.0, netcoreapp3.1, and net48. 66 | - As the package now uses nullable reference types, some method parameters now specify if they can accept nullable values. 67 | - `Config.ResetRoot()` relies on an private method that no longer silently catches all exceptions. 68 | 69 | ## 2.5.3 - 2021-08-11 70 | 71 | #### Changed 72 | 73 | - Changes "Quicken Loans" to "Rocket Mortgage". 74 | - Updates RockLib.Immutable to latest version, [1.0.7](https://github.com/RockLib/RockLib.Immutable/blob/main/RockLib.Immutable/CHANGELOG.md#107---2021-08-10). 75 | 76 | ## 2.5.2 - 2021-05-06 77 | 78 | #### Added 79 | 80 | - Adds SourceLink to nuget package. 81 | 82 | #### Changed 83 | 84 | - Updates RockLib.Immutable package to latest version, which includes SourceLink. 85 | 86 | ---- 87 | 88 | **Note:** Release notes in the above format are not available for earlier versions of 89 | RockLib.Configuration. What follows below are the original release notes. 90 | 91 | ---- 92 | 93 | ## 2.5.1 94 | 95 | Adds net5.0 target. 96 | 97 | ## 2.5.0 98 | 99 | Adds SetBasePath method to Config static class, allowing users to opt-in to setting the base path of the configuration builder when using the default config root. 100 | 101 | ## 2.4.4 102 | 103 | Adds icon to project and nuget package. 104 | 105 | ## 2.4.3 106 | 107 | Update dependency package. 108 | 109 | ## 2.4.2 110 | 111 | Updates to align with Nuget conventions. 112 | 113 | ## 2.4.1 114 | 115 | Fixes the list section ordering bug. 116 | 117 | ## 2.4.0 118 | 119 | Adds extension methods to create a composite configuration section from a configuration. 120 | 121 | ## 2.3.1 122 | 123 | Fixes a case sensitivity bug in the configuration manager provider. 124 | 125 | ## 2.3.0 126 | 127 | - Adds a `reloadOnChange` flag to the `AddAppSettingsJson` (for all targets) and `AddConfigurationManager` (for .NET Framework targets) extension methods. 128 | - The default value of `Config.Root` uses both extension methods and sends a value of true for each. 129 | 130 | ## 2.2.1 131 | 132 | Fix support for web projects with Web.Config 133 | 134 | ## 2.2.0 135 | 136 | - Adds support for RockLib.Secrets without a hard dependency 137 | - Adds .SetConfigRoot extension for IConfigurationBuilder 138 | 139 | ## 2.1.0 140 | 141 | For .NET Framework applications/libraries, the `AddConfigurationManager` extension method adds any `RockLibConfigurationSection` sections declared in app.config/web.config to the configuration builder. The default value of Config.Root also includes these sections. 142 | 143 | ## 2.0.0 144 | 145 | Changes the type of Config.Root from IConfigurationRoot to IConfiguration. 146 | 147 | ## 1.0.0 148 | 149 | Initial release. 150 | -------------------------------------------------------------------------------- /RockLib.Configuration/CompositeConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace RockLib.Configuration 7 | { 8 | /// 9 | /// Defines extension methods for obtaining composite configuration sections. 10 | /// That is, a configuration section composed of the settings contained with 11 | /// multiple sections at different keys within a single configuration root. 12 | /// 13 | public static class CompositeConfigurationExtensions 14 | { 15 | /// 16 | /// Gets a composite configuration sub-section with the specified keys. 17 | /// 18 | /// The source of the composite section. 19 | /// The keys of the configuration sections. 20 | /// The . 21 | public static IConfigurationSection GetCompositeSection(this IConfiguration configuration, IEnumerable keys) 22 | { 23 | #if NET6_0_OR_GREATER 24 | ArgumentNullException.ThrowIfNull(keys); 25 | #else 26 | if (keys is null) { throw new ArgumentNullException(nameof(keys)); } 27 | #endif 28 | return configuration.GetCompositeSection(keys.ToArray()); 29 | } 30 | 31 | /// 32 | /// Gets a composite configuration sub-section with the specified keys. 33 | /// 34 | /// The source of the composite section. 35 | /// The keys of the configuration sections. 36 | /// The . 37 | public static IConfigurationSection GetCompositeSection(this IConfiguration configuration, params string[] keys) 38 | { 39 | #if NET6_0_OR_GREATER 40 | ArgumentNullException.ThrowIfNull(configuration); 41 | ArgumentNullException.ThrowIfNull(keys); 42 | #else 43 | if (configuration is null) { throw new ArgumentNullException(nameof(configuration)); } 44 | 45 | if (keys is null) { throw new ArgumentNullException(nameof(keys)); } 46 | #endif 47 | 48 | if (keys.Length == 0) 49 | { 50 | throw new ArgumentException("Must contain at least one key.", nameof(keys)); 51 | } 52 | 53 | if (keys.Any(key => string.IsNullOrEmpty(key))) 54 | { 55 | throw new ArgumentException("Cannot contain null or empty keys.", nameof(keys)); 56 | } 57 | 58 | return new CompositeConfigurationSection(keys.Select(key => configuration.GetSection(key))); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RockLib.Configuration/CompositeConfigurationSection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Primitives; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace RockLib.Configuration 8 | { 9 | internal sealed class CompositeConfigurationSection : IConfigurationSection 10 | { 11 | private readonly Lazy> _allSections; 12 | private readonly Lazy _primarySection; 13 | private readonly Lazy> _children; 14 | 15 | public CompositeConfigurationSection(IEnumerable sections) 16 | { 17 | _allSections = new Lazy>(() => sections.ToList()); 18 | 19 | _primarySection = new Lazy(() => _allSections.Value.FirstOrDefault(section => section.Value is not null) 20 | ?? _allSections.Value.First()); 21 | 22 | _children = new Lazy>(() => 23 | _allSections.Value.SelectMany(section => section.GetChildren()) 24 | .GroupBy(s => s.Key, StringComparer.OrdinalIgnoreCase) 25 | .Select(s => new CompositeConfigurationSection(s)) 26 | .OrderBy(s => s.Key, CompositeSectionKeyComparer.Instance) 27 | .ToList()); 28 | } 29 | 30 | public IConfigurationSection GetSection(string key) => 31 | new CompositeConfigurationSection(_allSections.Value.Select(s => s.GetSection(key))); 32 | 33 | public IEnumerable GetChildren() => _children.Value; 34 | 35 | public IChangeToken GetReloadToken() => _primarySection.Value.GetReloadToken(); 36 | 37 | public string? this[string key] 38 | { 39 | get => _allSections.Value.FirstOrDefault(s => s[key] is not null)?[key]; 40 | set 41 | { 42 | foreach (var section in _allSections.Value) 43 | { 44 | if (section[key] is null) 45 | continue; 46 | section[key] = value; 47 | return; 48 | } 49 | _primarySection.Value[key] = value; 50 | } 51 | } 52 | 53 | public string Key => _primarySection.Value.Key; 54 | 55 | public string Path => _primarySection.Value.Path; 56 | 57 | public string? Value 58 | { 59 | #if NET8_0_OR_GREATER 60 | #pragma warning disable CS8603 // Possible null reference return. 61 | #endif 62 | get => _primarySection.Value.Value; 63 | #if NET8_0_OR_GREATER 64 | #pragma warning restore CS8603 // Possible null reference return. 65 | #endif 66 | #if NET8_0_OR_GREATER 67 | #pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). 68 | #endif 69 | set => _primarySection.Value.Value = value; 70 | #if NET8_0_OR_GREATER 71 | #pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). 72 | #endif 73 | } 74 | 75 | private sealed class CompositeSectionKeyComparer : IComparer 76 | { 77 | public static readonly IComparer Instance = new CompositeSectionKeyComparer(); 78 | 79 | private CompositeSectionKeyComparer() { } 80 | 81 | int IComparer.Compare(string? x, string? y) 82 | { 83 | var xIsInt = uint.TryParse(x, out var xValue); 84 | var yIsInt = uint.TryParse(y, out var yValue); 85 | 86 | // Do not change order when neither key is numeric. 87 | if (!xIsInt && !yIsInt) 88 | { 89 | return 0; 90 | } 91 | 92 | // Put numeric keys in ascending order. 93 | if (xIsInt && yIsInt) 94 | { 95 | return xValue.CompareTo(yValue); 96 | } 97 | 98 | // Put numeric keys after non-numeric keys. 99 | if (xIsInt) 100 | { 101 | return 1; 102 | } 103 | 104 | return -1; 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /RockLib.Configuration/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /RockLib.Configuration/RockLib.Configuration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Copyright 2017-2025 (c) Rocket Mortgage. All rights reserved. 4 | Embedded 5 | Provides a central location for an instance of IConfigurationRoot to be used as the "default" configuration by .NET libraries and applications. Replaces some of the functionality of the .NET Framework System.Configuration.ConfigurationManager class. 6 | True 7 | True 8 | RockLib.Configuration 9 | LICENSE.md 10 | false 11 | A changelog is available at https://github.com/RockLib/RockLib.Configuration/blob/main/RockLib.Configuration/CHANGELOG.md. 12 | https://github.com/RockLib/RockLib.Configuration 13 | 5.0.0 14 | icon.png 15 | Configuration ConfigurationRoot IConfigurationRoot ConfigurationManager AppSettings 16 | True 17 | 5.0.0 18 | 19 | 20 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /RockLib.Configuration/RockLib.Configuration.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31205.134 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration", "RockLib.Configuration.csproj", "{33869C6F-C539-4FC7-89A6-114BFA3B05D8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.CustomConfigTests", "..\Tests\RockLib.Configuration.CustomConfigTests\RockLib.Configuration.CustomConfigTests.csproj", "{39DDD7E6-739F-4631-AF64-7A7EB1D55A1A}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.AppSettingsConfigTests", "..\Tests\RockLib.Configuration.AppSettingsConfigTests\RockLib.Configuration.AppSettingsConfigTests.csproj", "{B256C4B7-00A4-4FB5-9820-96DDF5F16E30}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Configuration.Tests", "..\Tests\RockLib.Configuration.Tests\RockLib.Configuration.Tests.csproj", "{1FC97D1E-0A4B-446D-A605-18A5BC3ED207}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {33869C6F-C539-4FC7-89A6-114BFA3B05D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {33869C6F-C539-4FC7-89A6-114BFA3B05D8}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {33869C6F-C539-4FC7-89A6-114BFA3B05D8}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {33869C6F-C539-4FC7-89A6-114BFA3B05D8}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {39DDD7E6-739F-4631-AF64-7A7EB1D55A1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {39DDD7E6-739F-4631-AF64-7A7EB1D55A1A}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {39DDD7E6-739F-4631-AF64-7A7EB1D55A1A}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {39DDD7E6-739F-4631-AF64-7A7EB1D55A1A}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {B256C4B7-00A4-4FB5-9820-96DDF5F16E30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {B256C4B7-00A4-4FB5-9820-96DDF5F16E30}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {B256C4B7-00A4-4FB5-9820-96DDF5F16E30}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {B256C4B7-00A4-4FB5-9820-96DDF5F16E30}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {1FC97D1E-0A4B-446D-A605-18A5BC3ED207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1FC97D1E-0A4B-446D-A605-18A5BC3ED207}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1FC97D1E-0A4B-446D-A605-18A5BC3ED207}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1FC97D1E-0A4B-446D-A605-18A5BC3ED207}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {DF36CA15-3280-487B-AA81-A9F8C00A4CBB} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /RockLib.Configuration/RockLibConfigurationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | 4 | namespace RockLib.Configuration 5 | { 6 | /// 7 | /// Extension methods for adding configuration providers to an instance of . 8 | /// 9 | public static class RockLibConfigurationBuilderExtensions 10 | { 11 | /// 12 | /// The default value for whether a configuration should be reloaded when its source changes. 13 | /// 14 | public const bool DefaultReloadOnChange = false; 15 | 16 | /// 17 | /// Sets the value of the property by building the specified 18 | /// . 19 | /// 20 | /// 21 | /// The that will be the source of 22 | /// the property. 23 | /// 24 | /// The 25 | public static IConfigurationBuilder SetConfigRoot(this IConfigurationBuilder builder) 26 | { 27 | Config.SetRoot(() => builder.Build()); 28 | return builder; 29 | } 30 | 31 | /// 32 | /// Adds the ASP.NET Core appsettings.json configuration provider to the builder using the configuration file "appsettings.json", 33 | /// relative to the base path stored in of the builder. 34 | /// 35 | /// The to add to. 36 | /// If is null. 37 | /// The . 38 | public static IConfigurationBuilder AddAppSettingsJson(this IConfigurationBuilder builder) => 39 | builder.AddAppSettingsJson(DefaultReloadOnChange); 40 | 41 | /// 42 | /// Adds the ASP.NET Core appsettings.json configuration provider to the builder using the configuration file "appsettings.json", 43 | /// relative to the base path stored in of the builder. 44 | /// 45 | /// The to add to. 46 | /// Whether the configuration should be reloaded if the appsettings.json file changes. 47 | /// If is null. 48 | /// The . 49 | public static IConfigurationBuilder AddAppSettingsJson(this IConfigurationBuilder builder, bool reloadOnChange) 50 | { 51 | #if NET6_0_OR_GREATER 52 | ArgumentNullException.ThrowIfNull(builder); 53 | #else 54 | if (builder is null) { throw new ArgumentNullException(nameof(builder)); } 55 | #endif 56 | 57 | // we want the optional value to be true so that it will not throw a runtime exception if the file is not found 58 | builder = builder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange); 59 | 60 | var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); 61 | 62 | if (string.IsNullOrEmpty(environment)) 63 | environment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); 64 | 65 | if (string.IsNullOrEmpty(environment)) 66 | environment = Environment.GetEnvironmentVariable("ROCKLIB_ENVIRONMENT"); 67 | 68 | if (!string.IsNullOrEmpty(environment)) 69 | builder = builder.AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: reloadOnChange); 70 | 71 | return builder; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RockLib.Configuration/SoftLock.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace RockLib.Configuration; 4 | 5 | /// 6 | /// This is a copy of 7 | /// https://raw.githubusercontent.com/RockLib/RockLib.Threading/main/RockLib.Threading/SoftLock.cs. 8 | /// There are no updates to this code, so it was decided to inline the implementation into RockLib.Configuration 9 | /// 10 | internal sealed class SoftLock 11 | { 12 | private const int _lockNotAcquired = 0; 13 | private const int _lockAcquired = 1; 14 | 15 | private int _lock; 16 | 17 | /// 18 | /// Try to acquire the lock. Returns true if the lock is acquired. Returns false if the lock has 19 | /// already been acquired. 20 | /// 21 | /// True, if the lock was acquired. False, if another thread currently has the lock 22 | public bool TryAcquire() 23 | { 24 | return Interlocked.Exchange(ref _lock, _lockAcquired) == _lockNotAcquired; 25 | } 26 | 27 | /// 28 | /// Release the lock. Should only be called after successfully acquiring the lock. 29 | /// 30 | public void Release() 31 | { 32 | Interlocked.Exchange(ref _lock, _lockNotAcquired); 33 | } 34 | 35 | /// 36 | /// Gets a value indicating whether the lock has been acquired. 37 | /// 38 | public bool IsLockAcquired => _lock == _lockAcquired; 39 | } -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Primitives; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | using System.IO; 6 | using System.Threading; 7 | using Xunit; 8 | 9 | namespace RockLib.Configuration.AppSettingsConfigTests 10 | { 11 | public class ConfigTests 12 | { 13 | public ConfigTests() 14 | { 15 | // Set the environment variable before reading configs. 16 | Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); 17 | Environment.SetEnvironmentVariable("ROCKLIB_ENVIRONMENT", "Staging"); 18 | Environment.SetEnvironmentVariable("AppSettings:RockLib.Environment", "Prod"); 19 | } 20 | 21 | [Fact(DisplayName = "AppSettingsConfigTests: IsDefault is true by default.")] 22 | public void IsDefaultIsTrueByDefault() 23 | { 24 | Assert.True(Config.IsDefault); 25 | } 26 | 27 | [Fact(DisplayName = "AppSettingsConfigTests: 'appsettings.json' is used by default.")] 28 | public void FileConfigIsUsedByDefault() 29 | { 30 | Assert.Equal("201740", Config.AppSettings["ApplicationId"]); 31 | } 32 | 33 | [Fact(DisplayName = "AppSettingsConfigTests: Environment variables override values from 'appsettings.json'.")] 34 | public void EnvironmentVariablesOverrideFileConfig() 35 | { 36 | // Note that the "AppSettings:RockLib.Environment" environment variable was 37 | // set in the static constructor with a value of "Prod". 38 | Assert.Equal("Prod", Config.AppSettings["RockLib.Environment"]); 39 | } 40 | 41 | [Fact(DisplayName = "AppSettingsConfigTests: IsLocked is true after the ConfigurationRoot property has been accessed.")] 42 | public void IsLockedIsTrueAfterConfigurationRootHasBeenAccessed() 43 | { 44 | // Accessing ConfigurationRoot causes IsLocked to be true. 45 | var root = Config.Root; 46 | 47 | Assert.True(Config.IsLocked); 48 | } 49 | 50 | [Fact(DisplayName = "AppSettingsConfigTests: Changing environment variable values when IsLocked is true has no effect.")] 51 | public void EnvironmentVariablesDoNotOverrideAfterLocked() 52 | { 53 | // Accessing ConfigurationRoot causes IsLocked to be true. 54 | var root = Config.Root; 55 | 56 | // This is the same environment variable that we successfully changed in the static constructor. 57 | // Setting it this time, however, is too late. 58 | Environment.SetEnvironmentVariable("AppSettings:RockLib.Environment", "Beta"); 59 | 60 | // No effect. 61 | Assert.Equal("Prod", Config.AppSettings["RockLib.Environment"]); 62 | } 63 | 64 | [Fact(DisplayName = "AppSettingsConfigTests: appsettings.development.json is the loaded as FileName due to priority")] 65 | public void AppSettingsDevelopmentJsonIsLoaded() 66 | { 67 | Assert.Equal("appsettings.development.json", Config.AppSettings["FileName"]); 68 | } 69 | 70 | [Fact] 71 | public void ReloadTest() 72 | { 73 | try 74 | { 75 | var section = Config.Root!.GetSection("element_to_reload"); 76 | 77 | Assert.Equal("123", section.Value); 78 | 79 | using var waitHandle = new AutoResetEvent(false); 80 | 81 | ChangeToken.OnChange(section.GetReloadToken, () => waitHandle.Set()); 82 | 83 | WriteConfig(456); 84 | 85 | waitHandle.WaitOne(); 86 | 87 | Assert.Equal("456", section.Value); 88 | } 89 | finally 90 | { 91 | WriteConfig(123); 92 | } 93 | } 94 | 95 | private static void WriteConfig(int value) 96 | { 97 | var json = JObject.Parse(File.ReadAllText("appsettings.json")); 98 | ((JValue)json["element_to_reload"]!).Value = value; 99 | File.WriteAllText("appsettings.json", json.ToString(Formatting.Indented)); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/RockLib.Configuration.AppSettingsConfigTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | runtime; build; native; contentfiles; analyzers; buildtransitive 8 | all 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Always 22 | 23 | 24 | Always 25 | 26 | 27 | Always 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/appsettings.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "RockLib.Environment": "Dev", 4 | "FileName": "appsettings.development.json" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "ApplicationId": 201740, 4 | "RockLib.Environment": "Test", 5 | "FileName": "appsettings.json" 6 | }, 7 | 8 | "element_to_reload": 123 9 | } 10 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AppSettingsConfigTests/appsettings.staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "AppSettings": { 3 | "RockLib.Environment": "Beta", 4 | "FileName": "appsettings.staging.json" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AspNetCore.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AspNetCore.Tests/AspNetExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using Xunit; 7 | 8 | namespace RockLib.Configuration.AspNetCore.Tests; 9 | 10 | public class AspNetExtensionsTests 11 | { 12 | [Fact] 13 | public void SetConfigRootSetsConfigRoot() 14 | { 15 | var configRoot = new ConfigurationBuilder().Build(); 16 | 17 | var builder = new TestWebHostBuilder(configRoot); 18 | 19 | builder.SetConfigRoot(); 20 | 21 | Config.Root.Should().BeSameAs(configRoot); 22 | } 23 | 24 | private sealed class TestWebHostBuilder : IWebHostBuilder 25 | { 26 | private readonly IConfiguration _configRoot; 27 | 28 | public TestWebHostBuilder(IConfiguration configRoot) 29 | { 30 | _configRoot = configRoot; 31 | } 32 | 33 | public IWebHostBuilder ConfigureServices(Action configureServices) 34 | { 35 | var context = new WebHostBuilderContext { Configuration = _configRoot }; 36 | configureServices(context, null); 37 | return this; 38 | } 39 | 40 | public IWebHost Build() => throw new NotImplementedException(); 41 | public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) => throw new NotImplementedException(); 42 | public IWebHostBuilder ConfigureServices(Action configureServices) => throw new NotImplementedException(); 43 | public string GetSetting(string key) => throw new NotImplementedException(); 44 | public IWebHostBuilder UseSetting(string key, string? value) => throw new NotImplementedException(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AspNetCore.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.AspNetCore.Tests/RockLib.Configuration.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | all 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.CustomConfigTests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.CustomConfigTests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.CustomConfigTests/ConfigTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | 5 | #if NET8_0_OR_GREATER 6 | #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. 7 | #endif 8 | namespace RockLib.Configuration.CustomConfigurationManagerTests; 9 | 10 | public class ConfigTests 11 | { 12 | private readonly IConfiguration _configuration; 13 | 14 | public ConfigTests() 15 | { 16 | _configuration = new ConfigurationBuilder() 17 | .AddInMemoryCollection( 18 | new Dictionary 19 | { 20 | { "AppSettings:Environment", "Test" }, 21 | { "AppSettings:ApplicationId", "200001" } 22 | }) 23 | .Build(); 24 | 25 | Config.SetRoot(_configuration); 26 | } 27 | 28 | [Fact(DisplayName = "CustomConfigurationManagerTests: IsDefault is false when SetConfigurationRoot has been called.")] 29 | public void IsDefaultIsFalseWhenCustomized() 30 | { 31 | Assert.False(Config.IsDefault); 32 | } 33 | 34 | [Fact(DisplayName = "CustomConfigurationManagerTests: The ConfigurationRoot property is the same instance passed to SetConfigurationRoot.")] 35 | public void TheCustomConfigurationRootIsUsed() 36 | { 37 | Assert.Same(_configuration, Config.Root); 38 | Assert.Equal("Test", Config.AppSettings["Environment"]); 39 | Assert.Equal("200001", Config.AppSettings["ApplicationId"]); 40 | } 41 | } 42 | #if NET8_0_OR_GREATER 43 | #pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. 44 | #endif -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.CustomConfigTests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.CustomConfigTests/RockLib.Configuration.CustomConfigTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | runtime; build; native; contentfiles; analyzers; buildtransitive 8 | all 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/BlocklistSettingFilterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using System; 4 | using System.Collections.Generic; 5 | using Xunit; 6 | 7 | namespace RockLib.Configuration.MessagingProvider.Tests 8 | { 9 | public static class BlocklistSettingFilterTests 10 | { 11 | private static readonly string[] blockedSettings = new[] { "foo" }; 12 | 13 | [Fact] 14 | public static void ConstructorThrowsIfBlockedSettingsIsNull() 15 | { 16 | var action = () => new BlocklistSettingFilter(null!); 17 | action.Should().ThrowExactly(); 18 | } 19 | 20 | [Fact] 21 | public static void ConstructorSetsBlockedSettings() 22 | { 23 | var blockedSettings = new[] { "foo" }; 24 | 25 | var filter = new BlocklistSettingFilter(blockedSettings); 26 | 27 | filter.BlockedSettings.Should().BeEquivalentTo(blockedSettings); 28 | } 29 | 30 | [Fact] 31 | public static void ConstructorSetsInnerFilter() 32 | { 33 | var innerFilter = Mock.Of();// new FakeSettingFilter(); 34 | 35 | var filter = new BlocklistSettingFilter(blockedSettings, innerFilter); 36 | 37 | filter.InnerFilter.Should().BeSameAs(innerFilter); 38 | } 39 | 40 | [Fact] 41 | public static void ReturnsWhatTheInnerFilterReturnsWhenTheSettingIsNotInTheBlocklist() 42 | { 43 | var mockInnerFilter = new Mock(); 44 | mockInnerFilter 45 | .Setup(m => m.ShouldProcessSettingChange(It.IsAny(), It.IsAny>())) 46 | .Returns(false); 47 | 48 | var filter = new BlocklistSettingFilter(blockedSettings, mockInnerFilter.Object); 49 | 50 | var receivedMessageHeaders = new Dictionary(); 51 | 52 | filter.ShouldProcessSettingChange("bar", receivedMessageHeaders) 53 | .Should().Be(false); 54 | 55 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 56 | It.Is(s => s == "bar"), It.Is>(headers => headers == receivedMessageHeaders))); 57 | } 58 | 59 | [Fact] 60 | public static void ReturnsFalseWhenTheSettingIsInTheBlocklist() 61 | { 62 | var mockInnerFilter = new Mock(); 63 | 64 | var filter = new BlocklistSettingFilter(blockedSettings, mockInnerFilter.Object); 65 | 66 | filter.ShouldProcessSettingChange("foo", new Dictionary()) 67 | .Should().Be(false); 68 | 69 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 70 | It.IsAny(), It.IsAny>()), Times.Never); 71 | } 72 | 73 | [Fact] 74 | public static void ReturnsFalseWhenTheSettingIsAChildOfAnItemInTheBlocklist() 75 | { 76 | var mockInnerFilter = new Mock(); 77 | 78 | var filter = new BlocklistSettingFilter(blockedSettings, mockInnerFilter.Object); 79 | 80 | filter.ShouldProcessSettingChange("foo:bar", new Dictionary()) 81 | .Should().Be(false); 82 | 83 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 84 | It.IsAny(), It.IsAny>()), Times.Never); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/FakeReceiver.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging; 2 | using System; 3 | 4 | namespace RockLib.Configuration.MessagingProvider.Tests 5 | { 6 | // This class is only for the appsettings.json test. 7 | // Use Moq to create a mocked version of Receiver instead. 8 | public sealed class FakeReceiver : Receiver 9 | { 10 | public FakeReceiver(string name) 11 | : base(name) { } 12 | 13 | protected override void Start() 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/MessagingConfigurationSourceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Messaging; 4 | using System; 5 | using Xunit; 6 | 7 | namespace RockLib.Configuration.MessagingProvider.Tests 8 | { 9 | public static class MessagingConfigurationSourceTests 10 | { 11 | [Fact] 12 | public static void ConstructorThrowsIfReceiverIsNull() 13 | { 14 | var action = () => new MessagingConfigurationSource(null!); 15 | action.Should().ThrowExactly().WithMessage("*receiver*"); 16 | } 17 | 18 | [Fact] 19 | public static void ConstructorThrowsIfReceiverIsUsedByAnotherMessagingConfigurationSource() 20 | { 21 | using var receiver = new Mock("fake").Object; 22 | 23 | // Create a source with the receiver and throw it away. 24 | _ = new MessagingConfigurationSource(receiver); 25 | 26 | // Passing the same receiver to another source causes it to throw. 27 | var action = () => new MessagingConfigurationSource(receiver); 28 | action.Should().ThrowExactly().WithMessage("The same instance of IReceiver cannot be used to create multiple instances of MessagingConfigurationSource.*receiver*"); 29 | } 30 | 31 | [Fact] 32 | public static void ConstructorThrowsIfReceiverIsAlreadyStarted() 33 | { 34 | using var receiver = new Mock("fake").Object; 35 | receiver.Start(m => m.AcknowledgeAsync()); 36 | 37 | var action = () => new MessagingConfigurationSource(receiver); 38 | action.Should().ThrowExactly().WithMessage("The receiver is already started.*receiver*"); 39 | } 40 | 41 | [Fact] 42 | public static void ConstructorSetsReceiverProperty() 43 | { 44 | using var receiver = new Mock("fake").Object; 45 | 46 | var source = new MessagingConfigurationSource(receiver); 47 | 48 | source.Receiver.Should().BeSameAs(receiver); 49 | } 50 | 51 | [Fact] 52 | public static void ConstructorSetsSettingFilterProperty() 53 | { 54 | using var receiver = new Mock("fake").Object; 55 | var filter = Mock.Of(); 56 | 57 | var source = new MessagingConfigurationSource(receiver, filter); 58 | 59 | source.SettingFilter.Should().BeSameAs(filter); 60 | } 61 | 62 | [Fact] 63 | public static void BuildMethodReturnsMessagingConfigurationProvider() 64 | { 65 | using var receiver = new Mock("fake").Object; 66 | var filter = Mock.Of(); 67 | 68 | var source = new MessagingConfigurationSource(receiver, filter); 69 | 70 | var provider = source.Build(null!); 71 | 72 | provider.Should().BeOfType(); 73 | 74 | var messagingProvider = (MessagingConfigurationProvider)provider; 75 | 76 | messagingProvider.Receiver.Should().BeSameAs(receiver); 77 | messagingProvider.SettingFilter.Should().BeSameAs(filter); 78 | } 79 | 80 | [Fact] 81 | public static void BuildMethodReturnsSameMessagingConfigurationProviderEachTime() 82 | { 83 | using var receiver = new Mock("fake").Object; 84 | 85 | var source = new MessagingConfigurationSource(receiver); 86 | 87 | var provider1 = source.Build(null!); 88 | var provider2 = source.Build(null!); 89 | 90 | provider1.Should().BeSameAs(provider2); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/NullSettingFilterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | 5 | namespace RockLib.Configuration.MessagingProvider.Tests 6 | { 7 | public static class NullSettingFilterTests 8 | { 9 | [Fact] 10 | public static void AlwaysReturnsTrue() 11 | { 12 | NullSettingFilter.Instance.ShouldProcessSettingChange(null!, new Dictionary()).Should().BeTrue(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/RockLib.Configuration.MessagingProvider.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | xUnit1030 5 | 6 | 7 | 8 | 9 | 10 | 11 | Always 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | all 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/RockLibMessagingProviderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Configuration.Json; 4 | using Moq; 5 | using RockLib.Messaging; 6 | using Xunit; 7 | 8 | namespace RockLib.Configuration.MessagingProvider.Tests 9 | { 10 | public static class RockLibMessagingProviderExtensionsTests 11 | { 12 | [Fact] 13 | public static void AddRockLibMessagingProviderExtensionMethod1AddsAMessagingConfigurationSourceToTheConfigurationBuilder() 14 | { 15 | var filter = Mock.Of(); 16 | 17 | var builder = new ConfigurationBuilder() 18 | .AddJsonFile("appsettings.json") 19 | .AddRockLibMessagingProvider("fake", filter); 20 | 21 | builder.Sources.Should().HaveCount(2); 22 | builder.Sources[0].Should().BeOfType(); 23 | builder.Sources[1].Should().BeOfType(); 24 | 25 | var source = (MessagingConfigurationSource)builder.Sources[1]; 26 | 27 | source.Receiver.Should().BeOfType(); 28 | source.Receiver.Name.Should().Be("fake"); 29 | source.SettingFilter.Should().BeSameAs(filter); 30 | } 31 | 32 | [Fact] 33 | public static void AddRockLibMessagingProviderExtensionMethod2AddsAMessagingConfigurationSourceToTheConfigurationBuilder() 34 | { 35 | using var receiver = new Mock("fake").Object; 36 | var filter = Mock.Of(); 37 | 38 | var builder = new ConfigurationBuilder(); 39 | 40 | builder.AddRockLibMessagingProvider(receiver, filter); 41 | 42 | builder.Sources.Should().HaveCount(1); 43 | builder.Sources[0].Should().BeOfType(); 44 | 45 | var source = (MessagingConfigurationSource)builder.Sources[0]; 46 | 47 | source.Receiver.Should().BeSameAs(receiver); 48 | source.SettingFilter.Should().BeSameAs(filter); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/SafelistSettingFilterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using System; 4 | using System.Collections.Generic; 5 | using Xunit; 6 | 7 | namespace RockLib.Configuration.MessagingProvider.Tests 8 | { 9 | public static class SafelistSettingFilterTests 10 | { 11 | private static readonly string[] safeSettings = new[] { "foo" }; 12 | 13 | [Fact] 14 | public static void ConstructorThrowsIfSafeSettingsIsNull() 15 | { 16 | var action = () => new SafelistSettingFilter(null!); 17 | action.Should().ThrowExactly(); 18 | } 19 | 20 | [Fact] 21 | public static void ConstructorSetsSafeSettings() 22 | { 23 | var safeSettings = new[] { "foo" }; 24 | 25 | var filter = new SafelistSettingFilter(safeSettings); 26 | 27 | filter.SafeSettings.Should().BeEquivalentTo(safeSettings); 28 | } 29 | 30 | [Fact] 31 | public static void ConstructorSetsInnerFilter() 32 | { 33 | var innerFilter = Mock.Of(); 34 | 35 | var filter = new SafelistSettingFilter(safeSettings, innerFilter); 36 | 37 | filter.InnerFilter.Should().BeSameAs(innerFilter); 38 | } 39 | 40 | [Fact] 41 | public static void ReturnsWhatTheInnerFilterReturnsWhenTheSettingIsInTheSafelist() 42 | { 43 | var mockInnerFilter = new Mock(); 44 | mockInnerFilter 45 | .Setup(m => m.ShouldProcessSettingChange(It.IsAny(), It.IsAny>())) 46 | .Returns(false); 47 | 48 | var filter = new SafelistSettingFilter(safeSettings, mockInnerFilter.Object); 49 | 50 | var receivedMessageHeaders = new Dictionary(); 51 | 52 | filter.ShouldProcessSettingChange("foo", receivedMessageHeaders) 53 | .Should().Be(false); 54 | 55 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 56 | It.Is(s => s == "foo"), It.Is>(headers => headers == receivedMessageHeaders))); 57 | } 58 | 59 | [Fact] 60 | public static void ReturnsWhatTheInnerFilterReturnsWhenTheSettingIsAChildOfAnItemInTheAllowlist() 61 | { 62 | var mockInnerFilter = new Mock(); 63 | mockInnerFilter 64 | .Setup(m => m.ShouldProcessSettingChange(It.IsAny(), It.IsAny>())) 65 | .Returns(false); 66 | 67 | var filter = new SafelistSettingFilter(safeSettings, mockInnerFilter.Object); 68 | 69 | var receivedMessageHeaders = new Dictionary(); 70 | 71 | filter.ShouldProcessSettingChange("foo:bar", receivedMessageHeaders) 72 | .Should().Be(false); 73 | 74 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 75 | It.Is(s => s == "foo:bar"), It.Is>(headers => headers == receivedMessageHeaders))); 76 | } 77 | 78 | [Fact] 79 | public static void ReturnsFalseWhenTheSettingIsNotInTheSafelist() 80 | { 81 | var mockInnerFilter = new Mock(); 82 | 83 | var filter = new SafelistSettingFilter(safeSettings, mockInnerFilter.Object); 84 | 85 | filter.ShouldProcessSettingChange("bar", new Dictionary()) 86 | .Should().Be(false); 87 | 88 | mockInnerFilter.Verify(m => m.ShouldProcessSettingChange( 89 | It.IsAny(), It.IsAny>()), Times.Never); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.MessagingProvider.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "receivers": { 4 | "type": "RockLib.Configuration.MessagingProvider.Tests.FakeReceiver, RockLib.Configuration.MessagingProvider.Tests", 5 | "value": { 6 | "name": "fake" 7 | }, 8 | "reloadOnChange": false 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ObjectFactory.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | csharp_indent_case_contents = true 10 | csharp_indent_switch_labels = true 11 | csharp_new_line_before_catch = true 12 | csharp_new_line_before_else = true 13 | csharp_new_line_before_finally = true 14 | csharp_new_line_before_members_in_anonymous_types = false 15 | csharp_new_line_before_members_in_object_initializers = false 16 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 17 | csharp_new_line_between_query_expression_clauses = true 18 | csharp_prefer_braces = false:suggestion 19 | csharp_prefer_simple_default_expression = true:suggestion 20 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 21 | csharp_preserve_single_line_blocks = true 22 | csharp_preserve_single_line_statements = true 23 | csharp_space_after_cast = false 24 | csharp_space_after_colon_in_inheritance_clause = true 25 | csharp_space_after_keywords_in_control_flow_statements = true 26 | csharp_space_before_colon_in_inheritance_clause = true 27 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 28 | csharp_space_between_method_call_name_and_opening_parenthesis = false 29 | csharp_space_between_method_call_parameter_list_parentheses = false 30 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_parameter_list_parentheses = false 32 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 33 | csharp_style_expression_bodied_accessors = true:suggestion 34 | csharp_style_expression_bodied_constructors = false:suggestion 35 | csharp_style_expression_bodied_methods = false:suggestion 36 | csharp_style_expression_bodied_properties = true:suggestion 37 | csharp_style_inlined_variable_declaration = true:suggestion 38 | csharp_style_var_elsewhere = true:suggestion 39 | csharp_style_var_for_built_in_types = true:suggestion 40 | csharp_style_var_when_type_is_apparent = true:suggestion 41 | dotnet_sort_system_directives_first = false 42 | dotnet_style_explicit_tuple_names = true:suggestion 43 | dotnet_style_object_initializer = true:suggestion 44 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 47 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 48 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 49 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 50 | dotnet_style_qualification_for_field = false:suggestion 51 | dotnet_style_qualification_for_method = false:suggestion 52 | dotnet_style_qualification_for_property = false:suggestion 53 | 54 | 55 | # Analyzer Configuration 56 | # These are rules we want to either ignore or have set as suggestion or info 57 | 58 | # CA1014: Mark assemblies with CLSCompliant 59 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 60 | dotnet_diagnostic.CA1014.severity = none 61 | 62 | # CA1725: Parameter names should match base declaration 63 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 64 | dotnet_diagnostic.CA1725.severity = suggestion 65 | 66 | # CA2227: Collection properties should be read only 67 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 68 | dotnet_diagnostic.CA2227.severity = suggestion -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ObjectFactory.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ObjectFactory.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ObjectFactory.Tests/RockLib.Configuration.ObjectFactory.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | Tests 5 | Debug;Release;ReferenceModel 6 | 7 | 8 | TRACE;REFERENCE_MODEL 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ProxyFactory.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ProxyFactory.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.ProxyFactory.Tests/RockLib.Configuration.ProxyFactory.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Tests 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | all 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.Tests/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | 6 | [*.cs] 7 | # Styling 8 | indent_style = space 9 | indent_size = 4 10 | csharp_indent_case_contents = true 11 | csharp_indent_switch_labels = true 12 | csharp_new_line_before_catch = true 13 | csharp_new_line_before_else = true 14 | csharp_new_line_before_finally = true 15 | csharp_new_line_before_members_in_anonymous_types = false 16 | csharp_new_line_before_members_in_object_initializers = false 17 | csharp_new_line_before_open_brace = methods, control_blocks, types, properties, lambdas, accessors, object_collection_array_initializers 18 | csharp_new_line_between_query_expression_clauses = true 19 | csharp_prefer_braces = false:suggestion 20 | csharp_prefer_simple_default_expression = true:suggestion 21 | csharp_preferred_modifier_order = public,private,internal,protected,static,readonly,async,override,sealed:suggestion 22 | csharp_preserve_single_line_blocks = true 23 | csharp_preserve_single_line_statements = true 24 | csharp_space_after_cast = false 25 | csharp_space_after_colon_in_inheritance_clause = true 26 | csharp_space_after_keywords_in_control_flow_statements = true 27 | csharp_space_before_colon_in_inheritance_clause = true 28 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 29 | csharp_space_between_method_call_name_and_opening_parenthesis = false 30 | csharp_space_between_method_call_parameter_list_parentheses = false 31 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 34 | csharp_style_expression_bodied_accessors = true:suggestion 35 | csharp_style_expression_bodied_constructors = false:suggestion 36 | csharp_style_expression_bodied_methods = false:suggestion 37 | csharp_style_expression_bodied_properties = true:suggestion 38 | csharp_style_inlined_variable_declaration = true:suggestion 39 | csharp_style_var_elsewhere = true:suggestion 40 | csharp_style_var_for_built_in_types = true:suggestion 41 | csharp_style_var_when_type_is_apparent = true:suggestion 42 | dotnet_sort_system_directives_first = false 43 | dotnet_style_explicit_tuple_names = true:suggestion 44 | dotnet_style_object_initializer = true:suggestion 45 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 46 | dotnet_style_predefined_type_for_member_access = true:suggestion 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion 48 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 49 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 50 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 51 | dotnet_style_qualification_for_field = false:suggestion 52 | dotnet_style_qualification_for_method = false:suggestion 53 | dotnet_style_qualification_for_property = false:suggestion 54 | 55 | 56 | # Analyzer Configuration 57 | # These are rules we want to either ignore or have set as suggestion or info 58 | 59 | # CA1014: Mark assemblies with CLSCompliant 60 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1014 61 | dotnet_diagnostic.CA1014.severity = none 62 | 63 | # CA1725: Parameter names should match base declaration 64 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1725 65 | dotnet_diagnostic.CA1725.severity = suggestion 66 | 67 | # CA2227: Collection properties should be read only 68 | # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2227 69 | dotnet_diagnostic.CA2227.severity = suggestion 70 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.Tests/CompositeSectionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.Configuration; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace RockLib.Configuration.Tests; 8 | 9 | #if NET8_0_OR_GREATER 10 | #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. 11 | #endif 12 | public class CompositeSectionTests 13 | { 14 | [Fact] 15 | public void CompositeSectionCombinesSections() 16 | { 17 | var config = new ConfigurationBuilder() 18 | .AddInMemoryCollection(new Dictionary 19 | { 20 | { "foo.bar:garply:baz", "123" }, 21 | { "foo_bar:garply:qux", "abc" }, 22 | }).Build(); 23 | 24 | var foobarSection = config.GetCompositeSection("foo.bar", "foo_bar"); 25 | 26 | foobarSection["garply:baz"].Should().Be("123"); 27 | foobarSection["garply:qux"].Should().Be("abc"); 28 | 29 | var garplySection = foobarSection.GetSection("garply"); 30 | garplySection["baz"].Should().Be("123"); 31 | garplySection["qux"].Should().Be("abc"); 32 | 33 | var children = foobarSection.GetChildren().ToList(); 34 | children.Count.Should().Be(1); 35 | children[0]["baz"].Should().Be("123"); 36 | children[0]["qux"].Should().Be("abc"); 37 | } 38 | 39 | [Fact] 40 | public void CompositeSectionPathOrderIsSignificant() 41 | { 42 | var config = new ConfigurationBuilder() 43 | .AddInMemoryCollection(new Dictionary 44 | { 45 | { "foo_bar:garply:baz", "abc" }, // Both of these settings have 46 | { "foo.bar:garply:baz", "123" }, // the same composite path. 47 | }).Build(); 48 | 49 | // Since the dot section is the first key, its value should be selected. 50 | var foobarSection = config.GetCompositeSection("foo.bar", "foo_bar"); 51 | 52 | foobarSection["garply:baz"].Should().Be("123"); 53 | 54 | var garplySection = foobarSection.GetSection("garply"); 55 | garplySection["baz"].Should().Be("123"); 56 | 57 | var children = foobarSection.GetChildren().ToList(); 58 | children.Count.Should().Be(1); 59 | children[0]["baz"].Should().Be("123"); 60 | } 61 | 62 | [Fact] 63 | public void GetChildrenMaintainsOrderWhenTheChildAreListItemsAndTheSecondItemComesFromTheFirstSectionOfTheCompositeSection() 64 | { 65 | var config = new ConfigurationBuilder() 66 | .AddInMemoryCollection(new Dictionary 67 | { 68 | { "second_section:0:baz", "abc" }, 69 | { "first_section:1:baz", "xyz" }, 70 | }).Build(); 71 | 72 | var foobarSection = config.GetCompositeSection("first_section", "second_section"); 73 | 74 | var children = foobarSection.GetChildren().ToList(); 75 | children.Count.Should().Be(2); 76 | children[0]["baz"].Should().Be("abc"); 77 | children[1]["baz"].Should().Be("xyz"); 78 | } 79 | } 80 | #if NET8_0_OR_GREATER 81 | #pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. 82 | #endif -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | latest 9 | enable 10 | net48;net8.0 11 | NU1603,NU1701 12 | true 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | -------------------------------------------------------------------------------- /Tests/RockLib.Configuration.Tests/RockLib.Configuration.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | 5 | 6 | 7 | 8 | 9 | 10 | runtime; build; native; contentfiles; analyzers; buildtransitive 11 | all 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockLib/RockLib.Configuration/d960875310ad43a4331d443275c6045d5cfaf124/icon.png --------------------------------------------------------------------------------