Cannot inject DbContext in repository - c#

I try to setup the DI for a new ASP.NET Core site and I have this code:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Get the configuration from the app settings.
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
// Get app settings to configure things accordingly.
var appSettings = Configuration.GetSection("AppSettings");
var settings = new AppSettings();
appSettings.Bind(settings);
services
.AddOptions()
.Configure<AppSettings>(appSettings)
.AddSingleton<IConfigurationRoot>(config)
.AddDbContext<MyDbContext>(builder =>
{
builder.UseSqlServer(config.GetConnectionString("myConn"));
}, ServiceLifetime.Transient, ServiceLifetime.Transient);
services.AddSingleton<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
...
Now, the LoadTestCleanUpServiceRepository depends on the MyDbContext:
public class LoadTestCleanUpServiceRepository : ILoadTestCleanUpServiceRepository
{
private readonly MyDbContext _dbContext;
public LoadTestCleanUpServiceRepository(MyDbContext dbContext)
{
_dbContext = dbContext;
}
...
..and the DB Context is this:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> ctxOptions) : base(ctxOptions)
{
}
}
When I run the application, I get this error:
InvalidOperationException: Unable to resolve service for type
'MyCode.Infrastructure.Common.MyDbContext' while attempting to
activate
'MyCode.Infrastructure.LoadTestCleanUpService.LoadTestCleanUpServiceRepository'.
I have tried changing the ServiceLifetime options and adding this extra code:
services.AddTransient<MyDbContext>(sp => new MyDbContext(config));
...but nothing seems to help and I cannot understand why this doesn't work. It does try to construct the repository, but why can't it construct the DB Context too? It doesn't even reach the point where I call UseSqlServer()!
Any ideas?
UPDATE 1:
Hmm... I now see this. Most likely it is related:
UPDATE 2:
I have now :
Replaced EF 6 with Microsoft.EntityFrameworkCore.SqlServer
Upgraded to netcoreapp2.2 target framework to solve some conflicting assembly versions.
Made the repository scoped.
But I still get the same error.

I see you have registered LoadTestCleanUpServiceRepository as Singleton while MyDbContext as Transient and then you are trying to resolve MyDbContext from LoadTestCleanUpServiceRepository. That's the problem. According to ASP.NET Core Service lifetimes documentation:
It's dangerous to resolve a scoped service/transient service from a singleton. It may cause the service to have incorrect state when processing subsequent requests.
Solution is: register LoadTestCleanUpServiceRepository and MyDbContext as follows:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("YourConnectionStringName")));
services.AddScoped<ILoadTestCleanUpServiceRepository, LoadTestCleanUpServiceRepository>();
Now problem should go away.

Related

Need a way to get the current request URL to configure the database context in multi-tenant application

I am migrating a web app from asp.net mvc to .net core (.net 5), and this has got me stuck.
The site is configured in IIS to accept request from multiple URLs like site1.example.com and site2.example.com. Each site has its own database, accessed through entity framework core.
In the old .net framework, I was able to use one of the events in the global.asax.cs to parse the incoming request URL and lookup the correct tenant database from a configuration file. I'm trying to set up something similar in asp.net core mvc.
Here's the relevant part of my ConfigureServices method in the startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>((serviceProvider, dbContextBuilder) =>
{
var tenantIdentifier = serviceProvider.GetRequiredService<ITenantIdentifier>();
var connectionString = Configuration.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
dbContextBuilder.UseSqlServer(connectionString);
}, ServiceLifetime.Scoped);
//other services configured below...
}
Then the tenant identifier looks like this:
public interface ITenantIdentifier
{
string GetCurrentTenantId();
}
public class UrlTenantIdentifier : ITenantIdentifier
{
readonly IHttpContextAccessor _httpContextAccessor;
readonly ILogger<UrlTenantIdentifier> _logger;
public UrlTenantIdentifier(IHttpContextAccessor httpContextAccessor, ILogger<UrlTenantIdentifier> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string GetCurrentTenantId()
{
//_httpContextAccessor is null here
//logic below for parsing URL and finding if we're site1 or site2
}
}
Is there a correct way of doing this now that I'm not aware of? How can I set up the entity framework database context for dependency injection when I don't know the connection string key until runtime? Am I going to be stuck configuring separate sites and virtual directories in IIS?
Refactor the DbContext to override the OnConfiguring member. Inject configuration and context accessor and perform configuration there.
public class myDbContext : DbContext {
private readonly ITenantIdentifier tenantIdentifier;
private readonly IConfiguration configuration;
public myDbContext(IConfiguration configuration, ITenantIdentifier tenantIdentifier) {
this.configuration = configuration;
this.tenantIdentifier = tenantIdentifier;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
var connectionString = configuration
.GetConnectionString(tenantIdentifier.GetCurrentTenantId() + "myDataModel");
optionsBuilder.UseSqlServer(connectionString);
}
}
Trying to access the request context at the time the DbContext is being created/initialized is too early in the request flow to get access to the desired information. It needs to happen after the context has already been initialized and injected.
public void ConfigureServices(IServiceCollection services)
services.AddHttpContextAccessor();
services.AddSingleton<ITenantIdentifier, UrlTenantIdentifier>();
services.AddDbContext<myDbContext>(); //Simplified since configuration is internal
//other services configured below...
}
Reference DbContext Lifetime, Configuration, and Initialization

Dependency Injection - Multiple projects

I'm creating a wep api and this is the current structure:
API - The Web API (.net core web api project)
DAL - DbContext and Entities (.net core class library)
DTO - Data Transfert Objects - The classes I send to the client without sensible data (.net core class library)
REPO - Contains de Interfaces and Repositories (.net core class library)
For information I had everything on the same project and decided to split into multiple class libraries.
What I've done until now:
Added the references beetween each project
Update usings
Changed namespaces names to the correct ones
Solution as 0 errors
I think that my problem is related to dependency injection because when I try to access a controller from postman or from the browser this error happens:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: FootballManager.REPO.ILeagueRepository Lifetime: Scoped ImplementationType: FootballManager.REPO.LeagueRepository': Unable to resolve service for type 'FootballManager.DAL.FootballManagerAPIContext' while attempting to activate 'FootballManager.REPO.LeagueRepository'.
My Startup.cs looks like this:
using FootballManager.REPO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace FootballManager.API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
});
});
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddScoped<ILeagueRepository, LeagueRepository>();
services.AddScoped<IMatchRepository, MatchRepository>();
services.AddScoped<IPlayerRepository, PlayerRepository>();
services.AddScoped<IRefereeRepository, RefereeRepository>();
services.AddScoped<ITeamRepository, TeamRepository>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
This is my controller code where I do the injection:
public class LeaguesController : ControllerBase
{
private readonly ILeagueRepository _repo;
public LeaguesController(ILeagueRepository repo)
{
_repo = repo;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<LeagueDto>>> GetLeagues()
{
return await _repo.GetAll();
}
}
For my DbContext connection I did directly on the DAL project like this (I dont think that the problem is here):
public partial class FootballManagerAPIContext : DbContext
{
public FootballManagerAPIContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(#"Server =.\SQLEXPRESS; Database = FootballManagerAPI; Trusted_Connection = True;");
}
}
}
After hours on the web and stackoverflow I still can't find any working solution...
How can I solve this error and why I'm having this? Thank you.
You never instantiate your DbContext - the error is very explicit about that;
Unable to resolve service for type 'FootballManager.DAL.FootballManagerAPIContext'
You also need to register the DbContext you need in the startup including configuration
I cant add comments to you question so I leave this here:
Maybe its a stupid question but, maybe you forgot it:
Does LeagueRepository inherit from ILeagueRepository?
I think this will help you.
Check out this video in which i explain how to implement dependency injection using autofac.
https://studio.youtube.com/video/XvklkAj7qPg/edit
Also i sugest that you should use disposable database connection, connect and disconnect in every method. So do not use dependency injection for db context.
Check if you registered the db context.
services.AddDbContext(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
In the services configuration i can't see it.
Thanks,
Liviu

Intermittent dependency injection failure

I am seeing the following intermittent exception locally when trying to resolve a service within a .net core 2.1 azure function app. It only seems to happen when multiple messages are being processed by the function concurrently and it only fails some of the messages.
'System.Private.CoreLib: Exception while executing function: FunctionOne. Microsoft.Extensions.DependencyInjection: Unable to resolve service for type 'Microsoft.Extensions.Configuration.IConfiguration' while attempting to activate 'XXX.Service2'.
When the Service is in the same project as the function then everything works fine. It is only when I move it into another project that this occurs. The other project I created is just a simple .net standard 2.0 project with just this service in and a reference to the Microsoft.Extensions.Configuration nuget.
I know that this implementation uses a Service Locator which is an anti pattern but I still want to understand why this exception occurs.
[FunctionName("FunctionOne")]
public static void Run(
[QueueTrigger(_queue, Connection = _storageConnection)]
string queueItem,
ILogger trace)
{
// Startup
var services = Startup.GetServices();
// Services
var service = services.GetService<IService2>();
}
public static class Startup
{
public static Func<IServiceProvider> GetServices = CreateServices;
public static IConfiguration GetConfiguration()
{
return new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
}
private static IServiceProvider CreateServices()
{
var services = new ServiceCollection();
var config = GetConfiguration();
services
.AddSingleton(config)
.AddSingleton<IService2, Service2>();
return services.BuildServiceProvider();
}
}
public class Service2 : IService2
{
public Service2(IConfiguration configuration)
{
}
}
public interface IService2
{
}
Try injecting it as an IConfigurationRoot instead of IConfiguration:
public HomeController(IConfigurationRoot configuration
, IService2 service)
{
_mailService = service;
_to = configuration["emailAddress.Support"];
}
In this case, the line
services.AddSingleton(provider => Configuration);
is equivalent to
services.AddSingleton<IConfigurationRoot>(provider => Configuration);
because the Configuration property on the class is declared as such, and injection will be done by matching whatever type it was registered as. We can replicate this pretty easily, which might make it clearer:
try this and see if it helps.
It appears to be a 'feature' that was introduced with version 2.0.12408.0 of the runtime. It does not happen with 2.0.12382.0.

Unable to resolve service for type IOptions[DataAccessConfiguration] in non-ASP.NET Core app

All of our business services were previously set up to use Dependency Injection with IOptions because they were being consumed by ASP.NET Core apps, like so:
NotificationDataAccess.cs:
public class NotificationDataAccess : BaseDataAccess, INotificationDac<Notification>
{
public NotificationDataAccess(IOptions<DataAccessConfiguration> options, IClaimsAccessor claimsAccessor) :
base(options, claimsAccessor)
{
}
}
NotificationBusinessService.cs:
public class NotificationBusinessServices : INotificationServices<Notification>
{
private readonly INotificationDac<Notification> _notificationDataAccess;
public NotificationBusinessServices(
INotificationDac<Notification> notifcationDataAccess)
{
_notificationDataAccess = notifcationDataAccess;
}
}
Now I'm left with the unenviable task of trying to figure out how to leverage the same pattern from a windows service, which doesn't benefit from the built-in ASP.NET Core features for handling DI. When the service starts up, I execute the following code:
// Set up configuration, services, and logging.
IServiceCollection services = new ServiceCollection();
var startup = new Startup();
startup.ConfigureServices(services);
IServiceProvider serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetService<IConfigurationRoot>();
var notificationService = serviceProvider.GetService<INotificationServices<Notification>>();// TODO: This errors!
processor = new Processor(configuration, notificationService);
And here is the Startup.cs code, which is supposed to configure the services:
public class Startup
{
IConfigurationRoot Configuration { get; }
public Startup()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Path.Combine(AppContext.BaseDirectory))
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConfigurationRoot>(Configuration);
//services.AddMvc();
// Add application services.
Listings.Business.Configuration.Instance = new BusinessLayerConfiguration();
services.Configure<DataAccessConfiguration>(options => Configuration.GetSection("Data").Bind(options));
services.AddScoped(typeof(INotificationDac<Notification>), typeof(NotificationDataAccess));
services.AddScoped(typeof(INotificationServices<Notification>), typeof(NotificationBusinessServices));
}
}
Unfortunately, when I run the windows service it throws an exception when trying to get the notificationService:
var notificationService = serviceProvider.GetService<INotificationServices<Notification>>();
The exception is:
System.InvalidOperationException: 'Unable to resolve service for type
'Microsoft.Extensions.Options.IOptions`1[Rpr.Listings.DataAccess.DataAccessConfiguration]'
while attempting to activate
'Rpr.Listings.DataAccess.NotificationDataAccess'.'
I was hoping my "services.Configure" code would resolve this, but alas no. Clearly I need to register IOptions in my Startup.cs, however I have no idea how to do so. Is this something that usually happens out of the box with ASP.NET MVC? Does "services.AddMvc();" normally register this binding correctly? I can call that, but would need to import a ton of ASP.NET MVC packages into my windows service, which I'm reluctant to do.
Please let me know how to register the IOptions binding correctly, thanks!
It turns out that all I was missing was:
services.AddOptions();
Once I added that, the IOptions binding was registered correctly!
In case it helps anyone, I had this issue in a console app and it was caused by creating the service provider
IServiceProvider serviceProvider = services.BuildServiceProvider();
before I'd registered my config
services.Configure<DataAccessConfiguration>(Configuration.GetSection("Data"));

Using Configuration Settings in ASP.NET 5

I am learning about ASP.NET 5. One of the areas that has me really confused is configuration. For the life of me, I cannot figure out how to load a value from the configuration file in a class. Before ASP.NET 5, I would just use:
ConfigurationManager.AppSettings["KeyName"];
However, now with ASP.NET 5, I'm a little confused. Currently, I have the following in Startup.cs:
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
var configuration = new Configuration()
.AddJsonFile("config.json");
Configuration = configuration;
}
This seems to load the configuration settings just fine. However, lets say I have a class called "Customer.cs" which interacts with the database. The database connection string is in config.json. How do I get that value? It doesn't seem efficient to load "config.json" in all of my POCOs. At the same time, the approach above doesn't seem to allow me to access Configuration in a global manner.
How should I load configuration settings and retrieve the configuration values in my POCOs?
Thank you
You should not load the connection string in each POCO entity (Have you ever used a DB Context?). If you're using Entity Framework 7 you can load the database context service when your app starts.
If your config.json looks like this:
"Data": {
"DefaultConnection": {
"Connectionstring": "your_connection_string"
}
you can set up your environment like this:
public void ConfigureServices(IServiceCollection services)
{
// Add EF services to the services container
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Your__DB__Context__Class>(options =>
options.UseSqlServer(Configuration.Get("Data:DefaultConnection:ConnectionString")));
}
//... other services...
}
Then in your controller you can directly use the DbContext through dependency injection:
public class Your__Controller : Controller
{
[FromServices]
public Your__DB__Context__Class DbContext { get; set; }
//...
}
If you need to use the DbContext service outside from the controller you have to do like this:
class NotAControllerClass : NotAController
{
private readonly Your__DB__Context__Class _dbContext;
public NotAControllerClass(Your__DB__Context__Class DbContext)
{
_dbContext = DbContext;
}
//do stuff here...
}
Finally if you're not using EF7 but instead a prevoious version of EF or a custom ORM you need a custom dependency injection. Try to search it on the internet.

Categories