How to use Environment Variables in ASP.NET Core

In this article, I want to detail on the use of Environmental Variables and different ways of accessing them in an ASP.NET Core application

Environmental Variables don’t need an introduction – they exist almost everywhere. These are identifiers whose values are set outside of the application and configured inside the runtime where the application runs.

How are Environment Variables useful?

Although Environment Variables have a broad range of use cases and definitions, let’s stick on to our context of inside an ASP.NET Core application: We can vary an application behavior or running configuration without having to actually modify the binaries or the physical files.

For example, we might want to point our application to a different database without modifying the setup directly. In other cases, we maintain our connection Strings inside the appsettings.json file.

But this kind of approach may not work in places where the binaries are not accessible once deployed in an environment (such as Cloud or a Secured Client environment).

In such cases we can place our database connectionString inside an Environment Variable and let the application read the connectionString from the Environment Variables while connecting to the database. This way, we can simply change the connectionString in our Environment Variables and the application changes its behaviour accordingly.


How to Environment Variables them in ASP.NET Core?

In ASP.NET Core, we can access environment variables in two ways:

  1. Using the Environment class which is from the System namespace and
  2. via IConfiguration which has EnvironmentVariablesProvider added

Accessing Environment Variables inside the Startup

Now that we are aware of how we can access Environment variables inside our application in the above ways after once the application has started, let’s talk about the scenarios where we might need to access environment variables even before the application has started: In our previous case of accessing a connectionString from the environment variables and then configuring the database is a classical example of accessing an environment variable even before an application has started.

Let’s check whether we can access our connectionString variable inside our Startup class at the two obvious places: ConfigureServices() and Configure() methods.

First, we’ll add a couple of environment variables to use in our application: we can do so by adding them inside the launchSettings.json file under the Properties folder. When we create an application with the dotnetcore CLI (or even via VisualStudio) we have this json file created, which specifies how the application should run when given a command to run (e.g: dotnet run). You have two profiles here: IIS Express and a Self-Hosted approach.

Adding Variables in Runtime Profiles

Whatever the profile you choose, you have the “environmentVariables” object property, where we can add our environment variables in the form of key-value pairs. Keep in mind that an environment variable value can only be a “string”, so its on us to convert into any other non-string value inside our application when needed.

“dotnetcore runtime contains a default environment variable ASPNETCORE_ENVIRONMENT which indicates the kind of environment the application is running in.”

"EnvReadersApi": {
    "commandName": "Project",
    "launchBrowser": true,
    "launchUrl": "weatherforecast",
    "applicationUrl": "https://localhost:5001;http://localhost:5000",
    "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "ConnectionString": "your_connectionstring_comes_here",
        "ReaderType": "Admin",
        "IsAuthEnabled": "true",
        "IsSslEnabled": "true",
        "AdditionalSettings":"somesettings.json"
    }
}

This is how my Self-Hosting profile looked after I’ve added a few environmentVariables to use in my application later on.

Inside my ConfigureServices() method and Configure() method, I add the below Console.WriteLine() statements to see if i’m able to access the connectionString environmentVariable in my Startup.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        Console.WriteLine("In ConfigureServices via Environment class: " + Environment.GetEnvironmentVariable("ConnectionString"));
        Console.WriteLine("In ConfigureServices via IConfiguration: " + Configuration["ConnectionString"]);
        
        ...
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        Console.WriteLine("In Configure: " + Environment.GetEnvironmentVariable("ConnectionString"));
        Console.WriteLine("In Configure via IConfiguration: " + Configuration["ConnectionString"]);

        ...
    }
}

Observe that to access an environmentVariable using the System.Environment class we use:

Environment.GetEnvironmentVariable("ENVIRONMENT_VARIABLE_KEY");

whereas to access via IConfiguration we can simply access as:

Configuration["ENVIRONMENT_VARIABLE_KEY"]

The output is:

:EnvReadersApi>dotnet run
In ConfigureServices via Environment class: your_connectionstring_comes_here
In ConfigureServices via IConfiguration: your_connectionstring_comes_here
In Configure: your_connectionstring_comes_here
In Configure via IConfiguration: your_connectionstring_comes_here

info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: EnvReadersApi

The output confirms that we DO have access to the environmentVariables in both the methods of the Startup class.


Accessing while Building the Host

We don’t generally think about this scenario, but remember How we configured AWS Parameter Store to load Configuration into our application?

We added the SystemsManager provider to configuration like this:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        }).ConfigureAppConfiguration((builder) =>
                {
                    builder.AddSystemsManager(
                        path: "/myparams", 
                        awsOptions: new AWSOptions
                        {
                            Region = RegionEndpoint.USWest2
                        }, 
                        optional: false, 
                        reloadAfter: TimeSpan.FromMinutes(10));
                });;

What if we want to externalize this “/myparams” that our application looks up to fetch the configuration inside the AWS Parameter Store into Environmental Variables? There’s no way we can access the IConfiguration variable at this point, because the Configuration has not been built yet. In this case we have two options:

1 – During the Host building – we can include our SystemsManager provider and access the environmentVariable using Systems.Environment:

.ConfigureAppConfiguration((builder) =>
{
    var paramsPath = Environment.GetEnvironmentVariable("ParamStorePath");
    builder.AddSystemsManager(paramsPath, awsOptions: new AWSOptions
    {
        Region = RegionEndpoint.USWest2
    },
    optional: false);
});

2 – Or we can “rebuild” our Configuration object and add the new Provider to it, while accessing the already added providers from the IConfiguration injected.

public Startup(IConfiguration configuration)
{
    var builder = new ConfigurationBuilder();
    builder.SetBasePath(Directory.GetCurrentDirectory());
    builder.AddJsonFile("appsettings.json", optional: false);
    builder.AddEnvironmentVariables();
    builder.AddSystemsManager(
        path: configuration["ParamStorePath"],
            awsOptions: new AWSOptions
            {
                Region = RegionEndpoint.USWest2
            },
        optional: false,
        reloadAfter: TimeSpan.FromMinutes(10));
    
    Configuration = builder.Build();
}

How is this actually working?

If we observe the IConfiguration object that is being injected into the Startup constructor we can see that the IConfiguration.Providers already has five providers under it.

wp-content/uploads/2022/05/config_providers.png

Where are they getting added? In a previous article about Building the Host during App Startup, we learned that by default AppConfiguration adds a few default providers based on:

  • The appsettings files (appsettings.json and appsettings.{env}.json) (JsonConfigurationProvider)
  • The environmental variables (EnvironmentalVariablesConfigurationProvider)
  • Command-Line arguments (CommandLineConfigurationProvider)

So by the time the Startup class is invoked with an IConfiguration object injected, we already have access to the environment variables by means of the IConfiguration instance and its EnvironmentVariablesProvider. But at this point, we can’t add a new provider to the already “built” IConfiguration instance. So we either have to add this provider at the time when the AppConfiguration is “being built”, ie. the ConfigureAppConfiguration() on the IHostBuilder or we can “rebuild with a new provider added” inside the Startup class and pass it to other components.

In either the case, when we access this IConfiguration (later on, it shows up six providers instead of five.

wp-content/uploads/2022/05/config_providers_new.png

System.Environment or IConfiguration for environmentVariables?

Well, it depends on which context you’re trying to use. System.Environment is a native class from C# which should likely work at all places. However, from my personal experience when troubleshooting my APIs deployed in Cloud environments like AWS, I’ve observed a few scenarios where the System.Environment calls weren’t effective.

This made me realize that IConfiguration has a better potential for accessing environmentVariables in my application than relying on System.Environment at all times (just my opinion).

Buy Me A Coffee

Found this article helpful? Please consider supporting!

When I were to use environmentVariables inside the Startup for adding providers or for any other reasons, I’d prefer to “rebuild” my Configuration with the environment values I can access from the injected IConfiguration class. This might sound a bit redundant, but this just gets the job done.

Have any other opinions about it? I’d love to listen!

Reference – https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

One comment

  1. Thanks for the nice article. I’m wondering if we can store the variable values as System Environment variables (through the edit environment variables screen in Windows)
    I have tried reading a variable from ConfigureServices() method and it worked. However, I don’t know if this is a good practice (Especially, when deployed to IIS and storing values as environment variables in the Windows Server)
    Also, if we add values to launchSettings.json, someone who has access to the code repo can still see the file and see stored values. My main concern is hiding db connection strings (Without using services such as AWS parameter store/ Azure keyVault etc)
    What is your idea about this approach?

Leave a Reply

Your email address will not be published. Required fields are marked *