Card image cap

Accessing Environment Variables in ASP.NET Core - Inside Host and Startup

ASP.NET Core  • Posted 12 days ago

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. Although Environment Variables have a broad range of usecases and definitions, let's stick on to our context of inside an ASP.NET Core application: We can vary an application behaviour 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 connectionStrings 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.

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.

data/Admin/2020/7/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.

data/Admin/2020/7/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). And 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!

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept