TypeConverter and configuration

edit

Convert with a TypeConverter

The generator adds a TypeConverter to each ID and registers it with [TypeConverter]. Libraries that convert values through TypeDescriptor, such as Microsoft.Extensions.Configuration, MVC model binding and UI frameworks, handle IDs like any simple type:

var converter = TypeDescriptor.GetConverter(typeof(ProjectId));

Assert.Equal(ProjectId.FromInt32(42), converter.ConvertFromInvariantString("42"));
Assert.Equal(ProjectId.FromInt32(42), converter.ConvertFrom(42));
Assert.Equal("42", converter.ConvertToInvariantString(ProjectId.FromInt32(42)));

The converter converts:

  • from a string (with Parse), the underlying type or the ID itself;
  • to a string (ValueAsString), the underlying type or the ID itself.

The culture given to the converter is ignored: the conversion always uses the invariant culture. A string that does not parse throws a FormatException.

Bind configuration

The configuration binder converts the strings of appsettings.json, environment variables and the other configuration sources with the TypeConverter, so an options class can use IDs directly:

public sealed class ImportOptions
{
    public ProjectId DefaultProject { get; set; }
    public IList<ProjectId> ArchivedProjects { get; } = [];
}
var configuration = new ConfigurationBuilder()
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["Import:DefaultProject"] = "42",
        ["Import:ArchivedProjects:0"] = "7",
        ["Import:ArchivedProjects:1"] = "8",
    })
    .Build();

// The configuration binder converts the strings with the generated TypeConverter
var options = configuration.GetSection("Import").Get<ImportOptions>();
Assert.Equal(ProjectId.FromInt32(42), options!.DefaultProject);
Assert.Equal([ProjectId.FromInt32(7), ProjectId.FromInt32(8)], options.ArchivedProjects);

The same options class works with services.Configure<ImportOptions>(...) and IOptions<ImportOptions>.