Categories
Services

Environment Variables in Net Core 3 with React and Docker

This code replaces React Environment Variables with docker variables at runtime (first run only!). This makes it possible to present different environment vars at startup instead of at build time. This is especially useful when using docker containers. You can make one docker image for different environments.

Development mode : You can still use .env files (as described in react documentation). Nothing changed here.

Production or other build modes: The react environment vars will be replaced in the static html file at startup.

This approach has as advantage that you do not need to inject vars on every single page request (dynamic content). This solution changes the variables only once on startup and keeps the file static throughout the program runtime. This also means that this code cannot be used for variables that changes between requests.

Setup

Copy the Middleware class for React Environment.

    public static class ReactEnvironment {

        public static void AddReactEnvironment<CONFIG>(this IServiceCollection services, string buildPath, CONFIG settings) {

            var indexPath = buildPath + "/index.html";

            services.AddSpaStaticFiles(configuration => {
                configuration.RootPath = buildPath;
            });

            //edit index.html file if build path exists. 
            if (File.Exists(indexPath)) {
               
                var file = File.ReadAllText(indexPath);

                typeof(CONFIG).GetProperties().Select(prop => new {
                    ReactEnvNames = prop.GetCustomAttributes(false)
                                        .OfType<ReactEnvironmentVarAttribute>()
                                        .Select(p => p.VarName).ToList(),
                    Value = prop.GetValue(settings)
                }).ToList()
                .ForEach(
                    p => p.ReactEnvNames.ForEach(
                        env => file = file.Replace(@$"%{env}%", p.Value.ToString())));

                File.WriteAllText(indexPath, file);
            }
        }
    }

Copy the React Environment Attribute class:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class ReactEnvironmentVarAttribute : Attribute {
        public string VarName { get; set; }
        public ReactEnvironmentVarAttribute(string varName) 
            => VarName = varName;
        
    }

How to use:

  1. Create a poco class with properties and use the ‘ReactEnvironmentVar’ attribute to define the property as a React Environment Var. (must start with ‘REACT_APP_’)

Example:

public class SomeSettings {

    [ReactEnvironmentVar("REACT_APP_API_URL")]
    public string ApiUrl { get; set; }

    [ReactEnvironmentVar("REACT_APP_GA_ID")]
    public string GoogleAnalyticsId { get; set; }

    [ReactEnvironmentVar("REACT_APP_RECAPTCHA_SITE_KEY")]
    public string RecaptchaSiteKey { get; set; }
}
  1. Edit the index.html file in your SPA React app (ClientApp/public/index.html) and add the environment vars in a script tag as follow:
    <script>
        window.spaSettings = {
            siteApi: "%REACT_APP_API_URL%",
            gaId: "%REACT_APP_GA_ID%",
            recaptchaSiteKey: "%REACT_APP_RECAPTCHA_SITE_KEY%"
        };
    </script>

Optional typescript definitions:

/* React App.tsx */
declare global {
    interface Window {
        spaSettings: SpaSettings;
    }
}

/* react.app-env.d.ts */
interface SpaSettings {
    siteApi: string;
    gaId: string;
    recaptchaSiteKey: string;
}
  1. In order to make these options available as environment variables for docker containers we must connect our poco class with a section in the appsettings.json file (this approach only works when using ‘CreateDefaultBuilder’ in Program.cs). Create the section and enter the names of each property.
  "SomeSettings": {
    "ApiUrl": "//api.example.com",
    "GoogleAnalyticsId": "...",
    "RecaptchaSiteKey": "---"
  },
  "Logging": {
   ...
  },
  1. Define the poco class as a configuration instance:
public class Startup {
        public SomeSettings SomeSettings { get; set; } = new SomeSettings();

        public void ConfigureServices(IServiceCollection services){
                services.Configure<SomeSettings>(Configuration.GetSection("SomeSettings"));
                Configuration.GetSection("SomeSettings").Bind(SomeSettings);
        }
}
  1. Replace the next line in Startup.cs:
services.AddSpaStaticFiles(o => o.RootPath = "ClientApp/build")
services.AddReactEnvironment("ClientApp/build", SomeSettings);
  1. You can now use Environment variables as follow:
set SomeSettings__ApiUrl = //newapi.example.com
set SomeSettings__GoogleAnalyticsId = ...
SomeSettings:ApiUrl = //newapi.example.com
SomeSettings:GoogleAnalyticsId = ...

Build and code

Do not specify any environment variables when publishing the app or building the docker image. This keeps the react environment placeholders between % intact and thus the application can correctly replace these with the correct values at startup. If you need to change a variable, redeploy the whole app (which is normal in docker).

You can use ‘window.spaSettings. …’ in javascript to refer to the environment vars. for eaxmple: window.spaSettings.ApiUrl

Another Approach usefull when you know all variables for different environments at build time:

  • Define different appsettings.json files and fill in the variables for each environment
    • appsettings.json
    • appsettings.dev.json
    • appsettings.staging.json
  • use the built in variable ASPNETCORE_ENVIRONMENT to switch to the development environments.
    example: ASPNETCORE_ENVIRONMENT=dev

Leave a Reply

Your email address will not be published.