Passing DbContext through dependency injection to BackgroundService throwing exception - dependency-injection

I'm trying to pass EF Core Dbcontext to my BackgroundService through dependency injection but somehow cannot get the hang of it.
Can someone tell me what I'm doing wrong and how to do it right/better/easier/another way (not cumbersome if possible)?
I'm using services.AddDbContext<MyDbContext> to add the DbContext in HostBuilder, then trying to receive it in the constructor of my BackgroundService.
I added the DbContext constructor receiving the DbContextOptions<MyDbContext> options and passing it to the base constructor of DbContext in my context, which is the way described in the documentation
Here's the exception I keep getting when starting the application:
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: XXXX.SenderService': Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at XXXX.Program.Main(String[] args) in C:\WORK\REPOS\CRR\Main\XXXX\Program.cs:line 16
This exception was originally thrown at this call stack:
[External Code]
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: XXXX.SenderService': Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
Inner Exception 2:
InvalidOperationException: Cannot consume scoped service 'XXXX.Entities.MyDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
Here's the Program.cs:
public static class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).ConfigureLogging(config => config.ClearProviders())
.Build()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(builderContext.Configuration
.GetConnectionString(nameof(MyDbContext)))
).AddHostedService<SenderService>())
.UseWindowsService();
}
SenderService.cs:
public class SenderService : BackgroundService
{
public SenderService(IConfiguration configuration, MyDbContext context)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
}
}
and MyDbContext:
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
public DbSet<XXXX> XXXX { get; set; }
}

In the end, I figured it out!
Problem description
What I'm basically doing is using ConfigureServices to AddScoped object, which is what happens when AddDbContext<MyDbContext> is called with only the delegate action filling out the DbContextOptionsBuilder that is then passed to my MyDbcontext constructor.
The dog is buried in adding the context as scoped object, while Service needs it as singleton to accept it in its constructor.
How to remedy that
Well, I can update the piece of code inside the delegate in ConfigureServices to treat the DbContext as a singleton:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host
.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
{
//this adds the dbContext, needs action that accepts DbContextOptionsBuilder
services
.AddDbContext<MyDbContext>(
optionsAction: (optionsBuilder) =>
{
//prepare connection string from IConfiguration existing in HostBuilderContext
//that was passed to ConfigureServices
var connectionString = builderContext.Configuration
.GetConnectionString(nameof(MyDbContext));
//and tell the optionsBuilder to use the SqlServer with the connectionstring
//which is then passed as parameter to MyDbContext constructor
optionsBuilder.UseSqlServer(connectionString);
},
//!!!IMPORTANT!!! - also specify the lifetime of the context, default is Scoped
contextLifetime: ServiceLifetime.Singleton,
optionsLifetime: ServiceLifetime.Singleton)
.AddHostedService<SenderService>()
})
.UseWindowsService();
Or, better, DON'T PASS CONTEXT AS SINGLETON TO THE SERVICE, CREATE IT WHEN NEEDED!
Which I can do for example by passing just the optionsBuilder like this:
// ---------in Program.cs------------
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((builderContext, services) =>
services.AddSingleton((_) => {
//again, get connection string from configuration
var connectionString = builderContext.Configuration
.GetConnectionString(nameof(MyDbContext));
//and return DbContextOptions to be saved as singleton
//to be passed to my service as constructor parameter
return new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(connectionString).Options;
})
.AddHostedService<SenderService>())
.UseWindowsService();
// ---------in SenderService.cs------------
public class SenderService : BackgroundService
{
private readonly DbContextOptions<MyDbcontext> contextOptions;
public SenderService(IConfiguration configuration, DbContextOptions<MyDbContext> dbContextOptions)
{
contextOptions = dbContextOptions;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
....
using(var context = new MyDbContext(contextOptions))
{
....
}
....
}
}

Related

Use Autofac to register Microsoft's ILogger and resolve for ILogger<T>

I built my API to accept instances of an ILogger instead of an ILogger after reading:
Simple Injector: Register ILogger<T> by using ILoggerFactory.CreateLogger<T>()
and this:
Should I take ILogger, ILogger<T>, ILoggerFactory or ILoggerProvider for a library?
And I've been using SimpleInjector to correctly inject the correct typed ILogger automagically. SimpleInjectors .AddLogging method just makes that work. But you can also register the ILogger like this:
container.RegisterConditional(
typeof(ILogger),
c => typeof(LogManager<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
where LogManager looks something like this:
public class LogManager<T> : ILogger<T> {
private ILogger _logger;
public LogManager(ILoggerFactory factory) {
_logger = factory.CreateLogger<T>();
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) {
_logger.Log(logLevel, eventId, state, exception, formatter);
}
}
And maybe there's a class defined like this that I want to register:
public class MyFactory {
public ILogger Logger { get; }
public MyFactory(ILogger logger) {
Logger = logger;
}
}
Is there a way to use Autofac to also automagically inject the desired typed ILogger MyFactory> instance into the constructor?
UPDATE:
I tried this this morning:
containerBuilder.Register((c, p) => {
var operation = (IResolveOperation)c.GetType().GetProperty("Operation").GetValue(c);
Type serviceType = (Type)operation.InitiatingRequest.Service.GetType().GetProperty("ServiceType").GetValue(operation.InitiatingRequest.Service);
var loggerFactory = c.Resolve<ILoggerFactory>();
var theLogger = (Microsoft.Extensions.Logging.ILogger)Activator.CreateInstance(typeof(LogManager<>).MakeGenericType(serviceType));
theLogger.GetType().GetMethod("SetLogger").Invoke(theLogger, new object[] { loggerFactory });
theLogger.BeginScope(new Dictionary<string, object> {
{"OrderNumber", 12345 },
{"User", "theUser" }
});
return theLogger;
})
.As<Microsoft.Extensions.Logging.ILogger>()
.InstancePerDependency();
I noticed while debugging that I could find the class that was requesting the ILogger and had to use reflection to get at the type. I had to add an empty constructor and method to set the actual logger instance on my LogManager class. The result at the moment is possibly success? I checked some log output and found the following:
{"#t":"2021-10-28T15:27:33.5373547Z","#mt":"I'm a warning","#l":"Warning","SourceContext":"OTH.Shared.SetProblematic","OrderNumber":12345,"User":"theUser"}
Looks like what I was trying to achieve. SetProblematic's constructor requests an ILogger which using some crazy reflection I built an instance of my LogManager class and got that injected instead.
Is what I came up with here necessary or is there a better way to ensure the correct "SourceContext" gets logged along with a common set of properties for the unit of work?

How to set dbContext with a connection string when it is also injected as a service

I'm using EF-Core in a web-api project, and using the DI mechanism to inject the DBContext to action methods.
BUT - In addition - I would like to reach the DB using the EF dbContext, when the server is up, in the startup method - this is a non http context, meaning - the DBContext is not initiated yet - it is initiated in the ConfigureServices method, and it is initiate after the stratup method.
For further explanation, this is the Startup.cs and the rest of the relevant flow:
public class Startup
{
public IConfiguration Configuration {get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
RepositoriesManager.Initiate(configuration);
//In addition - I now would like to use it to initiate repository by using the EF Core dbContext
//Another option is to run this methos after adding the dbContext serice - in the ConfigureServices
}
public void ConfigureServices(IServiceCollection services)
{
services.Add....
services.AddDbContext<MyDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("myDB)));
//Option to execute it here instead of in the Startup
RepositoriesManager.Initiate(configuration);
}
}
RepositoriesManager is a static class that using the configuration to get data from external API
public static Class RepositoriesManager
{
static RepositoriesManager(){} //static constructor
public static void Initiate(IConfiguration configuration)
{
//to make a long story short:
GetDataFromDBUsingEF();
}
//This method can be called also from action method (controller) - where the myDBContext is passed - this is not the case here
public static GetDataFromDBUsingEF(MyDBContext myDBContext = null)
{
if (myDBContext == null)
{
//one option - not working:
var serviceCollection = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
var sp = serviceCollection.GetService<MyDBContext>();
//second option - also not working:
myDBContext = new MyDBContext(); // This should initiate the DBContext in some scenarios
}
}
}
I'm trying to use the dbContext OnConfiguration method:
protected override void OnConfiguration(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
IConfigurationRoot configuration = new ConfigurationBuilder().SetBasePath(AppDomain.CurrentDomain.BaseDirectory).AddJsonFile("appsettings.json").Build();
optionsBuilder.UseSqqlServer(configuration.GetConnectionString("<connection string key>"));
}
}
This method should be called in every DbContext initiation using its constructor.
Right now it is not being reached when I initiate the dbContext. I assume that the reason is what the documentation claims:
"If a model is explicitly set on the options for this context (via Microsoft.EntityFrameworkCore.DbContextOptionsBuilder.UseModel(Microsoft.EntityFrameworkCore.Metadata.IModel)) then this method will not be run".
How can I get the DBContext when its not injected ?
Option 1: Get context in Startup constructor via new
public Startup(IConfiguration configuration)
{
Configuration = configuration;
var contextOptions = new DbContextOptionsBuilder<MvcContext>()
.UseSqlServer(Configuration.GetConnectionString("MvcContext"))
.Options;
using var context = new MvcContext(contextOptions);
var configFromDb = context.MvcConfiguration.First();
}
Option 2: In ConfigureServices call Configure on required options and use context (it will be called when options will be actually used)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<MvcContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("MvcContext"));
});
services.AddOptions<DbConfigOptions>().Configure<IServiceProvider>((options, sp) =>
{
using var scope = sp.CreateScope();
using var dbContext = scope.ServiceProvider.GetRequiredService<MvcContext>();
options.UseDeveloperExceptionPage = dbContext.MvcConfiguration.Single().UseDeveloperExceptionPage;
});
}
Option 3: When DBContext is configured in ConfigureServices it can be injected in Startup.Configure and used there
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MvcContext dbContext)
{
var configFromDb = dbContext.MvcConfiguration.Single();
//...
}

Dependency Injection in Startup.cs difficulties

I am trying to inject some Data into the Startup.cs file without success. For example, this injection works fine on my Email Class and I am able to read the _data
public class EmailSender : IEmailSender
{
private readonly DataConfig _data;
private readonly UserManager<ApplicationUser> _signManager;
public EmailSender(UserManager<ApplicationUser> signManager, IOptions<DataConfig> data)
{
_data = data.Value;
_signManager = signManager;
}
// I am able to retrieve the _data.SG without issues from the SendEmailAsync method.
public Task SendEmailAsync(string email, string subject, string message)
{
var sendGridKey = _data.SG;
return Execute(sendGridKey, subject, message, email);
}
}
However, when I try to perform the same operation from Startup.cs, the Program.cs crashes:
public class Startup
{
public IConfiguration Configuration { get; }
private readonly DataConfig _data;
public Startup(IConfiguration configuration, IOptions<DataConfig> data)
{
Configuration = configuration;
_data = data.Value;
}
}
Then, Program.cs crashes :
An error occurred while starting the application.
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Options.IOptions`1[JSRoles.Services.DataConfig]' while attempting to activate 'JSRoles.Startup'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Options.IOptions`1[JSRoles.Services.DataConfig]' while attempting to activate 'JSRoles.Startup'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider)
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, object[] parameters)
Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
Microsoft.AspNetCore.Hosting.GenericWebHostBuilder+<>c__DisplayClass12_0.<UseStartup>b__0(HostBuilderContext context, IServiceCollection services)
Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
Microsoft.Extensions.Hosting.HostBuilder.Build()
JSRoles.Program.Main(string[] args) in Program.cs
-
namespace JSRoles
{
public class Program
{
public async static Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
JSRoles.Program.<Main>(string[] args)
How do I get around this issue, and still manage to get the data that I need in StartUp.cs?

Using Unity DI with HostBuilder based .NET Core console application

With ASP.NET MVC Core it is possible to use the Unity DI packages by loading the appropriate NuGet packages (Unity.Container and Unity.Microsoft.DependencyInjection) and then calling the UseUnityServiceProvider() extension method when building the web host in Program.cs. This extension method is based off of the IWebHostBuilder interface. After initializing the host it is possible to access the Unity DI functionality via the .NET Core GetService interface and any constructor injection.
I'm working on a console based application that will use HostBuilder and the IHostBuilder interface. I've tried reimplementing the registration logic from the UseUnityServiceProvider() extension (available here: https://github.com/unitycontainer/microsoft-dependency-injection/blob/master/src/HostingExtension.cs) as part of the ConfigureServices() method call against IHostBuilder, but the change in service provider does not appear to be visible to downstream DI calls.
Has anyone been successful in getting Unity to work with the Microsoft DI methods in an application created using HostBuilder?
Update As requested, here is an example of what I was trying (taken from the Unity source). Of course, this does not work. TestService has a constructor which should be injected with an object defined in MyUnityExtension. This does not happen.
private static async Task MainTest()
{
var container = new UnityContainer().AddNewExtension<MyUnityExtension>();
var factory = new ServiceProviderFactory(container);
var hostBuilder = new HostBuilder()
.ConfigureServices((hostBuilderContext, services) =>
{
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IUnityContainer>>(factory));
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(factory));
services.AddHostedService<TestService>();
});
await hostBuilder.RunConsoleAsync();
}
Not sure what you want to achieve with new UnityContainer().AddNewExtension<MyUnityExtension>().
If you just want some registered service to be injected into TestService, why not just use IUnityContainer.RegisterType<TInterface, TImplementation>() ?
Here is a working IHost setup in Program.cs (.NET Core 3.1, Microsoft.Extensions.Hosting v3.10, Unity.Microsoft.DependencyInjection v5.11.5):
public static async Task Main(string[] args)
{
var container = new UnityContainer();
container.RegisterType<IService, MyService>();
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<TestService>();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
})
.UseUnityServiceProvider(container);
await builder.RunConsoleAsync();
}
The interface:
public interface IService
{
string Name { get; }
}
The implementation:
public class MyService : IService
{
public string Name => "My name";
}
The TestService:
public class TestService : IHostedService
{
private readonly IService service;
private readonly ILogger logger;
public TestService(IService service, ILogger<TestService> logger)
{
this.service = service;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.logger.LogInformation("Hello {n}", this.service.Name);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

DbContext instance not changing with dependency injection

In my application, I have an API and a worker, that both need to use my database. I have a repository layer, that is accessed by both of these.
I use DI in my application, and I inject my dbContext in my repositories.
While I expect my repositories to use a new dbContext on every request, the instance seems to always be the same.
Since I have a background worker, which is a singleton, I was not able to use the default scoped lifetime of my dbContext. Therefore I have added my dbContext as transient in both my worker and my API. I have added an instanceId to my dbContext, which is set in my constructor.
Constructor of dbcontext:
public CatAPIDbContext()
{
InstanceId = Guid.NewGuid();
Database.EnsureCreated();
}
Worker configureservices:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<CatAPIDbContext>(ServiceLifetime.Transient);
services.AddTransient(typeof(IFeedingProfileRepository), typeof(FeedingProfileRepository));
services.AddTransient(typeof(IFeedingTimesRepository), typeof(FeedingTimesRepository));
services.AddTransient(typeof(IFeedHistoryRepository), typeof(FeedHistoryRepository));
services.AddTransient(typeof(IMotorController), typeof(MotorController));
services.AddTransient(typeof(IFoodDispenser), typeof(FoodDispenser));
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient(typeof(IFeedingTimeChecker), typeof(FeedingTimeChecker));
services.AddHostedService<Worker>();
});
API ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddDbContext<CatAPIDbContext>();
services.AddTransient(typeof(IFeedingProfileRepository), typeof(FeedingProfileRepository));
services.AddTransient(typeof(IFeedingTimesRepository), typeof(FeedingTimesRepository));
services.AddTransient(typeof(IFeedHistoryRepository), typeof(FeedHistoryRepository));
services.AddTransient(typeof(IMotorController), typeof(MotorController));
services.AddTransient(typeof(IFoodDispenser), typeof(FoodDispenser));
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
}
GenericRepo example:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
public CatAPIDbContext _dbContext { get; set; }
public GenericRepository(CatAPIDbContext dbContext)
{
_dbContext = dbContext;
}
public T GetById(object id)
{
return _dbContext.Set<T>().Find(id);
}
}
Repo that I use and does not get latest state example:
public class FeedingProfileRepository :
GenericRepository<FeedingProfile>, IFeedingProfileRepository
{
public FeedingProfileRepository(CatAPIDbContext dbContext) :
base(dbContext)
{
}
public FeedingProfile GetCurrentFeedingProfile()
{
var profile = _dbContext.FeedingProfiles
.Include(x => x.TimesToFeedOn)
.Where(x => x.CurrentlyActive == true).FirstOrDefault();
if (profile == null)
{
return null;
}
if (profile.TimesToFeedOn != null)
{
profile.TimesToFeedOn = profile.TimesToFeedOn.OrderByDescending(x => x.Time).ToList();
}
return profile;
}
}
When thw orker calls FeedingProfileRepository.GetCurrentFeedingProfile() I check the instanceId of the dbContext, and it is always the same during the entire lifetime of my application. As a result, the data I retrieve from the dbContext is outdated, and does not match the current state of the database, since the dbContext never gets disposed. Am I doing something wrong?
As I see in your code you've made dbContext Transient and it means that it is created a new instance every time they are injected or requested:
services.AddDbContext<CatAPIDbContext>(ServiceLifetime.Transient);
if you want to have a single instance for all of the requests, make it singleton try to change your code like below:
services.AddDbContext<CatAPIDbContext>(ServiceLifetime.Singleton);
But if you want to have a single instance for every request to application try to use scoped lifetime:
services.AddDbContext<CatAPIDbContext>(ServiceLifetime.Scoped);

Resources