Search code examples
c#dependency-injectionstartup

How to access Singleton directly from ConfigureServices without BuildServiceProvider?


How to access singletons from ConfigureServices? There's a reason that I can't use appsettings for few configs.

For example, let's say that I want to set swagger title and version from database, not appsettings. My actual problem is I want to set consul address from my database. The problem should be the same, that I need to access my database in ConfigureServices. I have a custom extension like this:

public static IServiceCollection AddConsulConfig(this IServiceCollection services, string address)
{
    services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>
    {
        consulConfig.Address = new Uri(address); 
    }));

    return services;
}

I call it from startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
    
    //I want to use IGlobalParameter here directly but without BuildServiceProvider
    //This part is the problem
    var service = ??
    var varTitle = service.GetById("Title").Result.Value;
    var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
    
    services.AddConsulConfig(varConsulAddress);
    
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = varTitle, Version = "v1" });
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // I can use it here or in the controller no problem
    var service = app.ApplicationServices.GetRequiredService<IGlobalParameter>();
    var varTitle = service.GetById("Title").Result.Value;
    var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
}

I DO NOT want to use BuildServiceProvider as it will make multiple instances, even visual studio gives warning about it. referenced in How to Resolve Instance Inside ConfigureServices in ASP.NET Core

I knew the existence of IConfigureOptions from the following link https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer

But, I can't seem to find how exactly do I use that in ConfigureService:

public class ConsulOptions : IConfigureOptions<IServiceCollection>
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    public ConsulOptions(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public void Configure(IServiceCollection services)
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var provider = scope.ServiceProvider;
            IGlobalParameter globalParameter = provider.GetRequiredService<IGlobalParameter>();
            var ConsulAddress = globalParameter.GetById("ConsulAddress").Result.Value;

            services.AddConsulConfig(ConsulAddress);
        }
    }
}

Set it in startup:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
    services.AddSingleton<IConfigureOptions<IServiceCollection>, ConsulOptions>(); // So what? it's not called
    
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // IConsulClient is still null here
}

Any solution to how do I achieve this?


Solution

  • Thank you Jeremy, it's as simple as that. I don't know why I spend way too much time figuring out how to set this

    The solution is to add singleton :

    services.AddSingleton<IConsulClient, ConsulClient>(
        p => new ConsulClient(consulConfig =>
        {
            var ConsulAddress = p.GetRequiredService<IGlobalParameter>().GetById("ConsulAddress").Result.Value;
            consulConfig.Address = new Uri(ConsulAddress);
        }
    ));