Running multiple backend services using iHostedService - asp.net-core

Currently my web API is able to run on a schedule and trigger another end point in order to sync data. The services that needs to be called are stored in a yml file. I have managed to get it working for one service to run a schedule. What I want is to be able to save multiple endpoints with schedules of their own and for them to be scheduled and executed at the right time.
Here is the code that I have now
I have done this using iHostedService interface.
This is the HostService class that implements iHostedService
public abstract class HostedService : IHostedService
{
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancel
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
cancellationToken.ThrowIfCancellationRequested();
}
// cancel
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
I am then extending this class and implementing what needs to be done in the ExecuteAsync as follows
public class DataRefreshService : HostedService
{
private readonly DataFetchService _dataFetchService;
public DataRefreshService(DataFetchService randomStringProvider)
{
_dataFetchService = randomStringProvider;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await _dataFetchService.UpdateData(cancellationToken);
TimeSpan span = _dataFetchService.GetNextTrigger();
await Task.Delay(span, cancellationToken);
}
} catch (Exception)
{
await StopAsync(cancellationToken);
throw new Exception("Error trigger Sync service");
}
}
}
This is what I have added to the Startup.cs file
services.AddSingleton<DataFetchService>();
services.AddSingleton<IHostedService, DataRefreshService>();

You could try
services.AddHostedService<DataRefreshService>;
You could also try in making the DataRefreshService inherit from
Microsoft.Extensions.Hosting.BackgroundService
You can read more about that here

Related

Can I pass CancellationToken as parameter in AspNetCore WebAPI

As far as I know if I use services.AddControllers() or services.AddMvc()extension in my Startup.cs "MVC will automatically bind any CancellationToken parameters in an action method.
I have the following TestController and TestService as Transient service.
According to this informations, when the auto-binded CancellationToken IsCancellationRequested will the tokens that I have passed as parameters be also canceled?
public class TestController : ControllerBase
{
private readonly ITestService _testService;
public TestController(ITestService testService)
{
_testService = testService;
}
[HttpGet, ActionName("Get")]
public async Task<IActionResult> GetAsync(CancellationToken cancellationToken)
{
await _testService.GetAsync(cancellationToken);
return Ok();
}
}
public class TestService : ITestService
{
public async Task GetAsync(CancellationToken cancellationToken)
{
//I also send the cancellationToken as a parameter to external API calls
}
}
when the auto-binded CancellationToken IsCancellationRequested will
the tokens that I have passed as parameters be also canceled?
By injecting a CancellationToken into your action method, which will be automatically bound to the HttpContext.RequestAborted token for the request. After the request is cancelled by the user refreshing the browser or click the "stop" button, the original request is aborted with a TaskCancelledException which propagates back through the MVC filter pipeline, and back up the middleware pipeline. Then, You could check the value of IsCancellationRequested and exit the action gracefully. In this scenario, there is no need to transfer the CancellationToken as parameters.
If you want to Cancel an Async task, since, CancellationTokens are lightweight objects that are created by a CancellationTokenSource. When a CancellationTokenSource is cancelled, it notifies all the consumers of the CancellationToken. So, you could call the Cancel() method to cancel the task.
Check the following sample:
var cts = new CancellationTokenSource();
//cts.CancelAfter(5000);//Request Cancel after 5 seconds
_logger.LogInformation("New Test start");
var newTask = Task.Factory.StartNew(state =>
{
int i = 1;
var token = (System.Threading.CancellationToken)state;
while (true)
{
Console.WriteLine(i);
i++;
if (i == 10)
{
cts.Cancel(); //call the Cancel method to cancel.
}
_logger.LogInformation("thread running " + DateTime.Now.ToString());
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
}
}, cts.Token, cts.Token);
try
{
newTask.Wait(10000);
}
catch
{
Console.WriteLine("Catch:" + newTask.Status);
}
_logger.LogInformation("Test end");
More detail information about using CancellationToken, please check the following articles:
Using CancellationTokens in ASP.NET Core MVC controllers

How to use Realm Collection Notifications in an ASP.NET Core Application?

I've tried using this sample code from the Realm .NET SDK but my handler code never gets called consistently:
var token = realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
I've tried running this from a number of different threads, but I suspect none of them have a looper/runloop, a requirement noted in the SDK. Is it even possible to create a looper/runloop thread in ASP.NET Core that will work with Realm? How would I do that?
Finally figured out how to implement the looper/run loop that works for Realm Notifications. The keys parts of the solution are:
Use a hosted service to start/stop the thread.
Use Nito.AsyncEx to establish a synchronization context for that thread.
Loop inside the thread, calling the RefreshAsync method on the realm instance.
Call Task.Delay between loop iterations to keep the thread cpu-friendly.
Here is the code:
public class RealmNotificationService : IHostedService
{
private System.Threading.Thread _thread;
public Task StartAsync(CancellationToken stoppingToken)
{
_thread = new System.Threading.Thread(() =>
Nito.AsyncEx.AsyncContext.Run(() => Looper(stoppingToken))
);
_thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
private async void Looper(CancellationToken stoppingToken)
{
using (var connection = await Realm.GetInstanceAsync({YOUR-CONFIG}))
{
var token = connection.All<{YOUR-OBJECT}>().SubscribeForNotifications((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
}
while (!stoppingToken.IsCancellationRequested)
{
await connection.Realm.RefreshAsync();
await Task.Delay(2000, stoppingToken);
}
}
}
Bill Raudabaugh's answer almost worked for me. However, the final while loop is outside of the using so connection is undefined.
I was able to get it to work by moving that loop inside of the using. Also I removed the RefreshAsync() as it seems it's not needed; Realm automatically updates in real time without having to refresh.
Here is my code:
public class RealmNotificationService : IHostedService
{
private System.Threading.Thread _thread;
public Task StartAsync(CancellationToken stoppingToken)
{
_thread = new System.Threading.Thread(() =>
Nito.AsyncEx.AsyncContext.Run(() => Looper(stoppingToken))
);
_thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
private async void Looper(CancellationToken stoppingToken)
{
using (var connection = await Realm.GetInstanceAsync({YOUR-CONFIG}))
{
var token = connection.All<{YOUR-OBJECT}>().SubscribeForNotifications((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(2000, stoppingToken);
}
}
}
}

How to get the current ClaimsPrincipal in SignalR core outside of a Hub

I have a SignalR Core hub which has a dependency on a service. That service itself has it's own dependencies and one of them requires access to the current ClaimsPrincipal.
I know, that I can access the ClaimsPrincipal inside the hub using the Context.User property and pass it as a parameter to the service, which can also pass it as a parameter and so on. But I really don't like to pollute the service API by passing this kind of ambient info as a parameter.
I've tried to use the IHttpContextAccessor as described in: https://learn.microsoft.com/en-us/aspnet/core/migration/claimsprincipal-current?view=aspnetcore-2.2
This seems to be working with a simple SignalR setup, but it isn't working with the Azure SignalR service, which will be our production setup.
Is there a reliable way how to get the ClaimsPrincipal outside of the hub that will work for both a simple local setup and Azure SignalR service?
In the current SignalR version (1.1.0) there is no support for this. I've created a feature request: https://github.com/dotnet/aspnetcore/issues/18657 but it was rejected. Eventually, I've ended up doing it like this:
services.AddSingleton(typeof(HubDispatcher<>), typeof(HttpContextSettingHubDispatcher<>));
public class HttpContextSettingHubDispatcher<THub> : DefaultHubDispatcher<THub> where THub : Hub
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextSettingHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext,
IOptions<HubOptions<THub>> hubOptions, IOptions<HubOptions> globalHubOptions,
ILogger<DefaultHubDispatcher<THub>> logger, IHttpContextAccessor httpContextAccessor) :
base(serviceScopeFactory, hubContext, hubOptions, globalHubOptions, logger)
{
_httpContextAccessor = httpContextAccessor;
}
public override async Task OnConnectedAsync(HubConnectionContext connection)
{
await InvokeWithContext(connection, () => base.OnConnectedAsync(connection));
}
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
{
await InvokeWithContext(connection, () => base.OnDisconnectedAsync(connection, exception));
}
public override async Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
{
switch (hubMessage)
{
case InvocationMessage _:
case StreamInvocationMessage _:
await InvokeWithContext(connection, () => base.DispatchMessageAsync(connection, hubMessage));
break;
default:
await base.DispatchMessageAsync(connection, hubMessage);
break;
}
}
private async Task InvokeWithContext(HubConnectionContext connection, Func<Task> action)
{
var cleanup = false;
if (_httpContextAccessor.HttpContext == null)
{
_httpContextAccessor.HttpContext = connection.GetHttpContext();
cleanup = true;
}
await action();
if (cleanup)
{
_httpContextAccessor.HttpContext = null;
}
}
}

How to send constantly updates using .Net Core SignalR?

I am new to SignalR and I would like to build such app -- every second a hub sends current time to all connected clients.
I found tutorial, but it is for .Net Framework (not Core): https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-high-frequency-realtime-with-signalr So on one hand I don't know how to translate it to .Net Core SignalR, on the other hand I don't know how to write it from scratch (the limiting condition is the fact a hub is a volatile entity, so I cannot have state in it).
I need something static (I guess) with state -- let's say Broadcaster, when I create some cyclic action which in turn will send updates to clients. If such approach is OK, how to initialize this Broadcaster?
Currently I added such static class:
public static class CrazyBroadcaster
{
public static void Initialize(IServiceProvider serviceProvider)
{
var scope = serviceProvider.CreateScope();
var hub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
var sub = Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(_ => hub.Clients.All.SendAsync("Bar", DateTimeOffset.UtcNow));
}
}
Yes, I know it is leaky. I call this method at the end of Startup.Configure, probably tons of violations here, but so far it is my best shot.
The missing piece was hosted service, i.e. the code that runs in the background -- https://learn.microsoft.com/en-US/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2.
So my crazy class is now transformed into:
public sealed class HostedBroadcaster : IHostedService, IDisposable
{
private readonly IHubContext<ChatHub> hubContext;
private IDisposable subscription;
public HostedBroadcaster(IHubContext<ChatHub> hubContext)
{
this.hubContext = hubContext;
}
public void Dispose()
{
this.subscription?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.subscription = Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(_ => hubContext.Clients.All.SendAsync("Bar", DateTimeOffset.UtcNow));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.subscription?.Dispose();
return Task.CompletedTask;
}
}

Execute DB call within Scheduled Job

I got a project built under ASP Core 2 that utilizes the Quartz.NET scheduler 3-beta1
I've got the following job that i want to execute:
public class TestJob: IJob
{
private readonly AppDbContext _dbContext;
public TestJob(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public Task Execute(IJobExecutionContext context)
{
Debug.WriteLine("Test check at " + DateTime.Now);
var testRun = _dbContext.TestTable.Where(o => o.CheckNumber > 10).ToList();
Debug.WriteLine(testRun.Count);
return Task.CompletedTask;
}
}
Unfortunately it never works and there are not error logs to indicate an issue.
Yet when i remove everything and just leave the Debug.WriteLine it works as per below example.
public class TestJob: IJob
{
public Task Execute(IJobExecutionContext context)
{
Debug.WriteLine("Test check at " + DateTime.Now);
return Task.CompletedTask;
}
}
How can i get my job to execute the database call?
EDIT 1: Job Creation
var schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = schedulerFactory.GetScheduler().Result;
_scheduler.Start().Wait();
var testJob = JobBuilder.Create<TestJob>()
.WithIdentity("TestJobIdentity")
.Build();
var testTrigger = TriggerBuilder.Create()
.WithIdentity("TestJobTrigger")
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever())
.Build();
if (CheckIfJobRegistered(testJob.Key).Result == false)
_scheduler.ScheduleJob(testJob, testTrigger).Wait();
The main problem here is that Quartz can't create the job and swallows the exception.
The Documentation states:
When a trigger fires, the JobDetail (instance definition) it is associated to is loaded, and the job class it refers to is instantiated via the JobFactory configured on the Scheduler. The default JobFactory simply calls the default constructor of the job class using Activator.CreateInstance, then attempts to call setter properties on the class that match the names of keys within the JobDataMap. You may want to create your own implementation of JobFactory to accomplish things such as having your application’s IoC or DI container produce/initialize the job instance.
Quartz provides the IJobFactory to achieve that. And it works really good with Dependency Injection. A JobFactory can look like this:
public class JobFactory : IJobFactory
{
//TypeFactory is just the DI Container of your choice
protected readonly TypeFactory Factory;
public JobFactory(TypeFactory factory)
{
Factory = factory;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
return Factory.Create(bundle.JobDetail.JobType) as IJob;
}
catch (Exception e)
{
//Log the error and return null
//every exception thrown will be swallowed by Quartz
return null;
}
}
public void ReturnJob(IJob job)
{
//Don't forget to implement this,
//or the memory will not released
Factory.Release(job);
}
}
Then just register your JobFactory with the scheduler and everything should work:
_scheduler.JobFactory = new JobFactory(/*container of choice*/);
Edit:
Additionally you can take a look to one of my previous answers.