Environment Variables and Configuration in ASP.NET Core Apps
Working with the local environment, configuration and settings of an application is a pretty common development task. Let’s have a look how this can be done with applications targeting the cross-platform .NET Core framework.
If you’ve worked with the full .NET framework so far, you might run into some issues in .NET Core. A lot has been changed in the new cross platform world. Fortunately, the dotnet team is working really hard on closing gaps and to bring a lot of functionality back into the BCLs (base class libraries, System.*).
Application Paths and Informationlink
One common question when writing console or web apps is, what is the directory the app is running in.
Depending on the type of the application and which API is used, asking for the application's base directory can return different results. For console apps, this should be the bin folder, which contains the EXE file or DLL. Depending on the project and build configuration, the path to the bin folder may vary.
For web apps, the base directory is usually the project's directory at debug time, or the directory you published the website into.
Targeting .NET 4.x you'd probably use the AppDomain
object like
var basePath = AppDomain.CurrentDomain.BaseDirectory;
var appName = AppDomain.CurrentDomain.ApplicationIdentity.FullName;
In .NET Core, AppDomain.CurrentDomain
does not exist anymore, but there are replacements for some things.
Version 1.x of ASP.NET Core ships with the Microsoft.Extensions.PlatformAbstractions
package.
The static class instance Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application
gives us the following information:
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
// the app's name and version
var appName = PlatformServices.Default.Application.ApplicationName;
var appVersion = PlatformServices.Default.Application.ApplicationVersion;
// object with some dotnet runtime version information
var runtimeFramework = PlatformServices.Default.Application.RuntimeFramework;
Now, that being said, the package will be removed soon1 and most of the functionality is already available in the BCLs anyways. This means we should not be using Microsoft.Extensions.PlatformAbstractions
anymore!
To replace PlatformAbstractions
, and to write code which hopefully will survive an upgrade to the next platform version, we can use the following:
To get the app's base path, use
System.AppContext.BaseDirectory
AppContext.BaseDirectory
,PlatformServices.Default.Application.ApplicationBasePath
orAppDomain.CurrentDomain.BaseDirectory
should all return the same.Directory.GetCurrentDirectory()
can be used, too. Although, this will return the project's directory instead of the bin folder in debugF5
mode for example.
Web Appslink
Web applications are a little bit special as there are two locations the app needs to work with. The "bin" folder, where compiled views and other dlls will be published into, and the wwwroot
folder, which contains static content and such.
In ASP.NET Core MVC, the IHostingEnvironment
keeps track of those two locations. The interface has two properties, WebRootPath
and ContentRootPath
. ContentRootPath
is actually the bin folder, naming is hard ;)
Important to note that both paths can be configured while building the web host
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWebRoot("notusingwwwroot")
...
The IHostingEnvironment
gets injected to the Startup
and can be used from there on.
Application Name and Versionlink
To get the application's name and version, the suggested solution is to use the assembly information
Assembly.GetEntryAssembly().GetName().Name
- and
Assembly.GetEntryAssembly().GetName().Version.ToString()
For the runtime framework, there is not really a good solution for now.
We can try to use the
System.Runtime.Versioning.FrameworkName
, but to initialize an instance, you'd need the string representation including the version number. To get there,AppContext.TargetFrameworkName
was intended to resolve this I guess, but it returnsnull
targeting .netstandard1.6 or netcoreapp1.1.Alternative could be to use the new attribute
TargetFrameworkAttribute
. This attribute gets added by the compiler and might not exist all the time!
string name = null;
#if !NETCORE
name = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName;
#endif
name = name ?? Assembly.GetEntryAssembly()
.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
if(!string.IsNullOrEmpty(name))
var version = new FrameworkName(name);
But this means we can rewrite the code from above to use:
var basePath = AppContext.BaseDirectory;
var appName = Assembly.GetEntryAssembly().GetName().Name;
var appVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
Hint:
System.AppContext
is also available since .NET 4.6, no#if
needed!
Runtime Informationlink
To figure out on which platform the application is actually running on, at runtime, we can use System.Runtime.InteropServices.RuntimeInformation
.
The following would check for Windows
, OSX and Linux.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// do something.
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// do something else
}
It also gives us convenient information about the OS platform, version and architecture:
// Runtime Info:
Console.WriteLine($@"
FrameworkDescription: {RuntimeInformation.FrameworkDescription}
OSDescription: {RuntimeInformation.OSDescription}
OSArchitecture: {RuntimeInformation.OSArchitecture}
ProcessArchitecture: {RuntimeInformation.ProcessArchitecture}
");
RuntimeInformation is not specific to .NET Core and is available since .NET 4.5.2.
Environment Variables and Configurationlink
When writing applications, the requirement to make it configurable usually comes up pretty quickly. It might need environment specific switches, logger settings or connection strings for example.
Environment variables and files are usually the first thing in mind to configure the application.
Environment.GetEnvironmentVariable
can be used to retrieve variables by name or get a list of all of them.
var path = Environment.GetEnvironmentVariable("PATH");
// get all
var enumerator = Environment.GetEnvironmentVariables().GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine($"{enumerator.Key,5}:{enumerator.Value,100}");
}
The methods are available on any platform and easy to use. But to read and manage the configuration, to fall back to constants in code or another file for example, a lot of custom code would be needed.
What about configuration files?
In .NET 4.x the ConfigurationManager
was responsible to load and bind configuration files like app/web.config
.
But in .NET Core, this doesn't exist anymore. To open and read files manually might not be a big deal, but validating and parsing them would again require a lot of custom code.
The alternative to all of this, and in a much more convenient way, is Microsoft.Extensions.Configuration
.
Taking a look at the the default templates of an ASP.NET Core MVC application, the Startup
of any website usually looks similar to 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();
}
The exact same thing can be used in console applications, too, by installing the necessary NuGet packages!
- Microsoft.Extensions.Configuration
- Microsoft.Extensions.Configuration.EnvironmentVariables
- If you want to load other configuration files, JSON or XML for example, install the corresponding packages...
- Optionally, Microsoft.Extensions.Configuration.Binder
To get access to all system and user environment variables in a console (or web) application, the configuration framework is really handy.
Just create a new ConfigurationBuilder
which will be used to specify what kind of configuration sources to load information from.
In this case, I'm loading the environment variables only:
var configuration = new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
With access to those variables we can do the same as above.
var path = configuration.GetSection("PATH").Value;
// or get all
foreach(var env in configuration.GetChildren())
{
Console.WriteLine($"{env.Key}:{ env.Value}");
}
The benefits from using the configuration framework are for example
- Simple binding to POCOs. Install the
Microsoft.Extensions.Configuration.Binder
NuGet and use one of the binder's methods likeconfiguration.Get<MyConfigurationPoco>();
- Use
Microsoft.Extensions.Options
which also does the binding, but has an injection and reload friendly architecture - Work with prefixed variables and load only those by calling
configuration.GetSection(prefix)
orbuilder.AddEnvironmentVariables(prefix)
in the beginning - Load other configuration sources (JSON, XML,...) and have the framework override the values depending on the order
The configuration framework comes with a lot other nice features like secret stores (supports User Secrets and Azure Key Vault). Secrets are values you usually do not want to see in plain text files, user and password for a database or an API token for example. You can read more about that here.
Did I miss anything, was something wrong or do you have further questions? Let me know in the comments!