Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!

How to Configure .NET Core Environments with Practical Examples

 

This blog post will share our experiences at Enlab Software about managing the configuration of environments in .NET core /ASP.NET core applications.

 

What Is “Environment” in Software Development?

An environment in software development is the collection of hardware, software, and tools needed for building and running the software. Commonly, a popular setup can include development, staging, and production environments.

Using multiple environments ensures that your software is properly tested before deployment and release to users. Therefore, you should remember to always keep your app creation flow organized to maximize the benefit of the Environment.

 

How Environments Affect the Coding Process

The Environments manage the setting for each environment (app configurations, 3rd party packages, service configurations, or a feature that needs to be enabled/disabled on each environment, etc.). Without a thorough configuration, the application may not function well in each environment and cause developers to rewrite codes.

Therefore, it is crucial to check and update your environment configuration correspondingly before you deploy it to the environment. Fortunately, the .NET core does support detecting the environment and loading the correct configuration by codes.

 

Configure .NET Core Environments

How .NET Core Support Environment Configurations

.NET core uses environment variables to indicate in which environment the application is running and to allow the app to be configured appropriately. They provide a static class Environment in the system namespace to access the environment variables.
Example: I used the GetEnvironmentVariable method to get the value of the “windir” environment variable as below:



using System;
namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var environment = Environment.GetEnvironmentVariable("windir");
 
            Console.WriteLine("windir path: " + environment);
 
            Console.ReadKey();
        }
    }
}

Code language: C# (cs)

 

The result:


environment

 

You will see the value of the “windir” environment variable via “C:\WINDOWS”.


.net core config example 2.1 2

 

When using Visual Studio, the environment variable can be specified during the development process in your project’s debug profiles. Below is my example:

 

.net core config example 2.1 3


How about configuration?

Configuration in .NET core is performed using one or more configuration providers, which will read configuration data from key-value pairs using a variety of configuration sources, such as:

  • File configuration (INI, JSON, and XML files)
  • Command-line arguments
  • Environment variables
  • Key per file
  • Directory files
  • In-memory

 

Built-In Environments in .NET Core/ASP.NET Core

Example: In the ASP.NET core application, the “ASPNETCORE_ENVIRONMENT” variable and file configuration provider (appsettings.json file) is used by default.

When checking the ASP.NET core project template, you should see that the “ASPNETCORE_ENVIRONMENT” variable with the value “Development” is set by default.

 

.net core config example 2.1 3


Those are three values that are used by convention: Development, Staging, and Production.

Furthermore, we have two default files with appsettings files (appsettings.json and appsettings.Development.json).


.net core config example 2.1 3

 

Besides, in the .NET core, you can use the IHostingEnvironment service (Changed to IWebHostEnvironment from .NET core 3) to work with environments and the IConfiguration to work with the appsettings files.

Those services are already provided by the ASP.NET hosting layer and can be injected via Dependency Injection. You can see this in the following ASP.NET project template:

 

.net core config example 2.1 3

See source code below:


namespace WebApplication
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
 
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }
 
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
 
            app.UseHttpsRedirection();
 
            app.UseRouting();
 
            app.UseAuthorization();
 
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
Code language: C# (cs)

 

To get the configuration in appsettings files you can use the IConfiguration service instance as follows:

  • AllowedHosts configuration in appsettings.json and appsettings.Development.json files

{
  "AllowedHosts": "*"
}
Code language: C# (cs)

and


{
  "AllowedHosts": "http://localhost:5000"
}
Code language: C# (cs)
  • Access via square brackets or GetValue method

 

.net core config example 2.1 3

See source code below (for the red highlighted part):


public class Startup 
{
    public IConfiguration Configuration { get; }
    
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        var allowedHosts = configuration["AllowedHosts"];
        var allowedHosts1 = configuration.GetValue("AllowedHosts");
    }
}
Code language: C# (cs)

 

As can be seen from above, the AllowedHosts config appears in both appsettings.json and appsettings.Development.json files. However, the value in runtime is in appsettings.Development.json because the app is running in the “Development” environment. Subsequently, asp.net core will replace the value in the appsettings.json with the value in appsettings.{Environment}.json (with the Environment being the value of “ASPNETCORE_ENVIRONMENT” environment variable)

By changing the “ASPNETCORE_ENVIRONMENT” variable value, you can change the configuration value to the appsettings file accordingly.

  • If you are using Visual Studio:

.net core config example 2.1 3

 

You can also run this PowerShell command:
$Env:ASPNETCORE_ENVIRONMENT = "Your environment name"

It is possible to add more appsettings.{Environment}.json for your Environment if needed. See my implementation for “Staging” environment as follows:


.net core config example 2.2 5


Best Practice to Store and Load Environment Settings

Several Ways to Manage the Environment Settings in .net Core (with Code Example)

Let's try something advanced! Suppose that your application needs to send emails using SMTP and there are different configurations for each environment. What you can do is to store the SMTP configuration in appsettings files as below:

appsettings.Staging.json file:


{
  "ConnectionString": "Server=192.168.2.231;Integrated Security=True;Database=Staging_DB;",
  "EmailSettings": {
    "SMTPLogin": "staging-email@gmail.com",
    "SMTPPassWord": "my-password",
    "SMTPPort": "587",
    "SMTPHostname": "smtp.gmail.com"
  }
}
Code language: C# (cs)

 


{
  "ConnectionString": "Server=localhost;Integrated Security=True;Database=Dev_DB;",
  "EmailSettings": {
    "SMTPEmail": "dev-email@gmail.com",
    "SMTPPassWord": "my-password",
    "SMTPPort": "587",
    "SMTPHostname": "smtp.gmail.com"
  }
}
Code language: C# (cs)

Now, how can we retrieve the configuration value at runtime?

As I mentioned above, you can use the IConfiguration service since it allows a way to retrieve configuration values, such as:

  • Get It Right Away

Basically, the appsettings.json file accepts any configuration in a JSON format and supports nested objects (such as EmailSettings above). To get the value directly, you can use the path that is separated by a colon (:):


public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[] 
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
 
    public WeatherForecastController(IConfiguration configuration)
    {
        var smtpEmail = configuration.GetValue("EmailSettings:SMTPEmail");
    }
}
Code language: C# (cs)

 

  • Use IConfigurationSection with GetSection Method, Followed by GetValue

public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
      "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
 
    public WeatherForecastController(IConfiguration configuration)
    {
       var emailSettingsSection = configuration.GetSection("EmailSettings");
       var smtpEmail = emailSettingsSection.GetValue("SMTPEmail");
    }
}
Code language: C# (cs)

 

GetSection will return an IConfigurationSection instance that represents a section of application configuration values (in this case, it’s JSON object in appsettings file). As can be seen from above, it has returned the “EmailSettings” configuration.

 

  • Use Defined Class

Microsoft team also introduced the Options pattern, which allows us to have strongly typed options. Once configured, they can inject the options into your services. Here is how you can use it:

  • Step 1: define a strongly typed class to hold your configuration (EmailSettings in this case)

namespace WebApplication.Models
{
    public class EmailSettings
    {
        public string SMTPEmail { get; set; }
        public string SMTPPassword { get; set; }
        public int SMTPPort { get; set; }
        public string SMTPHostname { get; set; }
    }
}
Code language: C# (cs)

 

  • Step 2: register it with IServiceCollection

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.Configure(Configuration.GetSection("EmailSettings"));
    }
}
Code language: C# (cs)

 

  • Final step: inject the options into your services, using the IOptions<T> interface with T being your defined class:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using WebApplication.Models;
using Microsoft.Extensions.Options;
 
namespace WebApplication.Controllers.old1
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        public WeatherForecastController(IOptions options)
        {
            var emailSettings = options.Value;
        }
    }
}
Code language: C# (cs)

 

Below is my result after debugging:

.net core config example 3.1 1

 

Our Best Configuration Tips

From the above examples, using a defined class stands out as the most effective to us. Besides, it is highly recommended in MS documentation. Reflecting on my experience working with .NET core, I would like to share some configuration tips and trips that can be useful for you:

  • Isolate your configuration by separating classes: the scenarios (classes) that depend on configuration settings are only affected by the configuration settings they use.
  • Make your defined class setters private: As the configurations are the read-only values and do not change during the application runtime, the private setters make sure that the configuration values can't be changed for any reason.
  • Use static properties for the configurations that you know will remain unchanged, otherwise, you will need to reset the app.
    • Static properties with private setters

namespace WebApplication.Models
{
    public class EmailSettings
    {
        public static string SMTPEmail { get; private set; }
        public static string SMTPPassword { get; private set; }
        public static int SMTPPort { get; private set; }
        public static string SMTPHostname { get; private set; }
    }
}

Code language: C# (cs)
  • After that, use the GET method with the option “BindNonPublicProperties = true” to set the values and you’’ be able to access your configuration values everywhere.

public void ConfigureServices(IServiceCollection services)
{
    Configuration.GetSection("EmailSettings").Get(options => options.BindNonPublicProperties = true);
    var smtpEmail = EmailSettings.SMTPEmail;
    services.AddControllers();
}
Code language: C# (cs)

 

  • Register your configuration class instead of using the Options pattern: Options pattern is a good choice when your configuration is unstable and should be recomputed on every request, or you want to manage the change options for notifications. However, it is less useful in my opinion because you won’t need it in most cases. If you must use it, please add the comment below:
    • Revert the static properties on the EmailSettings class but keep the private setters.

namespace WebApplication.Models
{
    public class EmailSettings
    {
        public string SMTPEmail { get; private set; }
        public string SMTPPassword { get; private set; }
        public int SMTPPort { get; private set; }
        public string SMTPHostname { get; private set; }
    }
}
Code language: C# (cs)
  • Register EmailSettings instead of IOptions<EmailSetting>

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        var emailConfigSection = Configuration.GetSection("EmailSettings");
        services.AddControllers();
        services.AddSingleton(
        emailConfigSection.Get(options => options.BindNonPublicProperties = true)
        );
    }
}
Code language: C# (cs)
  • If you want to apply changes without restarting the app, you need to change the registration code to use AddScoped instead of AddSingleton:

public class Startup
{
    public IConfiguration Configuration { get; }
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        var emailConfigSection = Configuration.GetSection("EmailSettings");
        services.AddControllers();
        
        // Register for EmailSettings class
        services.AddScoped(sp =>
        {
            return emailConfigSection.Get(options => options.BindNonPublicProperties = true);
        });
    }
}
Code language: C# (cs)

 

Conclusion

We hope our sharing can help you explore better the .NET core Environment configuration as it has many perks that are yet to be discovered. In our next blog about this topic, we will analyze “how to secure sensitive data in configuration” and provide you with hands-on examples. Stay tuned!

 

CTA Enlab Software

 

References

About the author

Tan Nguyen

Hi, my name is Tan! I am currently working as a Software Engineer at Enlab Software and I mostly focus on Software Architectures, Development, and Software Re-factoring. I love coding and learning new technologies so I'm excited to share my knowledge with you!

Up Next

September 07,2021 by Chuong Tran
In Object-Oriented Programming (OOP), SOLID is one of the most popular sets of design principles...
Domain-Driven Design in ASP.NET Core applications
May 31,2021 by Loc Nguyen
The technology industry has been thriving for the second half of the last century. It's...
How to create real time chat applications using WebSocket APIs in API Gateway
March 26,2021 by Trong Pham
My experience developing real-time messaging applications leveraging AWS services inspires me to share with the...
How to build and deploy a 3-tier architecture application with C#
March 11,2021 by Uyen Luu
Hi, back to the architecture patterns, in the last article I explained what the 3-layer...
Roll to Top

Can we send you our next blog posts? Only the best stuffs.

Subscribe