Application Settings - C#

Lets face it, there aren't many applications that don't use a configuration file, there are a multitude of file formats and standards available and they allow for a lot of flexibility, and as many arguments as there are against using Json files, the fact that they easily map to classes makes them an ideal storage mechanism for application configuration settings.

The fact that Json does not support comments or long strings is not a problem I have faced very often, however, there are certain issues which make them less than ideal.

Love it or hate it, Json is here to stay, the fact that .Net Core uses Json for AppSettings makes it a viable contender and so it should be.

Problems

There are a couple of problems I see with using the Json format:

  • Missing configuration data leaves properties in their default state (value types to zero/false etc and reference types to null).
  • No support for validating data past ensuring it matches the type being mapped to.

Solution

ApplicationSettings is a simple library which allows developers to apply attributes on properties within configuration classes, to ensure that the data being read using Json or other file formats complies with what is expected for the class.

Validating Properties

In the following example, a section is read from appsettings.json file and then validated using ApplicationSettings. After validation the method returns the class that has been validated.

protected T GetSettings<T>(in string jsonFile, in string sectionName)
{
    if (String.IsNullOrEmpty(jsonFile))
        throw new ArgumentNullException(nameof(jsonFile));
 
    if (String.IsNullOrEmpty(sectionName))
        throw new ArgumentNullException(nameof(sectionName));
 
    ConfigurationBuilder builder = new ConfigurationBuilder();
    IConfigurationBuilder configBuilder = builder.SetBasePath(System.IO.Directory.GetCurrentDirectory());
    configBuilder.AddJsonFile(jsonFile);
    IConfigurationRoot config = builder.Build();
    T Result = (T)Activator.CreateInstance(typeof(T));
    config.GetSection(sectionName).Bind(Result);
 
    return AppSettings.ValidateSettings<T>.Validate(Result);
}

Failed validations

The default behaviour for a property that fails validation is that SettingException is raised. This is not always ideal and can be overridden by passing an instance of ISettingError to the Validate method. This has a single method that is called on each failure. The application can determine how the failed validation is handled.

Overriding Properties

For testing purposes it can be quite handy to override properties for whatever reason, this is easily accomplished by passing an instance of ISettingOverride to the Validate method.

The application has an opportunity to evaluate the setting and provide another value if required.

Available Attributes

At present there are 12 attributes that can be applied to properties, these are then validated as part of the settings load method. Attributes can be chained together as seen below.

SettingDefault Applies a default value for a property, in the following example the default cache time in minutes is 120.

[SettingDefault(120)]
public int DefaultCacheDuration { getset; }
 
  

SettingRange Defines a numeric range which is valid for the property, in the following example the default cache time is 120 minutes, but can be any value between 30 and 2880.

[SettingDefault(120)]
[SettingRange(302880)]
public int DefaultCacheDuration { getset; }
 
  

SettingEmail Validates that the property contains an email address, you can also specify a separator char which automatically allows multiple emails to be defined. Each email is validated to ensure it is a valid email address.

[SettingDefault("support@mycompany.com;anotheremail@mycompany.com")]
[SettingEmail(';')]
public string Email { getset; }

[SettingDefault("support@mycompany.com")]
[SettingEmail]
public string Email { getset; }

SettingHttpResponse defines the available response codes, grouped by category (client errors, server errors etc). You can also have custom errors where you provide the codes that can be used.

[SettingDefault(400)]
[SettingHttpResponse(HttpResponseType.ClientErrors)]
public int BannedResponseCode { getset; }
 
[SettingDefault(429)]
[SettingHttpResponse(HttpResponseType.ClientErrors)]
public int TooManyRequestResponseCode { getset; }

// custom range
[SettingHttpResponse(new int[] { 200201400 })]
[SettingDefault(201)]
public int HttpResponse { getset; }

SettingNameValuePair Validates that the value is a name=value pair, with optional parameters to specify minimum and maximum count of items.

[SettingNameValuePair(';'219)]
[SettingDefault("Database=testdb;Username=dba;Password=mypass")]
public string DBConnectionString { getset; }
 
  

SettingOptional Indicates that the setting is an optional setting and may be omitted. If other attributes are also applied then further validation will take place as long as there is a value to validate.

[SettingOptional]
[SettingEmail]
public string NotifyAdminEmail { getset; }

SettingPathExists Will test for the existence of the path specified.

[SettingString(false)]
[SettingPathExists]
public string BackupPath { getset; }

SettingRange validates that a numeric or floating point number falls between the specified range. The following example has a minimum value of 5.

[SettingRange(5uint.MaxValue)]
[SettingDefault(100)]
public uint ConnectionsPerMinute { getset; }

SettingRegex Validates that the string specified matches the regex supplied. In the following example the string is a minimum of 8 characters and must contain at least 1 uppercase, 1 lowercase and 1 special character.

[SettingRegex("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$")]
[SettingDefault("Abbnn9@M8")]
public string EncryptionKey { getset; }

SettingString validates a string, specifying minimum and maximum lengths and whether it can be empty or not. The following example ensures the string length is between 30 and 80 characters long and does not all null or empty values.

[SettingString(false3080)]
[SettingRegex("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{30,}$zxcv")]
[SettingDefault("ASFDa;ilnc;f.dkcruqasdfo;cur$af((@adfasfdnwacrlkdfnn.zdcf")]
public string EncryptionKey { getset; }
 
  

SettingUri validates that the string matches a valid Uri either as an absolute Uri or relative. Optionally the Uri can be validated by connecting to it and ensuring it exists.

[SettingDefault("/Login/")]
[SettingUri(false, UriKind.RelativeOrAbsolute)]
public string LoginPage { getset; }

SettingValidPath validates that the string is a valid path, does not contain illegal characters. The following example does not allow null or empty strings, validates that the path name is legal and that it exists.

[SettingString(false)]
[SettingPathExists]
[SettingValidPath]
public string BackupPath { getset; }

Availability

Application settings is an open source project distributed using GPL 3 and is located on github at https://github.com/k3ldar/AppSettings. Nuget package manager can be used to install the library

PM> Install-Package ApplicationSettings -Version 1.0.1

Conclusion

ApplicationSettings is a simple solution for validating settings loaded from a file, or any other data store. Developers can add attributes which ensures the settings loaded are in the format or range expected and provide a more stable mechanism for validating settings without manually writing code for each setting.

References

Configuration in ASP.NET Core

Don’t Use JSON as a Configuration File Format. (Unless You Have To…)

JSON as configuration files: please don’t

Why JSON isn’t a Good Configuration Language

To view or add a comment, sign in

More articles by Simon Carter

Explore content categories