Home > Software engineering >  How can I check for a missing configuration section when using the Options pattern?
How can I check for a missing configuration section when using the Options pattern?

Time:05-28

I have a simple controller that reads some configuration from appsettings.json using the Options pattern. It works fine as long as appsettings.json is configured correctly. However, what if my configuration section is missing from appsettings.json? I hoped to get an Exception or null, but instead I get a MySettings object with default values (i.e. MyProperty is null).

MyController.cs

public class MyController : Controller
{
    private readonly string value;

    public MyController(IOptions<MySettings> options)
    {
        // Can I validate the `options` object here?
        this.value = options.Value.MyProperty;
    }

    [HttpGet("api/data")]
    public ActionResult<string> GetValue()
    {
        return this.Ok(this.value);
    }
}

MySettings.cs

public class MySettings
{
    public string MyProperty { get; set; }
}

Startup.cs (only showing relevant code)

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Tried this:
        services.Configure<MySettings>(Configuration.GetSection("MySettings"));

        // Tried this too:
        services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
    }
}

appsettings.json

{
  "MySettings": {
    "MyProperty": "hello world"
  }
}

CodePudding user response:

You can enforce it in the following way:

  1. Add data annotation to your model
public class MySettings
{
    [Required]
    public string MyProperty { get; set; }
}
  1. Call ValidateDataAnnotations after Bind call
services
   .Configure<MySettings>(options => Configuration
     .GetSection("MySettings")
     .Bind(options))
     .ValidateDataAnnotations();
  1. Wrap the property access in a try catch where you except OptionsValidationException
try
{
   this.value = options.Value.MyProperty;
}
catch(OptionsValidationException ex)
{
   //handle the validation exception
}

CodePudding user response:

I found one possible solution on this SO answer.

Add an extensions method to validate:

public static class ConfigurationExtensions
{
    public static T GetValid<T>(this IConfiguration configuration)
    {
        var config = configuration.Get<T>();

        try
        {
            Validator.ValidateObject(config, new ValidationContext(config), true);
        }
        catch(Exception e)
        {
            throw new NotSupportedException("Invalid Configuration", e);
        }

        return config;
    }
}

Use that extension method to validate & register the instance in Startup.cs:

var settings = Configuration.GetSection("MySettings").GetValid<MySettings>();
services.AddSingleton(settings);

Change the controller constructor to accept the MySettings object directly (instead of IOptions<MySettings>):

public MyController(MySettings options)
{
    this.value = options.MyProperty;
}

Another nice consequence of this approach is it removes our dependency on IOptions which simplifies the controller a bit.

  • Related