title: SignalR in ASP.NET Core 3.0 MVC connection keep alive not working?
body: <p>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?</p>
  
  <p>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: <a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-3.0&amp;tabs=visual-studio" rel="nofollow noreferrer">https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-3.0&amp;tabs=visual-studio</a> (minus creating a new web app, we just added it to our app instead)</p>
  
  <p>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.</p>
  
  <p>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.</p>
  
  <p>Here are the various pieces of our code:</p>
  
  <p><strong>Startup.cs ConfigureServices method:</strong></p>
  
  <pre><code>        services.AddSignalR(hubOptions =&gt;
          {
              hubOptions.EnableDetailedErrors = true;
              //hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(10);
              //hubOptions.ClientTimeoutInterval = TimeSpan.FromMinutes(1);
          });
  </code></pre>
  
  <p><strong>Startup.cs Configure method:</strong></p>
  
  <pre><code>        app.UseEndpoints(endpoints =&gt;
          {
              endpoints.MapControllerRoute(
                  name: "default",
                  pattern: "{controller=Home}/{action=Index}/{id?}");
              endpoints.MapRazorPages();
              endpoints.MapHub&lt;QuoteHub&gt;("/quoteHub");
          });
  </code></pre>
  
  <p><strong>Our QuoteHub:</strong></p>
  
  <pre><code>public class QuoteHub : Hub
  {
      public QuoteHub()
      {
  
      }
  
      public override Task OnConnectedAsync()
      {
          var quoteId = Context.GetHttpContext().Request.Query["quoteId"];
  
          return Groups.AddToGroupAsync(Context.ConnectionId, quoteId);
      }
  }
  </code></pre>
  
  <p><strong>and the javascript setup of the connection:</strong></p>
  
  <pre><code>const setupQuoteConnection = (quoteId) =&gt; {
      let connection = new signalR.HubConnectionBuilder()
      .withUrl("/quoteHub?quoteId=" + quoteId)
      .configureLogging(signalR.LogLevel.Debug)
      .withAutomaticReconnect()
      .build();
  
      connection.on("ReceiveUpdate", (update) =&gt; {
      alert(update);
      }
      );
  
      connection.start()
      .catch(err =&gt; console.error(err.toString()));
  };
  </code></pre>
  
  <p><strong>and, just to be thorough, the call to the hub to send the update to the clients:</strong></p>
  
  <pre><code>_quoteHub.Clients.Group(domainEvent.QuoteId.ToString()).SendAsync("ReceiveUpdate", domainEvent.TotalPrice);
  </code></pre>
  
  <h2>Update</h2>
  
  <p>I found the chat sample located at <a href="https://github.com/aspnet/SignalR-samples" rel="nofollow noreferrer">https://github.com/aspnet/SignalR-samples</a>. 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.</p>
  
  <p>I did notice some issues in my hub and fixed it, although this made no difference:</p>
  
  <pre><code>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();
      }
  }
  </code></pre>
  
  <p>Then I updated my javascript code to configure the connection and extend the server timeout. This <em>does work</em>, but why do I need this when the above linked chat sample doesn't have it and works fine without it?</p>
  
  <pre><code>    let connection = new signalR.HubConnectionBuilder()
      .withUrl("/quoteHub?quoteId=" + quoteId)
      .configureLogging(signalR.LogLevel.Debug)
      .withAutomaticReconnect()
      .build();
  
      connection.serverTimeoutInMilliseconds = 3600000; //1 hour
  </code></pre>