# How ro read and register configuration on app startup in .Net
This is a task that I seldom do. It is usually only done at the start of a project or when adding subtansial features. And I always have to look it up.
In the standard webapp-template for .Net, the configuration class is injected into the Startup.cs
file. However, some projects live quite well without Startup.cs
. Think worker services or the new slim and hip new style of top level Program.cs
. We will see how to get a hold of configuration, read the content into classes and register them with the Dependency Injection Engine in .Net. We will also see it in use.
Our example is a very useful 🐱-based background service.
Consider the following configuration, rather crutial info we need to run our application.
// appsettings.json
{
"Cat": {
"Name": "Lotta"
},
"CatWorkerConfiguration": {
"DelayInMs": 3000
}
}
It maps to the following in c#
// Cat.cs
public record Cat
{
public string Name { get; init; } = string.Empty;
}
// ICatWorkerConfiguration.cs
public interface ICatWorkerConfiguration
{
int DelayInMs { get; }
}
// CatWorkerConfiguration.cs
public record CatWorkerConfiguration : ICatWorkerConfiguration
{
public int DelayInMs { get; init; }
}
And the app entrypoint is as follows
// Program.cs
await Host
.CreateDefaultBuilder(args)
.ConfigureServices(services => services.ReadConfigAndAddConfigAndWorker())
.Build()
.RunAsync();
And now to the main point. What is .ReadConfigAndAddConfigAndWorker()
and what does it do. It is a builder-pattern extention method we will write and use to do all the config related stuff.
// ConfigLoaderHelper.cs
public static class ConfigLoaderHelper
{
public static IServiceCollection ReadConfigAndAddConfigAndWorker(
this IServiceCollection services)
{
// get a hold of the services container
ServiceProvider serviceProvider = services.BuildServiceProvider();
// When bootstrpping the application .Net auto loades a config class for us.
// .Net also will add whetever it finds in appsettings.json.
// This is how we ask to see that config class.
IConfiguration config = serviceProvider
.GetService<IConfiguration>()
?? throw new Exception("Missing congiguration");
// Read and DI-register based on class
// Let's do that for the cat part
Cat catConfig = config.GetSection(nameof(Cat)).Get<Cat>();
services.AddSingleton(catConfig);
// We can also read and DI-register based on an interface
// Let's do that with the CatWorkerConfiguration
CatWorkerConfiguration workerConfig = config
.GetSection(nameof(CatWorkerConfiguration))
.Get<CatWorkerConfiguration>();
services.AddSingleton<ICatWorkerConfiguration>(workerConfig);
// Finally we load our mighty service and return happily
services.AddHostedService<CatWorker>();
return services;
}
}
Now we can inject our config at will where ever we need it. Let's see that in action and finally see what use this mighty application does.
// CatWorker.cs
public class CatWorker : BackgroundService
{
private readonly ILogger<CatWorker> _logger;
private readonly ICatWorkerConfiguration _workerConfig;
private readonly Cat _cat;
public CatWorker(
Cat cat,
ICatWorkerConfiguration workerConfig,
ILogger<CatWorker> logger)
{
_logger = logger;
_workerConfig = workerConfig;
_cat = cat;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var logtext = $"{_cat.Name} is the cat's name 🐱 Time: {DateTimeOffset.Now}";
_logger.LogInformation(logtext);
try
{
await Task.Delay(_workerConfig.DelayInMs, stoppingToken);
}
catch (OperationCanceledException)
{
return;
}
}
}
}
The output is catastic 😽
info: CatWorkerService.CatWorker[0]
Lotta is the cat's name 🐱 Time: 09/04/2021 16:55:12 +02:00
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /Users/petter/Code/WorkerService/WorkerService
info: CatWorkerService.CatWorker[0]
Lotta is the cat's name 🐱 Time: 09/04/2021 16:55:15 +02:00
info: CatWorkerService.CatWorker[0]
Lotta is the cat's name 🐱 Time: 09/04/2021 16:55:18 +02:00
info: CatWorkerService.CatWorker[0]
Lotta is the cat's name 🐱 Time: 09/04/2021 16:55:21 +02:00
Some people like to use the options pattern. Others like a global static instance. Do it whatever way makes you happy. I happen to like this technique 🦄 🛸 🌈