ChatGPT解决这个技术问题 Extra ChatGPT

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?


s
spottedmahn

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>();

    ...
}

I
Ian Kemp

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();
    });
}

But why are you globally registering SecurityHeadersBuilder if you only want to execute a method? Just call the method on the constructor: public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options) { _options = options.Value; AddDefaultPolicy(); }
Builder has multiple policies and right now i am using Default Policy. I may use different policy in future. In such case i only have to change startup.cs to call different policy methods.
Ok. Then do not global register SecurityHeaderOptions if you will not inject it on SecurityHeadersBuilder. As you're code, you are simply instantiating the class and injecting it globally. There is no need to global register options. var option = Configuration.GetSection("SecurityHeaderOptions");
but Configuration.GetSection("SecurityHeaderOptions") does not return instance of SecurityHeaderOptions or IOptions<SecurityHeaderOptions> please see my 2nd question
This was helpful Thanks LP13
R
Rory

Docs specifically say:

Don't use IOptions or IOptionsMonitor 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


I
Ian Kemp

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();
});

When down voting please take the time to add a comment and briefly explain your reasoning to the person who took the time to provide an answer.
I
Ian Kemp

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>();

As i mentioned in my post that .net core can do implicit injection. However i want to call AddDefaultPolicy() method on instance during registration. Anyway i think i got the answer after further reading. I will post my answer below.
If you just want to call the method during registration, just place a call on the constructor to AddDefaultPolicy(). Then you don't need to do anything else neither register the SecurityHeadersBuilder...
z
zhuber

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());
}