├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ ├── Custom.md │ └── Feature_request.md └── workflows │ ├── dotnet.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Microsoft.Extensions.Configuration.Annotations.sln ├── README.md ├── README_zh.md ├── boilerplate.txt ├── examples ├── Annotations.ConsoleApp.Examples │ ├── Annotations.ConsoleApp.Examples.csproj │ ├── AppOptions.cs │ └── Program.cs └── Annotations.WebAPI.Examples │ ├── Annotations.WebAPI.Examples.csproj │ ├── Annotations.WebAPI.Examples.http │ ├── AppOptions.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── global.json ├── src └── Microsoft.Extensions.Configuration.Annotations │ ├── Binders │ ├── BinderContext.cs │ ├── ConfigurationOptionsBinder.cs │ └── ConfigurationOptionsBinderImpl.cs │ ├── ConfigurationServiceCollectionExtensions.cs │ ├── Microsoft.Extensions.Configuration.Annotations.csproj │ ├── OptionsAttribute.cs │ └── ValidateAttribute.cs ├── stylecop.json └── test └── Microsoft.Extensions.Configuration.Annotations.Test ├── ConfigBinderTest.cs ├── DevOptions.cs ├── Microsoft.Extensions.Configuration.Annotations.Test.csproj └── MyAppOptions.cs /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Using this version of the library '...' 16 | 2. Run this code '....' 17 | 3. With these arguments '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | Example. I'm am trying to do [...] but [...] 13 | 14 | **Describe the solution you'd like** 15 | A clear and concise description of what you want to happen. 16 | 17 | **Describe alternatives you've considered** 18 | A description of any alternative solutions or features you've considered. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check license 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Install licctl CLI 12 | run: | 13 | curl -# -L -o licctl.tar.gz https://github.com/seacraft/licctl/releases/download/v1.0.1/licctl-v1.0.1-linux-amd64.tar.gz 14 | tar -xzvf licctl.tar.gz 15 | sudo mv licctl /usr/local/bin/ 16 | sudo chmod +x /usr/local/bin/licctl 17 | licctl --help || true 18 | - name: Run License Check 19 | run: | 20 | licctl --check -f ./boilerplate.txt ./src 21 | licctl --check -f ./boilerplate.txt ./test 22 | 23 | unix-build: 24 | name: Running tests on ${{ matrix.os }} 25 | runs-on: ${{ matrix.os }} 26 | needs: check 27 | strategy: 28 | matrix: 29 | os: [ubuntu-latest, macOS-latest] 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Setup .NET SDK 33 | uses: actions/setup-dotnet@v4 34 | with: 35 | dotnet-version: 8.x 36 | - name: build 37 | run: dotnet build -c Release -v n 38 | - name: test 39 | run: | 40 | dotnet test ./test/Microsoft.Extensions.Configuration.Annotations.Test/Microsoft.Extensions.Configuration.Annotations.Test.csproj 41 | 42 | windows-build: 43 | runs-on: windows-latest 44 | needs: check 45 | steps: 46 | - uses: actions/checkout@v4 47 | - name: Setup .NET SDK 48 | uses: actions/setup-dotnet@v4 49 | with: 50 | dotnet-version: 8.x 51 | - name: build 52 | run: dotnet build -c Release -v n 53 | - name: test 54 | run: | 55 | dotnet test ./test/Microsoft.Extensions.Configuration.Annotations.Test/Microsoft.Extensions.Configuration.Annotations.Test.csproj 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release: 7 | description: Create a release 8 | required: true 9 | default: "true" 10 | type: boolean 11 | releaseNotes: 12 | description: Release notes 13 | required: false 14 | default: "" 15 | type: string 16 | version: 17 | description: Release version 18 | required: false 19 | default: "" 20 | type: string 21 | jobs: 22 | build: 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | os: [windows-latest, ubuntu-latest, macos-latest] 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Setup .NET 31 | uses: actions/setup-dotnet@v4 32 | with: 33 | dotnet-version: | 34 | 8.0.x 35 | # - name: Restore dependencies 36 | # run: dotnet restore 37 | - name: Run Build 38 | run: dotnet build -c Release -v n 39 | - name: Run Pack 40 | run: dotnet pack ./src/Microsoft.Extensions.Configuration.Annotations/Microsoft.Extensions.Configuration.Annotations.csproj --configuration Release --output ./artifacts/ 41 | - uses: actions/upload-artifact@v4 42 | if: ${{ matrix.os == 'windows-latest' }} 43 | with: 44 | name: packages 45 | path: artifacts/ 46 | if-no-files-found: error 47 | release: 48 | if: ${{ github.event.inputs.release && github.event.inputs.version != '' }} 49 | needs: build 50 | runs-on: windows-latest 51 | env: 52 | PACKAGE_VERSION: ${{ github.event.inputs.version }} 53 | steps: 54 | - run: echo "Releasing ${{ env.PACKAGE_VERSION }}" 55 | - name: Setup NuGet 56 | uses: NuGet/setup-nuget@v2 57 | with: 58 | nuget-version: latest 59 | - uses: actions/download-artifact@v4 60 | with: 61 | name: packages 62 | path: packages 63 | - name: Configure GitHub NuGet registry 64 | run: nuget sources add -name github -source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json -username ${{ github.repository_owner }} -password ${{ secrets.GITHUB_TOKEN }} 65 | - name: Push to GitHub package registry 66 | run: nuget push packages\*.nupkg -ApiKey ${{ secrets.GITHUB_TOKEN }} -Source github -SkipDuplicate 67 | - name: Push to NuGet.org 68 | run: nuget push packages\*.nupkg -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json 69 | - name: Create GitHub release 70 | uses: softprops/action-gh-release@v2 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | with: 74 | name: ${{ env.PACKAGE_VERSION }} 75 | tag_name: v${{ env.PACKAGE_VERSION }} 76 | body: | 77 | ## What's Changed 78 | ${{ github.event.inputs.releaseNotes }} 79 | 80 | ## How to get this update 81 | Packages have been posted to these feeds: 82 | 83 | #### NuGet.org 84 | https://nuget.org/packages/Ares.Extensions.Configuration.Annotations/${{ env.PACKAGE_VERSION }} 85 | 86 | #### GitHub Package Registry 87 | https://github.com/huhouhua?ecosystem=nuget&tab=packages&repo_name=Annotations 88 | 89 | draft: false 90 | append_body: true 91 | generate_release_notes: true 92 | prerelease: false 93 | files: packages/* 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## A streamlined .gitignore for modern .NET projects 2 | ## including temporary files, build results, and 3 | ## files generated by popular .NET tools. If you are 4 | ## developing with Visual Studio, the VS .gitignore 5 | ## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 6 | ## has more thorough IDE-specific entries. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | [Ww][Ii][Nn]32/ 18 | [Aa][Rr][Mm]/ 19 | [Aa][Rr][Mm]64/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | [Ll]ogs/ 25 | 26 | # .NET Core 27 | project.lock.json 28 | project.fragment.lock.json 29 | artifacts/ 30 | 31 | # ASP.NET Scaffolding 32 | ScaffoldingReadMe.txt 33 | 34 | # NuGet Packages 35 | *.nupkg 36 | # NuGet Symbol Packages 37 | *.snupkg 38 | 39 | # Others 40 | ~$* 41 | *~ 42 | CodeCoverage/ 43 | 44 | # MSBuild Binary and Structured Log 45 | *.binlog 46 | 47 | # MSTest test Results 48 | [Tt]est[Rr]esult*/ 49 | [Bb]uild[Ll]og.* 50 | 51 | # NUnit 52 | *.VisualState.xml 53 | TestResult.xml 54 | nunit-*.xml -------------------------------------------------------------------------------- /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 huhouhuam@outlook.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Kevin Berger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Microsoft.Extensions.Configuration.Annotations.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0FA65A07-CEDE-4D51-84E9-C8445139C50D}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B919ABC4-D7F5-425F-9A3E-5187D561F2E3}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{201E4D15-1D5A-4922-AE64-25E77083D1DD}" 8 | ProjectSection(SolutionItems) = preProject 9 | .gitignore = .gitignore 10 | LICENSE = LICENSE 11 | README.md = README.md 12 | global.json = global.json 13 | stylecop.json = stylecop.json 14 | README_zh.md = README_zh.md 15 | boilerplate.txt = boilerplate.txt 16 | CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md 17 | EndProjectSection 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Configuration.Annotations", "src\Microsoft.Extensions.Configuration.Annotations\Microsoft.Extensions.Configuration.Annotations.csproj", "{425C0637-EF92-4085-9648-48879A006815}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Configuration.Annotations.Test", "test\Microsoft.Extensions.Configuration.Annotations.Test\Microsoft.Extensions.Configuration.Annotations.Test.csproj", "{71615AD7-44AF-49BF-B15F-5E091F8846ED}" 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E0BCF406-E25A-4BCA-B0B0-F5168F7080D4}" 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{401D857E-C9DF-4148-89F0-886852E2984A}" 26 | ProjectSection(SolutionItems) = preProject 27 | .github\workflows\dotnet.yml = .github\workflows\dotnet.yml 28 | .github\workflows\release.yml = .github\workflows\release.yml 29 | EndProjectSection 30 | EndProject 31 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{F91FCCA3-8EEF-42F0-9503-719D137B5F9F}" 32 | EndProject 33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Annotations.ConsoleApp.Examples", "examples\Annotations.ConsoleApp.Examples\Annotations.ConsoleApp.Examples.csproj", "{3FDC9659-9714-420F-9836-BD60B2BC20FA}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Annotations.WebAPI.Examples", "examples\Annotations.WebAPI.Examples\Annotations.WebAPI.Examples.csproj", "{613E277D-A018-4BC7-8413-A8D3DA825F33}" 36 | EndProject 37 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{FC946277-D754-4053-8C74-A5DA96CA446F}" 38 | ProjectSection(SolutionItems) = preProject 39 | .github\ISSUE_TEMPLATE\Bug_report.md = .github\ISSUE_TEMPLATE\Bug_report.md 40 | .github\ISSUE_TEMPLATE\Custom.md = .github\ISSUE_TEMPLATE\Custom.md 41 | .github\ISSUE_TEMPLATE\Feature_request.md = .github\ISSUE_TEMPLATE\Feature_request.md 42 | EndProjectSection 43 | EndProject 44 | Global 45 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 46 | Debug|Any CPU = Debug|Any CPU 47 | Release|Any CPU = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 50 | {425C0637-EF92-4085-9648-48879A006815}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {425C0637-EF92-4085-9648-48879A006815}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {425C0637-EF92-4085-9648-48879A006815}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {425C0637-EF92-4085-9648-48879A006815}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {71615AD7-44AF-49BF-B15F-5E091F8846ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {71615AD7-44AF-49BF-B15F-5E091F8846ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {71615AD7-44AF-49BF-B15F-5E091F8846ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {71615AD7-44AF-49BF-B15F-5E091F8846ED}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {3FDC9659-9714-420F-9836-BD60B2BC20FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {3FDC9659-9714-420F-9836-BD60B2BC20FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {3FDC9659-9714-420F-9836-BD60B2BC20FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {3FDC9659-9714-420F-9836-BD60B2BC20FA}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {613E277D-A018-4BC7-8413-A8D3DA825F33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {613E277D-A018-4BC7-8413-A8D3DA825F33}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {613E277D-A018-4BC7-8413-A8D3DA825F33}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {613E277D-A018-4BC7-8413-A8D3DA825F33}.Release|Any CPU.Build.0 = Release|Any CPU 66 | EndGlobalSection 67 | GlobalSection(NestedProjects) = preSolution 68 | {425C0637-EF92-4085-9648-48879A006815} = {0FA65A07-CEDE-4D51-84E9-C8445139C50D} 69 | {71615AD7-44AF-49BF-B15F-5E091F8846ED} = {B919ABC4-D7F5-425F-9A3E-5187D561F2E3} 70 | {E0BCF406-E25A-4BCA-B0B0-F5168F7080D4} = {201E4D15-1D5A-4922-AE64-25E77083D1DD} 71 | {401D857E-C9DF-4148-89F0-886852E2984A} = {E0BCF406-E25A-4BCA-B0B0-F5168F7080D4} 72 | {3FDC9659-9714-420F-9836-BD60B2BC20FA} = {F91FCCA3-8EEF-42F0-9503-719D137B5F9F} 73 | {613E277D-A018-4BC7-8413-A8D3DA825F33} = {F91FCCA3-8EEF-42F0-9503-719D137B5F9F} 74 | {FC946277-D754-4053-8C74-A5DA96CA446F} = {E0BCF406-E25A-4BCA-B0B0-F5168F7080D4} 75 | EndGlobalSection 76 | EndGlobal 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binder Options with Microsoft.Extensions.Configuration.Annotations 2 | ![workflow ci](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/actions/workflows/dotnet.yml/badge.svg) 3 | [![NuGet](https://img.shields.io/nuget/v/Ares.Extensions.Configuration.Annotations.svg?style=flat-square)](https://www.nuget.org/Ares.Extensions.Configuration.Annotations) 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/blob/main/LICENSE) 5 | [![NuGet](https://img.shields.io/nuget/dt/Ares.Extensions.Configuration.Annotations?style=flat&logo=nuget&cacheSeconds=1&label=Downloads)](https://www.nuget.org/packages/Ares.Extensions.Configuration.Annotations) 6 | 7 | > English | [简体中文](README_zh.md) 8 | 9 | > Microsoft.Extensions.Configuration.Annotations is a library that extends the Microsoft.Extensions.Configuration 10 | > system by providing attribute-based support, allowing more flexible and structured control over configuration 11 | > items during the binding process via AOP. 12 | 13 | ## Features 14 | - Attribute Support: Automatically bind and verify configuration items through attributes 15 | - Automatic registration options 16 | - Compatibility with Existing Configuration System: Fully compatible with Microsoft.Extensions.Configuration 17 | and can be seamlessly integrated into existing projects 18 | 19 | ## Requirements 20 | 21 | This library requires .NET 8.0+. 22 | 23 | ## How to Use 24 | 25 | Install the [Nuget](https://www.nuget.org/packages/Ares.Extensions.Configuration.Annotations) package: 26 | 27 | ```sh 28 | Install-Package Ares.Extensions.Configuration.Annotations 29 | ``` 30 | 31 | Or via .NET CLI: 32 | 33 | ```sh 34 | dotnet add package Ares.Extensions.Configuration.Annotations 35 | ``` 36 | 37 | ### Configuration 38 | First, configure it in the Program.cs file as follows: 39 | 40 | ```c# 41 | var builder = WebApplication.CreateBuilder(args); 42 | 43 | // Add Ares.Extensions.Configuration.Annotations 44 | // Add all `OptionsAttribute` defined in the `Program` assembly to the IServiceCollection 45 | builder.Services.AddAttributeConfigurationOptions(builder.Configuration,true,typeof(Program).Assembly); 46 | ``` 47 | 48 | ### How to Define? 49 | 50 | The [examples](examples/) directory contains a couple of clear examples 51 | 52 | Example: Standard Options Class 53 | ```c# 54 | [Options("app")] 55 | public class MyAppOptions 56 | { 57 | public int Id { get; set; } 58 | 59 | public string Name {get; set; } 60 | ... 61 | } 62 | ``` 63 | 64 | Example: Bind Non-Public Properties 65 | ```c# 66 | [Options(SessionKey = "app", BindNonPublic = true)] 67 | public class AppOptions 68 | { 69 | private int Id { get; set; } 70 | 71 | public string Name { get;set; } 72 | ... 73 | } 74 | ``` 75 | Example: Throw Exception if Missing Properties 76 | ```c# 77 | // appsettings.json Configuration 78 | // 79 | // "App":{ 80 | // "Id":1, 81 | // "Name":"my app", 82 | // "Version": "1.0.0" 83 | // } 84 | [Options(SessionKey = "app", BindNonPublic = true,ThrowOnUnknownConfig = true)] 85 | public class AppOptions 86 | { 87 | private int Id { get; set; } 88 | 89 | public string Name { get;set; } 90 | 91 | ... 92 | } 93 | ``` 94 | ### Validators 95 | 96 | Example: Options Class with Validator 97 | ```c# 98 | [Validate] 99 | [Options("app")] 100 | public class MyAppOptions 101 | { 102 | [Range(1,20)] 103 | public int Id { get; set; } 104 | 105 | [Required] 106 | public string Name {get; set; } 107 | ... 108 | } 109 | ``` 110 | 111 | Example: Options Class with Custom Validator 112 | ```c# 113 | [Validate(typeof(MyAppValidateOptions))] 114 | [Options("app")] 115 | public class MyAppOptions 116 | { 117 | public int Id { get; set; } 118 | 119 | public string Name {get; set; } 120 | ... 121 | } 122 | 123 | public class MyAppValidateOptions : IValidateOptions 124 | { 125 | public ValidateOptionsResult Validate(string name, MyAppOptions options) 126 | { 127 | // To do validate for MyAppOptions 128 | 129 | return ValidateOptionsResult.Success; 130 | } 131 | } 132 | ``` 133 | ### Accessing Options 134 | 135 | Example: Accessing in Constructor 136 | ```c# 137 | public class MyService 138 | { 139 | private readonly MyAppOptions _myAppOptions; 140 | 141 | public MyService(IOptions myAppOptions) 142 | { 143 | _myAppOptions = myAppOptions.Value; 144 | } 145 | 146 | public void PrintSettings() 147 | { 148 | Console.WriteLine(_myAppOptions.Id); 149 | Console.WriteLine(_myAppOptions.Name); 150 | } 151 | } 152 | ``` 153 | 154 | Example: Accessing via IServiceProvider 155 | ```c# 156 | IServiceCollection services = new ServiceCollection(); 157 | 158 | IConfigurationBuilder builder = new ConfigurationBuilder(); 159 | 160 | // Add option data 161 | builder.AddInMemoryCollection(new Dictionary() 162 | { 163 | { "app:id", "1" }, 164 | { "app:name", "test app" }, 165 | { "app:version", "1.0.0" }, 166 | { "app:description", "test options bind" }, 167 | }); 168 | 169 | IConfigurationRoot configurationRoot = builder.Build(); 170 | 171 | services.AddAttributeConfigurationOptions(configurationRoot,true,typeof(Program).Assembly); 172 | 173 | var provider = services.BuildServiceProvider(); 174 | 175 | // Gets Options with Options Attribute 176 | var options = provider.GetService>(); 177 | 178 | Console.WriteLine(options.Value.Id); 179 | Console.WriteLine(options.Value.Name); 180 | 181 | ``` 182 | 183 | ## Contribute 184 | 185 | One of the easiest ways to contribute is to participate in discussions and discuss issues. You can also contribute by submitting pull requests with code changes. 186 | 187 | ### License 188 | 189 | [MIT](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/blob/main/LICENSE) 190 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # 使用 `Microsoft.Extensions.Configuration.Annotations` 绑定Options 2 | ![workflow ci](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/actions/workflows/dotnet.yml/badge.svg) 3 | [![NuGet](https://img.shields.io/nuget/v/Ares.Extensions.Configuration.Annotations.svg?style=flat-square)](https://www.nuget.org/Ares.Extensions.Configuration.Annotations) 4 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/blob/main/LICENSE) 5 | [![NuGet](https://img.shields.io/nuget/dt/Ares.Extensions.Configuration.Annotations?style=flat&logo=nuget&cacheSeconds=1&label=Downloads)](https://www.nuget.org/packages/Ares.Extensions.Configuration.Annotations) 6 | 7 | > [English](README.md) | 简体中文 8 | 9 | > Microsoft.Extensions.Configuration.Annotations 是一个用于扩展 `Microsoft.Extensions.Configuration` 配置功能库,提供注解支持, 10 | > 使得你可以在配置绑定过程中通过 `AOP`对配置项进行自动绑定与注册。 11 | 12 | ## 特性 13 | - 注解支持: 通过注解的方式自动绑定与验证配置项 14 | - 自动注册options 15 | - 与现有配置体系兼容: 完全兼容 Microsoft.Extensions.Configuration,可以在现有的项目中无缝集成。 16 | 17 | ## 要求 18 | 需要.NET 8.0+版本 19 | 20 | ## 如何使用 21 | 22 | 安装 [Nuget](https://www.nuget.org/packages/Ares.Extensions.Configuration.Annotations) 包. 23 | 24 | ```sh 25 | Install-Package Ares.Extensions.Configuration.Annotations 26 | ``` 27 | 28 | 或通过 .NET CLI: 29 | 30 | ```sh 31 | dotnet add package Ares.Extensions.Configuration.Annotations 32 | ``` 33 | 34 | ### Configuration 35 | 首先在 Program.cs 文件中,配置如下: 36 | 37 | ```c# 38 | var builder = WebApplication.CreateBuilder(args); 39 | 40 | // 添加 Ares.Extensions.Configuration.Annotations 41 | // `Program`程序集下的所有定义了`OptionsAttribute`的`Options` 添加到IServiceCollection 42 | builder.Services.AddAttributeConfigurationOptions(builder.Configuration,true,typeof(Program).Assembly); 43 | ``` 44 | 45 | ### 如何定义? 46 | 47 | [示例](examples/)目录包含几个清晰的示例 48 | 49 | 标准的Options类 50 | ```c# 51 | [Options("app")] 52 | public class MyAppOptions 53 | { 54 | public int Id { get; set; } 55 | 56 | public string Name {get; set; } 57 | ... 58 | } 59 | ``` 60 | 61 | 绑定非public的属性 62 | ```c# 63 | [Options(SessionKey = "app", BindNonPublic = true)] 64 | public class AppOptions 65 | { 66 | private int Id { get; set; } 67 | 68 | public string Name { get;set; } 69 | ... 70 | } 71 | ``` 72 | 缺少没有定义的属性,则抛出异常 73 | ```c# 74 | // appsettings.json配置 75 | // 76 | // "App":{ 77 | // "Id":1, 78 | // "Name":"my app", 79 | // "Version": "1.0.0" 80 | // } 81 | [Options(SessionKey = "app", BindNonPublic = true,ThrowOnUnknownConfig = true)] 82 | public class AppOptions 83 | { 84 | private int Id { get; set; } 85 | 86 | public string Name { get;set; } 87 | 88 | ... 89 | } 90 | ``` 91 | ### 验证器 92 | 93 | 带验证器的Options类 94 | ```c# 95 | [Validate] 96 | [Options("app")] 97 | public class MyAppOptions 98 | { 99 | [Range(1,20)] 100 | public int Id { get; set; } 101 | 102 | [Required] 103 | public string Name {get; set; } 104 | ... 105 | } 106 | ``` 107 | 108 | 自定义验证器的Options类 109 | ```c# 110 | [Validate(typeof(MyAppValidateOptions))] 111 | [Options("app")] 112 | public class MyAppOptions 113 | { 114 | public int Id { get; set; } 115 | 116 | public string Name {get; set; } 117 | ... 118 | } 119 | 120 | public class MyAppValidateOptions : IValidateOptions 121 | { 122 | public ValidateOptionsResult Validate(string name, MyAppOptions options) 123 | { 124 | // To do validate for MyAppOptions 125 | 126 | return ValidateOptionsResult.Success; 127 | } 128 | } 129 | ``` 130 | ### 获取Options 131 | 132 | 在构造函数中获取 133 | ```c# 134 | public class MyService 135 | { 136 | private readonly MyAppOptions _myAppOptions; 137 | 138 | public MyService(IOptions myAppOptions) 139 | { 140 | _myAppOptions = myAppOptions.Value; 141 | } 142 | 143 | public void PrintSettings() 144 | { 145 | Console.WriteLine(_myAppOptions.Id); 146 | Console.WriteLine(_myAppOptions.Name); 147 | } 148 | } 149 | ``` 150 | 151 | 通过IServiceProvider获取 152 | ```c# 153 | IServiceCollection services = new ServiceCollection(); 154 | 155 | IConfigurationBuilder builder = new ConfigurationBuilder(); 156 | 157 | // Add option data 158 | builder.AddInMemoryCollection(new Dictionary() 159 | { 160 | { "app:id", "1" }, 161 | { "app:name", "test app" }, 162 | { "app:version", "1.0.0" }, 163 | { "app:description", "test options bind" }, 164 | }); 165 | 166 | IConfigurationRoot configurationRoot = builder.Build(); 167 | 168 | services.AddAttributeConfigurationOptions(configurationRoot,true,typeof(Program).Assembly); 169 | 170 | var provider = services.BuildServiceProvider(); 171 | 172 | // Gets Options with Options Attribute 173 | var options = provider.GetService>(); 174 | 175 | Console.WriteLine(options.Value.Id); 176 | Console.WriteLine(options.Value.Name); 177 | 178 | ``` 179 | 180 | ## 贡献 181 | 182 | 贡献的最简单的方法之一就是是参与讨论和讨论问题(issue)。你也可以通过提交的 Pull Request 代码变更作出贡献。 183 | 184 | ### License 185 | 186 | [MIT](https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations/blob/main/LICENSE) 187 | -------------------------------------------------------------------------------- /boilerplate.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Kevin Berger Authors. All rights reserved. 2 | Licensed under the MIT license. See LICENSE file in the project root for full license information. -------------------------------------------------------------------------------- /examples/Annotations.ConsoleApp.Examples/Annotations.ConsoleApp.Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/Annotations.ConsoleApp.Examples/AppOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration.Annotations; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Annotations.ConsoleApp.Examples; 5 | 6 | [Validate(typeof(AppValidateOptions))] 7 | [Options(SessionKey = "app",BindNonPublic = true,ThrowOnUnknownConfig = true)] 8 | public class AppOptions 9 | { 10 | private int Id { get; set; } 11 | 12 | public string Name {get; set; } 13 | 14 | public string Version { get; set; } 15 | 16 | internal string Description { get; set; } 17 | } 18 | 19 | 20 | public class AppValidateOptions : IValidateOptions 21 | { 22 | public ValidateOptionsResult Validate(string name, AppOptions options) 23 | { 24 | // To do validate for AppOptions 25 | 26 | return ValidateOptionsResult.Success; 27 | } 28 | } -------------------------------------------------------------------------------- /examples/Annotations.ConsoleApp.Examples/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | 3 | using Annotations.ConsoleApp.Examples; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Options; 7 | 8 | IServiceCollection services = new ServiceCollection(); 9 | IConfigurationBuilder builder = new ConfigurationBuilder(); 10 | // Add option data 11 | builder.AddInMemoryCollection(InitialData()); 12 | 13 | IConfigurationRoot configurationRoot = builder.Build(); 14 | 15 | // Injection of Microsoft.Extensions.Configuration.Annotations components 16 | services.AddAttributeConfigurationOptions(configurationRoot,true , typeof(Program).Assembly); 17 | 18 | 19 | IServiceProvider provider = services.BuildServiceProvider(); 20 | 21 | // Gets Options with Options Attribute 22 | var options = provider.GetService>(); 23 | 24 | Console.WriteLine(options.Value.Name); 25 | Console.WriteLine(options.Value.Version); 26 | Console.WriteLine(options.Value.Description); 27 | 28 | static Dictionary InitialData() 29 | { 30 | return new Dictionary() 31 | { 32 | { "app:id", "1" }, 33 | { "app:name", "test app" }, 34 | { "app:version", "1.0.0" }, 35 | { "app:description", "test options bind" }, 36 | }; 37 | } -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/Annotations.WebAPI.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/Annotations.WebAPI.Examples.http: -------------------------------------------------------------------------------- 1 | @Annotations.WebAPI.Examples_HostAddress = http://localhost:5109 2 | 3 | GET {{Annotations.WebAPI.Examples_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/AppOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using Microsoft.Extensions.Configuration.Annotations; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Annotations.WebAPI.Examples; 6 | 7 | [Validate(typeof(AppValidateOptions))] 8 | [Options("app",true,false)] 9 | public class AppOptions 10 | { 11 | public int Id { get; set; } 12 | 13 | public string Name { get; } 14 | 15 | public string Version { get; set; } 16 | 17 | internal string Description { get; set; } 18 | } 19 | 20 | public class AppValidateOptions : IValidateOptions 21 | { 22 | public ValidateOptionsResult Validate(string name, AppOptions options) 23 | { 24 | // To do validate for AppOptions 25 | 26 | return ValidateOptionsResult.Success; 27 | } 28 | } -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/Program.cs: -------------------------------------------------------------------------------- 1 | using Annotations.WebAPI.Examples; 2 | using Microsoft.Extensions.Options; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | // Add services to the container. 7 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 8 | builder.Services.AddEndpointsApiExplorer(); 9 | builder.Services.AddSwaggerGen(); 10 | 11 | // Injection of Microsoft.Extensions.Configuration.Annotations components 12 | builder.Services.AddAttributeConfigurationOptions(builder.Configuration,false , typeof(Program).Assembly); 13 | 14 | var app = builder.Build(); 15 | 16 | // Configure the HTTP request pipeline. 17 | if (app.Environment.IsDevelopment()) 18 | { 19 | app.UseSwagger(); 20 | app.UseSwaggerUI(); 21 | } 22 | 23 | app.UseHttpsRedirection(); 24 | 25 | 26 | var summaries = new[] 27 | { 28 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 29 | }; 30 | 31 | app.MapGet("/weatherforecast", () => 32 | { 33 | var forecast = Enumerable.Range(1, 5).Select(index => 34 | new WeatherForecast 35 | ( 36 | DateOnly.FromDateTime(DateTime.Now.AddDays(index)), 37 | Random.Shared.Next(-20, 55), 38 | summaries[Random.Shared.Next(summaries.Length)] 39 | )) 40 | .ToArray(); 41 | return forecast; 42 | }) 43 | .WithName("GetWeatherForecast") 44 | .WithOpenApi(); 45 | 46 | // Use IOptions to get AppOptions 47 | app.MapGet("/app", (IOptions options) => 48 | { 49 | return options.Value; 50 | }); 51 | 52 | app.Run(); 53 | 54 | record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) 55 | { 56 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 57 | } -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:10808", 8 | "sslPort": 44373 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5109", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7026;http://localhost:5109", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "App":{ 9 | "Id":1, 10 | "Name":"my app", 11 | "Version": "1.0.0", 12 | "Description":"options bind" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/Annotations.WebAPI.Examples/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "App":{ 10 | "Id":1, 11 | "Name":"my app", 12 | "Version": "1.0.0", 13 | "Description":"options bind" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.100", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/Binders/BinderContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.Extensions.Configuration.Annotations.Binders 5 | { 6 | /// 7 | /// Binder context containing information needed for configuration binding. 8 | /// 9 | public class BinderContext 10 | { 11 | /// 12 | /// Gets the `OptionsAttribute` that marks the configuration class. 13 | /// 14 | public OptionsAttribute OptionsAttribute { get; } 15 | 16 | /// 17 | /// Gets the optional `ValidateAttribute` that provides a custom validation type. 18 | /// 19 | public ValidateAttribute? ValidateAttribute { get; } 20 | 21 | /// 22 | /// Gets whether global annotation is enabled. 23 | /// Global annotation enables automatic validation and binding behavior based on the global configuration. 24 | /// 25 | public bool EnableGlobalAnnotation { get; } 26 | 27 | /// 28 | /// Gets the optional action to configure the binding behavior. 29 | /// 30 | public Action? ConfigureBinder { get; } 31 | 32 | /// 33 | /// Initializes a new instance of `BinderContext` with the provided options and validation attributes. 34 | /// 35 | /// The `OptionsAttribute` marking the configuration class. 36 | /// An optional `ValidateAttribute` for validation information. 37 | /// 38 | /// A flag indicating whether global annotations should be enabled. When enabled, 39 | /// automatic validation and binding behavior is applied globally, making it unnecessary to 40 | /// specify these behaviors explicitly in each configuration class. 41 | /// Default is `true`. 42 | /// 43 | public BinderContext(OptionsAttribute optionsAttribute, 44 | ValidateAttribute? validateAttribute, 45 | bool enableGlobalAnnotation = true) 46 | { 47 | this.OptionsAttribute = optionsAttribute ?? throw new ArgumentNullException(nameof(optionsAttribute)); 48 | this.ValidateAttribute = validateAttribute; 49 | this.EnableGlobalAnnotation = enableGlobalAnnotation; 50 | this.ConfigureBinder = binderOptions => 51 | { 52 | // Configure binding options: whether to bind non-public properties and whether to throw errors for unknown configuration properties 53 | binderOptions.BindNonPublicProperties = OptionsAttribute.BindNonPublic; 54 | binderOptions.ErrorOnUnknownConfiguration = OptionsAttribute.ThrowOnUnknownConfig; 55 | }; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/Binders/ConfigurationOptionsBinder.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Microsoft.Extensions.Configuration.Annotations.Binders; 7 | 8 | /// 9 | /// Abstract base class for a configuration options binder, providing a `Bind` method to load settings from `IConfiguration`. 10 | /// 11 | public abstract class ConfigurationOptionsBinder 12 | { 13 | 14 | /// 15 | /// Abstract method to bind configuration to the specified `TOptions` type. 16 | /// 17 | /// The binding context that contains configuration and validation information. 18 | /// The configuration source to bind from. 19 | /// The options object to be populated with the configuration values. 20 | /// The type of the options class to bind. 21 | public abstract void Bind(BinderContext context, IConfiguration configuration, TOptions options) 22 | where TOptions : class; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/Binders/ConfigurationOptionsBinderImpl.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Microsoft.Extensions.Configuration.Annotations.Binders; 8 | 9 | /// 10 | /// A concrete implementation of `ConfigurationOptionsBinder` for binding `IConfiguration` 11 | /// 12 | internal class ConfigurationOptionsBinderImpl : ConfigurationOptionsBinder 13 | { 14 | private readonly IServiceCollection services; 15 | 16 | /// 17 | /// Initializes an instance of `ConfigurationOptionsBinderImpl 18 | /// 19 | /// Dependency injection container. 20 | public ConfigurationOptionsBinderImpl(IServiceCollection services) 21 | { 22 | this.services = services; 23 | } 24 | 25 | /// 26 | /// Binds the configuration to the specified options and registers services. 27 | /// 28 | /// The binder context containing configuration and validation information. 29 | /// The configuration source. 30 | /// The options object to bind. 31 | /// The type of the options class to bind. 32 | public override void Bind(BinderContext context, IConfiguration configuration,TOptions options) 33 | { 34 | // Register TOptions and bind it to the provided configuration section. 35 | var builder = this.services.AddOptions().Bind(configuration, context.ConfigureBinder); 36 | 37 | // If a custom validation type is provided, add it to the DI container 38 | if (context.ValidateAttribute?.Type is not null) 39 | { 40 | this.services.AddSingleton(typeof(IValidateOptions), context.ValidateAttribute.Type); 41 | return; 42 | } 43 | // Validate based on enable global configuration 44 | if (context.EnableGlobalAnnotation || context.ValidateAttribute is not null) 45 | { 46 | builder.ValidateDataAnnotations(); 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/ConfigurationServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Configuration.Annotations; 8 | using Microsoft.Extensions.Configuration.Annotations.Binders; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection; 11 | 12 | /// 13 | /// Extension methods for registering configuration options with attributes. 14 | /// 15 | public static class ConfigurationServiceCollectionExtensions 16 | { 17 | /// 18 | /// Adds services for all classes in the specified assemblies that are marked with `OptionsAttribute`, 19 | /// binding them to configuration sections using the provided binder. 20 | /// 21 | /// The DI container. 22 | /// The configuration source containing configuration data. 23 | /// The binder instance used to bind configuration data to options types. 24 | /// The assemblies to scan for option classes marked with `OptionsAttribute`. 25 | /// Global annotation enables automatic validation and binding behavior based on the ValidateDataAnnotations. 26 | /// The with registered options services. 27 | /// Thrown if any of the parameters are null. 28 | /// Thrown if the provided binder is not of type `ConfigurationOptionsBinder`. 29 | public static IServiceCollection AddAttributeConfigurationOptions( 30 | this IServiceCollection services, 31 | IConfiguration configuration, 32 | bool enableGlobalAnnotation, 33 | ConfigurationOptionsBinder binder, 34 | params Assembly[] assemblies) 35 | { 36 | if (services is null) throw new ArgumentNullException(nameof(services)); 37 | if (configuration is null) throw new ArgumentNullException(nameof(configuration)); 38 | if (binder is null) throw new ArgumentNullException(nameof(binder)); 39 | 40 | // Ensure that the binder is of type ConfigurationOptionsBinder 41 | var binderType = binder.GetType(); 42 | MethodInfo? binderMethod = binderType.GetMethod("Bind", BindingFlags.Public | BindingFlags.Instance); 43 | 44 | // Iterate through each assembly to find types with the `OptionsAttribute` 45 | foreach (var assembly in assemblies) 46 | { 47 | foreach (var optionsType in assembly.GetTypes().Where(q => q.IsDefined(typeof(OptionsAttribute), true))) 48 | { 49 | var optionsAttribute = optionsType.GetCustomAttribute(); 50 | 51 | // Get the optional `ValidateAttribute` for validation 52 | var validateAttribute = optionsType.GetCustomAttribute(); 53 | 54 | // Get the section key from the attribute or use the class name as default 55 | var key = optionsAttribute!.SessionKey ?? optionsType.Name; 56 | var section = configuration.GetSection(key); 57 | 58 | var context = new BinderContext(optionsAttribute, validateAttribute, enableGlobalAnnotation); 59 | var options = section.Get(optionsType, context.ConfigureBinder); 60 | 61 | // Invoke the binder's `Bind` method via reflection 62 | binderMethod?.MakeGenericMethod(optionsType) 63 | .Invoke(binder, new object?[] { context, section, options }); 64 | } 65 | } 66 | 67 | return services; 68 | } 69 | 70 | /// 71 | /// Adds services for all classes in the specified assemblies that are marked with `OptionsAttribute`, 72 | /// using the default implementation of `ConfigurationOptionsBinderImpl` for binding. 73 | /// 74 | /// The DI container. 75 | /// The configuration source containing configuration data. 76 | /// The assemblies to scan for option classes marked with `OptionsAttribute`. 77 | /// Global annotation enables automatic validation and binding behavior based on the ValidateDataAnnotations. 78 | /// The with registered options services. 79 | public static IServiceCollection AddAttributeConfigurationOptions( 80 | this IServiceCollection services, 81 | IConfiguration configuration, 82 | bool enableGlobalAnnotation = true, 83 | params Assembly[] assemblies) 84 | { 85 | // Use a default instance of ConfigurationOptionsBinderImpl when no binder is provided 86 | return services.AddAttributeConfigurationOptions(configuration, enableGlobalAnnotation, 87 | new ConfigurationOptionsBinderImpl(services), assemblies); 88 | } 89 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/Microsoft.Extensions.Configuration.Annotations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0; 5 | enable 6 | enable 7 | Ares.Extensions.Configuration.Annotations 8 | Kevin Berger 9 | Use Ares.Extensions.Configuration.Annotations attributes binding get Microsoft.Extensions.Configuration options. 10 | 11 | Release notes: 12 | 13 | 1.0.0: Upgrade Microsoft.NET.Sdk to net8.0. 14 | binding via attributes 15 | 1.0.2 16 | $(Description) 17 | System.Runtime.CompilerServices.RequiresLocationAttribute;System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 18 | https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations 19 | https://github.com/huhouhua/Microsoft.Extensions.Configuration.Annotations.git 20 | git 21 | docs/LICENSE 22 | docs/README.md 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/OptionsAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.Extensions.Configuration.Annotations; 5 | 6 | /// 7 | /// Represents an options attribute for marking configuration classes. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class OptionsAttribute : Attribute 11 | { 12 | /// 13 | /// Gets the configuration section name. 14 | /// 15 | public string? SessionKey { get; set; } 16 | 17 | /// 18 | /// Determines whether non-public properties should be bound. 19 | /// Default is false 20 | /// 21 | public bool BindNonPublic { get; set; } 22 | 23 | /// 24 | /// Specifies whether an error should be thrown for unknown configuration properties. 25 | /// Default is false 26 | /// 27 | public bool ThrowOnUnknownConfig { get; set; } 28 | 29 | 30 | /// 31 | /// Initializes an instance of `OptionsAttribute` with an empty section key. 32 | /// 33 | public OptionsAttribute() : this(string.Empty) 34 | { 35 | } 36 | 37 | /// 38 | /// Initializes an instance of `OptionsAttribute` with a specified section key. 39 | /// 40 | /// Configuration section name. 41 | public OptionsAttribute(string sessionKey) : this(sessionKey, false, false) 42 | { 43 | 44 | } 45 | 46 | /// 47 | /// Initializes an instance of `OptionsAttribute` with additional binding configurations. 48 | /// 49 | /// Configuration section name. 50 | /// Specifies whether non-public properties should be bound. 51 | /// Specifies whether an error should be thrown for unknown configuration properties. 52 | public OptionsAttribute(string sessionKey, bool bindNonPublic , bool throwOnUnknownConfig) 53 | { 54 | this.SessionKey = sessionKey; 55 | this.BindNonPublic = bindNonPublic; 56 | this.ThrowOnUnknownConfig = throwOnUnknownConfig; 57 | } 58 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Configuration.Annotations/ValidateAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.Extensions.Configuration.Annotations; 5 | 6 | /// 7 | /// Attribute used to mark a configuration class that requires validation. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class)] 10 | public class ValidateAttribute : Attribute 11 | { 12 | /// 13 | /// Gets or sets the custom validation type. 14 | /// 15 | public Type? Type { get; } 16 | 17 | /// 18 | /// Default constructor, creating a `ValidateAttribute` without a custom validation type. 19 | /// 20 | public ValidateAttribute() { } 21 | 22 | /// 23 | /// Initializes a `ValidateAttribute` with a specified validation type. 24 | /// 25 | /// The validation type. 26 | public ValidateAttribute(Type type) 27 | { 28 | this.Type = type; 29 | } 30 | } -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": 3 | "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 4 | "settings": { 5 | "documentationRules": { 6 | "companyName": "Kevin Berger Authors", 7 | "copyrightText": 8 | "Copyright (c) {companyName}. All rights reserved.\nLicensed under the {licenseName} license. See {licenseFile} file in the project root for full license information.", 9 | "variables": { 10 | "licenseFile": "LICENSE", 11 | "licenseName": "MIT" 12 | } 13 | }, 14 | "orderingRules": { 15 | "usingDirectivesPlacement": "outsideNamespace" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Configuration.Annotations.Test/ConfigBinderTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | using Xunit.Sdk; 7 | 8 | namespace Microsoft.Extensions.Configuration.Annotations.Test; 9 | 10 | public class ConfigBinderTest 11 | { 12 | [Theory] 13 | [MemberData(nameof(Setup))] 14 | public void EqualBindTest(IServiceProvider provider, IConfigurationRoot configurationRoot) 15 | { 16 | var options = provider.GetService>(); 17 | Assert.NotNull(options); 18 | Assert.Equal(options.Value.Id, Convert.ToInt32(configurationRoot["app:id"])); 19 | Assert.Equal(options.Value.Name, configurationRoot["app:name"]); 20 | Assert.Equal(options.Value.Version, configurationRoot["app:version"]); 21 | Assert.Equal(options.Value.Description, configurationRoot["app:description"]); 22 | } 23 | 24 | [Theory] 25 | [MemberData(nameof(Setup))] 26 | public void ValidateTest(IServiceProvider provider, IConfigurationRoot configurationRoot) 27 | { 28 | var options = provider.GetService>(); 29 | Assert.NotNull(options); 30 | Assert.Throws(() => 31 | { 32 | Assert.Empty(options.Value.Name); 33 | }); 34 | Assert.Throws(() => 35 | { 36 | Assert.NotEmpty(options.Value.Environment); 37 | }); 38 | } 39 | 40 | 41 | public static IEnumerable Setup() 42 | { 43 | IServiceCollection services = new ServiceCollection(); 44 | IConfigurationBuilder builder = new ConfigurationBuilder(); 45 | builder.AddInMemoryCollection(InitialData()); 46 | IConfigurationRoot configurationRoot = builder.Build(); 47 | services.AddAttributeConfigurationOptions(configurationRoot,false , typeof(ConfigBinderTest).Assembly); 48 | yield return new Object[] 49 | { 50 | services.BuildServiceProvider(), 51 | configurationRoot 52 | }; 53 | } 54 | 55 | private static Dictionary InitialData() 56 | { 57 | return new Dictionary() 58 | { 59 | { "app:id", "1" }, 60 | { "app:name", "test app" }, 61 | { "app:version", "1.0.0" }, 62 | { "app:description", "test options bind" }, 63 | 64 | 65 | {"dev:name",""}, 66 | {"dev:environment","test"}, 67 | {"dev:variableName","port"} 68 | }; 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Configuration.Annotations.Test/DevOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Microsoft.Extensions.Configuration.Annotations.Test; 7 | 8 | [Validate(typeof(DevValidateOptions))] 9 | [Options("dev")] 10 | public class DevOptions 11 | { 12 | public string Name { get; set; } 13 | 14 | public string Environment { get; set; } 15 | 16 | public string VariableName {get; set; } 17 | } 18 | 19 | public class DevValidateOptions : IValidateOptions 20 | { 21 | public ValidateOptionsResult Validate(string name, DevOptions options) 22 | { 23 | if (string.IsNullOrWhiteSpace(options.Name)) 24 | { 25 | return ValidateOptionsResult.Fail("name is null or empty"); 26 | } 27 | if (options.Name.StartsWith("dev")) 28 | { 29 | return ValidateOptionsResult.Fail("The name is incorrect. The beginning character of the name is missing dev"); 30 | } 31 | if (options.VariableName.Equals("port")) 32 | { 33 | return ValidateOptionsResult.Success; 34 | } 35 | 36 | return ValidateOptionsResult.Success; 37 | } 38 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Configuration.Annotations.Test/Microsoft.Extensions.Configuration.Annotations.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Configuration.Annotations.Test/MyAppOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Kevin Berger Authors. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using Microsoft.Extensions.Configuration.Annotations.Binders; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Microsoft.Extensions.Configuration.Annotations.Test; 8 | 9 | [Options("app")] 10 | public class MyAppOptions 11 | { 12 | public int Id { get; set; } 13 | 14 | public string Name {get; set; } 15 | 16 | public string Version { get; set; } 17 | 18 | public string Description { get; set; } 19 | } --------------------------------------------------------------------------------