Use appsettings to drive environment specific settings such as UseUrls - c#

When I'm developing locally using VS Code, I'm going to use port 3000 because I'm a hipster. The non-hipsters want it on port 8080 on the server. That's cool, we got this. Microsoft docs give me the following example:
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("hosting.json", optional: true)
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseKestrel()
.Configure(app =>
{
app.Run(async (context) => await context.Response.WriteAsync("Hi!"));
})
.Build();
host.Run();
}
I don't want to use hosting.json. Why would I want that? I have this appsettings.{environment}.json file for exactly this situation. Sweet, I'll just paste that bad boy in
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddCommandLine(args)
.Build();
What the compiler error? env does not exist in the current context. It only exists in the Startup.cs file--which is not called on startup, but called from the startup file, Program.cs, with sorcery.
So, how do I solve this problem? How can I store my environment-specific hosting settings in my environment-specific appsettings.json, and subsequently use it in while building my environment-specific web host via the WebHostBuilder in Program.cs?

This is possible. Expanding on the answer given here, by creating the WebHostBuilder and ConfigurationBuilder in the Program.cs, it is possible to have access to the host environment and then configure the host URL and port in environment-specific appsettings files.
Assuming an appsettings.json and an apppsettings.Development.json file each with the following:
"hostUrl": "http://*:<port number here>"
Modify the Main with the following:
public static void Main(string[] args)
{
var host = new WebHostBuilder();
var env = host.GetSetting("environment");
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env}.json", optional: true)
.AddEnvironmentVariables();
var configuration = builder.Build();
host.UseKestrel()
.UseUrls(configuration["hostUrl"])
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build()
.Run();
}
Using this code, the Startup.cs will still need to declare its own ConfigurationBuilder in order to publicly expose its Configuration property.

Or, starting from .NET Core 2.0, when creating the default IWebHostBuilder that will build the IWebHost implementation, you can use
return WebHost.CreateDefaultBuilder(args)
.UseConfiguration(configuration)
.ConfigureAppConfiguration((builderContext, config) =>
{
// nb: here you may clear configuration(s) loaded so far
var env = builderContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true);
})
.UseStartup<Startup>();

Related

.NET Core Console App - not setting appropriate appsettings based env variable at runtime

I am working on .NET Core 5 Console App and I was trying load appsettings.[environment].json at runtime based on the "ASPNETCORE_ENVIRONMENT = Development" which I setup in debug under project properties.
In the BuildConfig method I can see that
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
is returning the "Development" text correctly and also loading the 2 file (appsettings.json, appsettings.development.json).
but when I pass the config to different class via constructor and then inspecting config in that class I seeing the (appsettings.json, appsettings.production.json) file not the development file why?
I don't have appsettings.production.json file in my project yet.
static void Main(string[] args)
{
try
{
//setting for SeriLog
var builder = new ConfigurationBuilder();
BuildConfig(builder);
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.ReadFrom.Configuration(builder.Build())
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File(#"Log\Log.txt")
.CreateLogger();
Log.Logger.Information("Application Starting");
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddTransient<IRIUProcess, RIUProcess>();
})
.UseSerilog()
.Build();
var svc = ActivatorUtilities.CreateInstance<RIUProcess>(host.Services);
svc.Run(args);
}
catch (Exception ex)
{
Log.Logger.Error(ex.ToString());
}
}
static void BuildConfig(IConfigurationBuilder builder)
{
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
}
Configuration builder return self instance configurated like entityframework core
//setting for SeriLog
var builder = new ConfigurationBuilder();
BuildConfig(builder);
should be
//setting for SeriLog
var builder = new ConfigurationBuilder();
builder = BuildConfig(builder);
Without that, your configuration builder remain unchanged.
Adapt also the BuildConfig method for that.
Then you can remove the CreateDefaultBuilder (with use default AspNetCore Configuration) and directly use your instance of configuration :
new HostBuilder().ConfigureHostConfiguration(a => a.AddConfiguration(builder.Build()))
https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.hostbuilder.configurehostconfiguration?view=dotnet-plat-ext-5.0
You will have then your host with the same configuration for SeriLog and your host

IIS Express messing with HTTP Status on exception

I have an ASP.NET Core 2.1 REST API app. I want to be able to run it as a windows service as well as on IIS so I have created a startup project for each and they use a shared Core library for the "guts" of the app. I am using CORS which I think may be coming into play.
The Service project runs normally under dotnet.exe from visual studio and the IIS project runs using IIS Express from visual studio. Their startup code differs very little and is at the end of this post.
Like most rest API's, I want to rely on Http status codes to convey information. On one endpoint, I want to use the RequestSizeLimitAttribute to return a 413 status if the request exceeds a certain limit.
The windows service project behaves correctly, returning the correct status codes to the client. But when I run as IIS Express, the client first sends an OPTIONS request and gets a 204 back (as expected), but then the response after that is always a 502-Bad Gateway.
Does IIS mess with things if an exception happens because of the RequestSizeLimitAttribute?
//IIS Express Project Startup Code
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IConfiguration config => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
AND
//Windows Service startup code
public static IConfiguration config => new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.Development.json", optional: true)
.Build();
public static void Main(string[] args)
{
RunWebHost(args, logger);
}
public static void RunWebHost(string[] args)
{
var isService = true;
if (Debugger.IsAttached || args.Contains("--console"))
{
isService = false;
}
var pathToContentRoot = Directory.GetCurrentDirectory();
if (isService)
{
var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
pathToContentRoot = Path.GetDirectoryName(pathToExe);
}
var webHostArgs = args.Where(arg => arg != "--console").ToArray();
var host = WebHost.CreateDefaultBuilder(webHostArgs)
.UseContentRoot(pathToContentRoot)
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
if (isService)
{
host.RunAsService();
}
else
{
host.Run();
}
}

Start logging before webhost in ASP.NET Core 2

I want to start logging before webhost, so startup errors are logged. So I followed Serilog's recommended init order: 1) configuration, 2) logging, 3) webhost. I'm not using CreateDefaultBuilder().
So my Program.cs has:
// setup config
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional:false, reloadOnChange:true)
.AddJsonFile($"appsettings.{envName}.json", optional:true, reloadOnChange:true)
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
// setup Serilog logging
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // <<< uses config from above
.CreateLogger()
// setup webhost
new WebHostBuilder()
.UseConfiguration(configuration) // <<< uses config from above
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseSerilog()
.Build()
.Run();
This works, but changes in appsettings.json are not detected even though I specified reloadOnChange:true.
However when I use ConfigureAppConfiguration() then changes are detected:
new WebHostBuilder()
.ConfigureAppConfiguration((context, config) => {
// .. duplicate config here
})
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseSerilog()
.Build()
.Run();
But that means duplication and possibly unforseen problems - i.e. this is a hack. How do I solve this?
UPDATE
As per #StephenZeng's answer, UseConfiguration won't help me here, so I suppose I need to use ConfigureAppConfiguration. But the problem remains - how do I init my config first, then use it multiple times without redefining it?
From the document it is by design:
"UseConfiguration only copies keys from the provided IConfiguration to the host builder configuration. Therefore, setting reloadOnChange: true for JSON, INI, and XML settings files has no effect."
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/web-host?view=aspnetcore-2.1&tabs=aspnetcore2x
I've run into the same issue, and I created a method with the responsibility to configure the builder. I then call the method from both Main and when I build the web host. Something like this:
public static class Program
{
public static int Main()
{
var configuration = new ConfigurationBuilder()
.AddConfiguration()
.Build();
// setup Serilog logging
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
// Code removed for brevity...
}
private static IWebHost BuildWebHost() =>
WebHost.CreateDefaultBuilder()
.UseSerilog()
.UseStartup<Startup>()
.ConfigureAppConfiguration((hostingContext, config) =>
{
// Clear default configuration
config.Sources.Clear();
// Replace with our own configuration
config.AddConfiguration();
})
.Build();
private static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder self) =>
self
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional:false, reloadOnChange:true)
.AddJsonFile($"appsettings.{envName}.json", optional:true, reloadOnChange:true)
.AddEnvironmentVariables();
}
The solution is to avoid UseConfiguration and use ConfigureAppConfiguration instead:
// setup config
// ..as before
// ..this is done only once
// setup Serilog logging
// ...as before
// setup webhost
new WebHostBuilder()
.ConfigureAppConfiguration((context, config) => {
config.AddConfiguration(configuration); // <<< uses config from above
})
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseSerilog()
.Build()
.Run();

Publish a .NET Core Console application with right transformation settings

I'm quite new to .NET Core application, and I was wondering if there's a simple way to deploy the right application.json based on the profile.
In .NET Standard application it was quite simple since we have web.config transformation/Slowcheeta but It doesn't seems to work with .NET Core Console app.
I've also read online that with ASP.NET Core application, we can use
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) <--this
.AddEnvironmentVariables();
Configuration = builder.Build();
But on .NET Console app I don't have an env.EnvironmentName
I've also read that someone uses a #IF-#ENDIF statement to switch but this requires to put hands on code if a new publish profile is created
Any suggestion_
To improve on the answer I have upvoted by #MarkusDresch, I wonder if OP says this did not work still because they do not have something setting up the correct environment. You need to have this being set correctly such as from an env variable of hostsetting.json:
var host = new HostBuilder()
.ConfigureHostConfiguration(builder =>
{
builder.AddJsonFile("hostsettings.json", optional: true);
})
.ConfigureAppConfiguration((hostContext, builder) =>
{
builder.AddJsonFile("appsettings.json");
builder.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
})
With this example the hostsettings.json would have something like:
{
"environment": "Development",
}
You can use Microsoft.Extensions.Configuration.Json (https://www.nuget.org/packages/Microsoft.Extensions.Configuration.Json/)
The equivalent code would look something like this:
var builder = new HostBuilder();
builder.ConfigureAppConfiguration((ctx, b) => b
.AddJsonFile($"appsettings.{ctx.HostingEnvironment.EnvironmentName}.json"));
As far as i know this is only available for .NET Core 2.1. HostBuilder works pretty similar to ASP.NET WebHostBuilder and is available in NuGet package Microsoft.Extensions.Hosting.
You can try to do it like that:
public static void Main(string[] args)
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true)
.AddJsonFile($"appsettings.{environmentName}.json", true, true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Please remember about adding Microsoft.Extensions.Configuration.Json nuget package.
There is also a way to get ASPNETCORE_ENVIRONMENT like this:
new ConfigurationBuilder().AddEnvironmentVariables()
and finding that setting there.
And of course, you need to copy those configs to the output folder so that why will be accessible.
You do have access to Environment. That should be enough:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();

Unable to set Enviroment Variable when publishing dotnet core 2.0 project

I am having problems with running my dotnet core 2.0 API (on Ubuntu 16.04 using .NET Core CLI) using the correct Environment settings.
I added into the "~/.bash_profile" the following:
ASPNETCORE_ENVIRONMENT=staging
When i run the project (using dotnet run) it seems to work correctly, It detects that it should be on staging.
However, when i compile using "dotnet publish"
and try to run it using:
nohup dotnet API.dll --urls "http://*:1337" &> /var/log/dotnetlogs
it does not seem to detect the environment variable and always defaults to using the "Production" environment.
Startup.cs:
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();
}
Program.cs
public static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddCommandLine(args)
.Build();
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(config)
.UseStartup<Startup>()
.Build();
host.Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
Any idea how i can get this working?
Are you missing the export? The docs also say that Linux is case-sensitive, so you'll probably want to use Staging.
export ASPNETCORE_ENVIRONMENT=Staging

Categories