ASP.NET Core Blazor SignalR guidance

This article explains how to configure and manage SignalR connections in Blazor apps.

For general guidance on ASP.NET Core SignalR configuration, see the topics in the Overview of ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly app, for example in the Use ASP.NET Core SignalR with Blazor tutorial, or a standalone Blazor WebAssembly app that uses SignalR, see ASP.NET Core SignalR configuration.

Disable response compression for Hot Reload

When using Hot Reload, disable Response Compression Middleware in the Development environment. The following examples use the existing environment check in a project created from a Blazor project template. Whether or not the default code from a project template is used, always call UseResponseCompression first in the request processing pipeline.

In Program.cs of a Blazor Server app:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

In Program.cs of the Client project in a hosted Blazor WebAssembly solution:

if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseResponseCompression();
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

SignalR cross-origin negotiation for authentication (Blazor WebAssembly)

To configure SignalR's underlying client to send credentials, such as cookies or HTTP authentication headers:

  • Use SetBrowserRequestCredentials to set Include on cross-origin fetch requests.

    IncludeRequestCredentialsMessageHandler.cs:

    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components.WebAssembly.Http;
    
    public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
            return base.SendAsync(request, cancellationToken);
        }
    }
    
  • Where a hub connection is built, assign the HttpMessageHandler to the HttpMessageHandlerFactory option:

    private HubConnectionBuilder? hubConnection;
    
    ...
    
    hubConnection = new HubConnectionBuilder()
        .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
        {
            options.HttpMessageHandlerFactory = innerHandler => 
                new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
        }).Build();
    

    The preceding example configures the hub connection URL to the absolute URI address at /chathub, which is the URL used in the SignalR with Blazor tutorial in the Index component (Pages/Index.razor). The URI can also be set via a string, for example https://signalr.example.com, or via configuration. Navigation is an injected NavigationManager.

For more information, see ASP.NET Core SignalR configuration.

Render mode (Blazor WebAssembly)

If a Blazor WebAssembly app that uses SignalR is configured to prerender on the server, prerendering occurs before the client connection to the server is established. For more information, see the following articles:

Additional resources for Blazor WebAssembly apps

Use sticky sessions for webfarm hosting (Blazor Server)

A Blazor Server app prerenders in response to the first client request, which creates UI state on the server. When the client attempts to create a SignalR connection, the client must reconnect to the same server. Blazor Server apps that use more than one backend server should implement sticky sessions for SignalR connections.

Note

The following error is thrown by an app that hasn't enabled sticky sessions in a webfarm:

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Azure SignalR Service (Blazor Server)

We recommend using the Azure SignalR Service for Blazor Server apps hosted in Microsoft Azure. The service works in conjunction with the app's Blazor Hub for scaling up a Blazor Server app to a large number of concurrent SignalR connections. In addition, the SignalR Service's global reach and high-performance data centers significantly aid in reducing latency due to geography.

Sticky sessions are enabled for the Azure SignalR Service by setting the service's ServerStickyMode option or configuration value to Required. For more information, see Host and deploy ASP.NET Core Blazor Server.

Circuit handler options for Blazor Server apps

Configure the Blazor Server circuit with the CircuitOptions shown in the following table.

Option Default Description
DetailedErrors false Send detailed exception messages to JavaScript when an unhandled exception occurs on the circuit or when a .NET method invocation through JS interop results in an exception.
DisconnectedCircuitMaxRetained 100 Maximum number of disconnected circuits that the server holds in memory at a time.
DisconnectedCircuitRetentionPeriod 3 minutes Maximum amount of time a disconnected circuit is held in memory before being torn down.
JSInteropDefaultCallTimeout 1 minute Maximum amount of time the server waits before timing out an asynchronous JavaScript function invocation.
MaxBufferedUnacknowledgedRenderBatches 10 Maximum number of unacknowledged render batches the server keeps in memory per circuit at a given time to support robust reconnection. After reaching the limit, the server stops producing new render batches until one or more batches are acknowledged by the client.

Configure the options in Program.cs with an options delegate to AddServerSideBlazor. The following example assigns the default option values shown in the preceding table. Confirm that Program.cs uses the System namespace (using System;).

In Program.cs:

builder.Services.AddServerSideBlazor(options =>
{
    options.DetailedErrors = false;
    options.DisconnectedCircuitMaxRetained = 100;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
    options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
    options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

To configure the HubConnectionContext, use HubConnectionContextOptions with AddHubOptions. For option descriptions, see ASP.NET Core SignalR configuration. The following example assigns the default option values. Confirm that Program.cs uses the System namespace (using System;).

In Program.cs:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
        options.EnableDetailedErrors = false;
        options.HandshakeTimeout = TimeSpan.FromSeconds(15);
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
        options.MaximumParallelInvocationsPerClient = 1;
        options.MaximumReceiveMessageSize = 32 * 1024;
        options.StreamBufferCapacity = 10;
    });

Warning

The default value of MaximumReceiveMessageSize is 32 KB. Increasing the value may increase the risk of Denial of service (DoS) attacks.

For information on Blazor Server's memory model, see Host and deploy ASP.NET Core Blazor Server.

Blazor Hub endpoint route configuration (Blazor Server)

In Program.cs, Blazor Server apps call MapBlazorHub to map the Blazor Hub to the app's default path. The Blazor Server script (blazor.server.js) automatically points to the endpoint created by MapBlazorHub.

Reflect the connection state in the UI (Blazor Server)

When the client detects that the connection has been lost, a default UI is displayed to the user while the client attempts to reconnect. If reconnection fails, the user is provided the option to retry.

To customize the UI, define a single element with an id of components-reconnect-modal. The following example places the element in the host page.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

Note

If more than one element with an id of components-reconnect-modal are rendered by the app, only the first rendered element receives CSS class changes to display or hide the element.

Add the following CSS styles to the site's stylesheet.

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

The following table describes the CSS classes applied to the components-reconnect-modal element by the Blazor framework.

CSS class Indicates…
components-reconnect-show A lost connection. The client is attempting to reconnect. Show the modal.
components-reconnect-hide An active connection is re-established to the server. Hide the modal.
components-reconnect-failed Reconnection failed, probably due to a network failure. To attempt reconnection, call window.Blazor.reconnect() in JavaScript.
components-reconnect-rejected Reconnection rejected. The server was reached but refused the connection, and the user's state on the server is lost. To reload the app, call location.reload() in JavaScript. This connection state may result when:
  • A crash in the server-side circuit occurs.
  • The client is disconnected long enough for the server to drop the user's state. Instances of the user's components are disposed.
  • The server is restarted, or the app's worker process is recycled.

Customize the delay before the reconnection display appears by setting the transition-delay property in the site's CSS for the modal element. The following example sets the transition delay from 500 ms (default) to 1,000 ms (1 second).

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

To display the current reconnect attempt, define an element with an id of components-reconnect-current-attempt. To display the maximum number of reconnect retries, define an element with an id of components-reconnect-max-retries. The following example places these elements inside a reconnect attempt modal element in the host page following the previous example.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

When the custom reconnect modal appears, it renders content similar to the following based on the preceding code:

There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Render mode (Blazor Server)

By default, Blazor Server apps prerender the UI on the server before the client connection to the server is established. For more information, see Component Tag Helper in ASP.NET Core.

Blazor startup

Configure the manual start of a Blazor app's SignalR circuit in the Pages/_Host.cshtml file (Blazor Server) or wwwroot/index.html (hosted Blazor WebAssembly with SignalR implemented):

  • Add an autostart="false" attribute to the <script> tag for the blazor.{server|webassembly}.js script.
  • Place a script that calls Blazor.start() after the Blazor script is loaded and inside the closing </body> tag.

When autostart is disabled, any aspect of the app that doesn't depend on the circuit works normally. For example, client-side routing is operational. However, any aspect that depends on the circuit isn't operational until Blazor.start() is called. App behavior is unpredictable without an established circuit. For example, component methods fail to execute while the circuit is disconnected.

For more information, including how to initialize Blazor when the document is ready and how to chain to a JS Promise, see ASP.NET Core Blazor startup.

Configure SignalR timeouts and Keep-Alive on the client

Configure the following values for the client:

  • serverTimeoutInMilliseconds: The server timeout in milliseconds. If this timeout elapses without receiving any messages from the server, the connection is terminated with an error. The default timeout value is 30 seconds. The server timeout should be at least double the value assigned to the Keep-Alive interval (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: Default interval at which to ping the server. This setting allows the server to detect hard disconnects, such as when a client unplugs their computer from the network. The ping occurs at most as often as the server pings. If the server pings every five seconds, assigning a value lower than 5000 (5 seconds) pings every five seconds. The default value is 15 seconds. The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout (serverTimeoutInMilliseconds).

The following example for either Pages/_Host.cshtml (Blazor Server) or wwwroot/index.html (Blazor WebAssembly) uses default values:

<script src="_framework/blazor.{HOSTING MODEL}.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

In the preceding markup, the {HOSTING MODEL} placeholder is either server for a Blazor Server app or webassembly for a Blazor WebAssembly app.

When creating a hub connection in a component, set the ServerTimeout (default: 30 seconds), HandshakeTimeout (default: 15 seconds), and KeepAliveInterval (default: 15 seconds) on the built HubConnection. The following example, based on the Index component in the SignalR with Blazor tutorial, uses default values:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

When changing the values of the server timeout (ServerTimeout) or the Keep-Alive interval (KeepAliveInterval:

  • The server timeout should be at least double the value assigned to the Keep-Alive interval.
  • The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout.

For more information, see the Global deployment and connection failures sections of the following articles:

Modify the reconnection handler (Blazor Server)

The reconnection handler's circuit connection events can be modified for custom behaviors, such as:

  • To notify the user if the connection is dropped.
  • To perform logging (from the client) when a circuit is connected.

To modify the connection events, register callbacks for the following connection changes:

  • Dropped connections use onConnectionDown.
  • Established/re-established connections use onConnectionUp.

Both onConnectionDown and onConnectionUp must be specified.

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionHandler: {
          onConnectionDown: (options, error) => console.error(error),
          onConnectionUp: () => console.log("Up, up, and away!")
        }
      });
    </script>
</body>

Automatically refresh the page when reconnection fails (Blazor Server)

The default reconnection behavior requires the user to take manual action to refresh the page after reconnection fails. However, a custom reconnection handler can be used to automatically refresh the page:

Pages/_Host.cshtml:

<body>
    ...

    <div id="reconnect-modal" style="display: none;"></div>
    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script src="boot.js"></script>
</body>

wwwroot/boot.js:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
      onConnectionUp: () => {
        currentReconnectionProcess?.cancel();
        currentReconnectionProcess = null;
      },
    },
  });
})();

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Adjust the reconnection retry count and interval (Blazor Server)

To adjust the reconnection retry count and interval, set the number of retries (maxRetries) and period in milliseconds permitted for each retry attempt (retryIntervalMilliseconds).

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionOptions: {
          maxRetries: 3,
          retryIntervalMilliseconds: 2000
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Disconnect the Blazor circuit from the client (Blazor Server)

By default, a Blazor circuit is disconnected when the unload page event is triggered. To disconnect the circuit for other scenarios on the client, invoke Blazor.disconnect in the appropriate event handler. In the following example, the circuit is disconnected when the page is hidden (pagehide event):

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Blazor Server circuit handler

Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. The following example of a circuit handler tracks open SignalR connections.

TrackingCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Circuit handlers are registered using DI. Scoped instances are created per instance of a circuit. Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked.

In Program.cs:

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the Blazor Server circuit. To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. If any DI service throws an unhandled exception during disposal, the framework logs the exception. For more information, see ASP.NET Core Blazor dependency injection.

For general guidance on ASP.NET Core SignalR configuration, see the topics in the Overview of ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly app, for example in the Use ASP.NET Core SignalR with Blazor tutorial, or a standalone Blazor WebAssembly app that uses SignalR, see ASP.NET Core SignalR configuration.

Disable response compression for Hot Reload

When using Hot Reload, disable Response Compression Middleware in the Development environment. The following examples use the existing environment check in a project created from a Blazor project template. Whether or not the default code from a project template is used, always call UseResponseCompression first in the request processing pipeline.

In Program.cs of a Blazor Server app:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

In Program.cs of the Client project in a hosted Blazor WebAssembly solution:

if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseResponseCompression();
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

SignalR cross-origin negotiation for authentication (Blazor WebAssembly)

To configure SignalR's underlying client to send credentials, such as cookies or HTTP authentication headers:

  • Use SetBrowserRequestCredentials to set Include on cross-origin fetch requests.

    IncludeRequestCredentialsMessageHandler.cs:

    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components.WebAssembly.Http;
    
    public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
            return base.SendAsync(request, cancellationToken);
        }
    }
    
  • Where a hub connection is built, assign the HttpMessageHandler to the HttpMessageHandlerFactory option:

    private HubConnectionBuilder? hubConnection;
    
    ...
    
    hubConnection = new HubConnectionBuilder()
        .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
        {
            options.HttpMessageHandlerFactory = innerHandler => 
                new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
        }).Build();
    

    The preceding example configures the hub connection URL to the absolute URI address at /chathub, which is the URL used in the SignalR with Blazor tutorial in the Index component (Pages/Index.razor). The URI can also be set via a string, for example https://signalr.example.com, or via configuration. Navigation is an injected NavigationManager.

Note

To authenticate users for SignalR hubs, see Secure ASP.NET Core Blazor WebAssembly.

For more information, see ASP.NET Core SignalR configuration.

Render mode (Blazor WebAssembly)

If a Blazor WebAssembly app that uses SignalR is configured to prerender on the server, prerendering occurs before the client connection to the server is established. For more information, see the following articles:

Additional resources for Blazor WebAssembly apps

Use sticky sessions for webfarm hosting (Blazor Server)

A Blazor Server app prerenders in response to the first client request, which creates UI state on the server. When the client attempts to create a SignalR connection, the client must reconnect to the same server. Blazor Server apps that use more than one backend server should implement sticky sessions for SignalR connections.

Note

The following error is thrown by an app that hasn't enabled sticky sessions in a webfarm:

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Azure SignalR Service (Blazor Server)

We recommend using the Azure SignalR Service for Blazor Server apps hosted in Microsoft Azure. The service works in conjunction with the app's Blazor Hub for scaling up a Blazor Server app to a large number of concurrent SignalR connections. In addition, the SignalR Service's global reach and high-performance data centers significantly aid in reducing latency due to geography.

Sticky sessions are enabled for the Azure SignalR Service by setting the service's ServerStickyMode option or configuration value to Required. For more information, see Host and deploy ASP.NET Core Blazor Server.

Circuit handler options for Blazor Server apps

Configure the Blazor Server circuit with the CircuitOptions shown in the following table.

Option Default Description
DetailedErrors false Send detailed exception messages to JavaScript when an unhandled exception occurs on the circuit or when a .NET method invocation through JS interop results in an exception.
DisconnectedCircuitMaxRetained 100 Maximum number of disconnected circuits that the server holds in memory at a time.
DisconnectedCircuitRetentionPeriod 3 minutes Maximum amount of time a disconnected circuit is held in memory before being torn down.
JSInteropDefaultCallTimeout 1 minute Maximum amount of time the server waits before timing out an asynchronous JavaScript function invocation.
MaxBufferedUnacknowledgedRenderBatches 10 Maximum number of unacknowledged render batches the server keeps in memory per circuit at a given time to support robust reconnection. After reaching the limit, the server stops producing new render batches until one or more batches are acknowledged by the client.

Configure the options in Program.cs with an options delegate to AddServerSideBlazor. The following example assigns the default option values shown in the preceding table. Confirm that Program.cs uses the System namespace (using System;).

In Program.cs:

builder.Services.AddServerSideBlazor(options =>
{
    options.DetailedErrors = false;
    options.DisconnectedCircuitMaxRetained = 100;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
    options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
    options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

To configure the HubConnectionContext, use HubConnectionContextOptions with AddHubOptions. For option descriptions, see ASP.NET Core SignalR configuration. The following example assigns the default option values. Confirm that Program.cs uses the System namespace (using System;).

In Program.cs:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
        options.EnableDetailedErrors = false;
        options.HandshakeTimeout = TimeSpan.FromSeconds(15);
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
        options.MaximumParallelInvocationsPerClient = 1;
        options.MaximumReceiveMessageSize = 32 * 1024;
        options.StreamBufferCapacity = 10;
    });

Warning

The default value of MaximumReceiveMessageSize is 32 KB. Increasing the value may increase the risk of Denial of service (DoS) attacks.

For information on Blazor Server's memory model, see Host and deploy ASP.NET Core Blazor Server.

Blazor Hub endpoint route configuration (Blazor Server)

In Program.cs, Blazor Server apps call MapBlazorHub to map the Blazor Hub to the app's default path. The Blazor Server script (blazor.server.js) automatically points to the endpoint created by MapBlazorHub.

Reflect the connection state in the UI (Blazor Server)

When the client detects that the connection has been lost, a default UI is displayed to the user while the client attempts to reconnect. If reconnection fails, the user is provided the option to retry.

To customize the UI, define a single element with an id of components-reconnect-modal. The following example places the element in the layout page.

Pages/_Layout.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

Note

If more than one element with an id of components-reconnect-modal are rendered by the app, only the first rendered element receives CSS class changes to display or hide the element.

Add the following CSS styles to the site's stylesheet.

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

The following table describes the CSS classes applied to the components-reconnect-modal element by the Blazor framework.

CSS class Indicates…
components-reconnect-show A lost connection. The client is attempting to reconnect. Show the modal.
components-reconnect-hide An active connection is re-established to the server. Hide the modal.
components-reconnect-failed Reconnection failed, probably due to a network failure. To attempt reconnection, call window.Blazor.reconnect() in JavaScript.
components-reconnect-rejected Reconnection rejected. The server was reached but refused the connection, and the user's state on the server is lost. To reload the app, call location.reload() in JavaScript. This connection state may result when:
  • A crash in the server-side circuit occurs.
  • The client is disconnected long enough for the server to drop the user's state. Instances of the user's components are disposed.
  • The server is restarted, or the app's worker process is recycled.

Customize the delay before the reconnection display appears by setting the transition-delay property in the site's CSS for the modal element. The following example sets the transition delay from 500 ms (default) to 1,000 ms (1 second).

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

To display the current reconnect attempt, define an element with an id of components-reconnect-current-attempt. To display the maximum number of reconnect retries, define an element with an id of components-reconnect-max-retries. The following example places these elements inside a reconnect attempt modal element in the layout page following the previous example.

Pages/_Layout.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

When the custom reconnect modal appears, it renders content similar to the following based on the preceding code:

There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Render mode (Blazor Server)

By default, Blazor Server apps prerender the UI on the server before the client connection to the server is established. For more information, see Component Tag Helper in ASP.NET Core.

Blazor startup

Configure the manual start of a Blazor app's SignalR circuit in the Pages/_Layout.cshtml file (Blazor Server) or wwwroot/index.html (hosted Blazor WebAssembly with SignalR implemented):

  • Add an autostart="false" attribute to the <script> tag for the blazor.{server|webassembly}.js script.
  • Place a script that calls Blazor.start() after the Blazor script is loaded and inside the closing </body> tag.

When autostart is disabled, any aspect of the app that doesn't depend on the circuit works normally. For example, client-side routing is operational. However, any aspect that depends on the circuit isn't operational until Blazor.start() is called. App behavior is unpredictable without an established circuit. For example, component methods fail to execute while the circuit is disconnected.

For more information, including how to initialize Blazor when the document is ready and how to chain to a JS Promise, see ASP.NET Core Blazor startup.

Configure SignalR server timeout and Keep-Alive on the client

Blazor Server hub

This section only applies to Blazor Server.

Configure the following values for the Blazor Server hub connection on the client:

  • serverTimeoutInMilliseconds: The server timeout in milliseconds. If this timeout elapses without receiving any messages from the server, the connection is terminated with an error. The default timeout value is 30 seconds. The server timeout should be at least double the value assigned to the Keep-Alive interval (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: Default interval at which to ping the server. This setting allows the server to detect hard disconnects, such as when a client unplugs their computer from the network. The ping occurs at most as often as the server pings. If the server pings every five seconds, assigning a value lower than 5000 (5 seconds) pings every five seconds. The default value is 15 seconds. The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout (serverTimeoutInMilliseconds).

The following example for either Pages/_Layout.cshtml (Blazor Server) or wwwroot/index.html (Blazor WebAssembly) uses default values:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Hub connections created in Razor components

This section only applies to Blazor Server components and components in the Client project of a hosted Blazor WebAssembly solution.

When creating a hub connection in a component, set the ServerTimeout (default: 30 seconds), HandshakeTimeout (default: 15 seconds), and KeepAliveInterval (default: 15 seconds) on the built HubConnection. The following example, based on the Index component in the SignalR with Blazor tutorial, uses default values:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

When changing the values of the server timeout (ServerTimeout) or the Keep-Alive interval (KeepAliveInterval:

  • The server timeout should be at least double the value assigned to the Keep-Alive interval.
  • The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout.

For more information, see the Global deployment and connection failures sections of the following articles:

Modify the reconnection handler (Blazor Server)

The reconnection handler's circuit connection events can be modified for custom behaviors, such as:

  • To notify the user if the connection is dropped.
  • To perform logging (from the client) when a circuit is connected.

To modify the connection events, register callbacks for the following connection changes:

  • Dropped connections use onConnectionDown.
  • Established/re-established connections use onConnectionUp.

Both onConnectionDown and onConnectionUp must be specified.

Pages/_Layout.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionHandler: {
          onConnectionDown: (options, error) => console.error(error),
          onConnectionUp: () => console.log("Up, up, and away!")
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Adjust the reconnection retry count and interval (Blazor Server)

To adjust the reconnection retry count and interval, set the number of retries (maxRetries) and period in milliseconds permitted for each retry attempt (retryIntervalMilliseconds).

Pages/_Layout.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionOptions: {
          maxRetries: 3,
          retryIntervalMilliseconds: 2000
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Disconnect the Blazor circuit from the client (Blazor Server)

By default, a Blazor circuit is disconnected when the unload page event is triggered. To disconnect the circuit for other scenarios on the client, invoke Blazor.disconnect in the appropriate event handler. In the following example, the circuit is disconnected when the page is hidden (pagehide event):

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Blazor Server circuit handler

Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. The following example of a circuit handler tracks open SignalR connections.

TrackingCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Circuit handlers are registered using DI. Scoped instances are created per instance of a circuit. Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked.

In Program.cs:

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the Blazor Server circuit. To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. If any DI service throws an unhandled exception during disposal, the framework logs the exception. For more information, see ASP.NET Core Blazor dependency injection.

For general guidance on ASP.NET Core SignalR configuration, see the topics in the Overview of ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly app, for example in the Use ASP.NET Core SignalR with Blazor tutorial, or a standalone Blazor WebAssembly app that uses SignalR, see ASP.NET Core SignalR configuration.

SignalR cross-origin negotiation for authentication (Blazor WebAssembly)

To configure SignalR's underlying client to send credentials, such as cookies or HTTP authentication headers:

  • Use SetBrowserRequestCredentials to set Include on cross-origin fetch requests.

    IncludeRequestCredentialsMessageHandler.cs:

    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components.WebAssembly.Http;
    
    public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
            return base.SendAsync(request, cancellationToken);
        }
    }
    
  • Where a hub connection is built, assign the HttpMessageHandler to the HttpMessageHandlerFactory option:

    HubConnectionBuilder hubConnection;
    
    ...
    
    hubConnection = new HubConnectionBuilder()
        .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
        {
            options.HttpMessageHandlerFactory = innerHandler => 
                new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
        }).Build();
    

    The preceding example configures the hub connection URL to the absolute URI address at /chathub, which is the URL used in the SignalR with Blazor tutorial in the Index component (Pages/Index.razor). The URI can also be set via a string, for example https://signalr.example.com, or via configuration. Navigation is an injected NavigationManager.

Note

To authenticate users for SignalR hubs, see Secure ASP.NET Core Blazor WebAssembly.

For more information, see ASP.NET Core SignalR configuration.

Render mode (Blazor WebAssembly)

If a Blazor WebAssembly app that uses SignalR is configured to prerender on the server, prerendering occurs before the client connection to the server is established. For more information, see the following articles:

Additional resources for Blazor WebAssembly apps

Use sticky sessions for webfarm hosting (Blazor Server)

A Blazor Server app prerenders in response to the first client request, which creates the UI state on the server. When the client attempts to create a SignalR connection, the client must reconnect to the same server. Blazor Server apps that use more than one backend server should implement sticky sessions for SignalR connections.

Note

The following error is thrown by an app that hasn't enabled sticky sessions in a webfarm:

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Azure SignalR Service (Blazor Server)

We recommend using the Azure SignalR Service for Blazor Server apps hosted in Microsoft Azure. The service works in conjunction with the app's Blazor Hub for scaling up a Blazor Server app to a large number of concurrent SignalR connections. In addition, the SignalR Service's global reach and high-performance data centers significantly aid in reducing latency due to geography.

For prerendering support with the Azure SignalR Service, configure the app to use sticky sessions. For more information, see Host and deploy ASP.NET Core Blazor Server.

Circuit handler options for Blazor Server apps

Configure the Blazor Server circuit with the CircuitOptions shown in the following table.

Option Default Description
DetailedErrors false Send detailed exception messages to JavaScript when an unhandled exception occurs on the circuit or when a .NET method invocation through JS interop results in an exception.
DisconnectedCircuitMaxRetained 100 Maximum number of disconnected circuits that the server holds in memory at a time.
DisconnectedCircuitRetentionPeriod 3 minutes Maximum amount of time a disconnected circuit is held in memory before being torn down.
JSInteropDefaultCallTimeout 1 minute Maximum amount of time the server waits before timing out an asynchronous JavaScript function invocation.
MaxBufferedUnacknowledgedRenderBatches 10 Maximum number of unacknowledged render batches the server keeps in memory per circuit at a given time to support robust reconnection. After reaching the limit, the server stops producing new render batches until one or more batches are acknowledged by the client.

Configure the options in Startup.ConfigureServices with an options delegate to AddServerSideBlazor. The following example assigns the default option values shown in the preceding table. Confirm that Startup.cs uses the System namespace (using System;).

Startup.ConfigureServices:

services.AddServerSideBlazor(options =>
{
    options.DetailedErrors = false;
    options.DisconnectedCircuitMaxRetained = 100;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
    options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
    options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

To configure the HubConnectionContext, use HubConnectionContextOptions with AddHubOptions. For option descriptions, see ASP.NET Core SignalR configuration. The following example assigns the default option values. Confirm that Startup.cs uses the System namespace (using System;).

Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
        options.EnableDetailedErrors = false;
        options.HandshakeTimeout = TimeSpan.FromSeconds(15);
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
        options.MaximumParallelInvocationsPerClient = 1;
        options.MaximumReceiveMessageSize = 32 * 1024;
        options.StreamBufferCapacity = 10;
    });

Warning

The default value of MaximumReceiveMessageSize is 32 KB. Increasing the value may increase the risk of Denial of service (DoS) attacks.

For information on Blazor Server's memory model, see Host and deploy ASP.NET Core Blazor Server.

Blazor Hub endpoint route configuration (Blazor Server)

In Startup.Configure, Blazor Server apps call MapBlazorHub on the IEndpointRouteBuilder of UseEndpoints to map the Blazor Hub to the app's default path. The Blazor Server script (blazor.server.js) automatically points to the endpoint created by MapBlazorHub.

Reflect the connection state in the UI (Blazor Server)

When the client detects that the connection has been lost, a default UI is displayed to the user while the client attempts to reconnect. If reconnection fails, the user is provided the option to retry.

To customize the UI, define a single element with an id of components-reconnect-modal. The following example places the element in the host page.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

Note

If more than one element with an id of components-reconnect-modal are rendered by the app, only the first rendered element receives CSS class changes to display or hide the element.

Add the following CSS styles to the site's stylesheet.

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

The following table describes the CSS classes applied to the components-reconnect-modal element by the Blazor framework.

CSS class Indicates…
components-reconnect-show A lost connection. The client is attempting to reconnect. Show the modal.
components-reconnect-hide An active connection is re-established to the server. Hide the modal.
components-reconnect-failed Reconnection failed, probably due to a network failure. To attempt reconnection, call window.Blazor.reconnect() in JavaScript.
components-reconnect-rejected Reconnection rejected. The server was reached but refused the connection, and the user's state on the server is lost. To reload the app, call location.reload() in JavaScript. This connection state may result when:
  • A crash in the server-side circuit occurs.
  • The client is disconnected long enough for the server to drop the user's state. Instances of the user's components are disposed.
  • The server is restarted, or the app's worker process is recycled.

Customize the delay before the reconnection display appears by setting the transition-delay property in the site's CSS for the modal element. The following example sets the transition delay from 500 ms (default) to 1,000 ms (1 second).

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

To display the current reconnect attempt, define an element with an id of components-reconnect-current-attempt. To display the maximum number of reconnect retries, define an element with an id of components-reconnect-max-retries. The following example places these elements inside a reconnect attempt modal element in the host page following the previous example.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

When the custom reconnect modal appears, it renders content similar to the following based on the preceding code:

There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Render mode (Blazor Server)

By default, Blazor Server apps prerender the UI on the server before the client connection to the server is established. For more information, see Component Tag Helper in ASP.NET Core.

Blazor startup

Configure the manual start of a Blazor app's SignalR circuit in the Pages/_Host.cshtml file (Blazor Server) or wwwroot/index.html (hosted Blazor WebAssembly with SignalR implemented):

  • Add an autostart="false" attribute to the <script> tag for the blazor.{server|webassembly}.js script.
  • Place a script that calls Blazor.start() after the Blazor script is loaded and inside the closing </body> tag.

When autostart is disabled, any aspect of the app that doesn't depend on the circuit works normally. For example, client-side routing is operational. However, any aspect that depends on the circuit isn't operational until Blazor.start() is called. App behavior is unpredictable without an established circuit. For example, component methods fail to execute while the circuit is disconnected.

For more information, including how to initialize Blazor when the document is ready and how to chain to a JS Promise, see ASP.NET Core Blazor startup.

Configure SignalR server timeout and Keep-Alive on the client

Blazor Server hub

This section only applies to Blazor Server.

Configure the following values for the Blazor Server hub connection on the client:

  • serverTimeoutInMilliseconds: The server timeout in milliseconds. If this timeout elapses without receiving any messages from the server, the connection is terminated with an error. The default timeout value is 30 seconds. The server timeout should be at least double the value assigned to the Keep-Alive interval (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: Default interval at which to ping the server. This setting allows the server to detect hard disconnects, such as when a client unplugs their computer from the network. The ping occurs at most as often as the server pings. If the server pings every five seconds, assigning a value lower than 5000 (5 seconds) pings every five seconds. The default value is 15 seconds. The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout (serverTimeoutInMilliseconds).

The following example for either Pages/_Host.cshtml (Blazor Server) or wwwroot/index.html (Blazor WebAssembly) uses default values:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Hub connections created in Razor components

This section only applies to Blazor Server components and components in the Client project of a hosted Blazor WebAssembly solution.

When creating a hub connection in a component, set the ServerTimeout (default: 30 seconds), HandshakeTimeout (default: 15 seconds), and KeepAliveInterval (default: 15 seconds) on the built HubConnection. The following example, based on the Index component in the SignalR with Blazor tutorial, uses default values:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

When changing the values of the server timeout (ServerTimeout) or the Keep-Alive interval (KeepAliveInterval:

  • The server timeout should be at least double the value assigned to the Keep-Alive interval.
  • The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout.

For more information, see the Global deployment and connection failures sections of the following articles:

Modify the reconnection handler (Blazor Server)

The reconnection handler's circuit connection events can be modified for custom behaviors, such as:

  • To notify the user if the connection is dropped.
  • To perform logging (from the client) when a circuit is connected.

To modify the connection events, register callbacks for the following connection changes:

  • Dropped connections use onConnectionDown.
  • Established/re-established connections use onConnectionUp.

Both onConnectionDown and onConnectionUp must be specified.

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionHandler: {
          onConnectionDown: (options, error) => console.error(error),
          onConnectionUp: () => console.log("Up, up, and away!")
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Adjust the reconnection retry count and interval (Blazor Server)

To adjust the reconnection retry count and interval, set the number of retries (maxRetries) and period in milliseconds permitted for each retry attempt (retryIntervalMilliseconds).

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionOptions: {
          maxRetries: 3,
          retryIntervalMilliseconds: 2000
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Disconnect the Blazor circuit from the client (Blazor Server)

By default, a Blazor circuit is disconnected when the unload page event is triggered. To disconnect the circuit for other scenarios on the client, invoke Blazor.disconnect in the appropriate event handler. In the following example, the circuit is disconnected when the page is hidden (pagehide event):

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Blazor Server circuit handler

Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. The following example of a circuit handler tracks open SignalR connections.

TrackingCircuitHandler.cs:

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Circuit handlers are registered using DI. Scoped instances are created per instance of a circuit. Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked.

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
}

If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the Blazor Server circuit. To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. If any DI service throws an unhandled exception during disposal, the framework logs the exception. For more information, see ASP.NET Core Blazor dependency injection.

For general guidance on ASP.NET Core SignalR configuration, see the topics in the Overview of ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly app, for example in the Use ASP.NET Core SignalR with Blazor tutorial, or a standalone Blazor WebAssembly app that uses SignalR, see ASP.NET Core SignalR configuration.

SignalR cross-origin negotiation for authentication (Blazor WebAssembly)

To configure SignalR's underlying client to send credentials, such as cookies or HTTP authentication headers:

  • Use SetBrowserRequestCredentials to set Include on cross-origin fetch requests.

    IncludeRequestCredentialsMessageHandler.cs:

    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components.WebAssembly.Http;
    
    public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
            return base.SendAsync(request, cancellationToken);
        }
    }
    
  • Where a hub connection is built, assign the HttpMessageHandler to the HttpMessageHandlerFactory option:

    HubConnectionBuilder hubConnection;
    
    ...
    
    hubConnection = new HubConnectionBuilder()
        .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
        {
            options.HttpMessageHandlerFactory = innerHandler => 
                new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
        }).Build();
    

    The preceding example configures the hub connection URL to the absolute URI address at /chathub, which is the URL used in the SignalR with Blazor tutorial in the Index component (Pages/Index.razor). The URI can also be set via a string, for example https://signalr.example.com, or via configuration. Navigation is an injected NavigationManager.

Note

To authenticate users for SignalR hubs, see Secure ASP.NET Core Blazor WebAssembly.

For more information, see ASP.NET Core SignalR configuration.

Additional resources for Blazor WebAssembly apps

Use sticky sessions for webfarm hosting (Blazor Server)

A Blazor Server app prerenders in response to the first client request, which creates the UI state on the server. When the client attempts to create a SignalR connection, the client must reconnect to the same server. Blazor Server apps that use more than one backend server should implement sticky sessions for SignalR connections. For more information, see Host and deploy ASP.NET Core Blazor Server.

Note

The following error is thrown by an app that hasn't enabled sticky sessions in a webfarm:

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.

Azure SignalR Service (Blazor Server)

We recommend using the Azure SignalR Service for Blazor Server apps hosted in Microsoft Azure. The service works in conjunction with the app's Blazor Hub for scaling up a Blazor Server app to a large number of concurrent SignalR connections. In addition, the SignalR Service's global reach and high-performance data centers significantly aid in reducing latency due to geography.

For prerendering support with the Azure SignalR Service, configure the app to use sticky sessions. For more information, see Host and deploy ASP.NET Core Blazor Server.

Circuit handler options for Blazor Server apps

Configure the Blazor Server circuit with the CircuitOptions shown in the following table.

Option Default Description
DetailedErrors false Send detailed exception messages to JavaScript when an unhandled exception occurs on the circuit or when a .NET method invocation through JS interop results in an exception.
DisconnectedCircuitMaxRetained 100 Maximum number of disconnected circuits that the server holds in memory at a time.
DisconnectedCircuitRetentionPeriod 3 minutes Maximum amount of time a disconnected circuit is held in memory before being torn down.
JSInteropDefaultCallTimeout 1 minute Maximum amount of time the server waits before timing out an asynchronous JavaScript function invocation.
MaxBufferedUnacknowledgedRenderBatches 10 Maximum number of unacknowledged render batches the server keeps in memory per circuit at a given time to support robust reconnection. After reaching the limit, the server stops producing new render batches until one or more batches are acknowledged by the client.

Configure the options in Startup.ConfigureServices with an options delegate to AddServerSideBlazor. The following example assigns the default option values shown in the preceding table. Confirm that Startup.cs uses the System namespace (using System;).

Startup.ConfigureServices:

services.AddServerSideBlazor(options =>
{
    options.DetailedErrors = false;
    options.DisconnectedCircuitMaxRetained = 100;
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
    options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
    options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

To configure the HubConnectionContext, use HubConnectionContextOptions with AddHubOptions. For option descriptions, see ASP.NET Core SignalR configuration. The following example assigns the default option values. Confirm that Startup.cs uses the System namespace (using System;).

Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
        options.EnableDetailedErrors = false;
        options.HandshakeTimeout = TimeSpan.FromSeconds(15);
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
        options.MaximumParallelInvocationsPerClient = 1;
        options.MaximumReceiveMessageSize = 32 * 1024;
        options.StreamBufferCapacity = 10;
    });

Warning

The default value of MaximumReceiveMessageSize is 32 KB. Increasing the value may increase the risk of Denial of service (DoS) attacks.

For information on Blazor Server's memory model, see Host and deploy ASP.NET Core Blazor Server.

Blazor Hub endpoint route configuration (Blazor Server)

In Startup.Configure, Blazor Server apps call MapBlazorHub on the IEndpointRouteBuilder of UseEndpoints to map the Blazor Hub to the app's default path. The Blazor Server script (blazor.server.js) automatically points to the endpoint created by MapBlazorHub.

Reflect the connection state in the UI (Blazor Server)

When the client detects that the connection has been lost, a default UI is displayed to the user while the client attempts to reconnect. If reconnection fails, the user is provided the option to retry.

To customize the UI, define a single element with an id of components-reconnect-modal. The following example places the element in the host page.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

Note

If more than one element with an id of components-reconnect-modal are rendered by the app, only the first rendered element receives CSS class changes to display or hide the element.

Add the following CSS styles to the site's stylesheet.

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

The following table describes the CSS classes applied to the components-reconnect-modal element by the Blazor framework.

CSS class Indicates…
components-reconnect-show A lost connection. The client is attempting to reconnect. Show the modal.
components-reconnect-hide An active connection is re-established to the server. Hide the modal.
components-reconnect-failed Reconnection failed, probably due to a network failure. To attempt reconnection, call window.Blazor.reconnect() in JavaScript.
components-reconnect-rejected Reconnection rejected. The server was reached but refused the connection, and the user's state on the server is lost. To reload the app, call location.reload() in JavaScript. This connection state may result when:
  • A crash in the server-side circuit occurs.
  • The client is disconnected long enough for the server to drop the user's state. Instances of the user's components are disposed.
  • The server is restarted, or the app's worker process is recycled.

Render mode (Blazor Server)

By default, Blazor Server apps prerender the UI on the server before the client connection to the server is established. For more information, see Component Tag Helper in ASP.NET Core.

Blazor startup

Configure the manual start of a Blazor app's SignalR circuit in the Pages/_Host.cshtml file (Blazor Server) or wwwroot/index.html (hosted Blazor WebAssembly with SignalR implemented):

  • Add an autostart="false" attribute to the <script> tag for the blazor.{server|webassembly}.js script.
  • Place a script that calls Blazor.start() after the Blazor script is loaded and inside the closing </body> tag.

When autostart is disabled, any aspect of the app that doesn't depend on the circuit works normally. For example, client-side routing is operational. However, any aspect that depends on the circuit isn't operational until Blazor.start() is called. App behavior is unpredictable without an established circuit. For example, component methods fail to execute while the circuit is disconnected.

For more information, including how to initialize Blazor when the document is ready and how to chain to a JS Promise, see ASP.NET Core Blazor startup.

Configure SignalR server timeout and Keep-Alive on the client

Blazor Server hub

This section only applies to Blazor Server.

Configure the following values for the Blazor Server hub connection on the client:

  • serverTimeoutInMilliseconds: The server timeout in milliseconds. If this timeout elapses without receiving any messages from the server, the connection is terminated with an error. The default timeout value is 30 seconds. The server timeout should be at least double the value assigned to the Keep-Alive interval (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: Default interval at which to ping the server. This setting allows the server to detect hard disconnects, such as when a client unplugs their computer from the network. The ping occurs at most as often as the server pings. If the server pings every five seconds, assigning a value lower than 5000 (5 seconds) pings every five seconds. The default value is 15 seconds. The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout (serverTimeoutInMilliseconds).

The following example for either Pages/_Host.cshtml (Blazor Server) or wwwroot/index.html (Blazor WebAssembly) uses default values:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

Hub connections created in Razor components

This section only applies to Blazor Server components and components in the Client project of a hosted Blazor WebAssembly solution.

When creating a hub connection in a component, set the ServerTimeout (default: 30 seconds), HandshakeTimeout (default: 15 seconds), and KeepAliveInterval (default: 15 seconds) on the built HubConnection. The following example, based on the Index component in the SignalR with Blazor tutorial, uses default values:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

When changing the values of the server timeout (ServerTimeout) or the Keep-Alive interval (KeepAliveInterval:

  • The server timeout should be at least double the value assigned to the Keep-Alive interval.
  • The Keep-Alive interval should be less than or equal to half the value assigned to the server timeout.

For more information, see the Global deployment and connection failures sections of the following articles:

Modify the reconnection handler (Blazor Server)

The reconnection handler's circuit connection events can be modified for custom behaviors, such as:

  • To notify the user if the connection is dropped.
  • To perform logging (from the client) when a circuit is connected.

To modify the connection events, register callbacks for the following connection changes:

  • Dropped connections use onConnectionDown.
  • Established/re-established connections use onConnectionUp.

Both onConnectionDown and onConnectionUp must be specified.

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionHandler: {
          onConnectionDown: (options, error) => console.error(error),
          onConnectionUp: () => console.log("Up, up, and away!")
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Adjust the reconnection retry count and interval (Blazor Server)

To adjust the reconnection retry count and interval, set the number of retries (maxRetries) and period in milliseconds permitted for each retry attempt (retryIntervalMilliseconds).

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        reconnectionOptions: {
          maxRetries: 3,
          retryIntervalMilliseconds: 2000
        }
      });
    </script>
</body>

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Blazor Server circuit handler

Blazor Server allows code to define a circuit handler, which allows running code on changes to the state of a user's circuit. A circuit handler is implemented by deriving from CircuitHandler and registering the class in the app's service container. The following example of a circuit handler tracks open SignalR connections.

TrackingCircuitHandler.cs:

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new HashSet<Circuit>();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

Circuit handlers are registered using DI. Scoped instances are created per instance of a circuit. Using the TrackingCircuitHandler in the preceding example, a singleton service is created because the state of all circuits must be tracked.

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
}

If a custom circuit handler's methods throw an unhandled exception, the exception is fatal to the Blazor Server circuit. To tolerate exceptions in a handler's code or called methods, wrap the code in one or more try-catch statements with error handling and logging.

When a circuit ends because a user has disconnected and the framework is cleaning up the circuit state, the framework disposes of the circuit's DI scope. Disposing the scope disposes any circuit-scoped DI services that implement System.IDisposable. If any DI service throws an unhandled exception during disposal, the framework logs the exception. For more information, see ASP.NET Core Blazor dependency injection.

Blazor Server circuit handler to capture users for custom services

Use a CircuitHandler to capture a user from the AuthenticationStateProvider and set that user in a service. For more information and example code, see ASP.NET Core Blazor Server additional security scenarios.

Avoid IHttpContextAccessor/HttpContext in Razor components

Don't use IHttpContextAccessor/HttpContext directly or indirectly in the Razor components of Blazor Server apps. Blazor apps run outside of the ASP.NET Core pipeline context. The HttpContext isn't guaranteed to be available within the IHttpContextAccessor, and HttpContext isn't guaranteed to hold the context that started the Blazor app.

The recommended approach for passing request state to the Blazor app is through root component parameters during the app's initial rendering. Alternatively, the app can copy the data into a scoped service in the root component's initialization lifecycle event for use across the app. For more information, see ASP.NET Core Blazor Server additional security scenarios.

A critical aspect of Blazor Server security is that the user attached to a given circuit might become updated at some point after the Blazor circuit is established but the IHttpContextAccessor isn't updated. For more information on addressing this situation with custom services, see ASP.NET Core Blazor Server additional security scenarios.

Additional resources for Blazor Server apps