why IOptions is getting resolved even if not registered

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.