Read docker-compose environment variable from ASP.NET Core controller

Viewed 2629

I am trying to dockerize an ASP.NET Core 5 Web API project and so I am going to transfer all configs from appSettings.json to docker-compose environment section. And then going to read them from environment section in a controller. How should do this?

I have tried to use IConfiguration and Environment in the controller, but I have just got Null.

My Program.cs is:

namespace Order.Api
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
    }
}

And the StartUp.cs is:

namespace Order.Api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<OrderContext>(options =>
                options.UseSqlServer(Configuration.GetValue<string>("ConnectionString")));
                // options.UseSqlServer(Configuration.GetConnectionString("ConnectionString")));
            
            services.AddControllers();
            services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Order.Api", Version = "v1"}); });
        }

        // 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.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Order.Api v1"));
            }

            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
        }
    }
}

And the docker-compose.override is:

version: '3.4'

services:

  Order.Api :
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://0.0.0.0:80
      - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sqldata;Database=Order;User Id=sa;Password=Pass@word}
      - MyConfig=Active
    ports:
      - "5001:80"


  sqldata :
    environment:
      - SA_PASSWORD=Pass@word
      - ACCEPT_EULA=Y
    ports:
      - "5433:1433"
    volumes:
      - order-sqldata:/var/opt/mssql



volumes:
  order-sqldata:
    external: false

1 Answers

ASP.NET Core provides an ability to load configuration from different places:

  • appsettings.json
  • appsetting.<EnvName>.json
  • environment variables
  • user secrets
  • command line arguments

You shouldn't worry about where the settings came from. It's the same from developer's perspective. You can imagine it as a bunch of layer (each next layer overrides a setting from previous one). For example. When your application starts it tries to default load settings from appsettings.json, then override it by settings from appsetting.<EnvName>.json, then from environment variables, etc.

So, when you try to override ConnectionString in environment variables, the first thing you need to check it appsettings.json. How is it defined there? Let's creates simplest configuration:

{
  "ConnectionStrings": {
    "ConnectionString": "from config"
  },
  "MyConfig": {
    "Key": "from config"
  }
}

To read this connection in Startup.cs, you don't need to change anything. Use your options.UseSqlServer(Configuration.GetConnectionString("ConnectionString")));.

And to override it in environment variables you need to provide it with specific name: ConnectionStrings__ConnectionString=<new_connection>. It's pretty simple, you need to flatten your JSON configuration and use __ (double underscore) as a separator.

For custom configuration (like MyConfig) you need to do a little bit more. You need to create options class, like:

public class MyConfigOption
{
    public string Key { get; set; }
}

and configure it in Startup.cs:

services.Configure<MyConfigOption>(Configuration.GetSection("MyConfig"));

and then you will be able to override it in environment variables MyConfig__Key=value.

To use it in your controller, you need to ask DI for this options:

public Controller(IOptions<MyConfigOption> options)
{
    this.options = options;
}

options will contain your configuration.

See Docs for more details.

Related