2 minute read

Everybody loves class enums, right? Maybe not, but how do you use them when you want to map to settings read from configuration? You have a .Net app which reads JSON settings from appSettings.json and one of the settings needs to be mapped to an enumeration. But instead of an enum you would like to use an enumeration class. How do you do it? Type converters to the rescue.

Let’s say we have this appSettings.json:

{
  "MyOptions": {
    "MyEnumValue":  "enumTypeValue1"
  }
}

, and this enumeration class we try to map our MyEnumValue to:

public class MyEnumValue
{
    public static MyEnumValue EnumTypeValue1 = new("enumTypeValue1");
    public string Name { get; set; }
    public MyEnumValue(string name) => Name = name;
    public static implicit operator string(MyEnumValue enumValue) => enumValue.Name;
}

Note: usually these type of classes should implement IComparable or IEquatable<T> - removed for brevity.

The implicit operator is used just to be able to use string and MyEnumValue interchangeably and has no other relevance to the case being described here.

The options class used to read the whole settings object:

public class MyClass
{
    private readonly MyOptions _options;
    public MyClass(IOptions<MyOptions> options)
    {
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
    }
    public string GetValue() => _options.MyEnumValue;
}

To be complete, this is the simplest way to build your app host to automatically read your app settings json and to register it for dependency injection via an IOptions<T> type:

static void Main(string[] args)
{
  using IHost host = CreateHostBuilder(args).Build();
  var myClass = host.Services.GetService<MyClass>();
  Console.WriteLine(myClass.GetValue());
}

private static IHostBuilder CreateHostBuilder(string[] args) =>
  Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) => 
    {
      var configuration = context.Configuration;
      services.Configure<MyOptions>(configuration.GetSection(nameof(MyOptions)));
      services.AddSingleton<MyClass>();
    });

Note that Host.CreateDefaultBuilder will automatically load app IConfiguration from appSettings.json

If you would run your console application now, it would fail because the string value of enumTypeValue1 cannot be mapped to MyEnumValue.EnumTypeValue1 and the _options.MyEnumValue will be null.

By now you would already have guessed that the missing piece of the puzzle is the type converter:

	public class MyEnumValueTypeConverter : TypeConverter
	{
		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType == typeof(string))
				return true;
			return base.CanConvertFrom(context, sourceType);
		}
		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value is string strValue)
				return new MyEnumValue(strValue);
			return base.ConvertFrom(context, culture, value);
		}

And now, if we annotate our enum value class with the [TypeConverter(typeof(MyEnumValueTypeConverter))] attribute our app will work just fine.

This is it, the easy-peasy way of using enumeration classes mapped to appSettings.json options values. You can download a full working example from github

Updated: