In my Startup.cs, I have the following boilerplate code:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
What I want to do is access and re-use the already-built Configuration object with a Controller elsewhere in my project without having to build a new configuration object over there. How can I do that or is it even possible?
You have a few alternatives:
Store the configuration object in a global variable.
Pro: simple.
Con: creates coupling and makes everyone aware of the Configuration object. No DI. No type safety.
Add the configuration object to the DI container and consume it where you need it.
Pro: decoupling. Supports DI.
Con: you make other components aware of configuration. No type safety.
Create an options object either by using IOptions or by using ConfigurationBinder and add it to the container.
Pro: nice architecture where each component knows only about the options object. Supports DI. Type safe.
Con: more code.
For a Hello World size app, option #1 works just fine. For a real world application I would definitely consider #3.
Related
IMPORTANT: This is NOT a duplicate of How to get IConfiguration from within Main? !
I have read that topic, I have read answers and comments and NO, my case is completely different.
The exact point when I need my configuration is before invoking UseUrls from IHostBuilder.
This is invoked from Main. My URLs are defined in appsettings.json file, and now I get the configuration like this:
var configuration = (
new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true))
.Build();
But this seems wrong. I mean the framework somehow loads the configuration and injects it to the services. However, the point I need it is before the services are even configured.
I also have a special execution mode for my Main - when import command argument is used, my data context is initialized (again, using configuration from appsettings.json) to import data from external database. When this happens, no web server is started at all, the program behaves like a console application. So again, dependency injection is not available, I instantiate the configuration as described. This is well, OK, because the initialization is done only once.
However, my "hack" initializes the IConfiguration and then the framework does it again. This looks like a code smell and I wonder how to fix this.
Here's my CreateHostBuilder method:
private static IHostBuilder CreateHostBuilder(string[] args) {
var defaultBuilder = Host.CreateDefaultBuilder(args);
var configuration = (new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)).Build();
return defaultBuilder.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>().UseUrls(configuration.GetValue<string>("InternalSiteUrl"));
});
}
There is a better way, isn't it?
In this thread I was able to setup my simple console application using ASP.NET CORE's configuration system.
The code is as simple as:
static void Main(string[] args)
{
string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment}.json", optional: false)
.AddEnvironmentVariables();
config = configuration.Build();
var serviceProvider = new ServiceCollection()
.AddSingleton<IConfiguration>(config)
.AddDiscoveryClient(config)
.BuildServiceProvider();
Console.WriteLine(config["Test"]);
Console.Read();
}
However, since the application does not use IApplicationBuilder, I cannot invoke the .UseDiscoveryClient() method. I end up receiving an error on .AddDiscoveryClient(config):
"Discovery client type UNKNOWN, check configuration"
Is there a work around this? We would like to experiment using console applications with our Spring Cloud Config server. If there is no way to do it with Steeltoe, feel free to inform with other libraries that do.
The extension methods AddDiscoveryClient and UseDiscoveryClient are for use with Steeltoe service discovery. The error message you're seeing is due to Steeltoe not knowing which type of service registry your app should be a client of (ie: "client type UNKNOWN").
You only wish to access Spring Cloud Config server, so you don't need either of those methods. You can add the ConfigServerConfigurationProvider to you your config builder with .AddConfigServer.
you should add "appName" and "hostname" in the appsetting.json under instance
Hi I'm trying to write environment specific configuration for specflow test and I'm a little bit confused.
I know that in .net Core i have Environment variables and in web app i can just write this:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
But how to use this in the library class project? My Solution is divided into a main project (class library) with specflow cases, and project that will handle the connection with DB. The connection strings have to change depending on the environment. I wanted to create appsettings.{env}.Json for each, but how can I assign appsetting file depending on env if I don't have startup class?
I'm using ASP.NET Core 2.0 and I have configuration code like this in the Main method:
public static void Main(string[] args)
{
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{environment ?? "Production"}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
}
I have the reloadOnChange set to true, and in my controller I am using IOptionsSnapshot;
public HomeController(ILogger<HomeController> logger, IOptionsSnapshot<AppSettings> options)
But when I modify the values in my appsettings.json, I have to restart my app every time or the changes are not being picked up just by refreshing the browser. What am I doing wrong?
I've tried to run the app both with console and IIS Express; I've also tried IOptionsMonitor, same thing. What is the difference between IOptionsMonitor and IOptionsSnapshot?
As mentioned in the documentation, just enabling reloadOnChange and then injecting IOptionsSnapshot<T> instead of IOptions<T> will be enough. That requires you to have properly configured that type T though. Usually a configuration registration will look like this:
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
However, looking closer at your code, it does not seem that you are using the new ASP.NET Core 2.0 way of configuring your program. The configuration is now part of dependency injection, so you will set it up as part of the WebHostBuilder, using ConfigureAppConfiguration. That could for example look like this:
public static IWebHost BuildWebHost()
=> new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((builderContext, config) =>
{
IHostingEnvironment env = builderContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.UseStartup<Startup>()
.Build();
If you are using the default builder using WebHost.CreateDefaultBuilder(), then you don’t even need to do this, as the configuration is then automatically set up like that with reloadOnChange activated.
The difference between IOptionsSnapshot and IOptionsMonitor is that the IOptionsSnapshot will just give you a snapshot of the options at the time the IOptionsSnapshot<T> object is being constructed.
That’s why the usage is exactly the same as with IOptions<T>: You inject it in the constructor and then store the options.Value in the instance to access the options later. At that point, that object is fixed and will never change. It’s just that the IOptionsSnapshot<T> is registered as a scoped dependency instead of a singleton dependency like IOptions<T>, so it gets the chance to get the current configuration values on every request instead of just once.
The IOptionsMonitor<T> however is a singleton service that allows you to retrieve the current configuration value at any given time. So it is especially useful for singleton services that need to get the current configuration whenever they need it. In addition, the options monitor offers a push mechanism to get notified of configuration changes by the configuration sources. That way, you can explicitly handle configuration changes.
Options snapshots are designed to be used for transient or scoped dependencies, so you will be fine just using those most of the time. Only in rare occasions when you have to use a singleton service that also needs to have the most updated configuration, you should have the need to use an options monitor. In those cases, note that just switching from snapshots to a monitor will not be enough. Usually, you will have to handle changed configuration in some way (for example clean up state, clear caches etc.). So you should always think about whether you actually need reloadable configuration for everything or if just restarting the application isn’t a viable alternative.
On a typical ASP.NET Core application, the Main method creates a configuration and passes it to the WebHostBuilder. Is there a way to access this configuration inside any of the Startup methods (e.g. Configure or ConfigureServices)?
I need this configuration because it has access to the command line arguments.
Thanks
Pedro
You can just create a property in the class, where Main method exists, to persist the configuration, or use any other class for storage.
public IConfigurationRoot Configuration { get; set; }
From other side, you can read configuration few times. You are allowed to create another instance of ConfigurationBuilder and specify configuration sources (maybe even different than you use in main method):
var builder = new ConfigurationBuilder()
.AddEnvironmentVariables();
var anotherConfiguration = builder.Build();