Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why IOptions is getting resolved even if not registered

In my .NET Core project, I have below settings in Configure method:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    //services.AddOptions<UploadConfig>(Configuration.GetSection("UploadConfig"));
}

I have not registerd any IOptions and I am injecting it in a controller

[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ControllerBase
{
    public HelloWorldController(IOptions<UploadConfig> config)
    {
        var config1 = config.Value.Config1;
    }
 }

The IOptions is getting resolved with the default instance, and I get to know error only when I am trying to use it (and when I expect the value to be not null).

Can I somehow get it to fail, stating the instance type is not registered or something similar? I just want to catch errors as early as possible.

like image 979
harishr Avatar asked Mar 09 '19 06:03

harishr


Video Answer


2 Answers

The options framework is set up by the default host builder as part of its setup, so you do not need to AddOptions() yourself. This however also ensures that you can use IOptions<T> wherever you want since the framework will provide that exact options object for you.

The way options work is that the framework will always give you a T (as long as it can construct one). When you do set up configuration using e.g. AddOptions<T> or Configure<T>, what actually happens is that a configuration action gets registered for that type T. And when an IOptions<T> is later resolved, all those registered actions will run in the sequence they are registered.

This means that it’s valid to not have configured an options type. In that case, the default values from the object will be used. Of course, this also means that you are not able to detect whether you have actually configured the options type and whether the configuration is actually valid. This usually has to be done when you use the values.

For example, if you require Config1 to be configured, you should explicitly look for it:

public HelloWorldController(IOptions<UploadConfig> config)
{
    if (string.IsNullOrEmpty(config.Value.Config1))
        throw ArgumentException("Config1 is not configured properly");
}

An alternative would be to register a validation action for a type using OptionsBuilder.Validate. This will then be called automatically when you resovle the options object to validate the containing value. That way, you can have the validation set up in a central location:

services.AddOptions<UploadConfig>()
    .Bind(Configuration.GetSection("UploadConfig"))
    .Validate(c => !string.IsNullOrEmpty(c.Config1));

Unfortunately, this also means that you can only detect these problems when you actually use the values, which can be missed if you are not testing your application thoroughly. A way around this would be to resolve the options once when the application starts and validate them there.

For example, you could just inject your IOptions<T> within your startup’s Configure method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<UploadConfig> uploadOptions)
{
    // since the options are injected here, they will be constructed and automatically
    // validated if you have configured a validate action

    // …
    app.UseMvc();
}

Alternatively, if you have multiple options you want to validate and if you want to run logic that does not fit into the validation action, you could also create a service that validates them:

public class OptionsValidator
{
    private readonly IOptions<UploadConfig> uploadOptions;
    public OptionsValidator(IOptions<UploadConfig> uploadOptions)
    {
        _uploadOptions = uploadOptions;
    }

    public void Validate()
    {
        if (string.IsNullOrEmpty(_uploadOptions.Value.Config1))
            throw Exception("Upload options are not configured properly");
    }
}

And then inject that in your Configure:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, OptionsValidator optionsValidator)
{
    // validate options explicitly
    optionsValidator.Validate();

    // …
    app.UseMvc();
}

Whatever you do, keep also in mind that by default the configuration sources are configured to support updating the configuration at run-time. So you will always have a situation in which a configuration can be invalid temporarily at run-time.

like image 103
poke Avatar answered Oct 16 '22 10:10

poke


Great answer from poke, I just wanna complete it with this that you can fail-fast in your startup file when no configuration is provided:

public class MyOptions
{
    public string MyValue { get; set; }
}

public void ConfigureServices(IServiceCollection services)
{
    var options = Configuration.GetSection("MyOptions").Get<MyOptions>();
    if (string.IsNullOrWhiteSpace(options?.MyValue))
    {
        throw new ApplicationException("MyValue is not configured!");
    }
}

IOptions configuration values are read lazily. Although the configuration file might be read upon application startup, the required configuration object is only created when IOptions.Value is called for the first time.

When deserialization fails, because of application misconfiguration, such error will only appear after the call to IOptions.Value. This can cause misconfigurations to keep undetected for much longer than required. By reading -and verifying- configuration values at application startup, this problem can be prevented.

This articles also helps you to get the idea:

Is IOptions Bad?

ASP.NET Core 2.2 – Options Validation

like image 29
Moien Tajik Avatar answered Oct 16 '22 12:10

Moien Tajik