user12310517
user12310517

Reputation:

How to correctly resolve services to use in the ConfigureServices() in ASP.NET Core 3.1?

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]

Upvotes: 4

Views: 4689

Answers (3)

Rafi Henig
Rafi Henig

Reputation: 6424

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>();

Upvotes: 0

Nkosi
Nkosi

Reputation: 247068

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.

Upvotes: 3

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

Upvotes: -1

Related Questions