Implement A Custom Configuration Provider In .NET Web App

Implement A Custom Configuration Provider In .NET Web App

In last article, we have seen how to use the MemoryConfigurationProvider to load configurations from in-memory collections.

You can also create the custom configuration provider to load the configuration settings from any data source you want. In this article, let’s try to create the configuration provider to load data from database using Entity Framework Core.

What is the plan?

We are going to keep the configuration settings in the database. The database will contain a single table, ConfigurationSetting, with two columns, key and value.

We are going to use entity framework for accessing the settings from this database, so, we will create a DbContext implementation, CustomConfigurationProviderDbContext.

Then there are two interfaces are required to be implemented.

Lastly, we will create a new extension method AddCustomConfiguration, which will apply configurations from database for the use in application.

So, let’s get started !

Let’s Code !

As stated above, let’s start setup by step on the implementation. Create a .NET Core Web Application (MVC) using Visual Studio. Then perform below steps.

You would need to add references to two nuget packages:

Database Table

As we already know, the configuration in .NET is internally managed as a collection of key value pairs. So the ConfigurationSetting table should have three columns, ID, Key and Value as shown in the code snippet given below:

public class ConfigurationSetting
{
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}

The DbContext

Simple DbContext implementation, which defines a DbSet (i.e. the table) for ConfigurationSetting, as shown below.

using Microsoft.EntityFrameworkCore;
namespace CustomConfigProviderExample.CustomConfigProvider
{
public class CustomConfigurationProviderDbContext: DbContext
{
public CustomConfigurationProviderDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<ConfigurationSetting> ConfigurationSetting { get; set; }
}
}

Implement IConfigurationSource

As described earlier, this interface needs to be implemented and it will have responsibility to build the ConfigurationProvider instance.

The class CustomConfigurationSource below returning the instance of CustomConfigurationProvider instance. Note that the class CustomConfigurationProvider is not created yet, it will be created in the next step.

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigProviderExample.CustomConfigProvider
{
public class CustomConfigurationSource : IConfigurationSource
{
private readonly Action<DbContextOptionsBuilder> _optionsAction;
public CustomConfigurationSource(Action<DbContextOptionsBuilder> optionsAction)
{
_optionsAction = optionsAction;
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CustomConfigurationProvider(_optionsAction);
}
}
}

Implement IConfigurationProvider

This class will use the DbContext we created to read the configuration settings from the database.

For keeping the demo simple, we have hardcoded the configuration settings here in the same class. But in real world applications, you can create the tables and then get the data populated via some other process (like from the scheduled job, or from the admin application).

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigProviderExample.CustomConfigProvider
{
public class CustomConfigurationProvider : ConfigurationProvider
{
public CustomConfigurationProvider(Action<DbContextOptionsBuilder> optionsAction)
{
OptionsAction = optionsAction;
}
Action<DbContextOptionsBuilder> OptionsAction { get; }
public override void Load()
{
var builder = new DbContextOptionsBuilder<CustomConfigurationProviderDbContext>();
OptionsAction(builder);
using var dbContext = new CustomConfigurationProviderDbContext(builder.Options);
dbContext.Database.EnsureCreated();
Data = !dbContext.ConfigurationSetting.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.ConfigurationSetting.ToDictionary(c => c.Key, c => c.Value);
}
private static IDictionary<string, string> CreateAndSaveDefaultValues(
CustomConfigurationProviderDbContext dbContext)
{
var configValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Mode", "CustomConfigProvider" },
{ "MailFeature:0:Subject", "User account locked." },
{ "MailFeature:1:Subject", "Account Verification Required." }
};
dbContext.ConfigurationSetting
.AddRange(configValues.Select(kvp => new ConfigurationSetting
{
Key = kvp.Key,
Value = kvp.Value
})
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
}

Extension Method

This extension method AddCustomDatabaseConfiguration can be used while bootstrapping the application to tell runtime to use custom configuration source ( EF Core database in our case) to populate the key-value pair collection.

Note, we are going to use In memory database for this demo.

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace CustomConfigProviderExample.CustomConfigProvider
{
public static class CustomConfigProviderExtensions
{
public static IConfigurationBuilder AddCustomDatabaseConfiguration(
this IConfigurationBuilder builder, Action<DbContextOptionsBuilder> optionsAction)
{
return builder.Add(new CustomConfigurationSource(optionsAction));
}
}
}

App Configurations

For using this new custom configuration source, we need to call the above extension method to apply the custom configuration settings.

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddCustomDatabaseConfiguration(options => options.UseInMemoryDatabase("InMemoryDb"));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
view raw Program.cs hosted with ❤ by GitHub

For using the configurations, we just need to inject IConfiguration to the classes where we want to use the settings from custom configuration provider. You can run the application to debug and verify that the correct value is being fetched.

public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration configuration;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
this.configuration = configuration;
}
public IActionResult Index()
{
// Mode is the value from in-memory database,
// populated using custom configuration provider
ViewData["Mode"] = configuration["Mode"];
return View();
}
}
view raw HomeController.cs hosted with ❤ by GitHub

Where can we use this?

So, we just created a custom configuration provider, which reads the data from the database. But next question is, where should we use this feature.

For typical applications, appsettings.json may be sufficient for most of the cases. But for things like feature toggles, or the settings which change periodically (e.g. premium subscription charges of a site), they may change to time, so you may want to keep them in the database so that you can enable or disable features or apply new rates whenever required by business team.

Did you use this feature ? Are there any other scenarios where this feature can be used ? Let me know your thoughts.

Leave a Reply