Skip to content

Commit 874a958

Browse files
committed
feat: extend support for Bind Configuration Subsections OptionsBinding
1 parent 86bf1cd commit 874a958

File tree

10 files changed

+1202
-14
lines changed

10 files changed

+1202
-14
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ services.AddDependencyRegistrationsFromDomain(
300300
- Supports validation: `ValidateDataAnnotations`, `ValidateOnStart`, `ErrorOnMissingKeys` (fail-fast for missing sections), Custom validators (`IValidateOptions<T>`)
301301
- **Configuration change callbacks**: Auto-generated IHostedService for OnChange notifications with Monitor lifetime - perfect for feature flags and runtime configuration updates
302302
- **Named options support**: Multiple configurations of the same options type with different names (e.g., Primary/Secondary email servers)
303+
- **Nested subsection binding**: Automatic binding of complex properties to configuration subsections (e.g., `StorageOptions.Database.Retry``"Storage:Database:Retry"`) - supported out-of-the-box by Microsoft's `.Bind()` method
303304
- Supports lifetime selection: Singleton (IOptions), Scoped (IOptionsSnapshot), Monitor (IOptionsMonitor)
304305
- Requires classes to be declared `partial`
305306
- **Smart naming** - uses short suffix if unique, full name if conflicts exist

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ services.AddOptionsFromApp(configuration);
322322
- **🔔 Configuration Change Callbacks**: Auto-generated IHostedService for OnChange notifications with Monitor lifetime - perfect for feature flags and runtime config updates
323323
- **📛 Named Options**: Multiple configurations of the same options type with different names (e.g., Primary/Secondary email servers)
324324
- **🎯 Explicit Section Paths**: Support for nested sections like `"App:Database"` or `"Services:Email"`
325+
- **📂 Nested Subsection Binding**: Automatic binding of complex properties to configuration subsections (e.g., `StorageOptions.Database.Retry``"Storage:Database:Retry"`)
325326
- **📦 Multiple Options Classes**: Register multiple configuration sections in a single assembly with one method call
326327
- **🏗️ Multi-Project Support**: Smart naming generates assembly-specific extension methods (e.g., `AddOptionsFromDomain()`, `AddOptionsFromDataAccess()`)
327328
- **🔗 Transitive Registration**: Automatically discover and register options from referenced assemblies (4 overloads: default, auto-detect all, selective by name, selective multiple)

docs/OptionsBindingGenerators-FeatureRoadmap.md

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ This roadmap is based on comprehensive analysis of:
5858
- **Named options** - Multiple configurations of the same options type with different names
5959
- **Error on missing keys** - `ErrorOnMissingKeys` fail-fast validation when configuration sections are missing
6060
- **Configuration change callbacks** - `OnChange` callbacks for Monitor lifetime (auto-generates IHostedService)
61+
- **Nested subsection binding** - Automatic binding of complex properties to configuration subsections (e.g., `Storage:Database:Retry`)
6162
- **Lifetime selection** - Singleton (`IOptions`), Scoped (`IOptionsSnapshot`), Monitor (`IOptionsMonitor`)
6263
- **Multi-project support** - Assembly-specific extension methods with smart naming
6364
- **Transitive registration** - 4 overloads for automatic/selective assembly registration
@@ -76,7 +77,7 @@ This roadmap is based on comprehensive analysis of:
7677
|| [Post-Configuration Support](#3-post-configuration-support) | 🟡 Medium-High |
7778
|| [Error on Missing Configuration Keys](#4-error-on-missing-configuration-keys) | 🔴 High |
7879
|| [Configuration Change Callbacks](#5-configuration-change-callbacks) | 🟡 Medium |
79-
| | [Bind Configuration Subsections to Properties](#6-bind-configuration-subsections-to-properties) | 🟡 Medium |
80+
| | [Bind Configuration Subsections to Properties](#6-bind-configuration-subsections-to-properties) | 🟡 Medium |
8081
|| [ConfigureAll Support](#7-configureall-support) | 🟢 Low-Medium |
8182
|| [Options Snapshots for Specific Sections](#8-options-snapshots-for-specific-sections) | 🟢 Low-Medium |
8283
|| [Compile-Time Section Name Validation](#9-compile-time-section-name-validation) | 🟡 Medium |
@@ -457,41 +458,43 @@ internal sealed class FeaturesOptionsChangeListener : IHostedService
457458
### 6. Bind Configuration Subsections to Properties
458459

459460
**Priority**: 🟡 **Medium**
460-
**Status**: ❌ Not Implemented
461+
**Status**: ✅ **Implemented**
462+
**Inspiration**: Microsoft.Extensions.Configuration.Binder automatic subsection binding
461463

462-
**Description**: Support binding nested configuration sections to complex property types.
464+
**Description**: Microsoft's `.Bind()` method automatically handles nested configuration subsections. Complex properties are automatically bound to their corresponding configuration subsections without any additional configuration.
463465

464466
**User Story**:
465-
> "As a developer, I want to bind nested configuration sections to nested properties without manually creating separate options classes."
467+
> "As a developer, I want to bind nested configuration sections to nested properties without manually creating separate options classes or writing additional binding code."
466468
467469
**Example**:
468470

469471
```csharp
470472
// appsettings.json
471473
{
472474
"Email": {
475+
"From": "noreply@example.com",
473476
"Smtp": {
474477
"Host": "smtp.gmail.com",
475478
"Port": 587,
476479
"UseSsl": true
477480
},
478-
"From": "noreply@example.com",
479481
"Templates": {
480482
"Welcome": "welcome.html",
481483
"ResetPassword": "reset.html"
482484
}
483485
}
484486
}
485487

488+
// Options class - nested objects automatically bind!
486489
[OptionsBinding("Email")]
487490
public partial class EmailOptions
488491
{
489492
public string From { get; set; } = string.Empty;
490493

491-
// Nested object - should automatically bind "Email:Smtp" section
494+
// Automatically binds to "Email:Smtp" subsection - no special config needed!
492495
public SmtpSettings Smtp { get; set; } = new();
493496

494-
// Nested object - should automatically bind "Email:Templates" section
497+
// Automatically binds to "Email:Templates" subsection
495498
public EmailTemplates Templates { get; set; } = new();
496499
}
497500

@@ -509,12 +512,70 @@ public class EmailTemplates
509512
}
510513
```
511514

512-
**Implementation Notes**:
515+
**Real-World Example - Deeply Nested (3 Levels)**:
516+
517+
```csharp
518+
[OptionsBinding("Storage", ValidateDataAnnotations = true)]
519+
public partial class StorageOptions
520+
{
521+
// Binds to "Storage:Database"
522+
public DatabaseSettings Database { get; set; } = new();
523+
524+
// Binds to "Storage:FileStorage"
525+
public FileStorageSettings FileStorage { get; set; } = new();
526+
}
527+
528+
public class DatabaseSettings
529+
{
530+
[Required]
531+
public string ConnectionString { get; set; } = string.Empty;
532+
533+
[Range(1, 1000)]
534+
public int MaxConnections { get; set; } = 100;
535+
536+
// Binds to "Storage:Database:Retry" - 3 levels deep!
537+
public DatabaseRetryPolicy Retry { get; set; } = new();
538+
}
539+
540+
public class DatabaseRetryPolicy
541+
{
542+
[Range(0, 10)]
543+
public int MaxAttempts { get; set; } = 3;
544+
545+
[Range(100, 10000)]
546+
public int DelayMilliseconds { get; set; } = 500;
547+
}
548+
```
549+
550+
**Implementation Details**:
551+
552+
-**Zero configuration required** - Just use complex property types and `.Bind()` handles the rest
553+
-**Already supported by Microsoft.Extensions.Configuration.Binder** - Our generator leverages this natively
554+
-**Automatic path construction** - "Parent:Child:GrandChild" paths are built automatically
555+
-**Works with validation** - DataAnnotations validation applies to all nested levels
556+
-**Unlimited depth** - Supports deeply nested structures (e.g., CloudStorage → Azure → Blob)
557+
-**Collections supported** - List<T>, arrays, dictionaries all work automatically
558+
-**No breaking changes** - This feature works out-of-the-box with existing code
559+
560+
**What Gets Automatically Bound**:
561+
562+
- **Nested objects** - Properties with complex class types
563+
- **Collections** - List<T>, IEnumerable<T>, arrays
564+
- **Dictionaries** - Dictionary<string, string>, Dictionary<string, T>
565+
- **Multiple levels** - As deeply nested as needed
566+
567+
**Testing**:
568+
569+
- ✅ 9 comprehensive unit tests covering all scenarios
570+
- ✅ Sample project: CloudStorageOptions demonstrates Azure/AWS/Blob nested structure
571+
- ✅ PetStore.Api sample: StorageOptions demonstrates Database/FileStorage/Retry 3-level nesting
572+
573+
**Key Benefits**:
513574

514-
- Automatically bind complex properties using `Bind()`
515-
- No special attribute required for nested types
516-
- Already supported by Microsoft.Extensions.Configuration.Binder
517-
- Our generator should leverage this automatically
575+
- **Cleaner configuration models** - Group related settings without flat structures
576+
- **Better organization** - Mirrors natural hierarchy of configuration
577+
- **Type-safe all the way down** - Compile-time safety for nested properties
578+
- **Works with existing features** - Validation, change detection, all lifetimes
518579

519580
---
520581

0 commit comments

Comments
 (0)