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!
Frequently Asked Questions (FAQs)
What are .NET environment variables and why are they important in application configuration?

.NET environment variables are key-value pairs that define various settings needed to run .NET applications in different environments like development, staging, or production. They are crucial because they allow developers to configure applications to behave differently based on the environment, ensuring proper testing before deployment and facilitating a smoother development process.

How does .NET Core support environment configurations and what are the built-in environments?

.NET Core supports environment configurations through the use of environment variables and file configuration providers (like appsettings.json files). It provides a static class Environment in the system namespace to access these variables. The built-in environments in .NET Core are ‘Development’, ‘Staging’, and ‘Production’, and they can be managed using the ASPNETCORE_ENVIRONMENT variable and corresponding appsettings files (e.g., appsettings.Development.json).

What are some common practices for managing environment settings in .NET Core applications?

Common practices include isolating configurations by separating classes, using static properties with private setters for unchanged configurations, and registering configuration classes instead of using the Options pattern. This ensures that configurations are well-organized, unchangeable during runtime, and easily accessible throughout the application.

How do you access configuration values in a .NET Core application using environment variables?

Configuration values in a .NET Core application can be accessed using the IConfiguration service, which allows retrieval of configuration values from various sources like JSON files and environment variables. Values can be accessed directly using the path in the configuration file, or through strongly typed classes by using the Options pattern with IOptions<T> interface.

What are some best practices for storing and loading environment settings in .NET Core applications?

Best practices for storing and loading environment settings include keeping configuration values in appsettings files, using the IConfiguration service for accessing these values, and utilizing the Options pattern for strongly typed access. Developers should also consider isolating configurations, making setters private, and using dependency injection to register and access configurations, ensuring configurations are secure, maintainable, and easily testable.

Up Next

Big Data Technologies Transforming Software Development
July 05, 2024 by Dat Le
In the rapidly evolving world of software development, Big Data stands out as a transformative force....
June 27, 2024 by Dat Le
In today's rapidly evolving digital landscape, secure coding practices are paramount to safeguarding applications from a...
June 20, 2024 by Dat Le
In the rapidly evolving digital landscape, the role of User Interface (UI) and User Experience (UX)...
Leveraging UX Design Principles in Software Development
June 17, 2024 by Dat Le
In the dynamic world of software development, one element has emerged as crucial to success: User...
Roll to Top

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

Subscribe