Beyond Basics - ASP.Net Core Adding and Using Configuration

Adding strongly typed configuration settings and injected them through dependency injection.

Published on 06 June 2019

Introduction

In the last module we looked at an introduction example from Scott Allen ("ASP.NET Core Fundamentals") and started looking at how to apply more advanced software development patterns and practices to it. The goal is to help people who are early in their careers understand more complex constructs and ideas and how to apply them. This was a hurdle I experienced when I was starting out on my career so I hope these posts can help others.

The last module concentrated on adding a basic caching pattern into the Resturant view component. This initially had hard coded values for expiry time. In this module I want to look at how to make this configurable and touch on a few points about how to setup/use strongly typed settings POCOs.

In this module we will look at different ways of loading configuration values into strongly typed settings objects and how to use them in the ViewComponent through dependency injection.

Strongly Typed Settings

So what does strongly typed Settings give us? Well due to the nature of dependency injection of ASP.NET Core any service or component can request IConfiguration which is the top level configuration mechanism. However this gives the caller access to everything which isn't needed. It also violates the "Single Responsibility Principle" as the consumer of the configuration will have to both extract the required settings from IConfiguration but then also use them. This is a code smell.

Appsettings

Before we do anything we need to define the settings section in the appsettings.json file. This is the basic config file a New Project will give you for free.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Message": "Hello from appsettings!",
  "ConnectionStrings": {
    "OdeToFoodDb": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=OdeToFood;Integrated Security=True;"
  },
  "RestaurantCountCacheSettings": {
    "Seconds": 27 
  } 
}

Above is the whole appsettings.json file contents. I have added the RestaurantCountCacheSettings settings section at the bottom. This is all we have to do in this file.

Settings POCO

To access these app settings, which we've added to the configuration file, we need a strongly typed class definition we can load them into. Once the values are loaded into an instance of this class we can then look to use it.

public class RestaurantCountCacheSettings
{
    public int Seconds { get; set; }
}

The public properties of the POCO map to the properties in the appsettings.

But how do we load the data into the POCO?

Basic loading

Basic loading of configuration can be done using the Bind() mechanism. To use the basic Bind function you will need to instantiate an instance of your POCO. Once you have an instance to bind to it gets passed into the Bind method on the requested IConfigurationSection. This functionality will now attempt to bind values from configuration to the properties of the POCO instance.

Once the instance has been populated with the configuration values we need to make sure it has been registered with the services collection. If we don't we won't be able to request it through dependency injection later. You will notice as we continue through various techniques this is a common theme. Why Singleton? Well we don't want, or need, a new instance to be created each time as the configuration isn't changing.

// basic binding
var rccs = new RestaurantCountCacheSettings();
Configuration.GetSection("RestaurantCountCacheSettings").Bind(rccs);
services.AddSingleton(rccs);

Using IConfiguration Extension Method

I'm all about strong typing and if there is a generics based method to aid with a task then there needs to be a good argument not to use it. As part of the ConfigurationBinder extensions (microsoft.extensions.configuration.binder) there is a Get<T> extension method. So let's see how we can use this.

// config binding with Get extension method
var rccs = Configuration.GetSection("RestaurantCountCacheSettings").Get<RestaurantCountCacheSettings>();
services.AddSingleton(rccs);

We're still requesting the specific configuration section of the appsettings file. However we now don't have to create an instance of the specified type. Under the hood it will create an instance for you but that's for the framework to sort out.

Reusable Extension method

The above usage of Get<T> makes for easy use, however the developer is still required to remember to add it to the services collection as a Singleton. If there are a number of strongly typed settings classes which need intantiating and setting up this can be repetative and easily missed.

So let's see how we can wrap this into a custom extension method.

services.AddConfig<RestaurantCountCacheSettings>(Configuration.GetSection("RestaurantCountCacheSettings"));

The api surface area of the method looks good.

Let's look at the implementation.

public static class ServiceCollectionExtensions
{
    public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration)
        where TSettings : class, new()
    {
        return services.AddConfig<TSettings>(configuration, options => { });
    }

    public static TSettings AddConfig<TSettings>(this IServiceCollection services, IConfiguration configuration, Action<BinderOptions> configureOptions)
        where TSettings : class, new()
    {
        if (services == null) { throw new ArgumentNullException(nameof(services)); }
        if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); }

        TSettings setting = configuration.Get<TSettings>(configureOptions);
        services.TryAddSingleton(setting);
        return setting;
    }
}

This does some standard parameter checking. This is always a good idea to make sure items are not null. At the point where this extension method will be used in a regular ASP.NET Core Startup.cs class the likelihood of the IServiceCollection or IConfiguration being null is very small however it's always good to check.

Once the parameters are checked we use the same Get<T> extension method on IConfiguration as before to get the instance of the specified T before attempting to try and add the instance as a Singleton.

There are a number of similar implementations on the internet. This one was based on/inspired by Filip's blog post about not using IOptions.

But what about IOptions?

There are various opinions about using IOptions in your code. There are various reasons why you would want it, but also a number of reasons why you wouldn't want to use it.

Personally, due to the majority of the work I do, I don't see a need for using IOptions. It "infects" your code with an unnessessary abstraction and binds you projects/nuget packages to specific dependency versions.

How do we use it?

Like the other dependencies on our ViewComponent we add it to the constructor to be injected at runtime.

private readonly IRestaurantData _restaurantData;
private readonly IDistributedCache _cache;
private readonly RestaurantCountCacheSettings _settings;

public RestaurantCountViewComponent(
      IRestaurantData restaurantData, 
      IDistributedCache cache, 
      RestaurantCountCacheSettings settings)
{
    _restaurantData = restaurantData;
    _cache = cache;
    _settings = settings;
}

Once you've assigned it to a private field you can then use it, like the resturantData, inside the Invoke method.

var options = new DistributedCacheEntryOptions
{
    AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(_settings.Seconds)
};

Conclusion

In this post we have reviewed strongly typed configuration settings. Different ways of binding the configuration settings values to an instance of the settings POCO and how to consume the settings through dependency injection.

All the source code for this post can be found in the AddedConfiguration branch on my fork in Github.

Any questions/comments then please contact me on Twitter @WestDiscGolf

Further reading

Other Posts in this Series

Part 1 - Beyond Basics - ASP.Net Core Adding Caching

Part 2 - This Post

Part 3 - Beyond Basics - ASP.Net Core Using the Decorator Pattern