Quantcast
Channel: Active questions tagged blazor - Stack Overflow
Viewing all articles
Browse latest Browse all 4839

How Do I Create a DbContextFactory Within a Blazor Background Service?

$
0
0

I am working on my first Blazor Server application, which is also my first Entity Framework Core application. I am wanting to set up a background service which, once a day in the early morning, checks the database to see if any of a certain record type has been added with yesterday's date. If so, the relevant records are pulled, formatted, and then emailed to a stakeholder.

I have the EF, formatting, and emailing code working just fine when I trigger the report by manually visiting the page. The problem that I have is how to provide the background service with a DbContextFactory<OurAppContext> so that the EF and related code can execute.

Up to this point I've always used Razor-based dependency injection to inject the IDbContextFactory<OurAppContext> via an inject IDbContextFactory<OurAppContext> DbFactory at the top of the page, and then accessed the DbFactory via the DbFactory variable.

However, background services are (according to this Microsoft tutorial) set up through Program.cs, so I don't have access to Razor-based dependency injection there.

I have set up my background service (what I call the PhaseChangeReportService) as indicated in the above link, and it dutifully outputs to the console every 10 seconds that it is running with an updated execution count. I don't fully understand what's going on with the various layers of indirection, but it appears to be working as Microsoft intended.

I noted that the constructor for the background service takes in an ILogger as a parameter, specifically:

namespace miniDARTS.ScopedService{    public sealed class PhaseChangeReportService : IScopedProcessingService    {        private int _executionCount;        private readonly ILogger<PhaseChangeReportService> _logger;        public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger)        {            _logger = logger;                                }                    public async Task DoWorkAsync(CancellationToken stoppingToken)        {            while (!stoppingToken.IsCancellationRequested)            {++_executionCount;                _logger.LogInformation("{ServiceName} working, execution count: {Count}", nameof(PhaseChangeReportService), _executionCount);                await Task.Delay(10_000, stoppingToken);            }        }    }    }

I was (and am) confused that the constructor is never referenced within Visual Studio, but when I drop a breakpoint on its one line of code it is hit. I tried modifying this constructor's signature so that it took in an IDbFactory<OurAppContext> as well, so that whatever dark magic is allowing an ILogger<BackgroundServiceType> to come in for assignment to _logger might bring in a DbFactory<OurAppContext> as well, like so:

private readonly ILogger<PhaseChangeReportService> _logger;private readonly IDbContextFactory<miniDARTSContext> _dbContextFactory;public PhaseChangeReportService(ILogger<PhaseChangeReportService> logger, IDbContextFactory<miniDARTSContext> dbContextFactory){    _logger = logger;                            _dbContextFactory = dbContextFactory;}   

However, doing so just led to the constructor breakpoint being skipped over and not breaking, with no exception being thrown or any console output of any kind (i.e. the prior execution count console output no longer showed up). So, I gave up on that approach.

Here is the relevant section of Program.cs:

// Configure the database connection.string connectionString = builder.Configuration.GetConnectionString("miniDARTSContext");var serverVersion = new MySqlServerVersion(new Version(8, 0, 28));builder.Services.AddDbContextFactory<miniDARTSContext>(options => options.UseMySql(connectionString, serverVersion), ServiceLifetime.Scoped);IHost host = Host.CreateDefaultBuilder(args)    .ConfigureServices(services =>    {        services.AddHostedService<ScopedBackgroundService>();        services.AddScoped<IScopedProcessingService, PhaseChangeReportService>();            })    .Build();host.RunAsync();

Here's IScopedProcessingService.cs:

namespace miniDARTS.ScopedService{    public interface IScopedProcessingService    {        Task DoWorkAsync(CancellationToken stoppingToken);    }}

And here's ScopedBackgroundService.cs:

namespace miniDARTS.ScopedService;public sealed class ScopedBackgroundService : BackgroundService{    private readonly IServiceProvider _serviceProvider;    private readonly ILogger<ScopedBackgroundService> _logger;    public ScopedBackgroundService(IServiceProvider serviceProvider, ILogger<ScopedBackgroundService> logger)    {        _serviceProvider = serviceProvider;        _logger = logger;            }    protected override async Task ExecuteAsync(CancellationToken stoppingToken)    {        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is running.");        await DoWorkAsync(stoppingToken);    }    private async Task DoWorkAsync(CancellationToken stoppingToken)    {        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is working.");        using (IServiceScope scope = _serviceProvider.CreateScope())        {            IScopedProcessingService scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();            await scopedProcessingService.DoWorkAsync(stoppingToken);        }    }    public override async Task StopAsync(CancellationToken stoppingToken)    {        _logger.LogInformation($"{nameof(ScopedBackgroundService)} is stopping.");        await base.StopAsync(stoppingToken);    }}

I'm confident I'm misunderstanding something relatively fundamental here when it comes to services / dependency injection, but my Googling and review of past StackOverflow answers has not turned up anything I can run with.


Viewing all articles
Browse latest Browse all 4839

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>