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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

SignalR cross-origin negotiation for authentication

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 hubConnecton;
    
    ...
    
    hubConnecton = new HubConnectionBuilder()
        .WithUrl(new Uri(NavigationManager.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.

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

Render mode

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

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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

Circuit handler options

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;
    });

Blazor Hub endpoint route configuration

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

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 an element with an id of components-reconnect-modal in the <body> of the _Layout.cshtml Razor page.

Pages/_Layout.cshtml:

<div id="components-reconnect-modal">
    ...
</div>

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 {
    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

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 Server app's SignalR circuit in the Pages/_Layout.cshtml file:

  • Add an autostart="false" attribute to the <script> tag for the blazor.server.js script.
  • Place a script that calls Blazor.start after the blazor.server.js script's <script> tag 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 client logging

On the client builder, pass in the configureSignalR configuration object that calls configureLogging with the log level.

Pages/_Layout.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        configureSignalR: function (builder) {
          builder.configureLogging("information");
        }
      });
    </script>
</body>

In the preceding example, information is equivalent to a log level of LogLevel.Information.

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

Modify the reconnection handler

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

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.

Hide or replace the reconnection display

To hide the reconnection display, set the reconnection handler's _reconnectionDisplay to an empty object ({} or new Object()).

Pages/_Layout.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      window.addEventListener('beforeunload', function () {
        Blazor.defaultReconnectionHandler._reconnectionDisplay = {};
      });

      Blazor.start();
    </script>
</body>

To replace the reconnection display, set _reconnectionDisplay in the preceding example to the element for display:

Blazor.defaultReconnectionHandler._reconnectionDisplay = 
  document.getElementById("{ELEMENT ID}");

The placeholder {ELEMENT ID} is the ID of the HTML element to display.

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

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;
}

Disconnect the Blazor circuit from the client

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.

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.

Azure SignalR Service

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.

Additional resources

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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

SignalR cross-origin negotiation for authentication

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 hubConnecton;
    
    ...
    
    hubConnecton = new HubConnectionBuilder()
        .WithUrl(new Uri(NavigationManager.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.

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

Render mode

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

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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

Circuit handler options

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;
    });

Blazor Hub endpoint route configuration

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

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 an element with an id of components-reconnect-modal in the <body> of the _Host.cshtml Razor page.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    ...
</div>

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 {
    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

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 Server app's SignalR circuit in the Pages/_Host.cshtml file:

  • Add an autostart="false" attribute to the <script> tag for the blazor.server.js script.
  • Place a script that calls Blazor.start after the blazor.server.js script's <script> tag 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 client logging

On the client builder, pass in the configureSignalR configuration object that calls configureLogging with the log level.

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        configureSignalR: function (builder) {
          builder.configureLogging("information");
        }
      });
    </script>
</body>

In the preceding example, information is equivalent to a log level of LogLevel.Information.

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

Modify the reconnection handler

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

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.

Hide or replace the reconnection display

To hide the reconnection display, set the reconnection handler's _reconnectionDisplay to an empty object ({} or new Object()).

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      window.addEventListener('beforeunload', function () {
        Blazor.defaultReconnectionHandler._reconnectionDisplay = {};
      });

      Blazor.start();
    </script>
</body>

To replace the reconnection display, set _reconnectionDisplay in the preceding example to the element for display:

Blazor.defaultReconnectionHandler._reconnectionDisplay = 
  document.getElementById("{ELEMENT ID}");

The placeholder {ELEMENT ID} is the ID of the HTML element to display.

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

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;
}

Disconnect the Blazor circuit from the client

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.

Azure SignalR Service

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.

Additional resources

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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

SignalR cross-origin negotiation for authentication

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 hubConnecton;
    
    ...
    
    hubConnecton = new HubConnectionBuilder()
        .WithUrl(new Uri(NavigationManager.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.

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

Additional resources

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 Introduction to ASP.NET Core SignalR area of the documentation. To configure SignalR added to a hosted Blazor WebAssembly solution, see ASP.NET Core SignalR configuration.

Circuit handler options

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;
    });

Blazor Hub endpoint route configuration

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

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 an element with an id of components-reconnect-modal in the <body> of the _Host.cshtml Razor page.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    ...
</div>

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 {
    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

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 Server app's SignalR circuit in the Pages/_Host.cshtml file:

  • Add an autostart="false" attribute to the <script> tag for the blazor.server.js script.
  • Place a script that calls Blazor.start after the blazor.server.js script's <script> tag 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 client logging

On the client builder, pass in the configureSignalR configuration object that calls configureLogging with the log level.

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      Blazor.start({
        configureSignalR: function (builder) {
          builder.configureLogging("information");
        }
      });
    </script>
</body>

In the preceding example, information is equivalent to a log level of LogLevel.Information.

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

Modify the reconnection handler

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

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.

Hide or replace the reconnection display

To hide the reconnection display, set the reconnection handler's _reconnectionDisplay to an empty object ({} or new Object()).

Pages/_Host.cshtml:

<body>
    ...

    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
      window.addEventListener('beforeunload', function () {
        Blazor.defaultReconnectionHandler._reconnectionDisplay = {};
      });

      Blazor.start();
    </script>
</body>

To replace the reconnection display, set _reconnectionDisplay in the preceding example to the element for display:

Blazor.defaultReconnectionHandler._reconnectionDisplay = 
  document.getElementById("{ELEMENT ID}");

The placeholder {ELEMENT ID} is the ID of the HTML element to display.

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.

Azure SignalR Service

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.

Additional resources