SignalR connection closed while refreshing the page - signalr

Im using SignalR for real time notification. My problem is that, after initiating connection to the server HUB, the connection gets disconnected if I refresh the page. Can anybody give me an idea why the connection gets disconnected on every page refreshes.
public class TaskHub : Hub
{
public void AddSession(string sessionId)
{
Groups.Add(Context.ConnectionId, sessionId);
}
}

DI container : Hub must work singleton
builder.RegisterType<FeedHub>().ExternallyOwned().SingleInstance();
You manage OnReconnected event in hub.
public override Task OnConnected()
{
...
return base.OnConnected();
}
public override Task OnReconnected()
{
...
return base.OnReconnected();
}
public override Task OnDisconnected(bool stopCalled)
{
....
return base.OnDisconnected(stopCalled);
}

Related

Data Migration into Service Fabric Stateful Service

I have a stateful service that stores a bunch of data about my users that is stored in a reliable dictionary and obviously also retrieves it from there too.
However, I also have a SQL database that used to store this info. On initialization of a new stateful service instance, I need to migrate that info from my SQL database into the new reliable storage mechanism. From that point on, the stateful service is the source of truth. Ideally, I'd like to delay availability of my stateful service until this initialization process is completed.
Are there any suggestions on an approach for how to do this?
Something like does will do the trick:
public interface IStateful1 : IService
{
Task MyMethod();
}
internal sealed class Stateful1 : StatefulService, IStateful1
{
private bool isReady = false;
public Stateful1(StatefulServiceContext context)
: base(context)
{ }
public Task MyMethod()
{
if(!isReady)
throw new NotImplementedException(); // Probably throw or return something more meaningful :-)
return Task.CompletedTask; // Do your thing here
}
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
{
return new ServiceReplicaListener[0];
}
protected override async Task RunAsync(CancellationToken cancellationToken)
{
await Task.Run(() => {
// Simulation of some work
Thread.Sleep((int)TimeSpan.FromMinutes(5).TotalMilliseconds);
});
isReady = true;
}
}
In this setup the import from the DB into the reliable collection is done in the RunAsync method.
Unfortunately, AFAIK, there is not way to plug in the communication listeners at a later time. That would make things way easier.
If CreateServiceReplicaListeners would be an async operation we could await the initialization task here, but we can't right now. Using .Wait() is not going to work as it will report that the instance is taking to long to get running and will mark the instance as unhealthy.
A complete overview of the lifecycle of a service can be found in the docs
I am not sure if I got you right. But based on your comment I would suggest the following solution for returning the 'Not ready' response during the migration.
public interface IMigrationService
{
bool IsDone();
}
public class MigrationService : IMigrationService
{
private bool migrating = tu;
public bool BeginMigration()
{
this.migrating = true;
}
public bool EndMigration()
{
this.migrating = false;
}
public bool IsDone()
{
return this.migrating;
}
}
// WebHost startup class
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Register a middle-ware that would short circuit responses
// while migration is in progress.
app.Use(
async (context, next) =>
{
var migrationService =
context.RequestServices.GetService<IMigrationService>();
if (!migrationService.IsDone())
{
/* short circuit the response with approriate error message */
}
await next();
});
app.UseMvc();
}
}
public class Stateful : StatefulService
{
private readonly IMigrationService migrationService;
public Stateful(StatefulServiceContext context)
: base(context)
{
this.migrationService = new MigrationService();
}
protected override IEnumerable<ServiceReplicaListener>
CreateServiceReplicaListeners()
{
/*
Create a listener here with WebHostBuilder
Use Startup class with the middle-ware defined and
add configure services -> .ConfigureServices()
with services.AddSingleton<IMigrationService>(this.migrationService)
*/
}
protected override async Task
RunAsync(CancellationToken cancellationToken)
{
this.migrationService.StartMigration();
/* Migration code */
this.migrationService.EndMigration();
}
}
This would allow you to roll-out a new version of the service that would short circuit all requests with appropriate error message while the migration is in progress.
Hope this helps.

Asp.net web api and SignalR publish

I have a web api that gets realtime data from IoT device. My data is geographic coordinates (lon,lat)
public class DataController: ApiController{
public IHttpActionResult Post(Location loc){
// save loc to database.
}
}
in this first scenario, I am saving databse. So I have a client application that has a map. and I want to show incoming locations on my map as realtime.
But SignalR has a separated Hub class.
public class DataPublisherHub : Hub
{
public void PublishCoordinates()
{
// I want to send all clients incoming locations that web api action.
//????
Clients.All.sendAll();
}
}
Your client application should first of all subscribe itself to the server signalR Subscribe() method where you can add it to some group like below.
public void Subscribe(long clientId)
{
Groups.Add(Context.ConnectionId, clientId.ToString());
}
Then, after saving your coordinates to the database, call the PublishCordinates() method in the hub like:
public IHttpActionResult Post(Location loc)
{
// save loc to database.
DataPublisherHub.PublishCordinates(loc);
}
Change the PublishCordinates() method like:
public void PublishCoordinates(Location loc)
{
try
{
var context = GlobalHost.ConnectionManager.GetHubContext<DataPublisherHub>();
context.Clients.Group(clientId.ToString()).Recieve(loc);
}
catch(Exception ex)
{
return;
}
}
Maybe I misunderstood but you can create a static function in your hub like this :
public class DataPublisherHub : Hub
{
public static void PublishCoordinates(string lat, string long)
{
// I want to send all clients incoming locations that web api action.
//????
var context = GlobalHost.ConnectionManager.GetHubContext<DataPublisherHub>();
context.Clients.All.sendLatLong(lat,long);
}
}
and call it from your post function like this :
public class DataController: ApiController{
public IHttpActionResult Post(Location loc){
// save loc to database.
DataPublisherHub.PublishCoordinates(loc.lat,loc.long)
}
}
EDIT :
Dont forget to implements the OnConnected() function in your hub to get all existing data in the database :
public override Task OnConnected()
{
//Get all datas from db
foreach (var data in datas){
Clients.Caller.sendLatLong(data.lat,data.long);
}
return base.OnConnected();
}

Passing data from screen to screen conductor

Suppose I have two ViewModels and a Screen Conductor in my application.
public class ShellViewModel : Conductor<IScreen>, IShell
{
public ShellViewModel()
{
ShowConnectionScreen();
}
public void ShowConnectionScreen()
{
ActivateItem(new ConnectionViewModel());
}
public void ShowSetupScreen()
{
ActivateItem(new SetupViewModel());
}
}
The first ViewModel is displayed on start up, and contains some setup information and a Connect button which initializes a connect to somewhere.
If the connection is established successfully, then I would like the first ViewModel to close and the second ViewModel to display some information about the connection. If it fails, the first ViewModel should simply display that, and allow the user to attempt connection once again.
Thus I need the actual connection object to be passed from the first ViewModel to the second ViewModel and the Screen Conductor to change viewmodels on success.
How can this be achieved in Caliburn.Micro?
To illustrate #mvermef's comment:
use a common type between the 3 class objects, obviously create this type in question
This would be the connection object that the first view model populates and is used by the second view model.
public class Connection {
// props, methods, etc...
}
and pass it either in constructor or make it a property of all 3 classes
public class ShellViewModel : Conductor<IScreen>, IShell
{
public Connection Connection { get; set; }
public ShellViewModel()
{
Connection = new Connection();
ShowConnectionScreen();
}
public void ShowConnectionScreen()
{
ActivateItem(new ConnectionViewModel(Connection));
}
public void ShowSetupScreen()
{
ActivateItem(new SetupViewModel(Connection));
}
}
Do what you want with the Connection object inside ConnectionViewModel
public class ConnectionViewModel : Screen
{
public Connection Connection { get; set; }
// establish connection
// can call (Parent as IConductor).DeactivateItem(this)
// after connection is established
}
You can notify the parent conductor if a connection is established by (1) registering through ConnectionViewModel's Deactivated event (assuming you subclass Screen). Or (2) you can use EventAggregator to fire an event if the connection is established and having ShellViewModel implement IHandle. You can then call ShowSetupScreen() inside the Deactivated event handler or the Handle method.
Option 1:
// ShellViewModel
public void ShowConnectionScreen()
{
var connectionVM = new ConnectionViewModel();
connectionVM.Deactivated += ConnectionViewModel_Deactivated;
ActivateItem();
}
private void Scheduler_Deactivated1(object sender, DeactivationEventArgs e)
{
ShowSetupScreen();
}
Option 2:
public class ShellViewModel : Conductor<IScreen>,
IShell, IHandle<string>
{
private readonly IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
// from IHandle<string>. you can create a custom object to represent this event
public void Handle(string message)
{
if (message.Equals("connection.successful"))
{
ShowSetupScreen();
}
}
}
public class ConnectionViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
public ConnectionViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
// call _eventAggregator.PublishOnUIThread("connection.successful");
}

Call method on hub from HubPipelineModule OnIncomingError event

I need to implement custom error logging on signalR hubs. This can be done by creating ErrorModule which will inherit from HubPipelineModule and it will be registered on Startup like this
GlobalHost.HubPipeline.AddModule(new ErrorModule());
This is my hub
public class FooHub : Hub
{
public void MethodWithException()
{
throw new Exception();
}
public void FooMethod()
{
}
}
And this is my module
public class ErrorModule : HubPipelineModule
{
protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
{
//call foo method on foo hub
//log error
}
}
Lets say MethodWithException() is called. It is possible to call FooMethod() on hub which invoked event OnIncomingError ?
With something like this :
protected override void OnIncomingError(ExceptionContext exceptionContext,
IHubIncomingInvokerContext invokerContext)
{
dynamic caller = invokerContext.Hub.Clients.Caller;
caller.ExceptionHandler(exceptionContext.Error.Message);
//log something here
Debug.WriteLine(exceptionContext.Error.Message);
}
EDIT
To call a particular method of the hub you can do :
protected override void OnIncomingError(ExceptionContext exceptionContext,
IHubIncomingInvokerContext invokerContext)
{
var hub = (invokerContext.Hub as HubType); //
hub.MyMethod("myparam","mysecondparam");
//log something here
Debug.WriteLine(exceptionContext.Error.Message);
}
You can find the full code here : How to handle SignalR server exception at client?

Getting current hub in SignalR

Is there a good way to call methods in SignalR hub from a controller ?
Right now I have this:
public class StatsHub : Hub
{
private static readonly Lazy<StatsHub> instance = new Lazy<StatsHub>(() => new StatsHub());
public static StatsHub Instance { get { return instance.Value; } }
public StatsHub()
{
if (this.Clients == null)
{
var hubContext = SignalR.GlobalHost.ConnectionManager.GetHubContext<StatsHub>();
this.Clients = hubContext.Clients;
this.Groups = hubContext.Groups;
}
}
// methods here...
}
so in my controller actions I can just say, for example
StatsHub.Instance.SendMessage("blah");
and it's almost good, except that hubContext doesn't have Caller or Context properties of Hub - which are nice to have.
Hopefully, there's a better way to do this ?
If you want to broadcast over a hub from outside of the hub, you need GlobalHost.ConnectionManager.GetHubContext<MyHub>() to get ahold of the hub context. You can then use this context to broadcast via the .Clients property.
As indicated in your sample code you already get ahold of the hub context, but doing so inside the hub just doesn't feel right in my opinion. If you're only using the logic in SendMessage() from your controller actions, I'd move the code right into the controller action and use the hub context obtained via GetHubContext<T>() from there.
Please note that the Caller or Context property will always be null in this scenario, because SignalR wasn't involved when making a request to the server and therefore cannot provide the properties.
Found a DefaultHubManager, which is what I need, I think.
DefaultHubManager hd = new DefaultHubManager(GlobalHost.DependencyResolver);
var hub = hd.ResolveHub("AdminHub") as AdminHub;
hub.SendMessage("woohoo");
Works. If there's an even better/preferred way - please share.
As per the latest documentation, IHubContext can be injected by dependency injection.
documentation : https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-6.0
In service you could do
public class NotificationService : INotificationService
{
private readonly IHubContext<NotificationHub> _hubContext;
public NotificationService(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
}
In controller
public class HomeController : Controller
{
private readonly IHubContext<NotificationHub> _hubContext;
public HomeController(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
}
Once you have HubContext you could send message to group/client etc.
public async Task SendMessage()
{
return await _hubContext.Clients.All.SendAsync("Notify", $"Hello world");
}

Resources