How to use Realm Collection Notifications in an ASP.NET Core Application? - asp.net-core

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);
}
}
}
}

Related

How to wait for asp.net core configuration to be done for backgroundworkers?

I created a blazor server application which also makes use of BackgroundService / IHostedService - Unfortunately those services Start+Execute before the Configure method within Startup is called. Therefore migrations/database creation are not done before they are used in the services.
I already found a workaround for this, but i am wondering if there is a proper way to solve this.
My workaround for those who have the same issue is this:
public class ApplicationStateTransmitter
{
private readonly TaskCompletionSource _configurationDone;
public ApplicationStateTransmitter()
{
_configurationDone = new TaskCompletionSource(null);
}
public Task ConfigurationDone => _configurationDone.Task;
public void NotifyConfigurationDone() => _configurationDone.SetResult();
}
Startup.ConfigureServices:
services.AddSingleton<ApplicationStateTransmitter>();
Startup.Configure:
using (var serviceScope = serviceScopeFactory.CreateScope())
{
using (var context = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
if (!context.Database.CanConnect())
{
logger.LogInformation("Database does not exist yet - Creating database through migrations");
context.Database.Migrate();
}
else
{
if (context.Database.GetPendingMigrations().Any())
{
logger.LogInformation("Applying database migration");
context.Database.Migrate();
logger.LogInformation("Applying database migration done");
}
else
{
logger.LogDebug("No pending migrations found");
}
}
}
}
applicationStateTransmitter.NotifyConfigurationDone();
logger.LogInformation("Configuration done");
Inject instance and await like this
await _applicationStateTransmitter.ConfigurationDone;

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;
}
}
}

AspNetCore.SignalR SendAsync not firing inside OnConnectedAsync

I am having an issue where I would like to send an event to the frontend whenever somebody is connected to the hub, but the notification is not being received on the front end. I think I may be confused between calling methods directly from the hub vs. utilizing the IHubContext. I was not able to find much information related to these versions, so your help will be greatly appreciated!
Package versions:
Server side (.Net Core 2.2): Microsoft.AspNetCore.SignalR (1.1.0)
Client side (React): #aspnet/signalr:1.1.0
So this is my example Hub:
public class MyHub: Hub<IMyHub>
{
public override async Task OnConnectedAsync()
{
// This newMessage call is what is not being received on the front end
await Clients.All.SendAsync("newMessage", "test");
// This console.WriteLine does print when I bring up the component in the front end.
Console.WriteLine("Test");
await base.OnConnectedAsync();
}
public Task SendNewMessage(string message)
{
return Clients.All.SendAsync("newMessage", message);
}
}
Now the working call I have so far is in a service, but that is sending "newMessage" like so:
public class MessageService: IMessageService
{
private readonly IHubContext<MyHub> _myHubContext;
public MessageService(IHubContext<MyHub> myHubContext)
{
_myHubContext = myHubContext;
}
public async Task SendMessage(string message)
{
// I noticed tis calls SendAsync from the hub context,
// instead of the SendMessage method on the hub, so maybe
// the onConnectedAsync needs to be called from the context somehow also?
await _myHubContext.Clients.All.SendAsync("newMessage", message);
}
}
So the above service method call works and will contact the front end, this is an example of my front end connection in a react component:
const signalR = require('#aspnet/signalr');
class MessageComponent extends React.Component {
connection: any = null;
componentDidMount() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:9900/myHub')
.build();
this.connection.on('newMessage', (message: string) => {
// This works when called from the service IHubContext
// but not OnConncectedAsync in MyHub
console.log(message);
});
this.connection.start();
}
componentWillUnmount() {
this.connection.stop();
}
render() {
...
}
}
This is because you are using a Strongly Typed Hub (https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2#strongly-typed-hubs).
I assume you defined SendAsync on your IMyHub interface and so the server is sending a message with method = SendAsync, arguments = "newMessage", "test". If you removed your IMyHub type then this will work as expected.

Running multiple backend services using iHostedService

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

WCF - Multiple Processes

I have an wcf SERVICE that get a request of branch code and return 5 products list values.
In server side, each product process takes about 6 sec to be completed, so if I run synchronous it will take 30 seconds. I altered my server code and run them in parallel. It works fine, but it seems that each parallel process get threads from IIS threadspool.
Now I am thinking to run asynchronous each product processes and when all will be finished then return the whole list of products to client. It will be grateful if you help me about that..
How can I run asynchronous these processes in server side ? No matter if client run sync or async. That what I need is to run async product processes in order to serve the results faster.
Try this asynchronous approach with generics type:
namespace InterNameSpace
{
[ServiceContract]
public interface ITestClass
{
[OperationContract]
Task<AnyComplexType> xpto(string baseUrl, string getAsyncUrl);
}
}
namespace Test
{
ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode.PerSession)]
public class TestClass : ITestClass
{
public async Task<AnyComplexType> xpto(string baseUrl, string getAsyncUrl)
{
try
{
return await TestAux.Auxiliar.RetrieveDeserializeObject<AnyComplexType>(baseUrl, getAsyncUrl);
}
catch (Exception)
{
return null;
}
}
}
}
Create a Product complex-type, pass it to your call method. In this method use the keyword async and task to call the deserialized method as asynchronous.
Your deserialized method recieve the baseurl and the controller route. As a genetic type you can pass different complex-type and deserialaze this object and return it.
namespace TestAux
{
public class Auxiliar
{
public static async Task<T> RetrieveDeserializeObject<T>(string baseUrl, string getAsyncUrl) where T : class
{
try
{
T deserializeObject = null;
HttpClient httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.GetAsync(getAsyncUrl);
//throw an exception if not successful
if (response.EnsureSuccessStatusCode().IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
// DeserializeObject our concrete class into a JSON String
deserializeObject = await Task.Run(() => JsonConvert.DeserializeObject<T>(content));
}
return deserializeObject;
}
catch (Exception)
{
return null;
}
}
}
}