So I'm trying to setup the webapp enviroment name according to the company deployment policy based on transformations of appsettings file during the pipeline deployment. We are using IWebHostEnvironment for reading the env and rootpath later in startup process.
But I'm running into the issue that I don't know how properly resolve. Is there any option to prebuild the configuration so that I can read value from it before creating new builder or this is the 'way' how to do it. To have one default pre-builder for configuration and than create regular one for the normal app.
It looks like chicken-egg problem to me.
Other solution would be to read 'environment' from the configuration directly but doesn't look clean to me.
var configBuilder = WebApplication.CreateBuilder(args);
configBuilder.Configuration.SetupConfiguration(args);
var section = configBuilder.Configuration.GetSection("Hosting")["Environment"];
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = section
});
By default environment comes from environment variables (see the docs). If you really need to read the environment from config file then the approach with WebApplicationOptions is the way to go. You can improve it a bit by just reading the config, not using WebApplication.CreateBuilder for that:
var cfgBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(...); // setup your config probably call cfgBuilder.SetupConfiguration(args)
var cfg = cfgBuildeR.Build();
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
EnvironmentName = cfg.GetSection("Hosting")["Environment"]
});
Related
I have an asp.net core web api that I created from the wizard - and it's generated code like this:
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthorization();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
...
And if I run it, I get a swagger url, and I can go to
https://localhost:7100/swagger/v1/swagger.yaml
and get a yaml file... that's all good.
The Question
However, I need that yaml file in the CI/CD process for various reasons, so I want to get it dynamically after building, but starting a web server, calling it, and bringing it down has a lot of ways it can go wrong. What I'd like to do is just make a command line option to get the swagger file that never starts something listening on http. so I can just run "dotnet run myApi generate_swagger_yaml >swagger.yaml" Something like:
public static void Main(string[] args)
{
if (args[0] == "generate_swagger_yaml")
{
var yamlFile = ....somehow get the swagger yaml file...
Console.Writeline( yamlfile );
return 0;
}
...
but I can't figure out how to do it.
I would actually recommend not building this into your application directly. Swashbuckle (the library which actually generates the swagger doc at runtime) offers a CLI tool which can take an assembly DLL and spit out the swagger all in one command.
Check it out here: https://github.com/domaindrivendev/Swashbuckle.AspNetCore#swashbuckleaspnetcorecli
This can be used by your CI/CD to output the Swagger/OpenAPI document to a file .
Since my last answer, I've had to experiment more with Swashbuckle and Swagger generation and I've found the actual answer to your original question.
Once you create your builder:
var app = builder.Build();
You need to fetch the IServiceProvider from it and use it to resolve an instance of ISwaggerProvider. With this, you can get the swagger document and render it to Json or Yaml.
var swaggerProvider = app.Services.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerProvider.GetSwagger("v1");
var stringWriter = new StringWriter();
swagger.SerializeAsV3(new OpenApiYamlWriter(stringWriter));
var swaggerYaml = stringWriter.ToString();
Console.WriteLine(swaggerYaml);
Where "v1" should be the default name of your swagger doc which you can specify when setting it up like so:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v7", null);
});
I have an ASP.NET Core 6 Web API and I am adding a key in appsettings.json file and trying to read this as environment variable:
var x = Environment.GetEnvironmentVariable("FILE_STORAGE")?.ToUpper();
The value is returned as null.
I have added below code in the program.cs file:
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
configuration
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
appsettings.json:
{
"FILE_STORAGE": "Azure"
}
I know I can read through IConfiguration (_configuration["FILE_STORAGE"]), but can I read like Environment.GetEnvironmentVariable("FILE_STORAGE")?
Environment.GetEnvironmentVariable reads the Environment Variable, not the value from settings files, such as appsettings.json!
You should use _configuration["FILE_STORAGE"], then it will first try to read your environment variable FILE_STORAGE, if there is no value - read from appsettings.json
I'd recommend
var value = _configuration.GetValue<string>("FILE_STORAGE");
See Configuration in ASP.NET Core
My tests use HttpClient. So I have built a helper method init the client in the constructor of all of my test files.
I initially tried using AppContext.BaseDirectory to get the base directory for the project that I was trying to test. The problem is that its not picking up the correct project name so its loading the appsettings.Development.json from a different project causing the tests to fail. I ended up hard coding the project dir.
I cant figure out how I can force it to load the appsettings.Development.json from the project I want to test.
public static HttpClient GetHttpClient()
{
// projectDir one, loads tests project.
// var projectDir = AppContext.BaseDirectory;
var projectDir = #"C:\Work\Src\Application\bin\Debug\net5.0";
var server = new TestServer(new WebHostBuilder()
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath(projectDir)
.AddJsonFile("appsettings.Development.json")
.Build()
)
.UseStartup<Startup>()
);
return server.CreateClient();
}
The main issue being that it cant load the settings for RabbitMQ which come from the appsettings.Development.json file.
// Load RabbitMQ config.
var serviceClientSettingsConfig = Configuration.GetSection("RabbitMq");
services.Configure<RabbitMqConfiguration>(serviceClientSettingsConfig);
Note: I plan to mock this latter but for now my client needs to see end to end tests working.
In a .net core application, why I am always in "production", even if the ASPNETCORE_ENVIRONMENT is 'development'?
class Program {
static void Main(string[] args) {
var builder = new ConfigurationBuilder();
BuildConfig(builder);
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Build())
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
// ...
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
switch (serviceToUse) { // ... } })
.UseSerilog()
.Build();
IService svc;
// ... got the corresp service...
svc.Run();
}
static void BuildConfig(IConfigurationBuilder builder) {
string env = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
?? "Production";
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true)
.AddUserSecrets<Program>()
.AddEnvironmentVariables();
}
}
However, when I test
I get rather unexpected values in the test setting:
[13:36:55 ERR] Environment.GetEnvironmentVariable('ASPNETCORE_ENVIRONMENT')=Development
[13:36:55 ERR] Environment.GetEnvironmentVariable('DOTNET_ENVIRONMENT')=Development
[13:36:55 ERR] The test setting is 'test value from secrets.json';
having this in projet:
My question is why does it take the Production settings if I am in Development mode?
It’s unclear to me where you are retrieving the configuration using this._config.GetValue<string>("test"). My guess is that you are retrieving it from the IConfiguration injected via DI.
In that case, you should note that the Host.CreateDefaultBuilder will also already come with its own configuration setup which does not really care about what you are doing before with your BuildConfig.
The default host builder will already set up configuration using the files appsettings.json and appsettings.<env>.json (and others) and will register that configuration as the IConfiguration singleton that is being provided to your application. In order to register the environment-specific file, it will use the environment from the IHostEnvironment which is what decides what actual environment your application is running on.
With ASP.NET Core and the web host, it is possible to configure this environment using the ASPNETCORE_ENVIRONMENT environment variable. However, since you are not using ASP.NET Core but just the host builder on its own, this environment variable is not being used. Instead, the default host builder will look for the environment variable DOTNET_ENVIRONMENT.
Alternatively, you can also call hostBuilder.UseEnvironment to set the environment explicitly if you want to take it from a different environment variable.
Finally, when you are setting up your own configuration, I would suggest you to reconfigure your host so that it will use this configuration object itself instead of building its own (which may or may not match your desired setup). You can do that by calling hostBuilder.ConfigureAppConfiguration.
On an asp.net core 2 web api, I want to be able to set the url on which my api will listen (api run as windows service) based on a value in appsettings.json file. I can't find a way to achive it, how can I have acces to an instance of IConfiguration ?
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
return WebHost.CreateDefaultBuilder(args)
.UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.UseUrls({value_from_appsettings})
.Build()
.RunAsService();
In order to gain access to the configuration before you go down the WebHost.CreateDefaultBuilder path, you'll need to build your own IConfiguration instance using ConfigurationBuilder.
Taking the example from your question, you can use something like the following:
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
var appSettingsConfiguration = new ConfigurationBuilder()
.SetBasePath(pathToContentRoot)
.AddJsonFile("appsettings.json")
.Build();
return WebHost.CreateDefaultBuilder(args)
.UseContentRoot(pathToContentRoot)
.UseStartup<Startup>()
.UseUrls(appSettingsConfiguration["Your:Value"])
.Build()
.RunAsService();
This is somewhat explained in the docs where the example uses instead a hosting.json file to set this up. It also makes use of UseConfiguration, which allows you to specify a value for e.g. urls, which will be picked up automatically.