skip to Main Content

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"
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. 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
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search