Reputation: 34109
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
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
Reputation: 41827
Docs specifically say:
Don't use
IOptions<TOptions>
orIOptionsMonitor<TOptions>
inStartup.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
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
Reputation: 29879
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
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
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
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