The Options Pattern is used in OrderFlow.Core to bind configuration from appsettings.json to strongly-typed C# classes, providing type-safe access to configuration throughout the application.
File: Configuration/RabbitMqSettings.cs
public class RabbitMqSettings
{
public string HostName { get; set; } = null!;
public int Port { get; set; }
public string UserName { get; set; } = null!;
public string Password { get; set; } = null!;
public string ExchangeName { get; set; } = null!;
public string ExchangeType { get; set; } = null!;
public SubscriberSettings Subscribers { get; set; } = null!;
}
public class SubscriberSettings
{
public SubscriberConfig OrderProcessing { get; set; } = null!;
public SubscriberConfig Notification { get; set; } = null!;
public SubscriberConfig PaymentVerification { get; set; } = null!;
public SubscriberConfig Shipping { get; set; } = null!;
}
public class SubscriberConfig
{
public string QueueName { get; set; } = null!;
public string RoutingKey { get; set; } = null!;
}Key Points:
- Properties match JSON structure (case-insensitive)
null!indicates non-nullable reference that will be set by configuration- Nested classes for hierarchical configuration
File: appsettings.Development.json
{
"RabbitMq": {
"HostName": "localhost",
"Port": 5672,
"UserName": "admin",
"Password": "admin123",
"ExchangeName": "order_exchange",
"ExchangeType": "topic",
"Subscribers": {
"OrderProcessing": {
"QueueName": "order_processing_queue",
"RoutingKey": "order.created"
},
"Notification": {
"QueueName": "notification_queue",
"RoutingKey": "order.*"
}
}
}
}File: Program.cs
// Bind configuration section to options class
builder.Services.Configure<RabbitMqSettings>(
builder.Configuration.GetSection("RabbitMq"));What This Does:
- Gets the "RabbitMq" section from all configuration sources
- Binds JSON properties to
RabbitMqSettingsclass properties - Registers as
IOptions<RabbitMqSettings>in DI container
File: Infrastructure/RabbitMQ/RabbitMqSubscriberBase.cs
protected RabbitMqSubscriberBase(
IRabbitMqConnectionFactory connectionFactory,
IOptions<RabbitMqSettings> settings, // ← Injected via DI
ILogger logger)
{
ConnectionFactory = connectionFactory;
Settings = settings.Value; // ← Extract actual settings object
Logger = logger;
// Access configuration
_subscriberConfig = GetSubscriberConfig(ConfigurationKey);
}
private SubscriberConfig GetSubscriberConfig(string key)
{
var config = key switch
{
"OrderProcessing" => Settings.Subscribers.OrderProcessing,
"Notification" => Settings.Subscribers.Notification,
// ... access nested configuration
};
return config;
}┌────────────────────────────────────────────────────────────┐
│ 1. Application Startup │
│ ✓ ASP.NET Core reads appsettings.json │
└─────────────────┬──────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 2. Configuration Binding (Program.cs) │
│ builder.Services.Configure<RabbitMqSettings>(...) │
│ ✓ JSON → RabbitMqSettings class │
│ ✓ Registered in DI as IOptions<RabbitMqSettings> │
└─────────────────┬──────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 3. Dependency Injection │
│ new RabbitMqSubscriberBase(..., IOptions<Settings>) │
│ ✓ DI resolves and injects IOptions<RabbitMqSettings> │
└─────────────────┬──────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 4. Extract Configuration │
│ Settings = settings.Value │
│ ✓ Access strongly-typed configuration │
└────────────────────────────────────────────────────────────┘
| Benefit | Description |
|---|---|
| Type Safety | Compile-time checking of configuration access |
| IntelliSense | IDE autocomplete for configuration properties |
| Validation | Can add data annotations for validation |
| Testability | Easy to mock IOptions<T> in unit tests |
| Separation | Configuration separate from business logic |
| Environment-Specific | Override per environment (Dev/Staging/Prod) |
public class ShippingSubscriber : RabbitMqSubscriberBase
{
protected override string ConfigurationKey => "Shipping";
// Configuration automatically loaded from:
// appsettings.json → RabbitMq:Subscribers:Shipping
}private void InitializeInfrastructure()
{
// Uses Settings.ExchangeName from JSON
_channel.ExchangeDeclare(
exchange: Settings.ExchangeName, // "order_exchange"
type: Settings.ExchangeType); // "topic"
// Uses _subscriberConfig.QueueName from JSON
_channel.QueueDeclare(
queue: _subscriberConfig.QueueName); // "shipping_queue"
}ASP.NET Core loads configuration from multiple sources:
1. Command-line arguments (highest priority)
2. Environment variables
3. appsettings.{Environment}.json
4. appsettings.json (lowest priority)
Example Override:
// appsettings.json (base)
{
"RabbitMq": {
"HostName": "localhost"
}
}
// appsettings.Production.json (override)
{
"RabbitMq": {
"HostName": "prod-rabbitmq.example.com"
}
}- Use descriptive property names that match JSON keys
- Mark required properties with
= null!or data annotations - Validate configuration on startup
- Use nested classes for hierarchical configuration
- Document configuration in code comments
- Don't hardcode values in configuration classes
- Don't mix configuration with business logic
- Don't use mutable configuration without understanding implications
- Don't ignore validation errors
[Fact]
public void Subscriber_ShouldLoadConfiguration()
{
// Arrange
var settings = Options.Create(new RabbitMqSettings
{
HostName = "test-host",
Port = 5672,
Subscribers = new SubscriberSettings
{
Shipping = new SubscriberConfig
{
QueueName = "test_queue",
RoutingKey = "test.route"
}
}
});
// Act
var subscriber = new ShippingSubscriber(
connectionFactory,
settings, // ← Mock configuration
logger);
// Assert
Assert.Equal("test_queue", subscriber.ConfigurationKey);
}// In Program.cs
builder.Services.AddOptions<RabbitMqSettings>()
.Bind(builder.Configuration.GetSection("RabbitMq"))
.ValidateDataAnnotations() // ← Enable data annotation validation
.ValidateOnStart(); // ← Validate on startup
// In RabbitMqSettings.cs
public class RabbitMqSettings
{
[Required]
[MinLength(1)]
public string HostName { get; set; } = null!;
[Range(1, 65535)]
public int Port { get; set; }
[Required]
public string ExchangeName { get; set; } = null!;
}- Options Pattern in .NET
- Configuration in ASP.NET Core
- IOptions vs IOptionsSnapshot vs IOptionsMonitor
The Options Pattern in OrderFlow.Core:
- Defines strongly-typed configuration classes
- Binds JSON configuration to C# objects
- Injects configuration via
IOptions<T> - Accesses configuration in a type-safe manner
- Validates configuration on startup
- Supports environment-specific overrides
This provides a clean, maintainable, and testable way to manage application configuration.
🎯 Type-Safe Configuration with Options Pattern
Clean, Maintainable, and Testable Configuration Management