Reputation: 397
I'm relatively new to ASP.NET Core MVC and am running into issues with dependency injection.
I have a solution with multiple projects in which I want to share a EF database context class. I have defined an interface for a configuration manager so I can share common config across projects, but have project specific config as well.
When running the various API's dependency injection fails with a
"System.InvalidOperationException: Unable to resolve service for type IConfigManager"
error.
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[0] An unhandled exception has occurred while executing the request System.InvalidOperationException: Unable to resolve service for type 'NovaSec.Core.IConfigManager' while attempting to activate 'NovaSec.Core.Contexts.CustomDbContext'. at Microsoft.Extensions.DependencyInjection.ServiceLookup.Service.PopulateCallSites(ServiceProvider provider, ISet`1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
The DBContextClass
is part of a class library project I reference in the other projects.
I have no idea why it does not work. Can someone please help and explain this to me?
DBContext Class
public class CustomDbContext : IdentityDbContext<CustomIdentity, CustomRole, string>
{
public CustomDbContext(DbContextOptions<CustomDbContext> options, IConfigManager configManager) : base(options)
{
var optionsBuilder = new DbContextOptionsBuilder<CustomDbContext>();
optionsBuilder.UseSqlite(configManager._config.ConnectionStrings.FirstOrDefault(c => c.id == "IdentityDatabase").connectionString);
}
}
Config Manager interface and implementation class
public interface IConfigManager
{
IAppConfig _config { get; set; }
}
public class ConfigManager : IConfigManager
{
public IAppConfig _config { get; set; }
public ConfigManager(IAppConfig config)
{
}
}
Startup Method
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigManager, ConfigManager>(config =>
{
return new ConfigManager(_config);
});
IServiceProvider serviceProvider = services.BuildServiceProvider();
_configManager = (ConfigManager)serviceProvider.GetService<IConfigManager>();
services.AddDbContext<CustomDbContext>();
services.AddIdentity<CustomIdentity, CustomRole>(config => {
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<CustomDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddInMemoryClients(_configManager.GetClients())
.AddInMemoryIdentityResources(_configManager.GetIdentityResources())
.AddInMemoryApiResources(_configManager.GetApiResources())
.AddTemporarySigningCredential()
.AddAspNetIdentity<CustomIdentity>();
}
Upvotes: 1
Views: 1802
Reputation: 397
Ok I finally got it. The final result is:
DbContext class
public class CustomDbContext : IdentityDbContext<CustomIdentity, CustomRole, string>
{
private readonly IConfigManager _configManager;
public CustomDbContext(DbContextOptions<CustomDbContext> options, IConfigManager configManager) : base(options)
{
this._configManager = configManager;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(_configManager._config.ConnectionStrings.FirstOrDefault(c => c.id == "IdentityDatabase").connectionString);
}
}
Startup
public IConfigurationRoot Configuration { get; set; }
public ConfigManager ConfigManager { get; set; }
public AppConfig Config { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
this.Configuration = builder.Build();
this.Config = new AppConfig();
this.Configuration.Bind(this.Config);
this.ConfigManager = new ConfigManager(this.Config);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigManager, ConfigManager>(provider => this.ConfigManager);
services.AddDbContext<CustomDbContext>();
services.AddIdentity<CustomIdentity, CustomRole>(config => {
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<CustomDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddInMemoryClients(this.ConfigManager.GetClients())
.AddInMemoryIdentityResources(this.ConfigManager.GetIdentityResources())
.AddInMemoryApiResources(this.ConfigManager.GetApiResources())
.AddTemporarySigningCredential()
.AddAspNetIdentity<CustomIdentity>();
}
ConfigManager
public interface IConfigManager
{
IAppConfig _config { get; set; }
}
public class ConfigManager : IConfigManager
{
public IAppConfig _config { get; set; }
public ConfigManager(IAppConfig config)
{
this._config = config;
}
}
}
Upvotes: 0
Reputation: 247531
At this stage you are better off creating the manager manually, using it for the configuration and then registering it with the service collection.
Update context
public class CustomDbContext : IdentityDbContext<CustomIdentity, CustomRole, string> {
public CustomDbContext(DbContextOptions<CustomDbContext> options) : base(options) { }
}
You should also configure the context in the startup.
public void ConfigureServices(IServiceCollection services) {
var _configManager = new ConfigManager(_config); //Create new instance
services.AddSingleton<IConfigManager>(provider => _configManager); // add as singleton
services.AddDbContext<CustomDbContext>(options =>
options.UseSqlite(_configManager._config.ConnectionStrings.FirstOrDefault(c => c.id == "IdentityDatabase").connectionString)
);
services.AddIdentity<CustomIdentity, CustomRole>(config => {
config.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<CustomDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer()
.AddInMemoryClients(_configManager.GetClients())
.AddInMemoryIdentityResources(_configManager.GetIdentityResources())
.AddInMemoryApiResources(_configManager.GetApiResources())
.AddTemporarySigningCredential()
.AddAspNetIdentity<CustomIdentity>();
}
Upvotes: 2