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();
        }
    }
}

 

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 the 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();
            });
        }
    }
}

 

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

image23

 

{
  "AllowedHosts": "http://localhost:5000"
}

 

  • Access via square brackets or the GetValue method.

.net core config example 2.1 3

See the 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<string>("AllowedHosts");
        }
}

 

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 the “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 store the SMTP configuration in the appsettings files as below:

image12

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"
  }
}

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

 

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 (:):

image2

 

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<string>("EmailSettings:SMTPEmail");
    }
}

 

  • Use IConfigurationSection with GetSection Method, Followed by GetValue.

image17

 

GetSection will return an IConfigurationSection instance that represents a section of application configuration values (in this case, it’s a JSON object in the 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).

image21

 

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; }
    }
}

 

  • Step 2: Register it with IServiceCollection.

image13

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<EmailSettings>(Configuration.GetSection("EmailSettings"));
    }
}

 

  • 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<EmailSettings> options)
        {
            var emailSettings = options.Value;
        }
    }
}

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 for 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; }
    }
}

  • 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.

image3

public void ConfigureServices(IServiceCollection services)
{
    Configuration.GetSection("EmailSettings").Get<EmailSettings>(options => options.BindNonPublicProperties = true);
    var smtpEmail = EmailSettings.SMTPEmail;
    services.AddControllers();
}

  • 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.

image21

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; }
    }
}

  • Register EmailSettings instead of IOptions<EmailSetting>.

image11

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<EmailSettings>(
        emailConfigSection.Get<EmailSettings>(options => options.BindNonPublicProperties = true)
        );
    }
}

 

  • If you want to apply changes without restarting the app, you need to change the registration code to use AddScoped instead of AddSingleton:

image 20

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<EmailSettings>(sp =>
        {
            return emailConfigSection.Get<EmailSettings>(options => options.BindNonPublicProperties = true);
        });
    }
}

 

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

March 21, 2024 by Dat Le
In the dynamic arena of startup development, an innovative trend is reshaping how entrepreneurs bring their...
Unveiling the Web Development Outsourcing Landscape
February 15, 2024 by Dat Le
In the era where digitalization dictates the tempo of business evolution, understanding the intricate fabric of...
Understanding different types of Angular Modules with practical examples
November 25, 2022 by Trong Ngo
In software development, grouping multiple specific functionalities or features into different modules is the key...
How to apply Test Driven Development with practical examples
June 22, 2022 by Tuan Do
Over the last few years, test-driven development (a.k.a. TDD) has grown in popularity. Many programmers...
Roll to Top

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

Subscribe