Managing Code for Different Environments

This document will go through how to maintain different versions of code for different environments. For example, having a test login endpoint on DEV and UAT, but disabling the endpoint for PROD. We will go through how to set things up so that there won’t be a need to modify code when building/deploying for different environments.

There are 2 different methods:

  • Preprocessor Directives
  • IHostEnvironment Extensions

Prerequisites

Before reading this document, make sure you understand the following:

Sample Project

Refer to this sample project to see things in action.

Preprocessor Directives

Build Configurations

Firstly, create build configurations for every environment:

  • DEV
  • UAT/QA
  • PROD (can use the default Release)
  • and any other environments as required

Assuming that you’ve added Dev and UAT build configurations, you should see the following in your csproj file:

<Configurations>Debug;Release;UAT;Dev</Configurations>

Defining Preprocessor Directive Constants

Next, define the constants that you want to use for the different environments in the csproj file.

For example:

    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
        <DefineConstants>IS_PROD</DefineConstants>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='UAT|AnyCPU'">
        <DefineConstants>IS_UAT</DefineConstants>
    </PropertyGroup>
    <!-- Set IS_DEV for local and DEV environments -->
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
		<DefineConstants>IS_DEV</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev|AnyCPU'">
		<DefineConstants>IS_DEV</DefineConstants>
	</PropertyGroup>

Using Preprocessor Directives

After the setup, you can start using the preprocessor directives to write code that will be compiled for different platforms.

For example:

#if IS_PROD
  RunProdLoginCode();
#elif IS_UAT
  RunUatLoginCode();
#else
  RunDevLoginCode();
#endif

Function implementations can also be wrapped in preprocessor directives to prevent the functions from being used outside of their intended environments.

Building and Publishing

For building and publishing, use the build or publish commands with the appropriate build configuration that is required.

For example to build for UAT:

dotnet build -c UAT

Once this has been set up correctly, CICD pipelines can also make use of this by using the appropriate build configurations in the commands.

IHostEnvironment

Using IHostEnvironment

IHostEnvironment extensions are available in .NET by default. Use dependency injection to add IHostEnvironment to the class as required.

Once done, you can use the extensions to manage environment-specific behaviours as required.

For example:

if (HostEnvironmentEnvExtensions.IsDevelopment(_hostEnvironment))
{
    // run dev code
}
else {
    // run non-dev code
}

Changing the host environment

We can change the host environment at runtime by adding the environment flag when using dotnet run.

For example to set the environment as Staging:

dotnet run --environment Staging

Refer to the sample project for more information.