Validating .NET Settings Part 1 - Manual Validation
[C#, Validation]
This is Part 1 of a series on validating application settings.
- Validating .NET Settings Part 1 - Manual Validation (this post)
- Validating .NET Settings Part 2 - Validating Using FluentValidation
- Validating .NET Settings Part 3 - Validating Using Data Annotations
- Validating .NET Settings Part 4 - Validating Using IValidateOptions
When it comes to .NET applications, you have a number of options:
- Web APIs
- Web Applications
- Console Applications
- Service Applications
All of these would typically require some settings or their execution.
But the assumption is always that the settings are provided correctly. What if they are not?
Assume we have this class that stores our settings:
public class ApplicationOptions
{
public string APIKey { get; set; }
public int RetryCount { get; set; }
public int RequestsPerMinute { get; set; }
public int RequestsPerDay { get; set; }
}
Let us further assume we have the following constraints:
- The
APIKey
must be composed of uppercase characters with a maximum length of 10 - The
RetryCount
must be between1
and 5 - The
RequestsPerMinute
cannot be more than1000
- The
RequestsPerDay
cannot be more than theRequetsPerMinute
- All of these settings are mandatory
We then update our appsettings.json
to store these values:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ApplicationOptions": {
"APIKey": "ABCDEFGHIJ",
"RetryCount": 3,
"RequestsPerMinute": 3,
"RequestsPerDay": 100
}
}
Typically, we would configure our DI as follows:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOptions<ApplicationOptions>();
var app = builder.Build();
But then remember that we want to validate these options before configuring the pipeline.
A very simple way to do this would be to use direct binding, which we have discussed before.
Here, we would create and directly bind an object; then, we can validate that object. If at any point we find an invalid value, we throw an exception.
var settings = new ApplicationOptions();
builder.Configuration.GetSection(nameof(ApplicationOptions)).Bind(settings);
// Check API Key is present
if (string.IsNullOrWhiteSpace(settings.APIKey))
throw new ApplicationException("API key is missing.");
// Check API Key is composed of characters
var regex = new Regex("^[A-Z]{10}$");
if (!regex.IsMatch(settings.APIKey))
throw new ApplicationException("API key is invalid.");
// Check the retry count is between 1 and 5
if (settings.RetryCount is < 0 or > 5)
throw new ApplicationException("Retry count is invalid.");
// Check the Requests per minute are more than 3 and less than 1000
if (settings.RequestsPerMinute is < 3 or > 1000)
throw new ApplicationException("Requests per minute is invalid.");
// Check that the requests per minute do not exceed requests per day
if (settings.RequestsPerMinute > settings.RequestsPerDay)
throw new ApplicationException("Requests per day is invalid.");
If we start our application, it will start as usual because our settings are valid.
But if we changed the API key to abcdefghij
and restarted the application:
This approach has a number of problems:
- It is difficult to test the validation code
- The validation code is mixed in with the application startup code
- The validation code can be difficult to read, understand and maintain
In our next post, we will see how to validate more robustly using a library dedicated to validation.
The code is in my GitHub.
Happy hacking!