cleansharp.NET
"Clean code always looks like it was written by someone who cares."— Robert C. Martin (Uncle Bob)
Use the Options pattern to manage configuration settings in a type-safe, maintainable way.
The Options pattern provides a strongly-typed way to access configuration settings in your application.
Instead of directly accessing configuration values through strings, you create classes that represent your configuration sections,
making it easier to maintain, refactor, and validate your settings.
This approach is error-prone and hard to maintain:
public class EmailService
{
private readonly IConfiguration _configuration;
public EmailService(IConfiguration configuration)
{
_configuration = configuration;
}
public void SendEmail(string to)
{
var smtpServer = _configuration["Email:SmtpServer"]; // ⚠️ String-based access
var port = int.Parse(_configuration["Email:Port"]); // ⚠️ Manual parsing
var username = _configuration["Email:Username"];
var password = _configuration["Email:Password"];
// Use settings...
}
}
First, define your options class:
public class EmailOptions
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
Then, use it in your service:
public class EmailService(IOptions<EmailOptions> options)
{
private readonly EmailOptions _options = options.Value;
public void SendEmail(string to)
{
// Use _options.SmtpServer, _options.Port, etc.
}
}
Finally, register it in your dependency injection container:
// In Program.cs or Startup.cs
services.Configure<EmailOptions>(configuration.GetSection(nameof(EmailOptions)));
The Options pattern is particularly valuable in microservices architectures where configuration management is crucial.
It helps prevent runtime errors from typos in configurations and makes it easier to track which parts of your application depend on specific configuration settings.
When you need to change a setting, the compiler will help you find all the places that need to be updated.
To implement the Options pattern in your project:
<PackageReference Include="Microsoft.Extensions.Options" Version="X.Y.Z" />
appsettings.json
:{
"EmailOptions": {
"SmtpServer": "smtp.mail.com",
"Port": 587,
"Username": "my-email@mail.com",
"Password": "my-password"
}
}
public class EmailOptions
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
// In Program.cs or Startup.cs
builder.Services.Configure<EmailOptions>(builder.Configuration.GetSection(nameof(EmailOptions)));
public class EmailService(IOptions<EmailOptions> options)
{
private readonly EmailOptions _options = options.Value;
public void SendEmail(string to)
{
// Use _options.SmtpServer, _options.Port, etc.
}
}
You can add validation to your options classes using IValidatableObject
:
public class EmailOptions : IValidatableObject
{
public string SmtpServer { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(SmtpServer))
yield return new ValidationResult("SmtpServer is required");
if (Port <= 0 || Port > 65535)
yield return new ValidationResult("Port must be between 1 and 65535");
if (string.IsNullOrEmpty(Username))
yield return new ValidationResult("Username is required");
}
}
To enable validation, add the following to your service registration in Program.cs
or Startup.cs
:
builder.Services.AddOptions<EmailOptions>()
.Bind(builder.Configuration.GetSection(nameof(EmailOptions)))
.ValidateDataAnnotations()
.ValidateOnStart();
This throws a OptionsValidationException
on startup if any of the validation rules fail, ensuring your application won’t start with invalid configuration.