Adding a background service that deletes data every 7 days - c#

I have a class Issue which contains some information including their dates. I would like to create a background service that deletes data older than 7 days from the the database.
Here is the code I have tried:
using APIExample;
using Microsoft.Identity.Client;
public class BackgroundWorker : BackgroundService
{
private readonly AppDbContext _appDbContext;
public BackgroundWorker(AppDbContext appDbContext)
{
_appDbContext = appDbContext;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_appDbContext.Issue.RemoveRange(_appDbContext.Issue.Where(a => (a.Created.AddDays(7) >= DateTime.Now)));
_appDbContext.SaveChanges();
return Task.CompletedTask;
}
}
I have injected this in the program.cs class
builder.Services.AddHostedService<BackgroundWorker>();
This is the error I am getting:
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: BackgroundWorker': Cannot consume scoped service 'APIExample.AppDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)'

You can not inject DbContext like this way, use ServiceScopeFactory
public class BackgroundWorker : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public BackgroundWorker(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var _appDbContext= scope.ServiceProvider.GetService<AppDbContext>();
_appDbContext.Issue.RemoveRange(_appDbContext.Issue.Where(a => (a.Created.AddDays(7) >= DateTime.Now)));
_appDbContext.SaveChanges();
return Task.CompletedTask;
}
}
}

Related

Any way for long living log scopes in .NET

I have the need to add information to the log scopes in .NET and they need to survive longer than a single method call...
Usualy the samples always tell us to use log scopes like this
public void DoSomething()
{
using(Logger.BeginScope("Instance id {Guid}", strGuid)
{
Logger.LogInformation("did something");
}
}
This would need to wrap every public accessible with the begin scope call....
public class SampleServivce : IDisposable
{
public readonly ILogger<SampleService> Logger;
private IDisposable _logScope;
public SampleService(ILogger<SampleService> logger)
{
Logger = logger;
_logScope = Logger.BeginScope("Instance id {Guid}", Guid.NewGuid().ToString();
}
public void DoSomething()
{
Logger.LogInformation("did something");
}
public void Dispose()
{
logScope?.Dispose();
}
}
The second idea would be to create the scope in the constructor and dispose it in the dispose method. And this is where the
This post gives greater explanation why this would not work and yes, I can confirm this won't work because I am currently troubleshooting "wrong log scope info".
What our setup extends is the layer we have multiple instances of background services waiting for and doing tasks up on demand.
Unfortunatly some scoped services required us to create dedicated service scopes per instance of background workers and requesting the services on demand...
So we do ...
public class BackgroundWorker1 : BackgroundService
{
private string _workerInstance = Guid.NewGuid().ToString();
private IDisposable _logScope;
private IServiceScope _serviceScope;
public readonly IServiceScopeFactory ServiceScopeFactory;
public readonly ILogger<BackgroundWorker1> Logger;
private IServiceProvider _serviceProvider;
public BackgroundWorker1(IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_serviceScope = ServiceScopeFactory.CreateScope();
_serviceProvider = _serviceScope.ServiceProvider;
Logger = _serviceProvider.GetRequiredService<ILogger<BackgroundWorker1>>();
_logScope = Logger.BeginScope("worker instance id {WorkerId}", _workerInstance);
}
public void OneOfManyExternallyCalledMethods()
{
var service = _serviceProvider.GetRequiredService<Some3rdPartyService>();
service.DoSomething();
// all logs of all (incl. 3rd party/Microsoft) libs should contain the guid of the worker instance
}
public void Dispose()
{
_serviceScope?.Dispose();
_logScope?.Dispose();
}
}
The ultimate goal is to have each worker instance id in each log ....
and in some services the services (we can rewrite) the serivce instance id from the second sample ....

In window worker service dot net core while connectiong to database i am getting error?

Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: WindowServiceSample1.Worker': Cannot consume scoped service 'WindowServiceSample1.Data.ApplicationDbContext' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.)
using WindowServiceSample1;
using WindowServiceSample1.Data;
using Microsoft.EntityFrameworkCore;
IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "Abc Service New";
})
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("Server=DESKTOP-4NUBQP8\\SQLEXPRESS;Database=MyDatabase;Trusted_Connection=True;"));
})
.Build();
await host.RunAsync();
Worker class code -
namespace WindowServiceSample1
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly ApplicationDbContext _db;
public Worker(ILogger<Worker> logger, ApplicationDbContext db)
{
_logger = logger;
_db = db;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Notification item = new Notification();
item.EUID = "test";
item.StoreNumber = "test";
item.NotificationType = "test";
item.Status = "test";
_db.Notifications.Add(item);
await _db.SaveChangesAsync();
_logger.LogWarning("ABC Service running at: {time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
}
Application DB Context class
namespace WindowServiceSample1.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Notification> Notifications { get; set; }
}
}
As the error shows we should consume scope services in scoped instances to avoid data corruption or threading issue.
You should use Using ServiceScopeFactory to resolve scope instances.
Like this:
public Worker(ILogger<Worker> logger, , IServiceScopeFactory factory )
{
_logger = logger;
_db = factory.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>();
}
}

Cannot access a disposed context instance with N-layer architecture

I'm trying to make a N-layer architecture for my Telegram Bot. I created DAL, BLL and PL. I would like to add entity News to my DB. But I have some issue with my context.
My DB Context:
public class ApplicationContext : DbContext
{
public DbSet<News> News { get; set; }
public DbSet<User> Users { get; set; }
public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<News>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<User>().Property(tn => tn.Id).ValueGeneratedOnAdd();
modelBuilder.Entity<News>().Property(tn => tn.Title).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Href).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Image).IsRequired();
modelBuilder.Entity<News>().Property(tn => tn.Date).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserId).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.UserName).IsRequired();
modelBuilder.Entity<User>().Property(tn => tn.DateOfStartSubscription).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
Interface UoW:
public interface IUnitOfWork : IDisposable
{
INewsRepository News { get; }
IUserRepository Users { get; }
int Complete();
}
Class UoW:
public class UnitOfWork : IUnitOfWork
{
public IUserRepository Users { get; }
public INewsRepository News { get; }
private readonly ApplicationContext _context;
public UnitOfWork(ApplicationContext context)
{
_context = context;
Users = new UserRepository.UserRepository(_context);
News = new NewsRepository.NewsRepository(_context);
}
public int Complete() => _context.SaveChanges();
public void Dispose() => _context.Dispose();
}
My DAL Generic Repository:
async Task IGenericRepository<T>.AddAsync(T entity) => await _context.Set<T>().AddAsync(entity);
DAL Injection:
public static class DALInjection
{
public static void Injection(IServiceCollection services)
{
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient<IUserRepository, UserRepository.UserRepository>();
services.AddTransient<INewsRepository, NewsRepository.NewsRepository>();
services.AddTransient<IUnitOfWork, UnitOfWork.UnitOfWork>();
}
}
My BLL Service class:
public class ParserService : IParser
{
private IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
public ParserService(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
}
private async Task SaveArticles(IEnumerable<NewsDTO> articlesDTO)
{
var articles = _mapper.Map<IEnumerable<NewsDTO>, IEnumerable<News>>(articlesDTO);
await _unitOfWork.News.AddAsync(articles.First());
_unitOfWork.Complete();
}
BLL Injection:
public static class BLLInjection
{
public static void Injection(IServiceCollection services)
{
DALInjection.Injection(services);
services.AddTransient<IParser, ParserService>();
services.AddTransient<IArticleService, ArticleService>();
services.AddAutoMapper(typeof(CommonMappingProfile));
}
}
My PL:
private static async Task SendArticleAsync(long chatId, int offset, int count)
{
var articles = await _parser.MakeHtmlRequest(offset, count);
foreach (var article in articles)
{
var linkButton = KeyboardGoOver("Перейти", article.Href);
await _client.SendPhotoAsync(chatId: chatId, photo: article.Image,
caption: $"*{article.Title}*", parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown, replyMarkup: linkButton);
}
await OnLoadMoreNewsAsync(chatId, offset + count, count);
}
PL Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName)));
BLLInjection.Injection(services);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TelegramBot.WebApi", Version = "v1" });
});
}
When I tried to debug, I had this error but I could not resolve this issue.
_context = Database = {"Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may o...
Could someone help me with this issue?
There are few problems in your code.
Controllers are scoped entities, their instances created per http request and disposed after request is finished. It means controller is not good place to subscribe to events. When you call /start endpoint you create an instance of TelegramController and TelegramBotClient, but once the request is finished, the controller and all its non-singleton dependencies (IParser in your case) are disposed. But you subscribed for TelegramBotClient events that captured reference to IParser. It means all events that will arrive after request is finished will try to access disposed IParser instance and this is the reason for your exception.
For event based messages it's better to use IHostedService. You will need to use IServiceScopeFactory to create a scope for each message and resolve your dependencies from this scope.
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandlerAsync;
_client.OnCallbackQuery += OnLoadCallBackAsync;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static async void OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}
I moved call to _client.StartReceiving(); after event subscription otherwise there is a chance for race condition when you receive event but you don't yet have subscribers and this event will be lost.
The second issue is as #PanagiotisKanavos said: async void can't be awaited, hence once your code hit first true async method (like DB access, http request, file read or any other I/O operation) the control is returned to the point where async void method was called and continues execution without waiting for operation completion. The whole app can even crash if you throw unhandled exception from such method, hence async void should be avoided. To prevent these problems wrap your async event handlers with sync methods that will block the execution with Wait() method:
public class TelegramHostedService : IHostedService
{
private IServiceScopeFactory _scopeFactory;
public TimedHostedService(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_client = new TelegramBotClient(_token);
_client.OnMessage += OnMessageHandler;
_client.OnCallbackQuery += OnLoadCallBack;
_client.StartReceiving();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
// TODO: Unsubscribe from events
return Task.CompletedTask;
}
public static void OnMessageHandler(object sender, MessageEventArgs e)
{
OnMessageHandlerAsync(sender, e).Wait();
}
public static async Task OnMessageHandlerAsync(object sender, MessageEventArgs e)
{
using var scope = _scopeFactory.CreateScope();
var handler = scope.ServiceProvider.GetRequiredService<MessageHandler>();
await handler.Handle(TODO: pass required args); // Move the logic to separate handler class to keep hosted service clean
}
...
}

How to resolve MassTransit Mediator "some services are not able to be constructed" for request client

I'm having a look at MassTransit, and I used the masstransit dotnet temaplate to generate a worker, as per https://masstransit-project.com/getting-started/ ( everything up till RabbitMQ )
Then I was interested in getting the built in mediator working with responses, so changed the code according to https://masstransit-project.com/articles/mediator.html
so it the setup looks like ...
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMediator(x =>
{
x.AddConsumer<MessageConsumer>();
x.AddRequestClient<Message>();
});
services.AddMassTransit(x =>
{
x.AddConsumersFromNamespaceContaining<MessageConsumer>();
x.UsingInMemory((context,cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService(true);
services.AddHostedService<Worker>();
});
and the consumer / contract now looks like
public class Message { public string Text { get; set; } }
public class MessageResult { public string Text { get; set; } }
public class MessageConsumer : IConsumer<Message>
{
readonly ILogger<MessageConsumer> _logger;
public MessageConsumer(ILogger<MessageConsumer> logger)
{
_logger = logger;
}
public Task Consume(ConsumeContext<Message> context)
{
_logger.LogInformation("Received Text: {Text}", context.Message.Text);
return context.RespondAsync(new MessageResult() {Text = $"Got {context.Message.Text}"});
}
}
and the worker looks like
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IBus _bus;
private readonly IRequestClient<Message> _request;
public Worker(ILogger<Worker> logger, IBus bus, IRequestClient<Message> request)
{
_logger = logger;
_bus = bus;
_request = request;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var response = await _request.GetResponse<MessageResult>(new Message {Text = $"The time is {DateTimeOffset.Now}"}, stoppingToken);
await Task.Delay(1000, stoppingToken);
}
}
}
however when I run it, the injection of IRequestClient seems to fail ( for some reason it wasn't registered? ) with the exception
Error while validating the service descriptor 'ServiceType: Microsoft.Extensions.Hosting.IHostedService Lifetime: Singleton ImplementationType: MTGettingStarted.Worker': Cannot consume scoped service 'MassTransit.IRequestClient`1[MTGettingStarted.Message]' from singleton 'Microsoft.Extensions.Hosting.IHostedService'.
Which is what I thought x.AddRequestClient<Message>(); should have done. Maybe the documentation is incomplete? or I missed something?
If I change the worker to manaully get the request, then it does work
var client = _mediator.CreateRequestClient<Message>();
var response = await client.GetResponse<MessageResult>(new Message {Text = $"The time is {DateTimeOffset.Now}"}, stoppingToken);
But still curious why the DI doesn't work?
IRequestClient<T> is registered in the container as scoped, which the error you posted indicates:
Cannot consume scoped service
Your hosted service is a singleton.
To get the sample working you have to enable your worker to use scoped services.
Take a look at the docs at: https://learn.microsoft.com/en-us/dotnet/core/extensions/scoped-service
In the implementation behind IScopedProcessingService you have to inject IRequestClient<>.
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var scopedProcessingService = scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWorkAsync(stoppingToken);
}
}

C# Dependency Injection : Injecting multiple interfaces into other services

I'd like to inject a number of interfaces to another service.
Let's take a look at 2 services that I want to have their dependency injected.
Inside Term.cs
private readonly IWSConfig WSConfig;
private readonly IMemoryCache MemCache;
public Term(IWSConfig wsConfig, IMemoryCache memoryCache)
{
WSConfig = wsConfig;
MemCache = memoryCache;
}
public async Task LoadData()
{
List<ConfigTerm> configTerm = await WSConfig.GetData(); // This is a web service call
...
}
Inside Person.cs
private readonly PersonRepo PersonRepository;
private readonly IMemoryCache MemCache;
private readonly ITerm Term;
private readonly IWSLoadLeave LoadLeave;
private readonly IWSLoadPartics LoadPartics;
public Person(PersonRepo personRepository, IMemoryCache memCache, ITerm term, IWSLoadLeave loadLeave, IWSLoadPartics loadPartics)
{
PersonRepository = personRepository;
MemCache = memCache;
Term = term;
LoadLeave = loadLeave;
LoadPartics = loadPartics;
}
Code in Startup.cs
services.AddDbContext<DBContext>(opts => opts.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64)));
services.AddTransient<ILogging<ServiceLog>, ServiceLogRepo>();
services.AddSingleton<IMemoryCache, MemoryCache>();
services.AddHttpClient<IWSConfig, WSConfig>();
services.AddHttpClient<IWSLoadLeave, WSLoadLeave>();
services.AddHttpClient<IWSLoadPartics, WSLoadPartics>();
var optionsBuilder = new DbContextOptionsBuilder<DBContext>(); // Can we omit this one and just use the one in AddDbContext?
optionsBuilder.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64));
services.AddSingleton<ITerm, Term>((ctx) => {
WSConfig wsConfig = new WSConfig(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this to the IWSConfig and the ILogging<ServiceLog>
IMemoryCache memoryCache = ctx.GetService<IMemoryCache>();
return new Term(wsConfig, memoryCache);
});
services.AddSingleton<IPerson, Person>((ctx) => {
PersonRepo personRepo = new PersonRepo(new DBContext(optionsBuilder.Options)); // Can we change this?
IMemoryCache memoryCache = ctx.GetService<IMemoryCache>();
ITerm term = ctx.GetService<ITerm>();
WSLoadLeave loadLeave = new WSLoadLeave(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this?
WSLoadPartics loadPartics = new WSLoadPartics(new System.Net.Http.HttpClient(), new ServiceLogRepo(new DBContext(optionsBuilder.Options))); // Can we change this?
return new Person(personRepo, memoryCache, term, loadLeave, loadPartics);
});
But there are some duplication here and there. I've marked as the comments in the code above.
How to correct it ?
[UPDATE 1]:
If I change the declaration from singleton with the following:
services.AddScoped<ITerm, Term>();
services.AddScoped<IPerson, Person>();
I'm getting the following error when trying to insert a record using the DbContext.
{System.ObjectDisposedException: Cannot access a disposed object. A
common cause of this error is disposing a context that was resolved
from dependency injection and then later trying to use the same
context instance elsewhere in your application. This may occur if you
are calling Dispose() on the context, or wrapping the context in a
using statement. If you are using dependency injection, you should let
the dependency injection container take care of disposing context
instances. Object name: 'DBContext'.
In my WSConfig, it will inherit a base class. This base class also have reference to the ServiceLogRepo, which will call the DbContext to insert a record to the database
In WSConfig
public class WSConfig : WSBase, IWSConfig
{
private HttpClient WSHttpClient;
public WSConfig(HttpClient httpClient, ILogging<ServiceLog> serviceLog) : base(serviceLog)
{
WSHttpClient = httpClient;
//...
}
//...
}
The WSBase class:
public class WSBase : WSCall
{
private readonly ILogging<ServiceLog> ServiceLog;
public WSBase(ILogging<ServiceLog> serviceLog) : base(serviceLog)
{
}
...
}
The WSCall class:
public class WSCall
{
private readonly ILogging<ServiceLog> ServiceLog;
public WSCall(ILogging<ServiceLog> serviceLog)
{
ServiceLog = serviceLog;
}
....
}
And the ServiceLogRepo code
public class ServiceLogRepo : ILogging<ServiceLog>
{
private readonly DBContext _context;
public ServiceLogRepo(DBContext context)
{
_context = context;
}
public async Task<bool> LogRequest(ServiceLog apiLogItem)
{
await _context.ServiceLogs.AddAsync(apiLogItem);
int i = await _context.SaveChangesAsync();
return await Task.Run(() => true);
}
}
I also have the following in Startup.cs to do the web service call upon application load.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ITerm term)
{
....
System.Threading.Tasks.Task.Run(async () => await term.LoadData());
}
It seems when going into term.LoadData(), the DBContext is disposed already.
First properly register all the necessary dependencies in ConfigureServices using the appropriate liftetime scopes
services.AddDbContext<DBContext>(opts => opts.UseOracle(RegistryReader.GetRegistryValue(RegHive.HKEY_LOCAL_MACHINE, Configuration["AppSettings:RegPath"], "DB.ConnectionString", RegWindowsBit.Win64)));
services.AddTransient<ILogging<ServiceLog>, ServiceLogRepo>();
services.AddSingleton<IMemoryCache, MemoryCache>();
services.AddHttpClient<IWSConfig, WSConfig>();
services.AddHttpClient<IWSLoadLeave, WSLoadLeave>();
services.AddHttpClient<IWSLoadPartics, WSLoadPartics>();
services.AddScoped<ITerm, Term>();
services.AddScoped<IPerson, Person>();
Given the async nature of the method being called in Configure the DbContext is being disposed before you are done with it.
Now ideally given what you are trying to achieve you should be using a background service IHostedServive which will be started upon startup of the application.
public class TermHostedService : BackgroundService {
private readonly ILogger<TermHostedService> _logger;
public TermHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger) {
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
_logger.LogInformation("Term Hosted Service running.");
using (var scope = Services.CreateScope()) {
var term = scope.ServiceProvider.GetRequiredService<ITerm>();
await term.LoadData();
_logger.LogInformation("Data Loaded.");
}
}
public override async Task StopAsync(CancellationToken stoppingToken) {
_logger.LogInformation("Term Hosted Service is stopping.");
await Task.CompletedTask;
}
}
when registered at startup
services.AddHostedService<TermHostedService>();
Reference Background tasks with hosted services in ASP.NET Core

Categories