简体   繁体   中英

SignalR in ASP.NET Core 3.0 MVC connection keep alive not working?

The question in a sentence: Are SignalR connections kept open via keep-alives, if so, why aren't they working in .NET Core 3, and if not, what are the keep-alive settings for?

I have an ASP.NET Core 3.0 MVC web application, and I just added SignalR to it following the getting started tutorial located here: https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-3.0&tabs=visual-studio (minus creating a new web app, we just added it to our app instead)

I have a simple hub, and the client connects successfully, and if I test the SignalR method immediately after the connection is established, it works. However, the connection is closed if I don't use it for 30 seconds. If I'm understanding the documentation, the default value for keepalive is 15 seconds, but I don't see any keepalive messages. I have tried various settings for KeepAliveInterval and ClientTimeoutInterval, but nothing has resolved this.

I added.withAutomaticReconnect() to the HubConnectionBuilder call in our javascript, and that does work to re-establish the connection after the disconnect every 30 seconds. Is that how this is supposed to work, or should the connection be kept alive with pings and only have to reconnect due to network dropouts/etc.? I feel I'm missing something simple or I'm misunderstanding how this should work.

Here are the various pieces of our code:

Startup.cs ConfigureServices method:

        services.AddSignalR(hubOptions =>
        {
            hubOptions.EnableDetailedErrors = true;
            //hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(10);
            //hubOptions.ClientTimeoutInterval = TimeSpan.FromMinutes(1);
        });

Startup.cs Configure method:

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
            endpoints.MapHub<QuoteHub>("/quoteHub");
        });

Our QuoteHub:

public class QuoteHub : Hub
{
    public QuoteHub()
    {

    }

    public override Task OnConnectedAsync()
    {
        var quoteId = Context.GetHttpContext().Request.Query["quoteId"];

        return Groups.AddToGroupAsync(Context.ConnectionId, quoteId);
    }
}

and the javascript setup of the connection:

const setupQuoteConnection = (quoteId) => {
    let connection = new signalR.HubConnectionBuilder()
    .withUrl("/quoteHub?quoteId=" + quoteId)
    .configureLogging(signalR.LogLevel.Debug)
    .withAutomaticReconnect()
    .build();

    connection.on("ReceiveUpdate", (update) => {
    alert(update);
    }
    );

    connection.start()
    .catch(err => console.error(err.toString()));
};

and, just to be thorough, the call to the hub to send the update to the clients:

_quoteHub.Clients.Group(domainEvent.QuoteId.ToString()).SendAsync("ReceiveUpdate", domainEvent.TotalPrice);

Update

I found the chat sample located at https://github.com/aspnet/SignalR-samples . I downloaded and run that sample and it works fine, the connection just stays open, but for the life of me I can't see what is causing it to behave differently from my application.

I did notice some issues in my hub and fixed it, although this made no difference:

public class QuoteHub : Hub
{
    public override async Task OnConnectedAsync()
    {
        var quoteId = Context.GetHttpContext().Request.Query["quoteId"];

        await Groups.AddToGroupAsync(Context.ConnectionId, quoteId);

        await base.OnConnectedAsync();
    }
}

Then I updated my javascript code to configure the connection and extend the server timeout. This does work , but why do I need this when the above linked chat sample doesn't have it and works fine without it?

    let connection = new signalR.HubConnectionBuilder()
    .withUrl("/quoteHub?quoteId=" + quoteId)
    .configureLogging(signalR.LogLevel.Debug)
    .withAutomaticReconnect()
    .build();

    connection.serverTimeoutInMilliseconds = 3600000; //1 hour

I have already done this in one of my projects, and I have posted a detailed tutorial on this topic on my blog https://www.stackquery.com/articles/detail-implemenation-of-siganlr-in-asp-net-core-with-timecounter/85d3c7aa-c38b-47d1-bb94-e4f6eefbe1b2 . The tutorial gives a step by step instructions and complete implementation on how to implement SignalR with Time Counter in ASP.NET Core.

Create TimeManger.cs like this

using System;

using System.Threading;

using System.Threading.Tasks;



namespace Exmaple.Data

{

public class TimerManager

{

    private Timer  _timer;

    private AutoResetEvent  _autoResetEvent;

    private Action _action;

    public DateTime TimerStarted { get; set;}



    public TimerManager( Action action )

    {

        _action = action;

        _autoResetEvent = newAutoResetEvent(false);

        _timer = newTimer(Execute, _autoResetEvent, 5000, 5000);

        TimerStarted = DateTime.Now;

    }


    public void Execute(object stateInfo)

    {

        vartme= (DateTime.Now-TimerStarted).Seconds;

      // Replace 20 with your desired seconds 

       if (tme > 20)

        {

            _action();

            TimerStarted = DateTime.Now;

        }

      }

    }

  }

you can do something like below in your method:

 var timerManager=new TimerManager(() =>hub.Clients.All.SendAsync("exampleData", response));

I think you may also need to add options.Transports like this

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR(hubOptions =>
    {
        hubOptions.EnableDetailedErrors = true;
        hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1);
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<MyHub>("/myhub", options =>
        {
            options.Transports = HttpTransportType.LongPolling; // you may also need this
        });
    });
}

It appears we have identified the culprit for our constant 30-second disconnects.

When we updated our application from ASP.NET Core 2.2 MVC to 3.0, we went all in with the System.Text.Json move too (away from Newtonsoft.Json). Well, this had a side-effect of breaking our Telerik Reporting component which still requires Newtonsoft.Json. So, we reverted to using Newtonsoft.Json in our app, but the piece we were missing was found at https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#switch-to-newtonsoftjson

The key bit being "chain an AddNewtonsoftJsonProtocol method call to the AddSignalR method call in Startup.ConfigureServices":

services.AddSignalR()
.AddNewtonsoftJsonProtocol

Nothing else was needed for this all to 'just work'. If you're reading this, please reference the Chat Sample I mentioned in the update in the question ( https://github.com/aspnet/SignalR-samples ). The simplicity in that code and how it ran was the sign to me that something wasn't right with how we were seeing the constant disconnects - the red herring that caught me off guard was that our SignalR messages still worked.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM