LP13
LP13

Reputation: 34109

How to get IOptions in ConfigureServices method?

I have asp.net core application. I want to use IOptions pattern to inject values from appsettings.json. So I have a class SecurityHeaderOptions, and also have target class SecurityHeadersBuilder whose constructor takes IOptions<SecurityHeaderOptions> as parameter.

I know that .net core can implicitly create instance of SecurityHeadersBuilder by injecting IOptions<SecurityHeaderOptions> after registering both with container.

However i want to explicitly create instance of SecurityHeadersBuilder, call one of its method and then register the instance with the container.

public sealed class SecurityHeaderOptions
{
    public string FrameOption { get; set; }    
    public string XssProtection { get; set; }
}


public class SecurityHeadersBuilder
{
    private readonly SecurityHeaderOptions _options = null;

    public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
    {
        _options = options.Value;    
    }

    public SecurityHeadersBuilder AddDefaultPolicy()
    {
        AddFrameOptions();
        AddConetntSecurityPolicy();
        return this;
    }
}

ConfigureServices method

public void ConfigureServices(IServiceCollection services)
{        
    services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));

    services.AddScoped<SecurityHeadersBuilder>(provider => 
           new SecurityHeadersBuilder(?????).AddDefaultPolicy())
}

Questions
1> If i am explicitly passing options into constructor, do i need to register SecurityHeaderOptions with the container using service.Configure method?

2> Configuration.GetSection("SecurityHeaderOptions") can't return instance of IOptions<SecurityHeaderOptions> , instead it returns IConfigurationSection?

3>Either way, how do I retrieve and pass SecurityHeaderOptions into SecurityHeadersBuilder's constructor?

Upvotes: 80

Views: 74412

Answers (7)

pdouelle
pdouelle

Reputation: 229

SecurityHeaderOptions securityHeaderOptions = new SecurityHeaderOptions();
services.AddOptions<SecurityHeaderOptions>().Configure<IConfiguration>((options, config) =>
{
    IConfigurationSection section = config.GetSection("SecurityHeaderOptions");
    securityHeaderOptions = section.Get<SecurityHeaderOptions>();
    section.Bind(options);
});

securityHeaderOptions.YourVariable;

Upvotes: 1

Rory
Rory

Reputation: 41827

Docs specifically say:

Don't use IOptions<TOptions> or IOptionsMonitor<TOptions> in Startup.ConfigureServices. An inconsistent options state may exist due to the ordering of service registrations.

So you'll have to access the configuration some other way from Startup.ConfigureServices, e.g. Quinton's answer

Upvotes: 14

zhuber
zhuber

Reputation: 5524

You could do something like this

public static class IConfigurationExtensions
{
    public static TypedConfiguration<SecurityHeaderOptions> GetSecurityHeaderOptions(this IConfiguration configuration)
    {
        return new TypedConfiguration<SecurityHeaderOptions>(configuration.GetSection("SecurityHeaderOptions"));
    }
}

public class TypedConfiguration<T> where T : class
{
    public TypedConfiguration(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }
    public T Value => Configuration.Get<T>();

    public void InitializeOptions(IServiceCollection services)
    {
        services.Configure<T>(Configuration);
    }
}

Now from single place you've created object that has both IConfiguration, typed SecurityHeaderOptions and helper method for registering IOptions injection for that class.

Use it like this

public void ConfigureServices(IServiceCollection services)
{        
    var wrappedOptions = Configuration.GetSecurityHeaderOptions();
    wrappedOptions.InitializeOptions(services);

    var options = Options.Create(wrappedOptions.Value);
    services.AddScoped<SecurityHeadersBuilder>(provider => 
       new SecurityHeadersBuilder(options).AddDefaultPolicy());
}

Upvotes: 1

Firstly you need to add a second constructor to SecurityHeadersBuilder, that takes a plain SecurityHeaderOptions:

public class SecurityHeadersBuilder
{
    private readonly SecurityHeaderOptions _options;

    public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
    {
        _options = options.Value;    
    }

    public SecurityHeadersBuilder(SecurityHeaderOptions options)
    {
        _options = options;    
    }

    public SecurityHeadersBuilder AddDefaultPolicy()
    {
        AddFrameOptions();
        AddContentSecurityPolicy();

        return this;
    }
}

Then the answer entirely depends on whether or not you need to use those options outside of your Startup class.


If not, you can simply use the Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>() extension method:

services.AddScoped<SecurityHeadersBuilder>(provider =>
{
    var options = Configuration.GetSection("SecurityHeaderOptions")
        .Get<SecurityHeaderOptions>();

    return new SecurityHeadersBuilder(options)
        .AddDefaultPolicy();
});

(you can then delete the SecurityHeadersBuilder constructor that takes IOptions<SecurityHeaderOptions>).


If you will need to use these options elsewhere, then you can combine the above approach with the Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure() extension method:

var optionsSection = Configuration.GetSection("SecurityHeaderOptions");
services.Configure<SecurityHeaderOptions>(optionsSection);
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
    var options = optionsSection.Get<SecurityHeaderOptions>();

    return new SecurityHeadersBuilder(options)
        .AddDefaultPolicy();
});

Upvotes: 0

Jo&#227;o Pereira
Jo&#227;o Pereira

Reputation: 1667

Regarding your questions:

1. Yes, you need to register the options, but I believe you are doing it the wrong way (at least by your example). You should register as this:

services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));

2. I believe that the correct registration I refer above returns what you are expecting.

3. Just registering it and placing it on the SecurityHeaderBuilder constructor is enough. You do not need (neither does the default .NET Core IOC container allows) to pass constructor parameters when registering it. For that you would need to use other IOC's such as Autofac.

But you need to register SecurityHeadersBuilder in order to use it within other classes. Just use an interface for that.

public interface ISecurityHeadersBuilder
{
    SecurityHeadersBuilder AddDefaultPolicy();    
}

public class SecurityHeadersBuilder : ISecurityHeadersBuilder
{
    private readonly SecurityHeaderOptions _options = null;

    public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
    {
        _options = options.Value;    
    }

    public SecurityHeadersBuilder AddDefaultPolicy()
    {
        AddFrameOptions();
        AddContentSecurityPolicy();
        return this;
    }
}

Then, just register it in startup.cs as this

services.AddScoped<ISecurityHeadersBuilder, SecurityHeadersBuilder>();

Upvotes: 1

LP13
LP13

Reputation: 34109

This is how I register options and inject into SecurityHeadersBuilder

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));            
    services.AddScoped<SecurityHeadersBuilder>(provider =>
    {
        var option = provider.GetService<IOptions<SecurityHeaderOptions>>();
        return new SecurityHeadersBuilder(option)
            .AddDefaultPolicy();
    });
}

Upvotes: 27

Quinton Smith
Quinton Smith

Reputation: 2670

Using .NET Core 2 and not having a provider available (or caring to add it) in ConfigureServices I opted to go with something like this (using OP code as example):

public void ConfigureServices(IServiceCollection services)
{
    // secOpts available for use in ConfigureServices
    var secOpts = Configuration
        .GetSection("SecurityHeaderOptions")
        .Get<SecurityHeaderOptions>();

    ...
}

Upvotes: 100

Related Questions