A user can trigger a long-running job by sending a request to an ASP.NET Core controller. Currently, the controller executes the job and then sends a 200 OK response. The problem is that the client has to wait rather long for the response.
This is why I am currently trying to process the job in a background task. I am using an IBackgroundTaskQueue where all jobs are stored and an IHostedService that processes the jobs whenever a new one is enqueued. It is similar to the code in the Microsoft documentation.
But the job does need access to the database and therefore the user has to authenticate using Active Directory. Hence, I need access to the HttpContext.User property in the background task. Unfortunately, the HttpContext is disposed when the response is sent and before the processing of the job begins.
Demonstration
public class Job
{
public Job(string message)
{
Message = message;
}
public string Message { get; }
}
The controller enqueues a new job in the task queue.
[HttpGet]
public IActionResult EnqueueJob()
{
var job = new Job("Hello World");
this.taskQueue.QueueBackgroundWorkItem(job);
return Accepted();
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Job> jobs = new ConcurrentQueue<Job>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(Job job)
{
jobs.Enqueue(job);
signal.Release();
}
public async Task<Job> DequeueAsync(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
jobs.TryDequeue(out var job);
return job;
}
}
The IHostedService creates a new JobRunner for each job it dequeues. I'm using a IServiceScopeFactory here to have dependency injection available. JobRunner also has a lot more dependencies in the real code.
public class JobRunnerService : BackgroundService
{
private readonly IServiceScopeFactory serviceScopeFactory;
private readonly IBackgroundTaskQueue taskQueue;
public JobRunnerService(IServiceScopeFactory serviceScopeFactory, IBackgroundTaskQueue taskQueue)
{
this.serviceScopeFactory = serviceScopeFactory;
this.taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var job = await taskQueue.DequeueAsync(stoppingToken);
using (var scope = serviceScopeFactory.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var runner = serviceProvider.GetRequiredService<JobRunner>();
runner.Run(job);
}
}
}
}
public class JobRunner
{
private readonly ILogger<JobRunner> logger;
private readonly IIdentityProvider identityProvider;
public JobRunner(ILogger<JobRunner> logger, IIdentityProvider identityProvider)
{
this.logger = logger;
this.identityProvider= identityProvider;
}
public void Run(Job job)
{
var principal = identityProvider.GetUserName();
logger.LogInformation($"{principal} started a new job. Message: {job.Message}");
}
}
public class IdentityProvider : IIdentityProvider
{
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityProvider(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetUserName()
=> httpContextAccessor.HttpContext.User.Identity.Name; // throws NullReferenceException
}
Now, when sending a request, a NullReferenceException is thrown in JobRunner.Run() because httpContextAccessor.HttpContext is null.
What I've tried
I haven't had a good idea yet how to approach this problem. I know that it would be possible to copy the necessary information from the HttpContext, but don't know how to make them available to dependency injection services.
I thought that maybe I could create a new IServiceProvider that uses the services of an old one, but replaces the implementation for IHttpContextAccesor, but it does not seem to be possible.
How can I use the HttpContext in the background task although the response has been completed?
Related
I have an ASP.NET Core application running .NET 5 and C# 9. This also runs a Discord bot in the background. My ConfigureServices() method in Startup.cs looks like this.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var client = new DiscordSocketClient(new DiscordSocketConfig
{
AlwaysDownloadUsers = true,
MessageCacheSize = 10000,
GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMessages |
GatewayIntents.GuildMessageReactions | GatewayIntents.GuildPresences,
LogLevel = LogSeverity.Info
});
var commandService = new CommandService(new CommandServiceConfig
{
LogLevel = LogSeverity.Debug,
DefaultRunMode = RunMode.Sync,
CaseSensitiveCommands = false,
IgnoreExtraArgs = false,
});
services
.AddMediatR(Assembly.GetEntryAssembly())
.AddHostedService<StartupService>()
.AddHostedService<DiscordListener>()
.AddScoped<ITestService, TestService>()
.AddSingleton(client)
.AddSingleton(provider =>
{
commandService.AddModulesAsync(Assembly.GetEntryAssembly(), provider);
return commandService;
})
.AddSingleton(Configuration);
}
As you can see, I have added ITestService and TestService as a scoped service.
public class TestService : ITestService
{
public async Task<string> GetString()
{
await Task.Delay(1);
return "hey";
}
}
public interface ITestService
{
Task<string> GetString();
}
I then inject this service into my command module.
public class TestModule : ModuleBase<SocketCommandContext>
{
private readonly ITestService _testService;
public TestModule(ITestService testService)
{
_testService = testService;
}
[Command("ping")]
public async Task Ping()
{
var str = await _testService.GetString();
await ReplyAsync(str);
}
}
However, the application does not respond to the ping command. In fact, my handler for receiving messages is not hit at all (I have checked via breakpoint). This is the hosted services that listens for events and publishes the relevant MediatR notifications.
public partial class DiscordListener : IHostedService
{
private readonly DiscordSocketClient _client;
private readonly IServiceScopeFactory _serviceScopeFactory;
public DiscordListener(
DiscordSocketClient client,
IServiceScopeFactory serviceScopeFactory)
{
_client = client;
_serviceScopeFactory = serviceScopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_client.MessageReceived += MessageReceived;
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_client.MessageReceived -= MessageReceived;
return Task.CompletedTask;
}
// Creating our own scope here
private async Task MessageReceived(SocketMessage message)
{
using var scope = _serviceScopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Publish(new MessageReceivedNotification(message));
}
}
And this is the notification handler that handles the notification.
public class CommandListener : INotificationHandler<MessageReceivedNotification>
{
private readonly IConfiguration _configuration;
private readonly DiscordSocketClient _client;
private readonly CommandService _commandService;
private readonly IServiceProvider _serviceProvider;
public CommandListener(
IConfiguration configuration,
DiscordSocketClient client,
CommandService commandService,
IServiceProvider serviceProvider)
{
_configuration = configuration;
_client = client;
_commandService = commandService;
_serviceProvider = serviceProvider;
}
public async Task Handle(MessageReceivedNotification notification, CancellationToken cancellationToken)
{
if (!(notification.Message is SocketUserMessage message)
|| !(message.Author is IGuildUser user)
|| user.IsBot)
{
return;
}
var argPos = 0;
var prefix = _configuration["Prefix"];
if (message.HasStringPrefix(prefix, ref argPos))
{
var context = new SocketCommandContext(_client, message);
using var scope = _serviceProvider.CreateScope();
await _commandService.ExecuteAsync(context, argPos, scope.ServiceProvider);
}
}
}
Just to clarify, the breakpoint at _client.MessageReceoved += ... is not hit. If I change the ITestService and TestService implementation to a Singleton, then the handler is hit and the command works as expected. Any idea on what I'm doing wrong?
Here is the GitHub repo to the project if you want to see the full code. It is not too large.
This a typical problem when mixing Singleton and scoped services. If you end up with situation a singleton is resolving a scoped service it is not allowed.
From docs here
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
Do not resolve a scoped service from a singleton. It may cause the service to have incorrect state when processing subsequent requests. It's fine to:
Resolve a singleton service from a scoped or transient service.
Resolve a scoped service from another scoped or transient service.
By default, in the development environment, resolving a service from another service with a longer lifetime throws an exception. For more information, see Scope validation.
Also more discussion on https://dotnetcoretutorials.com/2018/03/20/cannot-consume-scoped-service-from-singleton-a-lesson-in-asp-net-core-di-scopes/amp/
I'm trying to make call to a function every specified interval of time, for that m using Background service, here is what I have done:
Here is the Alerting controller where I have the function:
public class AlertingController : ControllerBase
{
private readonly DatabaseContext _context;
private readonly IMapper _mapper;
public AlertingController(DatabaseContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public AlertingController()
{
}
//function that adds in the DB
public async Task<AlertingResponse> GetAlertingToDB()
{
AlertingResponse dataGet;
using (var httpClient = new HttpClient())
{
using (var response = await httpClient
.GetAsync(MetricApiLink))
{
string apiResponse = await response.Content.ReadAsStringAsync();
dataGet = JsonConvert.DeserializeObject<AlertingResponse>(apiResponse);
}
}
if (dataGet.data.alerts != null || dataGet.data.alerts.Count > 0)
{
foreach (var alert in dataGet.data.alerts)
{
CreateAlertQuery QueryAlert = new CreateAlertQuery();
QueryAlert.Name = alert.labels.alertname;
QueryAlert.Instance = alert.labels.instance;
QueryAlert.Serverity = alert.labels.severity;
QueryAlert.Summary = alert.annotations.summary;
QueryAlert.State = alert.state;
QueryAlert.ActiveAt = alert.activeAt;
var _Alert = _mapper.Map<AlertingDataModel>(QueryAlert);
_context.Alertings.Add(_Alert);
await _context.SaveChangesAsync();
}
}
return null;
}
}
I have tested the method with a HTTPGET request, it works fine, add the alerts into my database:
I have created a scooped service where I called the function GetAlertingToDB():
internal interface IScopedAlertingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedAlertingService : IScopedAlertingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedAlertingService(ILogger<ScopedAlertingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
AlertingController _AlertingToDB = new AlertingController();
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await _AlertingToDB.GetAlertingToDB();
await Task.Delay(10000, stoppingToken);
}
}
}
I have also created the Class that will consume my service, and will run in the BackGround:
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
I injected the dependencies on the Startup Class and added the hosted service:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedAlertingService, ScopedAlertingService>();
The functions are working just fine untill a call the GetAlertingToDB() function and it doesn't work.
Any help would be great, thanks everyone :)
Personally I would rearrange your solution so that your background service doesn't need to create a Controller. Instead the controller, if you still need it at all, should call into your ScopedAlertingService where the work is performed once. Your background service can simply loop forever, with an await Task.Delay().
public class ScopedAlertingService : IScopedAlertingService
{
public async Task DoWork(CancellationToken stoppingToken)
{
// move contents of your AlertingController.GetAlertingToDB here
}
}
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly IServiceProvider _services;
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
_services = services;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(10000, stoppingToken);
using (var scope = _services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedAlertingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
}
}
Hangfire RecurringJob would be an option for you case. you can check it here https://docs.hangfire.io/en/latest/background-methods/performing-recurrent-tasks.html.
The benefit of using it is: you have a dashboard to check when the task will be fired and the result of the task.
There are several options for doing this.
Please read the following link from the Microsoft Documentation which has several examples on how to do this in .NET Core and ASP.NET Core:
Worker Service In NET Core
It is called Worker Services.
You basically implement two interfaces: IHostedService, IDisposable
Then you register your service inside you Startup class in your ConfigureServices method like this:
services.AddHostedService<MyCoolWorkerServiceClass>();
For a Complete Example
One last sugestion. The example uses System.Threading.Timer... But I think it is better to use a System.Timers.Timer with AutoReset = false.
The reason is to avoid overlapping runs of your service. Once a run is done then you start your timer again.
But then again it all depends on what you want to achieve.
I am running a timed background task to send out emails, and in the email I want to include a generated link.
When I send out other emails via user interactions in the controller, I'm using this little method to generate the link:
public string BuildUrl(string controller, string action, int id)
{
Uri domain = new Uri(Request.GetDisplayUrl());
return domain.Host + (domain.IsDefaultPort ? "" : ":" + domain.Port) +
$#"/{controller}/{action}/{id}";
}
Of course, a background task does not know anything about the Http context, so I would need to replace the domain-part of the link, like this:
public string BuildUrl(string controller, string action, int id)
{
return aStringPassedInFromSomewhere + $#"/{controller}/{action}/{id}";
}
I'm starting the background task in startup.cs ConfigureServices like this:
services.AddHostedService<ProjectTaskNotifications>();
I was thinking to maybe get the domainname from a resource file, but then I might as well just hard code it into the task method.
Is there some way to pass this information dynamically to the background task?
MORE INFO
Here is the entire background task:
internal class ProjectTaskNotifications : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private readonly IServiceScopeFactory scopeFactory;
private readonly IMapper auto;
public ProjectTaskNotifications(
ILogger<ProjectTaskNotifications> logger,
IServiceScopeFactory scopeFactory,
IMapper mapper)
{
_logger = logger;
this.scopeFactory = scopeFactory;
auto = mapper;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
// Connect to the database and cycle through all unsent
// notifications, checking if some of them are due to be sent:
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<MyDbContext>();
List<ProjectTaskNotification> notifications = db.ProjectTaskNotifications
.Include(t => t.Task)
.ThenInclude(o => o.TaskOwner)
.Include(t => t.Task)
.ThenInclude(p => p.Project)
.ThenInclude(o => o.ProjectOwner)
.Where(s => !s.IsSent).ToList();
foreach (var notification in notifications)
{
if (DateTime.UtcNow > notification.Task.DueDate
.AddMinutes(-notification.TimeBefore.TotalMinutes))
{
SendEmail(notification);
notification.Sent = DateTime.UtcNow;
notification.IsSent = true;
}
}
db.UpdateRange(notifications);
db.SaveChanges();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
public void SendEmail(ProjectTaskNotification notification)
{ // Trimmed down for brevity
// Key parts
string toAddr = notification.Task.TaskOwner.Email1;
BodyBuilder bodyBuilder = new BodyBuilder
{
HtmlBody = TaskInfo(auto.Map<ProjectTaskViewModel>(notification.Task))
};
return;
}
public string TaskInfo(ProjectTaskViewModel task)
{ // Trimmed down for brevity
return $#"<p>{BuildUrl("ProjectTasks", "Edit", task.Id)}</p>";
}
public string BuildUrl(string controller, string action, int id)
{
// This is where I need the domain name sent in from somewhere:
return "domain:port" + $#"/{controller}/{action}/{id}";
}
}
You can pass in any object to the IHostedService provider via the constructor.
public ProjectTaskNotifications(IUrlPrefixProvider provider)
{
_urlPrefixProvider = urlPrefixProvider
}
private string BuildUrl(<Your args>)
{
var prefix = _urlPrefixProvider.GetPrefix(<args>);
....
}
In startup.cs you can have
services.AddSingleton<IUrlPrefixProvider, MyUrlPrefixProvider>()
services.AddHostedService<ProjectTaskNotifications>();
and let dependency injection take care of the rest.
I need to measure the time taken to process each request in Azure Function App, like below via ASP.NET (non ASP.NET CORE)
public class RequestLogHandler : DelegatingHandler
{
private readonly ILogger _logger;
public RequestLogHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string path = request.GetOwinContext().Request.Path.ToString();
string method = request.Method.Method;
var sw = Stopwatch.StartNew();
var response = await base.SendAsync(request, cancellationToken);
sw.Stop();
int statusCode = (int)response.StatusCode;
_logger.HttpRequest(path, method, sw.ElapsedMilliseconds.ToString(), statusCode.ToString());
return response;
}
}
Setup
public class WebServer : IWebServer
{
private readonly ILogger _logger;
private readonly HttpConfiguration _httpConfiguration;
private IDisposable _server;
public WebServer(ILogger logger, HttpConfiguration httpConfiguration)
{
_logger = logger;
_httpConfiguration = httpConfiguration;
}
public void Start(string url)
{
_server = WebApp.Start(url, (appBuilder) =>
{
_httpConfiguration.MapHttpAttributeRoutes();
_httpConfiguration.MessageHandlers.Add(new RequestLogHandler(_logger));
appBuilder.UseWebApi(_httpConfiguration);
});
}
public void Dispose()
{
if(_server != null)
{
_server.Dispose();
_server = null;
}
}
}
You could use Application Insights to get the Request Duration, in the application insights page you could get the request duration details.
This is the Duration description: This field is required as request telemetry represents the operation with the beginning and the end. You also could use the Microsoft.ApplicationInsights NuGet package to send custom telemetry data to Application Insights including the Duration.
I would like to implement a recurring (timed) IHostedService instance in ASPNET Core that can be stopped and started on demand. My understanding is that IHostedService(s) are started by the framework on application startup.
However, I would like to be able to start/stop the service 'manually', perhaps using an on/off toggle via a UI. Ideally the "off" state would dispose of currently running service, and the "on" state would then create a new instance.
I've read the MS docs here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1.
My initial thought was to get an instance of the running service and then call the public StopAsync(CancellationToken token) method. However I'm a little stuck when it comes to which token I should pass in, and the same could be said for the StartAsync(CancellationToken cancellationToken) method.
Any ideas on how this should be done, or if it's even advisable? Is my approach somehow going against the intended design of hosted services in ASPNET Core?
EDIT 7.27.2018
So it appears after some more research (aka actually reading the documentation :D) that hosted services StartAsync/StopAsync methods are indeed meant to coincide with the lifetime of the application. Registered IHostedServices seem to not be added to the DI container for injection into other classes.
Therefore I do not think my initial idea will work. For now I registered my services with configuration dependencies (IOptions<T>) that can be updated at runtime. As the hosted services is processing, it will check the configuration to see if it should continue, otherwise it will just wait (instead of stopping or disposing of the hosted service).
I'll probably mark this as my answer soon, unless I hear of some other ideas.
For StopAsync(CancellationToken token), you could pass new System.Threading.CancellationToken(). In the defination of public CancellationToken(bool canceled), canceled indicates state for the token. For your scenario, there is no need to specify the canceled since you want to Stop the service.
You could follow below step by step:
Create IHostedService
public class RecureHostedService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public RecureHostedService(ILogger<RecureHostedService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Starting");
_timer = new Timer(DoWork,null,TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_log.LogInformation("RecureHostedService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWork(object state)
{
_log.LogInformation("Timed Background Service is working.");
}
}
Register IHostedService
services.AddSingleton<IHostedService, RecureHostedService>();
Start and Stop Service
public class HomeController : Controller {
private readonly RecureHostedService _recureHostedService;
public HomeController(IHostedService hostedService)
{
_recureHostedService = hostedService as RecureHostedService;
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
_recureHostedService.StopAsync(new System.Threading.CancellationToken());
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
_recureHostedService.StartAsync(new System.Threading.CancellationToken());
return View();
} }
Using Blazor Server, you can start and stop background services in the following ways. Asp.net Core MVC or Razor is the same principle
First, implement an IHostService
public class BackService : IHostedService, IDisposable
{
private readonly ILogger _log;
private Timer _timer;
public bool isRunning { get; set; }
public BackService(ILogger<V2rayFlowBackService> log)
{
_log = log;
}
public void Dispose()
{
_timer.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation($"begin {DateTime.Now}");
_timer = new Timer(DoWorkAsync, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
isRunning = false;
_log.LogInformation($"{DateTime.Now} BackService is Stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private void DoWorkAsync(object state)
{
_log.LogInformation($"Timed Background Service is working. {DateTime.Now}");
try
{
isRunning = true;
// dosometing you want
}
catch (Exception ex)
{
isRunning = false;
_log.LogInformation("Error {0}", ex.Message);
throw ex;
}
}
}
Registration Service In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BackService>();
services.AddHostedService(sp => sp.GetRequiredService<BackService>());
}
Inject background services into Blazor components
public class IndexBase:ComponentBase
{
[Inject]
BackService BackService { set; get; }
protected override void OnInitialized()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
base.OnInitialized();
}
public void on()
{
if (!BackService.isRunning)
{
BackService.StartAsync(new System.Threading.CancellationToken());
}
}
public void off()
{
if (BackService.isRunning)
{
BackService.StopAsync(new System.Threading.CancellationToken());
}
}
}
#page "/"
#inherits IndexBase
<h1>Hello, world!</h1>
Welcome to your new app.
<button #onclick="on">Start</button>
<button #onclick="off">Stop</button>
reference
You should inherit your HostedService from your own Interface and register your service as Singleton, but in a difference way:
First register your service with AddHostedService generic method.
services.AddHostedService<TimerHostedService>();
Then add a public static field to your class named Instance, that holds the instance reference of your class and set its value in the constructor!
Then put a factory in the ConfigureServices for registering your service as singleton that returns the static instance field !
Here is the sample code:
(in your HostedService.cs: )
public interface ITimerHostedService : IHostedService
{
}
public class TimerHostedService : ITimerHostedService
{
private static TimerHostedService _instance;
public static TimerHostedService Instance => _instance;
public TimerHostedService(ILogger<TimerHostedService> logger)
{
if(_instance == null)
{
_instance = this;
}
}
}
And here is the code for registering your service as singleton ( in Startup.cs ):
services.AddHostedService<TimerHostedService>();
services.AddSingleton<ITimerHostedService, TimerHostedService>(serviceProvider =>
{
return TimerHostedService.Instance;
});
And here is the code in your Controller to manually start / stop your HostedService:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ITimerHostedService _hostedService;
public HomeController(ILogger<HomeController> logger, ITimerHostedService hostedService)
{
_logger = logger;
_hostedService = hostedService;
}
public async Task<IActionResult> Start()
{
await _hostedService.StartAsync(default);
return Ok();
}
public async Task<IActionResult> Stop()
{
await _hostedService.StopAsync(default);
return Ok();
}
}
Happy Coding!
Enjoy Your Lovely Moments :X