4

I have an ASP.NET Core 3.1 based app. During the app startup, in the ConfigureServices(IServiceCollection services) I want to register my services. But during configuring services, I want to boot the app based on settings found in the database.

Here is my code

public void ConfigureServices(IServiceCollection services)
{
    // Register context
    services.AddDbContext<AppDbContext>(options =>
    {
        options.UseMySql(Configuration.GetConnectionString("MySqlServerConnection"));
    });

    // Reister the setting provider
    services.AddSingleton<IAppSetting, AppSettings>();

    // Create a resolver which is WRONG!!
    var resolver = services.BuildServiceProvider();

    var setting = resolver.GetService<IAppSetting>();

    List<string> allowedCors = new List<string>()
    {
         setting.MainUrl.ToString()
    };

    if (setting.HasCustomAssetsUri)
    {
        allowedCors.Add(settings.AssetsUrl.ToString());
    }

    if (settings.HasCustomPhotosUri)
    {
        allowedCors.Add(settings.PhotosUrl.ToString());
    }

    services.AddCors(options =>
    {
        options.AddPolicy("AllowSubDomainTraffic",
        builder =>
        {
            builder.WithOrigins(allowedCors.ToArray())
                   .AllowAnyHeader()
                   .AllowAnyMethod();
        });
    });

    // More services
}

As you can see in the above code, I register the IAppSetting but I immediately want to use to access the database so get the current configuration. I am currently calling services.BuildServiceProvider() to create a new resolver in which I can then use it to create an instance of the IAppSetting.

My AppSetting implementation depends on the AppDbContext which is also registered. Here is my AppSetting

public class AppSetting : IAppSetting
{
    private AppDbContext context;

    public AppSetting(AppDbContext context)
    {
        this.context = context;
    }

    // methods...
}

Calling BuildServiceProvider() will result in an additional copy of singleton services being created. so I am trying to avoid having to do this.

How can I correctly register and then resolve IAppSetting in the ConfigureServices() method so I can use it to access the settings found in the database?

Updated

I attempted to use the solution presented by Nkosi, but getting the following error

System.InvalidOperationException: 'Cannot resolve scoped service 'IAppSetting' from root provider.'

Here is the full stack trace.

This exception was originally thrown at this call stack:
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(System.Type, Microsoft.Extensions.DependencyInjection.IServiceScope, Microsoft.Extensions.DependencyInjection.IServiceScope)
    Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(System.Type, Microsoft.Extensions.DependencyInjection.IServiceScope)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(System.Type, Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(System.Type)
    Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(System.IServiceProvider, System.Type)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(Microsoft.Extensions.DependencyInjection.ServiceLookup.FactoryCallSite, Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext)
    Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCallSite, Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext)
    ...
    [Call Stack Truncated]
3
  • You don't need AppSetting to begin wit. Use the configuration system to load settings from the database instead. Commented Feb 6, 2020 at 14:39
  • 2
    The custom configuration provider example in the docs loads settings from the database using EF Core. You can easily adopt this to load your settings. Commented Feb 6, 2020 at 14:41
  • As for accessing configuration during the DI setup phase, this is already supported. You can retrieve the config values you want from the IConfiguration object passed to Startup Commented Feb 6, 2020 at 14:44

3 Answers 3

3

Reference Use DI services to configure options

Configure the CORS options using DI, moving all the logic into the configure delegate

//...

// Register context
services.AddDbContext<AppDbContext>(options => {
    options.UseMySql(Configuration.GetConnectionString("MySqlServerConnection"));
});

// Reister the setting provider
services.AddScoped<IAppSetting, AppSettings>(); 

//configure CORS options using DI
services.AddOptions<CorsOptions>()
    .Configure<IServiceScopeFactory>((options, sp) => {
        using(var scope = sp.CreateScope()) {
            IAppSetting settings = scope.ServiceProvider.GetRequiredService<IAppSetting>();
            List<string> allowedCors = new List<string>() {
                 setting.MainUrl.ToString()
            };

            if (setting.HasCustomAssetsUri) {
                allowedCors.Add(settings.AssetsUrl.ToString());
            }

            if (settings.HasCustomPhotosUri) {
                allowedCors.Add(settings.PhotosUrl.ToString());
            }
            options.AddPolicy("AllowSubDomainTraffic", builder => {
                builder.WithOrigins(allowedCors.ToArray())
                       .AllowAnyHeader()
                       .AllowAnyMethod();
            });
        }
    });

services.AddCors();

//...

that way everything is now deferred to when they are actually needed and you avoid having to build the service collection prematurely.

Sign up to request clarification or add additional context in comments.

10 Comments

Thank you for this answer. I am getting the following error System.InvalidOperationException: 'Cannot resolve scoped service 'IAppSetting' from root provider.' I'll update my question with the full stack trace
@John the issue is that the DbContext is registered a scoped and settings is a singleton. singletons can't have scoped dependencies. Check warning here learn.microsoft.com/en-us/aspnet/core/fundamentals/…
Thank you. I am still getting the same error even after changing services.AddSingleton<IAppSetting, AppSettings>(); to services.AddScoped<IAppSetting, AppSettings>();
@John updated using a local scope. Note the changes
That worked. But doesn't CreateScope() creates a second instance of the ServiceProvider which causes a second instance settings service?
|
0

Consider using IConfigureOptions as in the following example (avoiding the need of creating a scope yourself):

public class ConfigureCorsOptions : IConfigureOptions<CorsOptions>
{
    private readonly IAppSetting _settings;

    public ConfigureCorsOptions(IAppSetting settings) => _settings = settings;


    public void Configure(CorsOptions options)
    {
        List<string> allowedCors = new List<string>()
        {
             _setting.MainUrl.ToString()
        };

        if (setting.HasCustomAssetsUri)
        {
            allowedCors.Add(_settings.AssetsUrl.ToString());
        }

        if (_settings.HasCustomPhotosUri)
        {
            allowedCors.Add(_settings.PhotosUrl.ToString());
        }

        options.AddPolicy("AllowSubDomainTraffic", builder =>
        {
           builder.WithOrigins(allowedCors.ToArray())
                  .AllowAnyHeader()
                  .AllowAnyMethod();
        });
    }
}

To register this class with the DI container you would use something like:

services.AddTransient<IConfigureOptions<CorsOptions>, ConfigureCorsOptions>();

Comments

-1
services.AddSingleton<AppSetting>();
services.AddSingleton<IAppSetting>(ctx =>
    {
      var setting = ctx.GetRequiredService<AppSetting>();
      return setting;
    });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.