The problem
I'm using .NET Core 2.2 with ASP.NET Core SignalR. Currently I'm saving all connection states in a SQL database (see this document; even though it's a manual for the "old" SignalR library, the logic is the same). I'm also using a Redis backplane, since my application can scale horizontally.
However, when restarting my application, current connections do not get closed and will get orphaned. The previously linked article states:
If your web servers stop working or the application restarts, the
OnDisconnected method is not called. Therefore, it is possible that
your data repository will have records for connection ids that are no
longer valid. To clean up these orphaned records, you may wish to
invalidate any connection that was created outside of a timeframe that
is relevant to your application.
The question
In the "old" SignalR there is an ITransportHeartbeat (which this script perfectly implements) but there's no such interface for the .NET Core version (atleast, I couldn't find it).
How do I know whether an connection is no longer alive? I want (or actually need) to clean up old connection id's.
The solution I came up with is as follows. It's not as elegant, but for now I see no other option.
I updated the model in the database to not only contain a ConnectionId but also a LastPing (which is a DateTime type). The client sends a KeepAlive message (custom message, not using the SignalR keepalive settings). Upon receiving the message (server side), I update the database with the current time:
var connection = _context.Connection.FirstOrDefault(x => x.Id == Context.ConnectionId);
connection.LastPing = DateTime.UtcNow;
To clean up the orphaned connections (which are not removed by SignalR's OnDisconnected method), I have a task running periodically (currently in Hangfire) which removes the connections where the LastPing field has not been updated recently.
Updated after #davidfowl's comments in the other answer.
.NET Core 2.1 with SignalR has IConnectionHeartbeatFeature which you can use to achieve something similar to what you could with ITransportHeartbeat in old SignalR.
The main crux of the code below is that we maintain an in-memory list that tracks connections that need to be updated in the database. This allows us to do expensive database operations at a controlled interval and in batch. IConnectionHeartbeatFeature.OnHeartbeat() is fired every second for each connection, so hitting the database at that frequency could take your server down at scale.
Firstly create an entity to maintain a list of connections in memory that the server has yet to update:
public interface IConnectionCounter
{
internal ConcurrentDictionary<string, DateTime> Connections { get; }
public void RecordConnectionLastSeen(string connectionId);
public void RemoveConnection(string connectionId);
}
/// <summary>
/// Maintains a dictionary of connections that need to be refreshed in the
/// database
/// </summary>
public class ConnectionCounter : IConnectionCounter
{
private readonly ConcurrentDictionary<string, DateTime> _connections;
ConcurrentDictionary<string, DateTime> IConnectionCounter.Connections
=> _connections;
public ConnectionCounter()
{
_connections = new ConcurrentDictionary<string, DateTime>();
}
public void RecordConnectionLastSeen(string connectionId)
{
var now = DateTime.UtcNow;
_connections.AddOrUpdate(
connectionId,
now, (existingConnectionId, oldTime) => now);
}
public void RemoveConnection(string connectionId)
{
_connections.Remove(connectionId, out _);
}
}
Note, this is NOT a definitive list of all online connections that need to be updated, as connections may be distributed across multiple servers. If you've got many servers, you could reduce the load further by storing these connections in a distributed in-memory store like Redis.
Next, set up the IConnectionCounter in the Hub so that connections are counted.
public class ChatHub : Hub
{
private readonly IConnectionCounter _connectionCounter;
public ChatHub(
IConnectionCounter connectionCounter)
{
_connectionCounter = connectionCounter;
}
[AllowAnonymous]
public override Task OnConnectedAsync()
{
var connectionHeartbeat =
Context.Features.Get<IConnectionHeartbeatFeature>();
connectionHeartbeat.OnHeartbeat(connectionId => {
_connectionCounter.RecordConnectionLastSeen((string)connectionId);
}, Context.ConnectionId);
return base.OnConnectedAsync();
}
}
Now create a service that takes the connections in IConnectionCounter and updates the database with the state of said connection:
public interface IPresenceDatabaseSyncer
{
public Task UpdateConnectionsOnlineStatus();
}
/// <summary>
/// Handles updating the online status of connections whose connections
/// that need to be updated in the database
/// </summary>
public class PresenceDatabaseSyncer : IPresenceDatabaseSyncer
{
private readonly MyDbContext _context;
private readonly IConnectionCounter _connectionCounter;
public PresenceDatabaseSyncer(
MyDbContext context,
IConnectionCounter connectionCounter)
{
_context = context;
_connectionCounter = connectionCounter;
}
public async Task UpdateConnectionsOnlineStatus()
{
if (_connectionCounter.Connections.IsEmpty)
return;
foreach (var connection in _connectionCounter.Connections)
{
var connectionId = connection.Key;
var lastPing = connection.Value;
var dbConnection = _context.Connection
.FirstOrDefault(x => x.ConnectionId == connectionId);
if (dbConnection != null)
dbConnection.LastPing = lastPing;
_connectionCounter.RemoveConnection(connectionId);
}
}
}
I then use a HostedService to continuously run the db sync above:
/// <summary>
/// Runs a periodic sync operation to ensure that connections are
/// recorded as being online correctly in the database
/// </summary>
public class PresenceDatabaseSyncerHostedService : IHostedService, IDisposable
{
private const int SyncIntervalSeconds = 10;
private readonly IServiceScopeFactory _serviceScopeFactory;
private Timer _timer;
public PresenceDatabaseSyncerHostedService(
IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(
DoWork,
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(SyncIntervalSeconds));
return Task.CompletedTask;
}
private async void DoWork(object state)
{
using var scope = _serviceScopeFactory.CreateScope();
var scopedProcessingService =
scope.ServiceProvider.GetRequiredService<IPresenceDatabaseSyncer>();
await scopedProcessingService.UpdateConnectionsOnlineStatus();
}
public Task StopAsync(CancellationToken stoppingToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Finally register these dependencies and services:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IConnectionCounter, ConnectionCounter>();
services.AddScoped<IPresenceDatabaseSyncer, PresenceDatabaseSyncer>();
services.AddHostedService<PresenceDatabaseSyncerHostedService>();
// ...
}
// ...
}
Of course there is still the matter of actually cleaning up the stale connections from the database. I handle this using another HostedService and will leave as an exercise to the reader.
If you're using the Azure SignalR Service, there's an additional benefit over manually sending a KeepAlive message as per #Devator's answer in that you don't need to pay for the message (since OnHeartbeat occurs internally).
Keep in mind that this feature is not really documented that well. I've been using this in production for a few months now, but I haven't seen other solutions using this technique.
Related
I'm trying to write a notification system between a server and multiple clients using gRPC server streaming in protobuf-net.grpc (.NET Framework 4.8).
I based my service off of this example. However, if I understand the example correctly, it is only able to handle a single subscriber (as _subscriber is a member variable of the StockTickerService class).
My test service looks like this:
private readonly INotificationService _notificationService;
private readonly Channel<Notification> _channel;
public ClientNotificationService(INotificationService notificationService)
{
_notificationService = notificationService;
_notificationService.OnNotification += OnNotification;
_channel = Channel.CreateUnbounded<Notification>();
}
private async void OnNotification(object sender, Notification notification)
{
await _channel.Writer.WriteAsync(notification);
}
public IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
return _channel.AsAsyncEnumerable(context.CancellationToken);
}
INotificationService just has an event OnNotification, which is fired when calling its Notify method.
I then realized that System.Threading.Channels implements the Producer/Consumer pattern, but I need the Publisher/Subscriber pattern. When trying it out, indeed only one of the clients gets notified, instead of all of them.
It would also be nice if the server knew when a client disconnects, which seems impossible when returning _channel.AsAsyncEnumerable.
So how can I modify this in order to
serve multiple clients, with all of them being notified when OnNotification is called
and log when a client disconnects?
For 1, you'd need an implementation of a publisher/subscriber API; each call to SubscribeAsync will always represent a single conversation between gRPC endpoints, so you'll need your own mechanism for broadcasting that to multiple consumers. Maybe RX is worth investigating there
For 2, context.CancellationToken should be triggered by client-disconnect
Many thanks to Marc Gravell
I rewrote the NotificationService like this, using System.Reactive.Subjects (shortened) - no need for an event, use an Action instead:
public class NotificationService<T>
{
private readonly Subject<T> _stream = new Subject<T>();
public void Publish(T notification)
{
_stream.OnNext(notification);
}
public IDisposable Subscribe(Action<T> onNext)
return _stream.Subscribe(onNext);
}
}
My updated ClientNotificationService, which is exposed as a code-first gRPC service:
public class ClientNotificationService : IClientNotificationService
{
private readonly INotificationService<Notification> _notificationService;
public ClientNotificationService(INotificationService<Notification> notificationService)
{
_notificationService = notificationService;
}
public async IAsyncEnumerable<Notification> SubscribeAsync(CallContext context = default)
{
try
{
Channel<Notification> channel = Channel.CreateUnbounded<Notification>(
new UnboundedChannelOptions { SingleReader = true, SingleWriter = true });
CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken);
using (_notificationService.Subscribe(n => channel.Writer.WriteAsync(n, cts.Token)))
{
await foreach (Notification notification in channel.AsAsyncEnumerable(cts.Token))
{
yield return notification;
}
}
}
finally
{
// canceled -> log, cleanup, whatever
}
}
}
Note: Solution provided by OP on question section.
Background
I'm working on updating legacy software library. The legacy code uses an infinitely looping System.Threading.Thread that executes processes in the queue. These processes perform multiple requests with another legacy system that can only process one request at a time.
I'm trying to modernize, but I'm new to WCF services and there may be a big hole in my knowledge that'd simplify things.
WCF Client-Side Host
In modernizing, I'm trying to move to a client-side WCF service. The WCF service allows requests to be queued from multiple a applications. The service takes a request and returns a GUID back so that I can properly associate via the callbacks.
public class SomeService : ISomeService
{
public Guid AddToQueue(Request request)
{
// Code to add the request to a queue, return a Guid, etc.
}
}
public interface ISomeCallback
{
void NotifyExecuting(Guid guid)
void NotifyComplete(Guid guid)
void NotifyFault(Guid guid, byte[] data)
}
WCF Client Process Queues
The problem I'm having is that the legacy processes can include more than one request. Process 1 might do Request X then Request Y, and based on those results follow up with Request Z. With the legacy system, there might be Processes 1-10 queued up.
I have a cludgy model where the process is executed. I'm handling events on the process to know when it's finished or fails. But, it just feels really cludgy...
public class ActionsQueue
{
public IList<Action> PendingActions { get; private set; }
public Action CurrentAction { get; private set; }
public void Add(Action action)
{
PendingAction.Add(action)
if (CurrentAction is null)
ExecuteNextAction();
}
private void ExecuteNextAction()
{
if (PendingActions.Count > 0)
{
CurrentAction = PendingActions[0];
PendingActions.RemoveAt(0);
CurrentAction.Completed += OnActionCompleted;
CurrentAction.Execute();
}
}
private OnActionCompleted(object sender, EventArgs e)
{
CurrentAction = default;
ExecuteNextAction();
}
}
public class Action
{
internal void Execute()
{
// Instantiate the first request
// Add handlers to the first request
// Send it to the service
}
internal void OnRequestXComplete()
{
// Use the data that's come back from the request
// Proceed with future requests
}
}
With the client-side callback the GUID is matched up to the original request, and it raises a related event on the original requests. Again, the implementation here feels really cludgy.
I've seen example of Async methods for the host, having a Task returned, and then using an await on the Task. But, I've also seen recommendations not to do this.
Any recommendations on how to untangle this mess into something more usable are appreciated. Again, it's possible that there's a hole in my knowledge here that's keeping me from a better solutiong.
Thanks
Queued communication between the client and the server of WCF is usually possible using a NetMsmqbinding, which ensures persistent communication between the client and the server. See this article for specific examples.
If you need efficient and fast message processing, use a non-transactional queue and set the ExactlyOnce attribute to False, but this has a security impact. Check this docs for further info.
In case anyone comes along later with a similar issue, this is a rough sketch of what I ended up with:
[ServiceContract(Name="MyService", SessionMode=Session.Required]
public interface IMyServiceContract
{
[OperationContract()]
Task<string> ExecuteRequestAsync(Action action);
}
public class MyService: IMyServiceContract
{
private TaskQueue queue = new TaskQueue();
public async Task<string> ExecuteRequestAsync(Request request)
{
return await queue.Enqueue(() => request.Execute());
}
}
public class TaskQueue
{
private SemaphoreSlim semaphore;
public TaskQueue()
{
semaphore = new SemaphoreSlim(1);
}
Task<T> Enqueue<T>(Func<T> function)
{
await semaphore.WaitAsync();
try
{
return await Task.Factory.StartNew(() => function.invoke();)
}
finally
{
semaphore.Release();
}
}
}
Net Core and EF core does not support AAD tokens out of the box like full framework. There are a workaroudn were you can set access token on the SqlConnection. Retrieving the token is a async operation. So I need a generic entrypoint that are async. In constructor of my DbContext I can inject and execute stuff, but I cant do it async so it not good enough.
Any ideas? Thanks
internal class DbTokenConfig : IDbContextConfig
{
private readonly ITokenProvider _tokenProvider;
public DbTokenConfig(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
public async Task Config(MyDbContext context)
{
var conn = context.Database.GetDbConnection() as SqlConnection;
conn.AccessToken = await _tokenProvider.GetAsync();
}
}
I need a async entrypoint were I can execute it, generic offcourse so any service that inject a DbContext will get it applied
edit: So basicly when doing
public class MyCommandHandler : ICommandHandler<MyCommand>
{
private readonly DbContext _ctx;
public MyCommandHandler(DbContext ctx)
{
_ctx = ctx;
}
public async Task Handle(MyCommand cmd)
{
await _ctx.Set<Foo>().ToListAsync(); //I want my access token to be applied before it opens connection
}
}
edit: Working solution
.AddDbContext<MyDbContext>(b => b.UseSqlServer(Configuration.GetConnectionString("MyDb")))
.AddScoped<DbContext>(p =>
{
var ctx = new AuthenticationContext("https://login.microsoftonline.com/xxx");
var result = ctx.AcquireTokenAsync("https://database.windows.net/", new ClientCredential("xxx", "xxx"))
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
var db = p.GetService<MyDbContext>();
((SqlConnection)db.Database.GetDbConnection()).AccessToken = result.AccessToken;
return db;
})
Just need to make the keys configurable, create a abstraction etc
There's a Github issue about this, so this is definitely not unclear. The issue is closed because there's no built-in support currently, a different issue tracks this.
The original issue describes a clever workaround though. First of all, UseSqlBuilder has an overload that accepts an existing DbConnection. This connection can be configured with an AAD token. If it's closed, EF will open and close it as needed. One could write :
services.AddDbContext<MyDBContext>(options => {
SqlConnection conn = new SqlConnection(Configuration["ConnectionString"]);
conn.AccessToken = (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/")
.Result;
options.UseSqlServer(conn);
});
The tricky part is how to dispose that connection.
The clever solution posted by Brian Ball is to implement an interface on the DbContext, and register that as the service that's used by controllers with a factory function. The DbContext still gets registered using its concrete type. The factory function gets that context and sets the AAD token to its connection :
services.AddDbContext<MyDbContext>(builder => builder.UseSqlServer(connectionString));
services.AddScoped<IMyDbContext>(serviceProvider => {
//Get the configured context
var dbContext = serviceProvider.GetRequiredService<MyDbContext>();
//And set the AAD token to its connection
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
if(connection == null) {/*either return dbContext or throw exception, depending on your requirements*/}
connection.AccessToken = //code used to acquire an access token;
return dbContext;
});
This way, the context's lifetime is still managed by EF Core. AddScoped<IMyDbContext> acts as a filter that takes that context and sets the AAD token
Next problem is how to write that //code used to acquire an access token; so it doesn't block.
This isn't so much of a problem because, according to the docs :
The AzureServiceTokenProvider class caches the token in memory and retrieves it from Azure AD just before expiration.
This code could be extracted into a factory method, and even get injected as a dependency.
Moving the goal posts
The main problem is that constructors can't be asynchronous yet so constructor injection can't retrieve tokens asynchronously.
What can be done though, is to register an asynchronous Func<> factory or service that's called in a controller's asynchronous actions instead of the constructor. Let's say :
//Let's inject configuration too
//Defaults stolen from AzureServiceTokenProvider's source
public class TokenConfig
{
public string ConnectionString {get;set;};
public string AzureAdInstance {get;set;} = "https://login.microsoftonline.com/";
public string TennantId{get;set;}
public string Resource {get;set;}
}
class DbContextWithAddProvider
{
readonly AzureServiceTokenProvider _provider;
readonly TokenConfig _config;
readonly IServiceProvider _svcProvider;
public DbContextWithAddProvider(IServiceProvider svcProvider, IOption<TokenConfig> config)
{
_config=config;
_provider=new AzureServiceTokenProvider(config.ConnectionString,config.AzureAdInstance);
_svcProvider=svcProvider;
}
public async Task<T> GetContextAsync<T>() where T:DbContext
{
var token=await _provider.GetAccessTokenAsync(_config.Resource,_config.TennantId);
var dbContext = _svcProvider.GetRequiredService<T>();
var connection = dbContext.Database.GetDbConnection() as System.Data.SqlClient.SqlConnection;
connection.AccessToken = token;
return dbContext;
}
}
This service should be registered as a singleton as it doesn't keep any state except the cached token, which we do want to keep around.
This can now be injected in a constructor, and called in an async action :
class MyController:Controller
{
DbContextWithAddProvider _ctxProvider;
public MyController(DbContextWithAddProvider ctxProvider)
{
_ctxProvider=ctxProvider;
}
public async Task<IActionResult> Get()
{
var dbCtx=await _ctxProvider.GetContextAsync<MyDbContext>();
...
}
}
I went through a similar though process almost 2 years ago where, in my last job, we decided to implement dynamic refreshing of the credentials for a DbContext object which it retrieved from Key Vault on the applications initial startup and then cached the credentials, if a connection failed then it was assumed that the credentials had changed or expired and it would retrieve them again and refresh the SqlConnection object (happy-path scenario, obviously there are other reasons for a connection to fail).
The problem then, and in this case, is that IServiceCollection has no asynchronous method available which allow you to invoke asynchronous delegates, so you have to use .Result when registering a service with asynchronous logic as a prerequisite.
What you could do is create a SqlConnection object with your access token and pass that to SqlServerDbContextOptionsExtensions.UseSqlServer within the AddDbContext<T> service registration in ConfigureServices. This ensures that every DbContext which is created will have an access token assigned, and with it being scoped by default it will have a new token per request.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<ITokenProvider, TokenProvider>();
services.AddScoped<ISqlConnectionProvider, SqlConnectionProvider>();
services.AddDbContext<TestDbContext>((provider, options) =>
{
var connectionTokenProvider = provider.GetService<ITokenProvider>();
var sqlConnectionProvider = provider.GetService<ISqlConnectionProvider>();
var accessToken = connectionTokenProvider.GetAsync().Result; // Yes, I consider this to be less than elegant, but marking this delegate as async & awaiting would result in a race condition.
var sqlConnection = sqlConnectionProvider.CreateSqlConnection(accessToken);
options.UseSqlServer(sqlConnection);
});
}
The interface for ISqlConnectionProvider is
internal interface ISqlConnectionProvider
{
SqlConnection CreateSqlConnection(string accessToken);
}
In the implementation of ISqlConnectionProvider you'd have to
Inject an IOptions<T> object which contains the connection string details
Build or assign the connection string
Assign the access token
Return the SqlConnection object
I have .NET Core Web API solution. In each call, I need to perform some database operations. The issue is at a time multiple db connections get opened & close. So to avoid it, I want to implement Queue of objects to be sent to database and then want a separate thread to perform db operation.
I've tried some code as below. But here, Consumer thread never executes assigned function. There is no separate thread for Producer, I am simply feeding queue with object.
What modifications I should do? Need some guidance as I'm new to Threading stuff.
public static class BlockingQueue
{
public static Queue<WebServiceLogModel> queue;
static BlockingQueue()
{
queue = new Queue<WebServiceLogModel>();
}
public static object Dequeue()
{
lock (queue)
{
while (queue.Count == 0)
{
Monitor.Wait(queue);
}
return queue.Dequeue();
}
}
public static void Enqueue(WebServiceLogModel webServiceLog)
{
lock (queue)
{
queue.Enqueue(webServiceLog);
Monitor.Pulse(queue);
}
}
public static void ConsumerThread(IConfiguration configuration)
{
WebServiceLogModel webServiceLog = (WebServiceLogModel)Dequeue();
webServiceLog.SaveWebServiceLog(configuration);
}
public static void ProducerThread(WebServiceLogModel webServiceLog)
{
Enqueue(webServiceLog);
Thread.Sleep(100);
}
}
I've created and started thread in StartUp.cs:
public Startup(IConfiguration configuration)
{
Thread t = new Thread(() => BlockingQueue.ConsumerThread(configuration));
t.Start();
}
In Controller, I've written code to feed the queue:
[HttpGet]
[Route("abc")]
public IActionResult GetData()
{
BlockingQueue.ProducerThread(logModel);
return StatusCode(HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound, ApplicationConstants.Message.NoBatchHistoryInfo);
}
First of all, try to avoid static classes and methods. Use pattern singleton in that case (and if you really need this).
Second, try to avoid lock, Monitor - those concurrency primitives significantly lower your performance.
In such situation, you can use BlockingCollection<> as 'Adam G' mentioned above, or you can develop your own solution.
public class Service : IDisposable
{
private readonly BlockingCollection<WebServiceLogModel> _packets =
new BlockingCollection<WebServiceLogModel>();
private Task _task;
private volatile bool _active;
private static readonly TimeSpan WaitTimeout = TimeSpan.FromSeconds(1);
public Service()
{
_active = true;
_task = ExecTaskInternal();
}
public void Enqueue(WebServiceLogModel model)
{
_packets.Add(model);
}
public void Dispose()
{
_active = false;
}
private async Task ExecTaskInternal()
{
while (_active)
{
if (_packets.TryTake(out WebServiceLogModel model))
{
// TODO: whatever you need
}
else
{
await Task.Delay(WaitTimeout);
}
}
}
}
public class MyController : Controller
{
[HttpGet]
[Route("abc")]
public IActionResult GetData([FromServices] Service service)
{
// receive model form somewhere
WebServiceLogModel model = FetchModel();
// enqueue model
service.Enqueue(model);
// TODO: return what you need
}
}
And in Startup:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Service>();
// TODO: other init staffs
}
}
You even can add Start/Stop methods to the service instead of implementing IDisposable and start your service in the startup class in the method Configure(IApplicationBuilder app).
I think your consumer thread is executed just once if there is something in the queue and then immediately returns. If you want to have a thread doing work in background, which is started just once, it should never return and should catch all exceptions. Your thread from BlockingQueue.ConsumerThread is invoked once in Stratup and returns.
Also please be aware that doing such solution is not safe. ASP.NET doesn't guarantee background threads to be running if there are no requests coming in. Your application pool can recycle (and by default it recycles after 20 minutes of inactivity or every 27 hours), so there is a chance that your background code won't be executed for some queue items.
Also, while it doesn't solve all issues, I would suggest using https://www.hangfire.io/ to do background tasks in ASP.NET server. It has persistence layer, can retry jobs and has simple API's. In your request handler you can push new jobs to Hangfire and then have just 1 job processor thread.
In my web application (ASP.NET Core), I want to run a job in the background that is listening to a remote server, calculating some results and pushing it to the client on Pusher (a websocket).
I'm not sure where I'm supposed to start this task. Currently I start it at the end of
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
in Startup.cs
but I think there is something wrong about that, it doesn't make sense to start background jobs in a method called "Configure". I was expecting to find a Start method somewhere
Also, when I try to use EF Core to generate initial database migration file, it actually executes that method and starts my tasks.. which clearly doesn't make any sense:
dotnet ef migrations add InitialCreate
running that from console creates migration code which will be used to create the database on SQL Server based on my data models.
Why isn't there a method where I can start some a Task? I don't want this to be on a separate process, it really doesn't need its own process and it is essentially a part of the web server because it does communicate with the client (browser) via a websocket, so it makes sense to run it as part of the web server.
I believe you're looking for this
https://blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/
And i did a 2 hour self-proclaimed-award-winning hackathon against myself to learn abit of that.
https://github.com/nixxholas/nautilus
You can refer the injections here and implement the abstracts from there too.
Many MVC projects are not really required to operate persistent background tasks. This is why you don't see them baked into a fresh new project via the template. It's better to provide developers an interface to tap on and go ahead with it.
Also, with regards to opening that socket connection for such background tasks, I have yet to establish a solution for that. As far as I know/did, I was only able to broadcast payload to clients that are connected to my own socketmanager so you'll have to look elsewhere for that. I'll definitely beep if there is anything regarding websockets in an IHostedService.
Ok anyway here's what happens.
Put this somewhere in your project, its more of an interface for you to overload with to create your own task
/// Copyright(c) .NET Foundation.Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
protected readonly IServiceScopeFactory _scopeFactory;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
public BackgroundService(IServiceScopeFactory scopeFactory) {
_scopeFactory = scopeFactory;
}
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Here's how you can actually use it
public class IncomingEthTxService : BackgroundService
{
public IncomingEthTxService(IServiceScopeFactory scopeFactory) : base(scopeFactory)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<NautilusDbContext>();
Console.WriteLine("[IncomingEthTxService] Service is Running");
// Run something
await Task.Delay(5, stoppingToken);
}
}
}
}
If you noticed, there's a bonus there. You'll have to use a servicescope in order to access db operations because its a singleton.
Inject your service in
// Background Service Dependencies
services.AddSingleton<IHostedService, IncomingEthTxService>();