I'm writing a basic app to learn ASP.NET 5. One area I find very confusing is configuration. Prior to ASP.NET 5, I could do the following:
var settingValue = ConfigurationManager.AppSettings["SomeKey"];
I would have lines of code like that sprinkled throughout my code. Now, in the vNext world, I have a config.json file that looks like this:
config.json
{
"AppSettings": {
"SomeKey":"SomeValue"
}
}
Then in Startup.cs, I have the following:
Startup.cs
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
Configuration = new Configuration()
.AddJsonFile("config.json");
}
From there, I'm totally stumped. I have MyClass.cs in /src/Website/Code/Models/MyClass.cs.
MyClass.cs
public class MyClass
{
public string DoSomething()
{
var result = string.Empty;
var keyValue = string.Empty; // TODO: What do I do here? How do I get the value of "AppSettings:SomeKey"?
return result;
}
}
How do I get the value of "AppSettings:SomeKey"?
ASP.NET 5 makes heavy use of Dependency Injection, so if you are also using Dependency Injection then this is very simple. If you examine the sample MVC6 project, you can see how this works:
First, there's a class AppSettings defined in Properties, which is a strongly-typed version of the options your class supports. In the sample project, this just contains SiteTitle.
public class AppSettings
{
public string SiteTitle { get; set; }
}
Then, this class is initialised through dependency injection in ConfigureServices. Configuration here is the one you created in the constructor of the Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
// ...
}
Then, assuming your class is instantiated by the dependency injection container, you can simply ask for an IOptions and you'll get one. For example, in a controller you could have the following:
public class HomeController
{
private string _title;
public HomeController(IOptions<AppSettings> settings)
{
_title = settings.Options.SiteTitle;
}
}
I use ASP.NET 5 dependency injection, like so.
config.json:
{
"random": "Hello World!"
}
startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json");
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IConfiguration>(sp => { return Configuration; });
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Controller:
public class HomeController : Controller
{
IConfiguration config;
public HomeController(IConfiguration config)
{
this.config = config;
}
public IActionResult Index()
{
var template = "<marquee>{0}</marquee>";
var content = string.Format(template, config.Get("random"));
return Content(content, "text/html");
}
}
I highly recommend using the OptionsModel instead of reading the configuration directly. It allows strong typed model binding to configuration.
Here is an example: GitHub.com/aspnet/Options/test/Microsoft.Extensions.Options.Test/OptionsTest.cs
For your particular case create a model:
class AppSettings {
public string SomeSetting {get;set;}
}
and then bind it to your configuration:
var config = // The configuration object
var options = ConfigurationBinder.Bind<AppSettings>(config);
Console.WriteLine(options.SomeSetting);
That way you don't have to worry from where the setting comes from, how it is stored or what is the structure. You simply predefine your options model and magic happens.
Use this:
var value = Configuration.Get("AppSettings:SomeKey");
Based on this blog post. The colon is similar to dot notation and is used for navigation down the hierarchy.
If you need the value in other classes, you should inject it in. ASP.NET has built in dependency injection, but if you just need one instance of MyClass you can new it up instead of setting up a DI container.
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
Configuration = new Configuration()
.AddJsonFile("config.json");
//generally here you'd set up your DI container. But for now we'll just new it up
MyClass c = new MyClass(Configuration.Get("AppSettings:SomeKey"));
}
public class MyClass
{
private readonly string Setting; //if you need to pass multiple objects, use a custom POCO (and interface) instead of a string.
public MyClass(string setting) //This is called constructor injection
{
Setting = setting;
}
public string DoSomething()
{
var result = string.Empty;
//Use setting here
return result;
}
}
Related
I have an issue that I cannot access the settings within my appsettings.json file.
I have set the class as this :
public class apisettings
{
public const string SectionName = "LocalConfig";
public string Url { get; set; }
}
The section in the appsettings.json is as follows :
"LocalConfig": {
"Url": "https://someurl.com/api/"
}
In my Startup.cs I have the following (please excuse all the additional parts for Azure Authentication) :
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddOptions();
services.Configure<apisettings>(Configuration.GetSection(apisettings.SectionName));
}
In my controller I have it set as per the following :
public static string URL = "";
public IActionResult Index(IOptions<apisettings> apisettings)
{
URL = apisettings.Value.Url;
return View();
}
Now every time I access that particular section of the application I get an error that states "Could not create an instance of type 'Microsoft.Extensions.Options.IOptions`1[[ManagementApplication.Models.apisettings, ManagementApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Record types must have a single primary constructor. Alternatively, give the 'apisettings' parameter a non-null default value."
I am sure I am missing something very simple, but please help?
UPDATE
I have edited the class as per the advice below so it now shows as :
public class apisettings
{
public apisettings()
{
}
public const string SectionName = "LocalConfig";
public string Url { get; set; }
}
It is still giving me exactly the same error, I know I am missing something simple but can't find it :(
Looks like the apisettings class has got some other constructor configured and you are trying to call the default constructor. A very good explanation has been provided here
Add a default constructor as below
public apisettings() {
};
After much trawling through the answers and the internet I was missing a very simple piece of the puzzle. The IOptions was being created in the services and I needed to refer to that in the Controller code to make sure it knew where to look.
The below is a cut down version of all the code with only the relevant parts included so anyone can follow the solution.
I have set the class as this :
public class apisettings
{
public apisettings() { }
public apisettings(string url)
{ Url = url; }
public string Url { get; set; }
}
The section in the appsettings.json is as follows :
"LocalConfig": {
"Url": "https://someurl.com/api/"
}
In my Startup.cs I have the following :
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
services.AddOptions();
services.Configure<apisettings> Configuration.GetSection("LocalConfig"));
}
In my controller I have it set as per the following :
public static string URL = "";
public IActionResult Index([FromServices] IOptions<apisettings> apisettings)
{
URL = apisettings.Value.Url;
return View();
}
The key to the solution was adding the [FromServices] to the declaration.
Model bound complex types must not be abstract or value types and must
have a parameterless constructor
Add a constructor method without parameters
public class apisettings
{
public apisettings() {
}
public const string SectionName = "LocalConfig";
public string Url { get; set; }
}
Since you are still getting the same error, I think you should check that you are following this pattern
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0
I know there are two ways to inject configuration into my other layers class:
first one Configuration binding:
services.AddConfiguration() // enable Configuration Services
var config = new WeblogConfiguration();
Configuration.Bind("Weblog", config); // <--- This
services.AddSingleton(config);
usage:
public class PostsController : Controller
{
PostRepository PostRepo { get; }
WeblogConfiguration Config { get; }
public PostsController(PostRepository postRepo,
WeblogConfiguration config)
{
PostRepo = postRepo;
Config = config;
}
...
}
and IOption Pattern:
services.Configure<WeblogConfiguration>(option => Configuration.GetSection("WeblogConfiguration").Bind(option));
services.AddOptions();
usage:
public class PostsController : Controller
{
PostRepository PostRepo { get; }
WeblogConfiguration Config { get; }
public PostsController(PostRepository postRepo,
IOptions<WeblogConfiguration> config)
{
PostRepo = postRepo;
Config = config.Value;
}
...
}
I want to know the difference between these methods and the cons and pros for each of them.
So far only one difference come into my mind:
The possibility to reload the configuration.
When you bind your WeblogConfiguration and add it as singleton it will be loaded once and never changes.
Once you use the IOptions pattern you could use the reloadOnChange argument when adding the json file and will get updated values via the IOptions.Value method.
I need to register an IOptions<T> with a .NET Core ServiceCollection.
Normally the method would be something like this:
var configSection = configuration.GetSection("sectionName");
serviceCollection.Configure<KnownClassName>(configSection);
This registers a strongly typed IOptions<KnownClassName> with the container.
I need to register an IOptions<UnknownName>. I have the Type of the class. I can't seem to find a method that will allow me to register the Type and the configuration section.
This is what am attempting to do:
interface ILoggingProvider
{
Type GetSettingType();
string ProviderName {get;}
}
IList<ILoggingProvider> loggingProviders = GetLoggingProviders();
foreach(var provider in loggingProvider)
{
var providerSection = configuration.GetSection(providerSection);
var providerSettingType = provider.GetSettingType();
// can't find an overload or other method to do the same thing as
// serviceCollection.Configure<LoggerSettings>(providerSection);
serviceCollection.Configure(providerSection, providerSettingType);
}
I don't think that this is possible; checking the source of IServiceCollection.Configure<TOptions> shows that the entire options/configuration functionality is built around generic types.
Also, as far as I know, there is no non-generic IOptions interface, so there is not really a justification for providing a non-generic configuration registration method.
However, this implies that your ILoggingProvider instances have to make use of IOptions<UnknownName> themselves, else they would not be able to retrieve the configuration from the DI container. In this case, you may either expose the options class of the particular providers, or add an (extension-)method ILoggingProvider.RegisterOptions(IServiceCollection, IConfiguration) which does this registration itself.
In Startup.cs, you would then simply call
provider.RegisterOptions(serviceCollection, providerSection);
for each provider.
This of course requires write access to the source code of the ILoggingProvider implementations.
You could use services.ConfigureOptions, if it is possible to return type that implements IConfigureOptions<T> from your ILoggingProvider.GetSettingType()
public class CustomConfiguration
{
public string Data { get; set; }
}
public class CustomConfigurationOptions : IConfigureOptions<CustomConfiguration>
{
private readonly IConfiguration configuration;
public CustomConfigurationOptions(IConfiguration configuration)
{
this.configuration = configuration;
}
public void Configure(CustomConfiguration options)
{
var optionsFromConfig = configuration
.GetSection(nameof(CustomConfiguration))
.Get<CustomConfiguration>();
options.Data = optionsFromConfig.Data;
}
}
public class LoggingProvider
{
public Type Type { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var loggingProviders = new List<LoggingProvider>()
{
new LoggingProvider()
{
Type = typeof(CustomConfigurationOptions)
}
};
foreach (var loggingProvider in loggingProviders)
{
services.ConfigureOptions(loggingProvider.Type);
}
}
...
}
I have a class
public TimeSeriesBusinessComponent(IContextRepository contextRepository) { ...
I pass the dependency injection through my class ServiceExtension, which I call from my Startup.cs.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRepositories(Configurations);
services.AddBusinessComponents();
ServiceExtension:
public static void AddRepositories(this IServiceCollection serviceCollection, Configurations con)
{
serviceCollection.AddScoped<IContextRepository>(serviceProvider =>
{
string createContextFunctionName = con.Get(CREATE_CONTEXT_FUNCTION_NAME);
return new ContextRepository(createContextFunctionName);
});
}
public static void AddBusinessComponents(this IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ITimeSeriesBusinessComponent, TimeSeriesBusinessComponent>();
}
Later I use my Class TimeSeriesBusinessComponent like that:
public class TimeSeriesController : Controller
{
private ITimeSeriesBusinessComponent BusinessComponent { get; }
public TimeSeriesController(
ITimeSeriesBusinessComponent businessComponent
)
{
BusinessComponent = businessComponent;
}
But now I need to transfer a argument from my ServicExtension to my TimeSeriesBusinessComponent too, like ..
public TimeSeriesBusinessComponent(IContextRepository contextRepository,. string value) { ...
The string value I get from my configurations (EnvirenmentVariables).
How can I use dependency injection and normal argument in the same time?
So, since your string value comes from Environment variable, you should use IOptions.
You create your MyOptions class (that will hold a value you need)
You load a value from your environment (or from wherever) in your Startup.cs
You register your options with DI container
You inject it into target class just like any other service
for example:
services.Configure<MyOptions>(options =>
{
options.MyString = Environment.GetEnvironmentVariable("MY_STRING_KEY");
});
and then you use it like:
public TimeSeriesBusinessComponent(IContextRepository contextRepository, IOptions<MyOptions> myOptions) {
_myOptionsOptions = options.MyString;
}
docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
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?
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>();
...
}
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();
});
}
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
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();
});
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>();
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());
}