Load .Env file into IConfiguration Provider

rmaurodev

Ricardo

Posted on August 14, 2024

Load .Env file into IConfiguration Provider

Managing environment variables efficiently is crucial for modern .NET applications, especially when configuring environments like development, staging, and production.

.NET Core's IConfiguration interface is a powerful tool for managing application settings, and it can be extended to include environment variables stored in a .env file using a custom configuration provider.

In This Series

  • Reading .env Files in C#
  • Loading DotEnv file into IConfiguration (You're here)
  • Creating NuGet Package for EnvReader (coming soon)

In this post, we’ll demonstrate how to integrate the EnvReader class into IConfiguration, enabling seamless configuration management for your C# applications with support for DotEnv files.

Load .Env file into IConfiguration Provider

This article is a follow-up to our previous post, Reading a .env File in C#, where we introduced the EnvReader class to load environment variables from a .env file.

Setting Up the Project

Create a new .NET Standard Class Library and add the dependency packages.

# creates class library
dotnet new classlib -o Extensions.Configuration.EnvFile

# add to solution
dotnet sln add Extensions.Configuration.EnvFile/Extensions.Configuration.EnvFile.csproj

#add dependency packages
dotnet add package Microsoft.Extensions.Configuration.Abstractions
dotnet add package Microsoft.Extensions.Configuration.FileExtensions
Enter fullscreen mode Exit fullscreen mode

The .csproj file should look like this.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.1" />
  </ItemGroup>

</Project>

Enter fullscreen mode Exit fullscreen mode

Implementing the Configuration reader

To have a fully functional EnvReader integrated with Microsoft.Extensions.Configuration.IConfiguration interface lets us set up 4 different classes.

  • EnvConfigurationExtensions - Here we will have the AddEnvFile method and overloads
  • EnvConfigurationProvider - Provider to read the .env file
  • EnvConfigurationSource - Source of Configuration that integrates with the Provider
  • EnvReader - The .env file reader/parser

Step 1: Set Up the EnvReader Class

For this version of EnvReader let's use Stream instead of file path.

using System.Collections.Generic;
using System.IO;

internal static class EnvReader
{
    public static IEnumerable<KeyValuePair<string, string>> Load(Stream stream)
    {
        StreamReader reader = new StreamReader(stream);

        while (reader.Peek() > -1)
        {
            string line = reader.ReadLine();

            if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
                continue; // Skip empty lines and comments

            var parts = line.Split('=', 2);
            if (parts.Length != 2)
                continue; // Skip lines that are not key-value pairs

            var key = parts[0].Trim();
            var value = parts[1].Trim();

            yield return new KeyValuePair<string, string>(key, value);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Custom Configuration Provider

To integrate EnvReader into IConfiguration, let's create a custom configuration provider.

This provider will load environment variables from a .env file and make them available through IConfiguration.

using Microsoft.Extensions.Configuration;
using System.IO;

internal class EnvConfigurationProvider : FileConfigurationProvider
{
    public EnvConfigurationProvider(FileConfigurationSource source) : base(source)
    {
    }

    public override void Load(Stream stream)
    {
        foreach (var item in EnvReader.Load(stream))
        {
            Data[item.Key] = item.Value;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Custom Configuration Source

Next, let's create a custom configuration source that will be used to add our EnvConfigurationProvider to the IConfigurationBuilder.

using Microsoft.Extensions.Configuration;

public class EnvConfigurationSource : FileConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);

        return new EnvConfigurationProvider(this);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Add Extension Method to IConfigurationBuilder

To make it easy to use our custom configuration provider, create a set of extension methods to register the EnvReader Provider.

To make registering easier, create extension methods in the namespace Microsoft.Extensions.Configuration

using Extensions.Configuration.EnvFile;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.FileProviders.Physical;
using System;

namespace Microsoft.Extensions.Configuration;

public static class EnvConfigurationExtensions
{
    public static IConfigurationBuilder AddEnvFile(
        this IConfigurationBuilder builder,
        string path = ".env",
        bool optional = false,
        bool reloadOnChange = true)
    {
        var fileProvider = new PhysicalFileProvider(AppContext.BaseDirectory, ExclusionFilters.Hidden | ExclusionFilters.System);
        return AddEnvFile(builder, path: path, optional: optional, reloadOnChange: reloadOnChange, provider: fileProvider);
    }

    public static IConfigurationBuilder AddEnvFile(
        this IConfigurationBuilder builder,
        IFileProvider provider,
        string path,
        bool optional,
        bool reloadOnChange)
    {
        if (builder == null)
            throw new ArgumentNullException(nameof(builder));

        if (string.IsNullOrEmpty(path))
            throw new ArgumentException("invalid path", nameof(path));

        return builder.AddEnvFile(s =>
        {
            s.FileProvider = provider;
            s.Path = path;
            s.Optional = optional;
            s.ReloadOnChange = reloadOnChange;
            s.ResolveFileProvider();
        });
    }

    public static IConfigurationBuilder AddEnvFile(
        this IConfigurationBuilder builder,
        Action<EnvConfigurationSource> configureSource)
        => builder.Add(configureSource);
}
Enter fullscreen mode Exit fullscreen mode

Demonstration

Now that everything is set up, let's integrate it into an ASP.NET Core application.

In the Program.cs or Startup.cs, add the following code to include your .env file in the configuration.

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvFile();

var app = builder.Build();

app.MapGet("/", (IConfiguration configuration) =>
{
    // Access the environment variables
    string apiKey = configuration["API_KEY"] ?? throw new ArgumentException("Missing API_KEY env variable");
    string databaseUrl = configuration["DATABASE_URL"] ?? throw new ArgumentException("Missing DATABASE_URL env variable");
    string debug = configuration["DEBUG"] ?? throw new ArgumentException("Missing DEBUG env variable");

    // Output the values
    Console.WriteLine($"API Key: {apiKey}");
    Console.WriteLine($"Database URL: {databaseUrl}");
    Console.WriteLine($"Debug Mode: {debug}");

    return new { apiKey, databaseUrl, debug };
});

app.Run();

Enter fullscreen mode Exit fullscreen mode

Run the application and we should see the result below.

Load .Env file into IConfiguration Provider

Source Code

Source code available on Github repo.

GitHub - ricardodemauro/Rmauro.Extensions.Configuration.EnvFiles: https://github.com/ricardodemauro/Rmauro.Extensions.Configuration.EnvFiles

Conclusion

By extending IConfiguration with a custom configuration provider, you can easily manage environment variables from a .env file in your ASP.NET Core applications.

This approach keeps your configuration clean and organized, making it easier to handle different environments without hardcoding sensitive values.


Again, feel free to enhance this solution by adding features like default values, nested configuration, or more sophisticated parsing logic. Happy coding! 😎

💖 💪 🙅 🚩
rmaurodev
Ricardo

Posted on August 14, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related