Configuration Validation

Configuration Validation
Image by Freepik

The fourth and final article of my series on Configuration Management. In this section, I will delve into the crucial aspects of configuration validation and testing within the context of .NET Core. In my initial post of this series, I mentioned that this can be quite a challenge, and indeed it is. However, frameworks have come a long way, making it easier to implement robust validation practices. I will show you how to do this effectively in .NET Core.

The Importance of Configuration Validation

Validating configuration data is a fundamental practice that significantly contributes to the reliability, security, and maintainability of your software applications. Its importance lies in several key benefits:

  • Early Error Detection: Configuration validation catches errors early in the development process, preventing potential issues that might arise at runtime.
  • Data Integrity: Proper validation reduces the risk of data corruption by ensuring that configuration values adhere to defined standards.
  • Enhanced Security: Validation safeguards sensitive data within configurations, such as credentials, making sure they are not exposed inadvertently.
  • Environment Compatibility: Validation ensures that your application functions correctly in various environments by confirming that configurations are appropriate for each context.

By validating configuration data, you can build more robust and dependable software solutions.

Strategies for Configuration Validation

There are numerous strategies you can employ when it comes to configuring validation and how to approach it.

Some may prefer relying solely on Code Review/Pull Request validation, which can catch certain faults and may suffice. However, there is a caveat. The intent of the software developer who has designed Feature X with its associated settings matters. They know what configuration values should be considered valid. For instance, they might have set a limit on a SemaphoreSlim implementation for a specific reason—perhaps the hardware or software cannot handle 1000 concurrent threads, but the integer configuration value theoretically allows for changes down the road.

Personally, I favor the approach of using DataAnnotation validation for configuration for several reasons:

  1. Simplicity: DataAnnotation validation is straightforward to implement.
  2. Built-in Attributes: Microsoft provides a range of useful attributes that make the validation process intuitive.
  3. Ease of Implementation: It's incredibly easy to implement, making it accessible for developers.

Now, the question arises: when should you perform this validation? The answer depends on your preference and workflow.

My preferred method involves Startup Validation, where the application terminates if it doesn't pass the Configuration Validation step. This is complemented by a configurable integration test designed for the same purpose. In a production release pipeline, I usually follow these steps:

  1. Integration Test: Conduct an Integration Test that configures the application with production settings or connects to a Configuration Service endpoint.
  2. Run Configuration Tests: Execute the configuration tests to verify their correctness.
  3. Error Handling: Print error information if the test fails, providing valuable insights into what went wrong.
  4. Deployment: If the test passes, proceed with the application deployment as usual. However, remember that the application could still fail configuration validation if the test was configured incorrectly. Therefore, consider incorporating deployment strategies such as Blue/Green, Red/Black, or Canary deployments for added safety and reliability.

Implementation

To implement configuration validation effectively, leverage the Options Pattern. This pattern enables you to register options and validate them during application startup. While manually wiring up options in the Program.cs class is possible, it can become cumbersome, especially when dealing with numerous unique options.

To streamline this process, I've developed a library that automates the registration of options by recursively searching through a class structure for the [AddOptionsValidation] Attribute. For example:

Suppose you have a class that defines various settings:

public class AppSettings
{
    [AddOptionsValidation]
    public CatalogSettings Catalog { get; set; } = new();
}

public class CatalogSettings
{
    [AddOptionsValidation]
    public EndpointSettings Endpoint { get; set; } = new();
}

public class EndpointSettings
{
    [Required, Url]
    public string Endpoint { get; set; } = string.Empty;
    [Required]
    public string RequiredProperty { get; set; } = string.Empty;
    [StringLength(10)]
    public string StringLengthProperty { get; set; } = "A String longer than 10 characters";
    [RegularExpression("[a-z]")]
    public string RegularExpressionProperty { get; set; } = "ONLY LOWERCASE";
    [Range(0, 10)]
    public string RangeProperty { get; set; } = "11";
}

When you call the library method, it automatically traverses the properties, registering and validating those marked with the [AddOptionsValidation] attribute:

builder.Services.AddOptionsValidationRecursivly<AppSettings>();

It's important to note that the library includes safeguards to prevent the registration of multiple instances of the same type with the same OptionsName, ensuring a clean and efficient process.

You can access the code for this library in the GitHub repository.

For those with a keen eye, I intentionally introduced errors into the EndpointSettings class, which the integration test detects and reports:

Error Message:
    Configuration is not valid. Configuration Source: https://appcs-dist-cfg-nonprod-01.azconfig.io
EndpointSettings
 DataAnnotation validation failed for 'EndpointSettings' members: 'RequiredProperty' with the error: 'The RequiredProperty field is required.'.
 DataAnnotation validation failed for 'EndpointSettings' members: 'StringLengthProperty' with the error: 'The field StringLengthProperty must be a string with a maximum length of 10.'.
 DataAnnotation validation failed for 'EndpointSettings' members: 'RegularExpressionProperty' with the error: 'The field RegularExpressionProperty must match the regular expression '[a-z]'.'.
 DataAnnotation validation failed for 'EndpointSettings' members: 'RangeProperty' with the error: 'The field RangeProperty must be between 0 and 10.'.
 ---

Wrapping up

In this exploration of Configuration Validation, we've delved into the critical aspects of ensuring that your application's settings are not just correctly loaded but also validated for reliability, security, and correctness. By implementing robust validation practices, you can fortify your software applications and significantly reduce the likelihood of runtime errors and data corruption.

We've covered a range of topics, including:

The Importance of Configuration Validation: Understanding why configuration validation is essential, from early error detection to data integrity and enhanced security. Valid configurations are the foundation of a stable application.

Strategies for Configuration Validation: We've discussed various strategies, from Code Review/Pull Request validation to DataAnnotation validation, each with its strengths and use cases. The choice of strategy depends on your specific project requirements.

Implementation: We've seen how to implement configuration validation effectively, leveraging the Options Pattern in .NET Core. Additionally, we explored a library that automates the registration and validation of configuration options, making your development process more efficient.

Lastly, Remember that configuration validation is not a one-time effort but an ongoing practice. Regularly validating configurations ensures that your application remains robust, secure, and reliable throughout its lifecycle.

As you continue to build and maintain your software applications, consider integrating configuration validation into your development process. Whether you opt for built-in validation attributes or custom validation logic, prioritizing configuration validation will pay off in terms of smoother deployments, fewer runtime issues, and a more resilient application.

We hope this guide has provided you with valuable insights and practical techniques for implementing configuration validation effectively in your projects. Keep exploring, learning, and refining your configuration management practices to create software that stands the test of time.

Github