Newer
Older
Warehouse / src / Infrastructure / Common / Extensions / ConfigurationExtensions.cs
@Derek Comartin Derek Comartin on 22 Aug 2023 2 KB Init
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace MyWarehouse.Infrastructure;

public static class ConfigurationExtensions
{
    const string ConfigMissingErrorMessage = "Options type of '{0}' was requested as required, but the corresponding section was not found in configuration. Make sure one of your configuration sources contains this section.";

    /// <summary>
    /// Returns part of the configuration, bound to the specified strongly typed options instance. Use this method to get the configuration values needed in startup logic.
    /// </summary>
    /// <typeparam name="T">The options type to bind. The name of this type is used as the section name as well.</typeparam>
    /// <param name="requiredToExistInConfiguration">
    /// If true, the configuration must be backed by an explicitly existing section in the configuration file, and an exception is thrown if not.
    /// If false, a default instance of the configuration type is returned.
    /// </param>
    public static T GetMyOptions<T> (this IConfiguration configuration, bool requiredToExistInConfiguration = false) where T : class, new()
    {
        var bound = configuration.GetSection(typeof(T).Name).Get<T>();

        if (bound is null && requiredToExistInConfiguration)
            throw new InvalidOperationException(string.Format(ConfigMissingErrorMessage, typeof(T).Name));

        bound ??= new T();
        Validator.ValidateObject(bound, new ValidationContext(bound), validateAllProperties: true);

        return bound;
    }

    /// <summary>
    /// Registers a type as a strongly typed configuration, automatically taking care of any data annotation validation on startup, if exists.
    /// The registered type is retrievable from DI directly as a singleton, or wrapped in IOptions<>.
    /// </summary>
    /// <typeparam name="T">The options type to bind. The name of this type is used as the section name as well.</typeparam>
    /// <param name="requiredToExistInConfiguration">
    /// If true, the configuration must be backed by an explicitly existing section in the configuration file, and an exception is thrown if not.
    /// If false, a default instance of the configuration type is returned.
    /// </param>
public static void RegisterMyOptions<T>(this IServiceCollection services, bool requiredToExistInConfiguration = true) where T : class
{
    var optionsBuilder = services.AddOptions<T>()
        .BindConfiguration(typeof(T).Name)
        .ValidateDataAnnotations()
        .ValidateOnStart();

    if (requiredToExistInConfiguration)
        optionsBuilder.Validate<IConfiguration>((_, configuration)
            => configuration.GetSection(typeof(T).Name).Exists(), string.Format(ConfigMissingErrorMessage, typeof(T).Name));

    services.AddSingleton(resolver => resolver.GetRequiredService<IOptions<T>>().Value);
}
}