0

I have a Singleton service, GameServer which keeps track of guests to a particular page, a Game.razor page, and a Scoped service, GameBridge, between them. Gamebridge is injected into Game.razor, and GameServer is injected into Gamebridge.

GameServer sends out a ping event, which is relayed by GameBridge to the page. The page then calls a method in Gamebridge, which is relayed to GameServer, and sets a LastSeen DateTime to DateTime.Now for the user.

The idea was that if the user closes their browser, the Scoped service would be disposed, and the GameServer would no longer get update pings from the user, i.e. the user has timed out, and games can be forfeited, game rooms abandoned and so on.

The problem is that even if the browser is closed, Game.razor and GameBridge keep chugging along like everything's fine-- which I can see because the time value for the missing user keeps updating. As of right now, I cannot detect the user disconnection even when they close the browser in blazor server side.

Cleptus
  • 3,446
  • 4
  • 28
  • 34
Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16
  • Blazor server? https://stackoverflow.com/questions/59980882/how-can-i-get-the-event-while-page-close-in-blazor-server-side – Steve Greene Aug 15 '21 at 15:29
  • Yes, blazor server. That link looks like it's probably the right track. I'll drink another cup of coffee and see if I can get it working tonight. (It's midnight here, but I'm pretty keen to get this done) – Bennyboy1973 Aug 15 '21 at 15:32
  • @Steve Greene, thank you very much! With a little tinkering, I was able to isolate the circuit for several windows on a couple browsers. I'm quite confident I can move forward, and I can drop all the events and most of the method calls. I'm a little worried, though-- why are `GameBridge` and `Game.razor` still instantiated? Do I need to dispose of all event subscriptions etc. etc. ? – Bennyboy1973 Aug 15 '21 at 16:42
  • @Bennyboy1973 I have done this before by using Signalr and overriding ondisconnect in the hub. However this was in WASM but the principal is the same. – Brian Parker Aug 15 '21 at 16:56
  • 1
    Thanks guys. I didn't find many examples about this, and the MSDN docs, which are usually pretty good, weren't helpful. Hopefully if I have time later, I can post a bare-bones user server here. – Bennyboy1973 Aug 15 '21 at 22:21

1 Answers1

1

My solution hooks into the browser window beforeunload event, and uses a Guid to track SPA sessions.

This is my demo system which you an adapt to fit your code.

Add a Singleton Service and register it.

using System;

namespace Blazor.App.Services
{
    public class SessionTrackerService
    {
        // Session List, blah, blah, ..

        public void NotifyNewSession(Guid ID)
        {
            //  Do whatever you want
            Debug.WriteLine($"Session {ID} Registered");
        }

        public void NotifySessionClosed(Guid ID)
        {
            var x = 0;
            Debug.WriteLine($"Session {ID} Closed");
            // Do re-registering
        }
    }
}

site.js registered in base page.

window.blazor_setExitEvent = function (dotNetHelper) {
    blazor_dotNetHelper = dotNetHelper;
        window.addEventListener("beforeunload", blazor_SetSPAClosed);
}

var blazor_dotNetHelper;

window.blazor_SetSPAClosed = function() {
    blazor_dotNetHelper.invokeMethodAsync('SPASessionClosed')
    blazor_dotNetHelper.dispose();
}

Add some code to App.razor. Cascade the SessionID if you need to use it elsewhere. Call window.blazor_setExitEvent to set the window beforeunload event and pass it a reference to App.

<CascadingValue Name="SessionID" Value="this.ID">
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        .....
    </Router>
</CascadingValue>

@code {
    [Inject] private IJSRuntime _js { get; set; }
    [Inject] private SessionTrackerService sessionTrackerService { get; set; }
    public Guid ID { get; set; } = Guid.NewGuid();

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            sessionTrackerService.NotifyNewSession(ID);
            var dotNetReference = DotNetObjectReference.Create(this);
            await _js.InvokeVoidAsync("window.blazor_setExitEvent", dotNetReference);
        }
    }

    [JSInvokable("SPASessionClosed")]
    public void SPASessionClosed()
    {
        sessionTrackerService.NotifySessionClosed(ID);
    }

I tested in on Edge, Chrome and Firefox.

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • That's a nice-looking solution, but what happens if someone alt-F4? I'm trying to implement a game server that will be used by children, and they're pretty crafty that way. I've got the circuit system working fine-- been programming too much to share it, and not sure it isn't overkill. I have an auxiliary question about that-- I found that if I implement CircuitHelper as a singleton, it shows ALL circuits, whereas scoped just gives own circuit. I'd like to have BOTH implementations-- any ideas about that? – Bennyboy1973 Aug 20 '21 at 23:45
  • @Bennyboy1973. Have you tested this? Alt-F4 triggers the event in the same way that closing the tab or Browser does. I've just tested it to be sure. – MrC aka Shaun Curtis Aug 21 '21 at 20:02
  • No I haven't tested it, as I already had CircuitHandler working by the time I read your answer. I was just guessing that if you kill the browser, JavaScript will immediately stop functioning. – Bennyboy1973 Aug 23 '21 at 00:46