1

I've created the following extension to simplify the app configurations in our many projects

public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services,
    IConfiguration configuration, string sectionName = BaseAppSettings.DefaultSectionName,
    ServiceLifetime lifetime = ServiceLifetime.Scoped)
    where TAppSettings : BaseAppSettings
{
    var appSettingsSection = configuration.GetSection(sectionName);
    var appSettings = appSettingsSection.Get<TAppSettings>();
    if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
    services.Configure<TAppSettings>(appSettingsSection);
    services.Add(new ServiceDescriptor(typeof(TAppSettings),serviceProvider =>appSettings , lifetime));
    return appSettings;
}

Which allows me to call it like

services.AddAppSettings<AppSettings>(context.Configuration);

Is there anyway to bind an already defined object instance,like one with some default values?

I've tried the following code, but any value inside IOptions are empty

public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services, IConfiguration configuration,
    TAppSettings appSettings,
    ServiceLifetime lifetime = ServiceLifetime.Scoped)
    where TAppSettings : BaseAppSettings
{

    if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
    services.Configure<TAppSettings>(options=>options=appSettings);
    return appSettings;
}

Update

I know that is unusual but imagine that i've an application that doesn't uses appsettings.json. I want to set some values to my configurations(I know it's possible to set the default values in the class), but imagine that don't want to set some default values there, because they can change from app to app that doesn't uses appsettings. but i still want to inject IOptions;

Any ideas?

Vinicius Andrade
  • 151
  • 1
  • 4
  • 22
  • You have to copy the property values over to the delegate parameter when using Configure. – Nkosi Jan 11 '23 at 04:11
  • property by property? Any other suggestion to automate it? – Vinicius Andrade Jan 11 '23 at 04:14
  • I was taking a look in some DeepClone answers here in stackoverflow and i found out some methods. [Deep Clone Usign Activator](https://stackoverflow.com/a/56691124/9114389). According to some [benchmarks](https://stackoverflow.com/a/69211283/9114389) this is the best way to do it. I'll try those aproachs and provide an update or an answer if it works – Vinicius Andrade Jan 11 '23 at 07:00
  • So you just want to inject a manual instantiated class as an IOptions of that type as shown in your 2nd block of code? The 1st block showing the IConfiguration related code is rather not applicable? – pfx Jan 11 '23 at 08:54
  • Yes, that is it. I want to be able to inject IOptions given an already defined instance of AppSettings – Vinicius Andrade Jan 11 '23 at 08:59

3 Answers3

2

Given that you want to register an object (representing some settings) as IOptions<T>, you just need to wrap that instance with an IOptions<T>.

Options.Create<T> does that.

Creates a wrapper around an instance of TOptions to return itself as an IOptions<TOptions>.

public static class Extenions
{
    public static TAppSettings AddAppSettings<TAppSettings>(
        this IServiceCollection services, TAppSettings appSettings,
        ) where TAppSettings : class
    {
        services.AddSingleton(typeof(IOptions<TAppSettings>), Options.Create(appSettings));

        return appSettings;
    }
}

The registration exists of creation an AppSettings instance, setting some properties and calling that extension method.

The lifetime of such an existing instance will always need to be a singleton, since you'll be creating that instance only once.

var appSettings = new AppSettings();
// Set some properties on appSettings.
services.AddAppSettings(appSettings);

Now your other classes can have an IOptions<AppSettings> instance injected.

pfx
  • 20,323
  • 43
  • 37
  • 57
  • 1
    I didn't thought about using this way. It's a good aproach giving the context I've provided, I'll test in a few hours and provide a better feedback about It. – Vinicius Andrade Jan 11 '23 at 10:40
1

this line

services.Configure<TAppSettings>(options=>options=appSettings);

Won't work

You need to construct an Action like

public static void AddAppSettings<TAppSettings>(this IServiceCollection services,TAppSettings appSettings)
    where TAppSettings : class
    {

        if (appSettings == null) throw new NullReferenceException(nameof(appSettings));


        

        ParameterExpression paraexpression = Expression.Parameter(appSettings.GetType(), "x");
         
        foreach (var prop in appSettings.GetType().GetProperties())
        {
            if (prop.PropertyType.Name=="String")
            {
                ConstantExpression val = Expression.Constant(prop.GetValue(appSettings));
                MemberExpression memberexpression = Expression.PropertyOrField(paraexpression, prop.Name);

                BinaryExpression assign = Expression.Assign(memberexpression, val);

                Expression<Action<TAppSettings>> exp = Expression.Lambda<Action<TAppSettings>>(assign, new ParameterExpression[] { paraexpression });
                services.Configure<TAppSettings>(exp.Compile());
            }
        }
    }
Ruikai Feng
  • 6,823
  • 1
  • 2
  • 11
  • It still has some problem in my case,i would deliever my whole solution to you tomorrow – Ruikai Feng Jan 11 '23 at 10:00
  • I'll try It in a few hours and provide a feedback about, as soon asap – Vinicius Andrade Jan 11 '23 at 10:41
  • 1
    The above codes would work for sting fields,and If you want to assign values to nested objects,you have to try with expression like x=>{x.Prop.nestProp2=someval,x.Prop.nestProp2=another val} instead of x=>x.Prop=someobj – Ruikai Feng Jan 12 '23 at 06:19
0

Based on @pfx answer

I've improved a little bit adding also the IOptionsMonitor

Create a AppSettingsExtensions.cs class:

public static class AppSettingsExtensions
{
    public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services,
        IConfiguration configuration, string sectionName = "AppSettings",
        ServiceLifetime lifetime = ServiceLifetime.Singleton)
        where TAppSettings : class
    {
        var appSettingsSection = configuration.GetSection(sectionName);
        var appSettings = appSettingsSection.Get<TAppSettings>();
        if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
        services.Configure<TAppSettings>(appSettingsSection);
        services.Add(new ServiceDescriptor(typeof(TAppSettings), serviceProvider => appSettings, lifetime));
        return appSettings;
    }
    public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services, IConfiguration configuration,
        TAppSettings appSettings,
        Func<IEnumerable<IOptionsChangeTokenSource<TAppSettings>>> optionsChangeTokenSourceBuilder = null,
        ServiceLifetime lifetime = ServiceLifetime.Singleton)
        where TAppSettings : class
    {

        if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
        var appSettingsConfigureOptions = configuration.CreateAppSettingsConfigureOptions<TAppSettings>();
        appSettingsConfigureOptions.Configure(appSettings);
        var appSettingsOptions = Options.Create(appSettings);
        var appSettingsMonitor = AddAppSettingsMonitor(services, configuration, appSettingsConfigureOptions);
        services.Add(appSettingsOptions,lifetime);
        services.Add(appSettingsMonitor, lifetime);
        return appSettings;
    }

    private static IOptionsMonitor<TAppSettings> AddAppSettingsMonitor<TAppSettings>(IServiceCollection services,
        IConfiguration configuration, ConfigureFromConfigurationOptions<TAppSettings> appSettingsConfigureOptions)
        where TAppSettings : class
    {
        var appSettingsOptionsFactory = services.CreateAppSettingsOptionsFactory(appSettingsConfigureOptions);
        var appSettingsMonitor = new OptionsMonitor<TAppSettings>(appSettingsOptionsFactory,
            configuration.CreateAppSettingsOptionsChangeTokenSources<TAppSettings>(), new OptionsCache<TAppSettings>());
        return appSettingsMonitor;
    }

    private static IEnumerable<IOptionsChangeTokenSource<TAppSettings>> CreateAppSettingsOptionsChangeTokenSources<TAppSettings>(this IConfiguration configuration,
        Func<IEnumerable<IOptionsChangeTokenSource<TAppSettings>>> builder=null) where TAppSettings : class
    {
        return builder?.Invoke()??Enumerable.Empty<IOptionsChangeTokenSource<TAppSettings>>();
    }

    public static ConfigureFromConfigurationOptions<TAppSettings> CreateAppSettingsConfigureOptions<TAppSettings>(this IConfiguration configuration) where TAppSettings : class
    {
        return new ConfigureFromConfigurationOptions<TAppSettings>(configuration);
    }

    public static OptionsFactory<TAppSettings> CreateAppSettingsOptionsFactory<TAppSettings>(this IServiceCollection services,ConfigureFromConfigurationOptions<TAppSettings> appSettingsConfigureOptions) where TAppSettings : class
    {
        return new OptionsFactory<TAppSettings>(new[] {appSettingsConfigureOptions},
            Enumerable.Empty<IPostConfigureOptions<TAppSettings>>());
    }
}

Create a DependencyInjectionExtensions.cs class:

public static class DependencyInjectionExtensions
{
    public static void Add<TService>(
        this IServiceCollection services,
        ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
        where TService : class
    {
        switch (serviceLifetime)
        {
            case ServiceLifetime.Singleton:
                services.AddSingleton<TService>();
                break;
            case ServiceLifetime.Scoped:
                services.AddScoped<TService>();
                break;
            case ServiceLifetime.Transient:
                services.AddTransient<TService>();
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
        }
    }


    public static void Add<TService, TImplementation>(
        this IServiceCollection services,
        ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
        where TService : class
        where TImplementation : class, TService
    {
        switch (serviceLifetime)
        {
            case ServiceLifetime.Singleton:
                services.AddSingleton<TService, TImplementation>();
                break;
            case ServiceLifetime.Scoped:
                services.AddScoped<TService, TImplementation>();
                break;
            case ServiceLifetime.Transient:
                services.AddTransient<TService, TImplementation>();
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
        }
    }

    public static void Add<TService>(
        this IServiceCollection services,
        TService service,
        ServiceLifetime serviceLifetime = ServiceLifetime.Singleton)
        where TService : class
    {
        switch (serviceLifetime)
        {
            case ServiceLifetime.Singleton:
                services.AddSingleton<TService>(service);
                break;
            case ServiceLifetime.Scoped:
                services.AddScoped<TService>((Func<IServiceProvider, TService>) (_ => service));
                break;
            case ServiceLifetime.Transient:
                services.AddTransient<TService>((Func<IServiceProvider, TService>) (_ => service));
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
        }
    }
}

I know that there's a lot going on here, but my goal was to provide a library to help resolving common code between projects, this way I could simplify some dependency injection code.

Hope it helps, and please feel free to make suggestions to this code, I'll be trully grateful to make those extensions even better

Vinicius Andrade
  • 151
  • 1
  • 4
  • 22