Reputation:
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
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
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
Reputation: 38
services.AddSingleton<AppSetting>();
services.AddSingleton<IAppSetting>(ctx =>
{
var setting = ctx.GetRequiredService<AppSetting>();
return setting;
});
Upvotes: -1