Neovolve.Configuration.DependencyInjection 1.2.0

dotnet add package Neovolve.Configuration.DependencyInjection --version 1.2.0
NuGet\Install-Package Neovolve.Configuration.DependencyInjection -Version 1.2.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Neovolve.Configuration.DependencyInjection" Version="1.2.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Neovolve.Configuration.DependencyInjection --version 1.2.0
#r "nuget: Neovolve.Configuration.DependencyInjection, 1.2.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Neovolve.Configuration.DependencyInjection as a Cake Addin
#addin nuget:?package=Neovolve.Configuration.DependencyInjection&version=1.2.0

// Install Neovolve.Configuration.DependencyInjection as a Cake Tool
#tool nuget:?package=Neovolve.Configuration.DependencyInjection&version=1.2.0

Introduction

The Neovolve.Configuration.DependencyInjection NuGet package provides IHostBuilder extension methods for registering strong typed configuration bindings as services. It supports registration of nested configuration types and hot reload support.

GitHub license   Nuget   Nuget

Actions Status

Installation

The package can be installed from NuGet using Install-Package Neovolve.Configuration.DependencyInjection.

Usage

This package requires that the application bootstrapping provide a root configuration class that matches the configuration structure that the application uses.

The ConfigureWith<T> extension method registers the configuration type, all nested configuration types and all interfaces found as services in the host application. It will also ensure that IOptions<>, IOptionsSnapshot<> and IOptionsMonitor<> types are registered with the class types found under the root config type as well as all their interfaces.

For example consider the following nested configuration type structure:


public interface IRootConfig
{
    string RootValue { get; }
}

public class RootConfig : IRootConfig
{
    public FirstConfig First { get; set; } = new();
    public string RootValue { get; set; } = string.Empty;
}

public interface IFirstConfig
{
    string FirstValue { get; }
}

public class FirstConfig : IFirstConfig
{
    public string FirstValue { get; set; } = string.Empty;
    public SecondConfig Second { get; set; } = new();
}

public interface ISecondConfig
{
    string SecondValue { get; }
}

public class SecondConfig : ISecondConfig
{
    public string SecondValue { get; set; } = string.Empty;
    public ThirdConfig Third { get; set; } = new();
}

public interface IThirdConfig
{
    string ThirdValue { get; }
    TimeSpan Timeout { get; }
}

public class ThirdConfig : IThirdConfig
{
    public int TimeoutInSeconds { get; set; } = 123;

    public TimeSpan Timeout => TimeSpan.FromSeconds(TimeoutInSeconds);

    public string ThirdValue { get; set; } = string.Empty;
}

The json configuration source for this data could be something like the following.

{
  "RootValue": "This is the root value",
  "First": {
    "Second": {
      "Third": {
        "ThirdValue": "This is the third value",
        "TimeoutInSeconds":  123
      },
      "SecondValue": "This is the second value"
    },
    "FirstValue": "This is the first value"
  }
}

For an ASP.Net system, this would be registered like the following:

var builder = WebApplication.CreateBuilder(args);

// Register all configuration types
builder.Host.ConfigureWith<RootConfig>();

For other platforms, such as console applications, this would be registered like the following:

var builder = Host.CreateDefaultBuilder()
    .ConfigureWith<RootConfig>();

Given the above example, the following services would be registered with the host application:

Type IOptions<T> IOptionsSnapshot<T> IOptionsMonitor<T> Supports hot reload
RootConfig No No No No
IRootConfig No No No No
FirstConfig Yes Yes Yes Yes, except for IOptions<FirstConfig>
IFirstConfig Yes Yes Yes Yes, except for IOptions<IFirstConfig>
SecondConfig Yes Yes Yes Yes, except for IOptions<SecondConfig>
ISecondConfig Yes Yes Yes Yes, except for IOptions<ISecondConfig>
ThirdConfig Yes Yes Yes Yes, except for IOptions<ThirdConfig>
IThirdConfig Yes Yes Yes Yes, except for IOptions<IThirdConfig>

See Options pattern in .NET → Options interfaces for more information on the IOptions<>, IOptionsSnapshot<> and IOptionsMonitor<> types.

Hot reload support

The options binding system in .NET Core supports hot reload of configuration data which is implemented by some configuration providers like the providers for json and ini files. This is typically done by watching the configuration source for changes and then reloading the configuration data. This is useful for scenarios where configuration data is stored in a file and the application needs to react to changes in the file without needing to restart the application. This support is provided by the IOptionsSnapshot<> and IOptionsMonitor<> services.

One of the benefits of this package is that it supports hot reloading of injected raw configuration services by default. A raw type is a configuration class and its defined interfaces that are found under the root configuration type. In this definition, a raw type is anything other than IOption<>, IOptionsSnapshot<> or IOptionsMonitor<>.

In the above configuration example, the raw types that support hot reloading are:

  • IFirstConfig
  • FirstConfig
  • ISecondConfig
  • SecondConfig
  • IThirdConfig
  • ThirdConfig

This package detects when a configuration change has occurred by watching IOptionsMonitor<>.OnChange on all configuration services registered under the root configuration type. The package then updates the existing raw type in memory which works because the raw types are registered as singleton services. This allows the application class to receive updated configuration data at runtime by injecting a T configuration class/interface without needing to use IOptionsMonitor<T> or IOptionsSnapshot<T>. Logging is provided as the mechanism for recording that an injected raw type has been updated.

The reason to use IOptionsMonitor<> instead of the raw type is when the application class wants to hook into the IOptionsMonitor.OnChange method itself to run some custom code when the configuration changes.

The hot reload support for raw configuration types can be disabled by setting the ReloadInjectedRawTypes option to false in the ConfigureWith<T> overload.

Options

The following are the default options that ConfigureWith<T> uses.

Option Type Default Description
CustomLogCategory string string.Empty The custom log category used when LogCategoryType is LogCategoryType.Custom
LogCategoryType LogCategoryType LogCategoryType.TargetType The log category to use when logging messages for configuration updates on raw types. Supported values are TargetType or Custom.
LogReadOnlyPropertyLevel LogLevel LogLevel.Warning in Development; otherwise Debug The log level to use when logging that updates are detected for read-only properties
LogReadOnlyPropertyType LogReadOnlyPropertyType LogReadOnlyPropertyType.ValueTypesOnly The types of read-only properties to log when they are updated. Supported values are All, ValueTypesOnly and None.
ReloadInjectedRawTypes bool true Determines if raw types that are injected into the configuration system should be reloaded when the configuration changes

These options can be set in the ConfigureWith<T> overload.

var builder = Host.CreateDefaultBuilder()
    .ConfigureWith<RootConfig>(x => {
        x.CustomLogCategory = "MyCustomCategory";
        x.LogCategoryType = LogCategoryType.Custom;
        x.LogReadOnlyPropertyLevel = LogLevel.Information;
        x.LogReadOnlyPropertyType = LogReadOnlyPropertyType.All;
        x.ReloadInjectedRawTypes = false;
    });

Recommendations

Use read-only interface definitions for configuration types

Configuration class definitions require that properties are mutable to allow the configuration binding system to set the values. There is a risk of an application class mutating the configuration data after it is injected into a class constructor. The way to prevent unintended mutation of configuration data at runtime is to define a read-only interface for the configuration class. This will allow the configuration system to set the values but the application code will not be able to change the values.

The ConfigureWith<T> extension method supports this by registering any configuration interfaces found under the root configuration class.

Properties for child configuration types should be classes

Assuming that any configuration interfaces hide unnecessary child configuration types, all properties that represent child configuration types should be defined as their classes rather than interfaces on the parent configuration class. The ConfigureWith<T> extension method uses reflection to walk the type hierarchy from the root configuration type by finding and recursing through all the properties.

For example, if the First property on RootConfig above was defined as IFirstConfig rather than FirstConfig then the Second property on FirstConfig would not be found and registered as a service. This is because the IFirstConfig does not define the Second property but FirstConfig does.

Avoid resolving the root config service

The root configuration type provided to ConfigureWith<T> is registered as a service of T however using this service would typically break Law of Demeter in the application. Additionally, ConfigureWith<T> explicitly removes the service registrations of the root config for the IOptions<T>, IOptionsSnapshot<T> or IOptionsMonitor<T> services as they do not support hot reload. If you really do need to resolve root level configuration then use an interface like IRootConfig in the example above. In this case, hot reloaded data will still not be available on these services.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.0 113 4/13/2024
1.2.0-beta0002 73 4/13/2024
1.2.0-beta0001 62 4/13/2024
1.1.0 105 9/28/2023
1.0.1-beta0001 76 9/28/2023
0.1.0-beta0006 79 9/26/2023
0.1.0-beta0003 101 8/30/2023
0.1.0-beta0001 106 8/30/2023