Jason Sultana

Follow this space for writings (and ramblings) about interesting things related to software development.

Adding Configuration to .NET 6 Projects using the IOptions pattern

08 Aug 2022 » dotnet, configuration

G’day guys!

Following on from my last post on Dependency Injection, I wanted to compliment it with an up-to-date guide on adding configuration. We’ll start with a Web API (since its the simplest), and then look at adding config to other project types like console apps or tests.

Let’s start with the API

If you create a new Web API project in Visual Studio, you’ll be presented with a couple of default files, but the ones relevant to us are:

  • appsettings.json This is our configuration file. We’ll add some new config here in a sec.

  • Program.cs This is the bootstrapping file that sets everything up. We’ll modify this shortly to read the configuration and expose it to our controller.

  • WeatherForecastController.cs This is a default sample controller that Visual Studio created. We’ll modify this to read the configuration.

Step 1: Add config

Add a custom ApiSettings section to your config. Your appsettings.json file should look something like this.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ApiSettings": {
    "ApiName": "My Awesome API"
  }
}

Step 2: Add a strongly typed config class

Our custom section was called ApiSettings with a single string ApiName field, so let’s define a class with the same schema.

    public class ApiSettings
    {
        public string ApiName { get; set; }
    }

Step 3: Register config in Program.cs

Note: If you’re using a .NET version below .NET 6, then this will take place in the ConfigureServices method of Startup.cs instead.

        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.

        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var app = builder.Build();

Above is the auto-generated Program.cs contents of your .NET6 Web Api. Before calling Build on the builder, add the following:

        builder.Services.Configure<ApiSettings>(builder.Configuration.GetSection("ApiSettings"));

This registers our ApiSettings configuration section, associated with the strongly typed class we created earlier, with the .NET Dependency Injection container.

Step 4: Profit?

Now we’ve done all the heavy lifting, we can use the relevant config by injecting it into a controller or service. For demo purposes, let’s inject it into the auto-generated WeatherForecastController.

    public WeatherForecastController(ILogger<WeatherForecastController> logger, IOptions<ApiSettings> options)
    {
        _logger = logger;
        _apiSettings = options.Value;
    }

Put a breakpoint in the constuctor and observe that options has been populated with the value(s) from our JSON config, thanks to Dependency Injection and the minimal configuration setup we did in the previous step. Just don’t forget to define the config parameters IOptions<T> instead of just T, which is a common mistake newcomers will make. Remember that configuration in .NET Core (and nowadays just .NET) is based on IOptions.

Hey, what about for non-API projects?

You didn’t think we were done, did you? Well, I guess if you just wanted samples for a Web API project you can leave now. But for everyone else - if you read my last post on Dependency Injection, you may remember that what makes adding DI for non-web projects non straightforward is just how much scaffolding happens automatically with the web projects. The same is true for configuration. We can achieve the same results in non-web projects, we just need to do a bit more of the heavy lifting ourselves.

Step 1: Add packages

  1. Microsoft.Extensions.Configuration
  2. Microsoft.Extensions.Configuration.Json
  3. Microsoft.Extensions.DependencyInjection
  4. Microsoft.Extensions.DependencyInjection.Abstractions
  5. Microsoft.Extensions.Options

I’ll let you decide what versions to use - but these are the core packages that will get us started.

Step 2: Add Config file

I recommend adding the appsetting.json from our Web API project as a link to our console or test file, just to save having to reconfigure everything. Oh, and you’ll also want to add a reference to whatever project your ApiSettings class is defined in - probably the Web API project, if you’ve just been following this guide.

Step 3: Profit?

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SampleNet6Api;

var config = new ConfigurationBuilder()
                 .SetBasePath(Directory.GetCurrentDirectory())
                 .AddJsonFile("appsettings.json")
                 .Build();

var services = new ServiceCollection()
   .AddOptions()
   .Configure<ApiSettings>(config.GetSection("ApiSettings"))
   .BuildServiceProvider();

var apiSettings = services.GetService<IOptions<ApiSettings>>();

Console.WriteLine(apiSettings.Value.ApiName);
Console.ReadLine();

Above is the contents of my Program.cs in a console project, but it’ll be quite similar for test projects as well.

First, we create a ConfigurationBuilder and populate it with the config from our JSON file. Then, we create a ServiceCollection (the .NET DI container), add IOptions to it and register our ApiSettings class from the config. These two steps (minus the Configure bit) happen automatically under the hood with Web projects.

Lastly, we grab the IOptions<ApiSettings> service from the DI container and write the config value to the console. This is more-or-less that happened in the constructor of WeatherForecastController in our Web API project.

And that’s about it. Happy coding (or should I say configuring), and catch ya later!