Compartir a través de


Guía de BlazorSignalR para ASP.NET Core

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulta la versión .NET 8 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

En este artículo se explica cómo configurar y administrar conexiones de SignalR en aplicaciones Blazor.

Para obtener instrucciones generales sobre la configuración de SignalR para ASP.NET Core, vea los temas del área Información general de ASP.NET Core SignalR de la documentación, especialmente la Configuración SignalR de ASP.NET Core.

Las aplicaciones del lado del servidor usan ASP.NET Core SignalR para comunicarse con el explorador. SignalRLas condiciones de hospedaje y escalado de se aplican a las aplicaciones del lado del servidor.

Blazor funciona mejor cuando se usa WebSockets como transporte de SignalR debido a su menor latencia, confiabilidad y seguridad. SignalR usa el sondeo largo cuando WebSockets no está disponible o cuando la aplicación está configurada explícitamente para usarlo.

Servicio de Azure SignalR con reconexión con estado

La reconexión con estado (WithStatefulReconnect) se publicó con .NET 8, pero actualmente no se admite para el servicio de Azure SignalR. Para obtener más información, consulte ¿Compatibilidad con la reconexión con estado? (Azure/azure-signalr #1878).

Compresión de WebSocket para componentes de Servidor interactivo

De forma predeterminada, los componentes de Interactive Server:

  • Habilite la compresión para las conexiones de WebSocket. ConfigureWebsocketOptions controla la compresión de WebSocket.

  • Adopte una directiva frame-ancestorsDirectiva de seguridad de contenido (CSP) establecida en 'self', que solo permite insertar la aplicación en un <iframe> del origen desde el que se sirve la aplicación cuando se habilita la compresión o cuando se proporciona una configuración para el contexto de WebSocket. ContentSecurityFrameAncestorPolicy controla el CSP de frame-ancestors.

El CSP de frame-ancestors se puede quitar manualmente estableciendo el valor de ConfigureWebSocketOptions en null, ya que puede configurar el CSP de forma centralizada. Cuando el CSP de frame-ancestors se administra de forma centralizada, se debe tener cuidado para aplicar una directiva cada vez que se represente el primer documento. No se recomienda quitar completamente la directiva, ya que podría hacer que la aplicación sea vulnerable a ataques.

Ejemplos de uso:

Deshabilite la compresión estableciendo ConfigureWebSocketOptions en null, lo que reduce la vulnerabilidad de la aplicación para atacar, pero puede provocar un rendimiento reducido:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

Cuando la compresión está habilitada, configure un CSP de frame-ancestors más estricto con un valor de 'none' (comillas simples requeridas), lo que permite la compresión de WebSocket, pero impide que los exploradores inserte la aplicación en cualquier <iframe>:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Cuando la compresión está habilitada, quite el frame-ancestorsCSP de estableciendo ContentSecurityFrameAncestorsPolicy en null. Este escenario solo se recomienda para las aplicaciones que establecer el CSP de forma centralizada:

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

Importante

Los exploradores aplican directivas CSP desde varios encabezados CSP mediante el valor de directiva de directiva más estricto. Por lo tanto, un desarrollador no puede agregar una directiva más débil frame-ancestors que 'self' a propósito o por error.

Las comillas simples son necesarias en el valor de cadena pasado a ContentSecurityFrameAncestorsPolicy:

Valores no admitidos:none, self

Valores admitidos:'none', 'self'

Entre las opciones adicionales se incluyen especificar uno o varios orígenes de host y orígenes de esquema.

Para ver las implicaciones de seguridad, vea Guía de mitigación de amenazas para ASP.NET Core Blazor representación interactiva del lado servidor. Para obtener más información sobre la directiva frame-ancestors, vea CSP: frame-ancestors (documentación de MDN).

Deshabilitación de la compresión de respuesta para Recarga activa

Al usar Recarga activa, deshabilite el middleware de compresión de respuesta en el entorno Development. Tanto si se utiliza el código predeterminado de una plantilla de proyecto como si no, siempre se llama primero a UseResponseCompression en la canalización de procesamiento de solicitudes.

En el archivo Program:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
}

Negociación entre orígenes de SignalR del lado cliente para la autenticación

En esta sección se explica cómo configurar el cliente subyacente de SignalR para que envíe credenciales, como cookies o encabezados de autenticación HTTP.

Usa SetBrowserRequestCredentials para establecer Include en solicitudes de fetch entre orígenes.

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

Si existe una conexión con un centro, asigna HttpMessageHandler a la opción HttpMessageHandlerFactory:

private HubConnectionBuilder? hubConnection;

...

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

En el ejemplo anterior se configura la dirección URL de conexión del centro a la dirección URI absoluta en /chathub. El URI también se puede establecer por medio de una cadena (por ejemplo, https://signalr.example.com) o mediante configuración. Navigation es un objeto NavigationManager insertado.

Para obtener más información, consulta Configuración de SignalR en ASP.NET Core.

Representación del lado cliente

Si se configura la representación previa, esta se produce antes de establecer la conexión de cliente con el servidor. Para obtener más información, consulta Representación previa de componentes Razor de ASP.NET Core .

Si se configura la representación previa, esta se produce antes de establecer la conexión de cliente con el servidor. Para obtener más información, consulta los artículos siguientes:

Tamaño de estado antes de la representación y límite de tamaño del mensaje SignalR

Un tamaño grande de estado anterior a la representación puede superar el límite de tamaño del mensaje de circuito SignalR, lo que da como resultado lo siguiente:

  • El circuito SignalR no se puede inicializar con un error en el cliente: Circuit host not initialized.
  • La interfaz de usuario de reconexión del cliente aparece cuando se produce un error en el circuito. No es posible la recuperación.

Para resolver el problema, usa uno de los enfoques siguientes:

  • Reduce la cantidad de datos que se están colocando en el estado anterior a la representación.
  • Aumenta el límite de tamaño del mensajeSignalR. ADVERTENCIA: Aumentar el límite puede aumentar el riesgo de ataques por denegación de servicio (DoS).

Recursos adicionales del lado cliente

Uso de afinidad de sesión (sesiones permanentes) para el hospedaje de granja de servidores del lado servidor

Cuando hay más de un servidor backend en uso, la aplicación debe implementar afinidad de sesión, también llamada sesiones permanentes. La afinidad de sesión garantiza que el circuito de un cliente se vuelva a conectar al mismo servidor si se quita la conexión, lo que es importante porque el estado del cliente solo se mantiene en la memoria del servidor que estableció primero el circuito del cliente.

El siguiente error lo produce una aplicación que no ha habilitado la afinidad de sesión en una granja de servidores:

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

Para obtener más información sobre la afinidad de sesión con el hospedaje de Azure App Service, consulta Hospedaje e implementación de aplicaciones Blazor del lado servidor ASP.NET Core.

Azure SignalR Service

Azure SignalR Service opcional funciona junto con el centro SignalR de la aplicación para escalar verticalmente una aplicación de servidor a un gran número de conexiones simultáneas. Además, los centros de datos de alto rendimiento y alcance global del servicio son de gran ayuda a la hora de reducir la latencia ocasionada por la geografía.

El servicio no es necesario para las aplicaciones Blazor hospedadas en Azure App Service o Azure Container Apps, pero puede ser útil en otros entornos de hospedaje:

  • Para facilitar el escalado horizontal de la conexión.
  • Controlar la distribución global.

Para obtener más información, consulta Hospedaje e implementación de Blazoraplicaciones del lado servidor de ASP.NET Core.

Opciones del controlador de circuitos del lado servidor

Configura el circuito con CircuitOptions. Visualiza los valores predeterminados en el origen de referencia.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Lee o establece las opciones del archivo Program con un delegado de opciones en AddInteractiveServerComponents. El marcador de posición {OPTION} representa la opción y el marcador de posición {VALUE} es el valor.

En el archivo Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
    options.{OPTION} = {VALUE};
});

Lee o establece las opciones del archivo Program con un delegado de opciones en AddServerSideBlazor. El marcador de posición {OPTION} representa la opción y el marcador de posición {VALUE} es el valor.

En el archivo Program:

builder.Services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Lee o establece las opciones de Startup.ConfigureServices con un delegado de opciones en AddServerSideBlazor. El marcador de posición {OPTION} representa la opción y el marcador de posición {VALUE} es el valor.

En Startup.ConfigureServices de Startup.cs:

services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

Para configurar HubConnectionContext, usa HubConnectionContextOptions con AddHubOptions. Visualiza los valores predeterminados de las opciones de contexto de la conexión del centro en el origen de referencia. Para obtener las descripciones de las opciones de la documentación SignalR, consulta ASP.NET Core SignalR Configuración. El marcador de posición {OPTION} representa la opción y el marcador de posición {VALUE} es el valor.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

En el archivo Program:

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

En el archivo Program:

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

En Startup.ConfigureServices de Startup.cs:

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Advertencia

El valor predeterminado de MaximumReceiveMessageSize es 32 KB. Aumentar el valor puede aumentar el riesgo de ataques por denegación de servicio (DoS).

Blazor se basa en MaximumParallelInvocationsPerClient establecido en 1, que es el valor predeterminado. Para obtener más información, consulta MaximumParallelInvocationsPerClient > 1 interrumpe la carga de archivos en el Blazor Server modo (dotnet/aspnetcore #53951).

Para obtener más información sobre la administración de memoria, consulta Hospedaje e implementación de aplicaciones Blazor del lado servidor de ASP.NET Core.

Opciones del centro deBlazor

Configura las opciones de MapBlazorHub para controlar HttpConnectionDispatcherOptions del centro de Blazor. Visualiza los valores predeterminados de las opciones de distribuidor de la conexión del centro en el origen de referencia. El marcador de posición {OPTION} representa la opción y el marcador de posición {VALUE} es el valor.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Coloca la llamada a app.MapBlazorHub después de la llamada a app.MapRazorComponents en el archivo Program de la aplicación:

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Se produce un error en la configuración del concentrador usado por AddInteractiveServerRenderMode con MapBlazorHub con AmbiguousMatchException:

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.

Para solucionar el problema de las aplicaciones destinadas a .NET 8, asigna mayor prioridad al centro configurado personalizado Blazor mediante el método WithOrder:

app.MapBlazorHub(options =>
{
    options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);

Para obtener más información, consulte los siguientes recursos:

Proporciona las opciones para app.MapBlazorHub en el archivo Program de la aplicación:

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Proporciona las opciones para app.MapBlazorHub en la configuración de enrutamiento de puntos de conexión:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub(options =>
    {
        options.{OPTION} = {VALUE};
    });
    ...
});

Tamaño máximo del mensaje de recepción

Esta sección solo se aplica a los proyectos que implementan SignalR.

El tamaño máximo de mensaje SignalR entrante permitido para los métodos de concentrador está limitado por HubOptions.MaximumReceiveMessageSize (valor predeterminado: 32 KB). Los mensajes SignalR mayores que MaximumReceiveMessageSize producen un error. El marco no impone ningún límite de tamaño para un mensaje SignalR desde el concentrador a un cliente.

Cuando el registro de SignalR no está establecido en SignalR o Seguimiento, solo aparece un error relativo al tamaño del mensaje en la consola de herramientas de desarrollo del explorador:

Error: Conexión desconectada con el error "Error: El servidor ha devuelto un error al cerrarse: Conexión cerrada con un error.".

Cuando el registro del lado servidor de SignalR se establece en Depurar o Seguimiento, el registro del lado servidor inicia una excepción InvalidDataException relativa a un error del tamaño del mensaje.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      ...
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Error:

System.IO.InvalidDataException: Se ha superado el tamaño máximo del mensaje de 32768B. El tamaño del mensaje se puede configurar en AddHubOptions.

Un enfoque implica aumentar el límite estableciendo MaximumReceiveMessageSize en el archivo Program. En el ejemplo siguiente se establece el tamaño máximo del mensaje de recepción en 64 KB:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar el límite de tamaño del mensaje entrante SignalR implica requerir más recursos del servidor y aumenta el riesgo de ataques por denegación de servicio (DoS). Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Una mejor opción para leer cargas grandes consiste en enviar el contenido en fragmentos más pequeños y procesar la carga como Stream. Esto se puede usar al leer cargas grandes de interoperabilidad de JSON de JavaScript (JS) o si los datos de interoperabilidad de JS están disponibles como bytes sin formato. Para obtener un ejemplo en el que se muestra el envío de cargas binarias de gran tamaño en aplicaciones del lado servidor que usan técnicas similares a las del componente InputFile, consulta la Aplicación de ejemplo de envíos binarios y el Ejemplo de componente BlazorInputLargeTextArea.

Nota

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Los formularios que procesan cargas de gran tamaño de SignalR también pueden usar la interoperabilidad de streaming de JS directamente. Para obtener más información, consulta Llamada a métodos de .NET desde funciones de JavaScript en ASP.NET Core Blazor. Para obtener un ejemplo de formularios que transmite datos <textarea> al servidor, consulta Solucionar problemas de formularios ASP.NET Core Blazor.

Un enfoque implica aumentar el límite estableciendo MaximumReceiveMessageSize en el archivo Program. En el ejemplo siguiente se establece el tamaño máximo del mensaje de recepción en 64 KB:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar el límite de tamaño del mensaje entrante SignalR implica requerir más recursos del servidor y aumenta el riesgo de ataques por denegación de servicio (DoS). Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Una mejor opción para leer cargas grandes consiste en enviar el contenido en fragmentos más pequeños y procesar la carga como Stream. Esto se puede usar al leer cargas grandes de interoperabilidad de JSON de JavaScript (JS) o si los datos de interoperabilidad de JS están disponibles como bytes sin formato. Para obtener un ejemplo en el que se muestra el envío de cargas binarias de gran tamaño en Blazor Server que usa técnicas similares a las del componente InputFile, consulta la aplicación de ejemplo de envíos binarios y el ejemplo de componente InputLargeTextArea de Blazor.

Nota

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, consulta Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Los formularios que procesan cargas de gran tamaño de SignalR también pueden usar la interoperabilidad de streaming de JS directamente. Para obtener más información, consulta Llamada a métodos de .NET desde funciones de JavaScript en ASP.NET Core Blazor. Para ver un ejemplo de formularios que transmite datos <textarea> en una aplicación Blazor Server, consulta Solucionar problemas de formularios ASP.NET CoreBlazor.

Para aumentar el límite, establece MaximumReceiveMessageSize en Startup.ConfigureServices:

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

Aumentar el límite de tamaño del mensaje entrante SignalR implica requerir más recursos del servidor y aumenta el riesgo de ataques por denegación de servicio (DoS). Además, la lectura de una gran cantidad de contenido en la memoria, como cadenas o matrices de bytes, también puede dar lugar a que las asignaciones funcionen de forma deficiente con el recolector de elementos no utilizados, lo que puede reducir significativamente el rendimiento.

Ten en cuenta las instrucciones siguientes al desarrollar código que transfiera un gran volumen de datos:

  • Aprovecha la compatibilidad de interoperabilidad de transmisión nativa de JS para transferir datos mayores que el límite de tamaño de mensajes entrantes (SignalR):
  • Sugerencias generales:
    • No asignes objetos grandes en código JS y C#.
    • Libera la memoria consumida al completar o cancelar el proceso.
    • Aplica los requisitos adicionales siguientes por motivos de seguridad:
      • Declara el tamaño máximo del archivo o los datos que se pueden pasar.
      • Declara la tasa mínima de carga desde el cliente al servidor.
    • Después de que el servidor reciba los datos, los datos se pueden:
      • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
      • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.
  • Segmenta los datos en partes más pequeñas y envía los segmentos de datos secuencialmente hasta que el servidor reciba todos los datos.
  • No asignes objetos grandes en código JS y C#.
  • No bloquees el subproceso de interfaz de usuario principal durante períodos largos al enviar o recibir datos.
  • Libera la memoria consumida al completar o cancelar el proceso.
  • Aplica los requisitos adicionales siguientes por motivos de seguridad:
    • Declara el tamaño máximo del archivo o los datos que se pueden pasar.
    • Declara la tasa mínima de carga desde el cliente al servidor.
  • Después de que el servidor reciba los datos, los datos se pueden:
    • Almacenar temporalmente en un búfer de memoria hasta que se recopilen todos los segmentos.
    • Consumir inmediatamente. Por ejemplo, los datos se pueden almacenar inmediatamente en una base de datos o escribir en el disco a medida que se reciba cada segmento.

Configuración de la ruta del punto de conexión del centro Blazor del lado servidor

En el archivo Program, llama a MapBlazorHub para asignar el BlazorHub a la ruta de acceso predeterminada de la aplicación. El script de Blazor (blazor.*.js) apunta automáticamente al punto de conexión creado por MapBlazorHub.

Reflejar el estado de conexión del lado servidor en la interfaz de usuario

Cuando el cliente detecta que se ha perdido la conexión, se muestra al usuario una interfaz de usuario predeterminada mientras el cliente intenta volver a conectarse. Si se produce un error en la reconexión, se proporciona al usuario la opción de volver a intentarlo.

Para personalizar la interfaz de usuario, define un único elemento con un id de components-reconnect-modal. En el ejemplo siguiente se coloca el elemento en el componente App.

App.razor:

Para personalizar la interfaz de usuario, define un único elemento con un id de components-reconnect-modal. En el ejemplo siguiente se coloca el elemento en la página de host.

Pages/_Host.cshtml:

Para personalizar la interfaz de usuario, define un único elemento con un id de components-reconnect-modal. En el ejemplo siguiente se coloca el elemento en la página de diseño.

Pages/_Layout.cshtml:

Para personalizar la interfaz de usuario, define un único elemento con un id de components-reconnect-modal. En el ejemplo siguiente se coloca el elemento en la página de host.

Pages/_Host.cshtml:

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

Nota:

Si la aplicación representa más de un elemento con un id de components-reconnect-modal, solo el primer elemento representado recibirá los cambios de clase CSS para mostrar u ocultar el elemento.

Agrega los siguientes estilos CSS a la hoja de estilos del sitio.

wwwroot/app.css:

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

En la siguiente tabla se describen las clases de CSS que el marco Blazor aplica al elemento components-reconnect-modal.

Clase de CSS Indica…
components-reconnect-show Una conexión perdida. El cliente intenta volver a conectarse. Se muestra el modal.
components-reconnect-hide Se restablece una conexión activa con el servidor. Se oculta el modal.
components-reconnect-failed Error de reconexión, probablemente debido a un error de la red. Para intentar la reconexión, llama a window.Blazor.reconnect() en JavaScript.
components-reconnect-rejected Reconexión rechazada. Se ha alcanzado el servidor pero se ha rechazado la conexión y se ha perdido el estado del usuario en el servidor. Para volver a cargar la aplicación, llama a location.reload() en JavaScript. Este estado de conexión se puede producir cuando:
  • Se produce un bloqueo en el circuito del lado servidor.
  • El cliente se desconecta el tiempo suficiente para que el servidor quite el estado del usuario. Las instancias de los componentes del usuario se eliminan.
  • El servidor se reinicia o se recicla el proceso de trabajo de la aplicación.

Personalice el retraso antes de que aparezca la interfaz de usuario de reconexión estableciendo la propiedad transition-delay en el CSS del sitio para el elemento modal. En el siguiente ejemplo, el retraso de la transición se cambia de 500 ms (valor predeterminado) a 1000 ms (1 segundo).

wwwroot/app.css:

wwwroot/css/site.css:

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

Para mostrar el intento de reconexión actual, defina un elemento con un id de components-reconnect-current-attempt. Para mostrar el número máximo de reintentos de reconexión, defina un elemento con un id de components-reconnect-max-retries. En el ejemplo siguiente se colocan estos elementos dentro de un elemento modal de intento de reconexión en base al ejemplo anterior.

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

Cuando aparece el elemento modal de reconexión personalizado, representa contenido similar al siguiente en función del código anterior:

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

Representación del lado servidor

De forma predeterminada, los componentes realizan una representación previa en el servidor antes de establecer la conexión del cliente con él. Para más información, consulte Representación previa de componentes Razor de ASP.NET Core .

De forma predeterminada, los componentes realizan una representación previa en el servidor antes de establecer la conexión del cliente con él. Para más información, consulte Asistente de etiquetas de componente en ASP.NET Core.

Supervisión de la actividad del circuito del lado servidor

Supervise la actividad del circuito entrante mediante el método CreateInboundActivityHandler en CircuitHandler. La actividad de circuito entrante es cualquier actividad enviada desde el explorador al servidor, como eventos de interfaz de usuario o llamadas de interoperabilidad de JavaScript a .NET.

Por ejemplo, puede usar un controlador de actividad de circuito para detectar si el cliente está inactivo y registrar su identificador de circuito (Circuit.Id):

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
    private Circuit? currentCircuit;
    private readonly ILogger logger;
    private readonly Timer timer;

    public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, 
        IOptions<IdleCircuitOptions> options)
    {
        timer = new Timer
        {
            Interval = options.Value.IdleTimeout.TotalMilliseconds,
            AutoReset = false
        };

        timer.Elapsed += CircuitIdle;
        this.logger = logger;
    }

    private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
    {
        logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        currentCircuit = circuit;

        return Task.CompletedTask;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return context =>
        {
            timer.Stop();
            timer.Start();

            return next(context);
        };
    }

    public void Dispose() => timer.Dispose();
}

public class IdleCircuitOptions
{
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services, 
        Action<IdleCircuitOptions> configureOptions)
    {
        services.Configure(configureOptions);
        services.AddIdleCircuitHandler();

        return services;
    }

    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitHandler, IdleCircuitHandler>();

        return services;
    }
}

Registrar el servicio en el archivo Program. En el ejemplo siguiente se configura el tiempo de espera de inactividad predeterminado de cinco minutos a cinco segundos para probar la implementación anterior IdleCircuitHandler:

builder.Services.AddIdleCircuitHandler(options => 
    options.IdleTimeout = TimeSpan.FromSeconds(5));

Los controladores de actividad de circuito también proporcionan un enfoque para acceder a los servicios de Blazor con ámbito desde otros ámbitos de inserción de dependencias (DI) que no son de Blazor. Para más información y ejemplos, consulte:

Inicio de Blazor

Configura el inicio manual de un circuito SignalR de la aplicación Blazor en el archivo App.razor de Blazor Web App:

Configure el inicio manual de un circuito Blazor de la aplicación SignalR en el archivo Pages/_Host.cshtml (Blazor Server):

Configure el inicio manual de un circuito Blazor de la aplicación SignalR en el archivo Pages/_Layout.cshtml (Blazor Server):

Configure el inicio manual de un circuito Blazor de la aplicación SignalR en el archivo Pages/_Host.cshtml (Blazor Server):

  • Agregue un atributo autostart="false" a la etiqueta <script> para el script blazor.*.js.
  • Coloque un script que llame a Blazor.start() después de que se cargue el script Blazor y dentro de la etiqueta de cierre </body>.

Cuando autostart está deshabilitado, cualquier aspecto de la aplicación que no depende del circuito funciona normalmente. Por ejemplo, el enrutamiento del lado cliente está operativo. Sin embargo, cualquier aspecto que dependa del circuito no funcionará hasta que se llame a Blazor.start(). El comportamiento de la aplicación es imprevisible sin ningún circuito establecido. Por ejemplo, los métodos de componente no se ejecutan mientras el circuito está desconectado.

Para obtener más información, incluida la forma de inicializar Blazor cuando el documento está listo y de encadenar a JS Promise, vea Inicio de Blazor en ASP.NET Core.

Configure los tiempos de espera de SignalR y Keep-Alive en el cliente

Configure los siguientes valores para el cliente:

  • withServerTimeout: configura el tiempo de espera del servidor en milisegundos. Si este tiempo de espera transcurre sin recibir ningún mensaje del servidor, la conexión finaliza con un error. El valor predeterminado del tiempo de espera es de 30 segundos. El tiempo de espera del servidor debe ser al menos el doble del valor asignado al intervalo de Keep-Alive (withKeepAliveInterval).
  • withKeepAliveInterval: configura el intervalo de Mantener conexión en milisegundos (intervalo predeterminado en el que se hace ping al servidor). Esta configuración permite al servidor detectar desconexiones físicas, como cuando un cliente desconecta su equipo de la red. El ping se produce como máximo las veces que el servidor hace ping. Si el servidor hace ping cada cinco segundos, asignar un valor inferior a 5000 (5 segundos) hace ping cada cinco segundos. El valor predeterminado es 15 segundos. El intervalo de Keep-Alive debe ser menor o igual que la mitad del valor asignado al tiempo de espera del servidor (withServerTimeout).

En el ejemplo siguiente del archivo App.razor (Blazor Web App) se muestra la asignación de los valores predeterminados.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
      }
    }
  });
</script>

En el ejemplo siguiente del archivo Pages/_Host.cshtml (Blazor Server, todas las versiones excepto ASP.NET Core en .NET 6) o el archivo Pages/_Layout.cshtml (Blazor Server, ASP.NET Core en .NET 6).

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(30000).withKeepAliveInterval(15000);
    }
  });
</script>

En el ejemplo anterior, el marcador de posición {BLAZOR SCRIPT} es la ruta de acceso del script y el nombre de archivo de Blazor. Para obtener la ubicación del script y la ruta de acceso que se va a usar, consulta ASP.NET Core Blazor estructura de proyecto.

Al crear una conexión de concentrador en un componente, establece ServerTimeout (valor predeterminado: 30 segundos) y KeepAliveInterval (valor predeterminado: 15 segundos) en HubConnectionBuilder. Establece el valor HandshakeTimeout (valor predeterminado: 15 segundos) en la complicación HubConnection. En el ejemplo siguiente se muestra la asignación de valores predeterminados:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(30))
        .WithKeepAliveInterval(TimeSpan.FromSeconds(15))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

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

    await hubConnection.StartAsync();
}

Configura los siguientes valores para el cliente:

  • serverTimeoutInMilliseconds: tiempo de espera del servidor en milisegundos. Si este tiempo de espera transcurre sin recibir ningún mensaje del servidor, la conexión finaliza con un error. El valor predeterminado del tiempo de espera es de 30 segundos. El tiempo de espera del servidor debe ser al menos el doble del valor asignado al intervalo de Keep-Alive (keepAliveIntervalInMilliseconds).
  • keepAliveIntervalInMilliseconds: intervalo predeterminado en el que se va a hacer ping al servidor. Esta configuración permite al servidor detectar desconexiones físicas, como cuando un cliente desconecta su equipo de la red. El ping se produce como máximo las veces que el servidor hace ping. Si el servidor hace ping cada cinco segundos, asignar un valor inferior a 5000 (5 segundos) hace ping cada cinco segundos. El valor predeterminado es 15 segundos. El intervalo de Keep-Alive debe ser menor o igual que la mitad del valor asignado al tiempo de espera del servidor (serverTimeoutInMilliseconds).

En el ejemplo siguiente del archivo Pages/_Host.cshtml (Blazor Server, todas las versiones excepto ASP.NET Core en .NET 6) o el archivo Pages/_Layout.cshtml (Blazor Server, ASP.NET Core en .NET 6):

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

En el ejemplo anterior, el marcador de posición {BLAZOR SCRIPT} es la ruta de acceso del script y el nombre de archivo de Blazor. Para obtener la ubicación del script y la ruta de acceso que se va a usar, consulta ASP.NET Core Blazor estructura de proyecto.

Al crear una conexión de concentrador en un componente, establece ServerTimeout (valor predeterminado: 30 segundos), HandshakeTimeout (valor predeterminado: 15 segundos) y KeepAliveInterval (valor predeterminado: 15 segundos) en el HubConnection compilado. En el ejemplo siguiente se muestra la asignación de valores predeterminados:

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

Al cambiar los valores del tiempo de espera del servidor (ServerTimeout) o el intervalo para mantener la conexión (KeepAliveInterval):

  • El tiempo de espera del servidor debe ser al menos el doble del valor asignado al intervalo de Keep-Alive.
  • El intervalo de Keep-Alive debe ser menor o igual que la mitad del valor asignado al tiempo de espera del servidor.

Para obtener más información, consulta las secciones Errores de implementación global y conexión de los siguientes artículos:

Modificación del controlador de reconexión del lado servidor

Los eventos de conexión del circuito del controlador de reconexión pueden modificarse para obtener comportamientos personalizados, por ejemplo:

  • Para notificar al usuario si la conexión se ha quitado.
  • Para realizar el registro (desde el cliente) cuando un circuito está conectado.

Para modificar los eventos de conexión, registra las devoluciones de llamada para los siguientes cambios de conexión:

  • Las conexiones que se quitan usan onConnectionDown.
  • Las conexiones establecidas o restablecidas usan onConnectionUp.

Es necesario especificar tanto onConnectionDown como onConnectionUp .

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: (options, error) => console.error(error),
        onConnectionUp: () => console.log("Up, up, and away!")
      }
    }
  });
</script>

Blazor Server:

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

En el ejemplo anterior, el marcador de posición {BLAZOR SCRIPT} es la ruta de acceso del script y el nombre de archivo de Blazor. Para obtener la ubicación del script y la ruta de acceso que se va a usar, consulta ASP.NET Core Blazor estructura de proyecto.

Actualizar automáticamente la página cuando se produzca un error de reconexión del lado servidor

El comportamiento de reconexión predeterminado requiere que el usuario realice una acción manual para actualizar la página después de que se produzca un error en la reconexión. Sin embargo, se puede usar un controlador de reconexión personalizado para actualizar automáticamente la página:

App.razor:

Pages/_Host.cshtml:

<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>

En el ejemplo anterior, el marcador de posición {BLAZOR SCRIPT} es la ruta de acceso del script y el nombre de archivo de Blazor. Para obtener la ubicación del script y la ruta de acceso que se va a usar, consulta ASP.NET Core Blazor estructura de proyecto.

Crear el archivo wwwroot/boot.js siguiente.

Blazor Web App:

(() => {
  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({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
        onConnectionUp: () => {
          currentReconnectionProcess?.cancel();
          currentReconnectionProcess = null;
        }
      }
    }
  });
})();

Blazor Server:

(() => {
  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;
      }
    }
  });
})();

Para más información sobre el inicio de Blazor, vea Inicio de Blazor en ASP.NET Core.

Ajustar el número y el intervalo de reintentos de reconexión del lado servidor

Para ajustar el intervalo y el número de reintentos de reconexión, establezca el número de reintentos (maxRetries) y el período en milisegundos permitido en cada reintento (retryIntervalMilliseconds).

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionOptions: {
        maxRetries: 3,
        retryIntervalMilliseconds: 2000
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionOptions: {
      maxRetries: 3,
      retryIntervalMilliseconds: 2000
    }
  });
</script>

En el ejemplo anterior, el marcador de posición {BLAZOR SCRIPT} es la ruta de acceso del script y el nombre de archivo de Blazor. Para obtener la ubicación del script y la ruta de acceso que se va a usar, consulte ASP.NET Core Blazor estructura de proyecto.

Cuando el usuario vuelve a una aplicación con un circuito desconectado, se intenta volver a conectar inmediatamente en lugar de esperar la duración del siguiente intervalo de reconexión. Este comportamiento busca reanudar la conexión lo antes posible para el usuario.

El tiempo de reconexión predeterminado usa una estrategia de retroceso calculada. Los primeros intentos de reconexión se producen en sucesión rápida antes de que se introduzcan retrasos calculados entre intentos. La lógica predeterminada para calcular el intervalo de reintento es un detalle de implementación sujeto a cambios sin previo aviso, pero puede encontrar la lógica predeterminada que usa el marco Blazor de en la función de computeDefaultRetryInterval (origen de referencia).

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, usa la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Personalice el comportamiento del intervalo de reintento especificando una función para calcular el intervalo de reintento. En el siguiente ejemplo de retroceso exponencial, el número de intentos de reconexión anteriores se multiplica por 1000 ms para calcular el intervalo de reintento. Cuando el recuento de intentos anteriores de volver a conectarse (previousAttempts) es mayor que el límite máximo de reintentos (maxRetries), null se asigna al intervalo de reintento (retryIntervalMilliseconds) para detener los intentos de reconexión adicionales:

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
        previousAttempts >= maxRetries ? null : previousAttempts * 1000
    },
  },
});

Una alternativa es especificar la secuencia exacta de intervalos de reintento. Después del último intervalo de reintento especificado, los reintentos se detienen porque la función retryIntervalMilliseconds devuelve undefined:

Blazor.start({
  circuit: {
    reconnectionOptions: {
      retryIntervalMilliseconds: 
        Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
    },
  },
});

Para más información sobre el inicio de Blazor, vea Inicio de Blazor en ASP.NET Core.

Controlar cuándo aparece la interfaz de usuario de reconexión

Controlar cuándo aparece la interfaz de usuario de reconexión puede ser útil en las siguientes situaciones:

  • Una aplicación implementada muestra con frecuencia la interfaz de usuario de reconexión debido a los tiempos de espera de ping causados por la red interna o la latencia de Internet, y le gustaría aumentar el retraso.
  • Una aplicación debe informar a los usuarios de que la conexión se ha eliminado antes y le gustaría acortar el retraso.

El tiempo de aparición de la interfaz de usuario de reconexión se ve influido por el ajuste del intervalo de mantenimiento de conexión y los tiempos de espera en el cliente. La interfaz de usuario de reconexión aparece cuando se alcanza el tiempo de espera del servidor en el cliente (withServerTimeoutsección Configuración de cliente). Sin embargo, cambiar el valor de withServerTimeout requiere cambios en otros valores de Keep-Alive, timeout y handshake que se describen en las instrucciones siguientes.

Como recomendaciones generales para las instrucciones siguientes:

  • El intervalo de mantenimiento de conexión debe coincidir con las configuraciones del cliente y del servidor.
  • Los tiempos de espera deben ser al menos el doble del valor asignado al intervalo de mantenimiento de conexión.

Configuración del servidor

Configure las opciones siguientes:

  • ClientTimeoutInterval (valor predeterminado: 30 segundos): los clientes de la ventana de tiempo tienen que enviar un mensaje antes de que el servidor cierre la conexión.
  • HandshakeTimeout (valor predeterminado: 15 segundos): el intervalo que usa el servidor para esperar las solicitudes de protocolo de enlace entrantes de los clientes.
  • KeepAliveInterval (valor predeterminado: 15 segundos): intervalo que usa el servidor para enviar pings de mantenimiento de conexión a los clientes conectados. Tenga en cuenta que también hay una configuración de intervalo de mantenimiento de conexión en el cliente, que debe coincidir con el valor del servidor.

El ClientTimeoutInterval y HandshakeTimeout pueden aumentar, y el KeepAliveInterval puede permanecer igual. La consideración importante es que, si cambia los valores, se asegure de que los tiempos de espera son al menos el doble del valor del intervalo de mantenimiento de conexión y que el intervalo de mantenimiento de conexión del servidor y del cliente coinciden. Para obtener más información, consulta la sección Configuración de tiempos de espera de SignalR y mantenimiento del cliente.

En el ejemplo siguiente:

  • El ClientTimeoutInterval se incrementa a 60 segundos (valor predeterminado: 30 segundos).
  • El HandshakeTimeout se incrementa a 30 segundos (valor predeterminado: 15 segundos).
  • El KeepAliveInterval no se establece en el código de desarrollador y usa su valor predeterminado de 15 segundos. Al reducir el valor del intervalo de mantenimiento de conexión, aumenta la frecuencia de pings de comunicación, lo que aumenta la carga en la aplicación, el servidor y la red. Se debe tener cuidado para evitar introducir un rendimiento deficiente al reducir el intervalo de mantenimiento de conexión.

Blazor Web App (.NET 8 o posteriorr later) en el archivo Program del proyecto del servidor:

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options =>
{
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
    options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Blazor Server en el archivo Program:

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });

Para obtener más información, consulta la sección Opciones del controlador de circuitos del lado servidor.

Configuración de cliente

Configura las opciones siguientes:

  • withServerTimeout (valor predeterminado: 30 segundos): configura el tiempo de espera del servidor, especificado en milisegundos, para la conexión del concentrador del circuito.
  • withKeepAliveInterval (valor predeterminado: 15 segundos): intervalo, especificado en milisegundos, en el que la conexión envía mensajes de mantenimiento de conexión.

Se puede aumentar el tiempo de espera del servidor y el intervalo de mantenimiento de conexión puede permanecer igual. La consideración importante es que, si cambias los valores, te asegures de que el tiempo de espera del servidor sea al menos el doble del valor del intervalo de mantenimiento de conexión y de que los valores del intervalo de mantenimiento de conexión del servidor y del cliente coinciden. Para obtener más información, consulta la sección Configuración de tiempos de espera de SignalR y mantenimiento del cliente.

En el siguiente ejemplo de configuración de inicio (ubicación del script Blazor), se usa un valor personalizado de 60 segundos para el tiempo de espera del servidor. El intervalo de mantenimiento de conexión (withKeepAliveInterval) no se establece y usa su valor predeterminado de 15 segundos.

Blazor Web App:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

Al crear una conexión de concentrador en un componente, establece el tiempo de espera del servidor (WithServerTimeout, valor predeterminado: 30 segundos) en HubConnectionBuilder. Establece el valor HandshakeTimeout (valor predeterminado: 15 segundos) en la complicación HubConnection. Confirma que los tiempos de espera son al menos el doble del intervalo de mantenimiento de conexión (WithKeepAliveInterval/KeepAliveInterval) y que el valor de mantenimiento de conexión del servidor y del cliente coinciden.

El ejemplo siguiente se basa en el componente Index del tutorial SignalR con Blazor. El tiempo de espera del servidor aumenta a 60 segundos y el tiempo de espera del protocolo de enlace aumenta a 30 segundos. El intervalo de mantenimiento de conexión no se establece y usa su valor predeterminado de 15 segundos.

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

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

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

    await hubConnection.StartAsync();
}

Configura las opciones siguientes:

  • serverTimeoutInMilliseconds (valor predeterminado: 30 segundos): configura el tiempo de espera del servidor, especificado en milisegundos, para la conexión del concentrador del circuito.
  • keepAliveIntervalInMilliseconds (valor predeterminado: 15 segundos): intervalo, especificado en milisegundos, en el que la conexión envía mensajes de mantenimiento de conexión.

Se puede aumentar el tiempo de espera del servidor y el intervalo de mantenimiento de conexión puede permanecer igual. La consideración importante es que, si cambias los valores, te asegures de que el tiempo de espera del servidor sea al menos el doble del valor del intervalo de mantenimiento de conexión y de que los valores del intervalo de mantenimiento de conexión del servidor y del cliente coinciden. Para obtener más información, consulta la sección Configuración de tiempos de espera de SignalR y mantenimiento del cliente.

En el siguiente ejemplo de configuración de inicio (ubicación del script Blazor), se usa un valor personalizado de 60 segundos para el tiempo de espera del servidor. El intervalo de mantenimiento de conexión (keepAliveIntervalInMilliseconds) no se establece y usa su valor predeterminado de 15 segundos.

En Pages/_Host.cshtml:

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

Al crear una conexión de concentrador en un componente, establece ServerTimeout (valor predeterminado: 30 segundos) y HandshakeTimeout (valor predeterminado: 15 segundos) en el HubConnection compilado. Confirma que los tiempos de espera son al menos el doble del intervalo de mantenimiento de conexión. Confirma que el intervalo de mantenimiento de conexión del servidor y del cliente coinciden.

El ejemplo siguiente se basa en el componente Index del tutorial SignalR con Blazor. El ServerTimeout se incrementa a 60 segundos y HandshakeTimeout se incrementa a 30 segundos. El intervalo de mantenimiento de conexión (KeepAliveInterval) no se establece y usa su valor predeterminado de 15 segundos.

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

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

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

    await hubConnection.StartAsync();
}

Desconexión del circuito Blazor del cliente

Un circuito Blazor se desconecta cuando se desencadena el evento de página unload. A fin de desconectar el circuito para otros escenarios en el cliente, invoca Blazor.disconnect en el controlador de eventos adecuado. En el ejemplo siguiente, el circuito se desconecta cuando la página está oculta (evento pagehide):

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

Para obtener más información sobre el inicio de Blazor, consulta Inicio de Blazor en ASP.NET Core.

Controlador de circuitos del lado servidor

Puedes definir un controlador de circuito, que permite ejecutar el código en los cambios realizados en el estado del circuito de un usuario. Un controlador de circuito se implementa derivando de CircuitHandler y registrando la clase en el contenedor de servicios de la aplicación. El ejemplo siguiente de un controlador de circuito realiza un seguimiento de las conexiones abiertas de SignalR.

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

Los controladores de circuito se registran mediante DI. Las instancias con ámbito se crean por instancia de un circuito. Mediante el uso de TrackingCircuitHandler en el ejemplo anterior, se crea un servicio singleton porque se debe realizar un seguimiento del estado de todos los circuitos.

En el archivo Program:

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

En Startup.ConfigureServices de Startup.cs:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Si los métodos de un controlador de circuito personalizado producen una excepción no controlada, la excepción es grave para el circuito de . Para tolerar excepciones en el código de un controlador o en los métodos llamados, encapsule el código en una o varias instrucciones try-catch con control de errores y registro.

Cuando un circuito finaliza porque un usuario se ha desconectado y el marco está limpiando el estado del circuito, el marco desecha el ámbito de DI del circuito. Al desechar el ámbito, se eliminan también todos los servicios de DI con ámbito del circuito que implementan System.IDisposable. Si un servicio de DI produce una excepción no controlada durante la eliminación, el marco registra la excepción. Para obtener más información, vea Inserción de dependencias de Blazor de ASP.NET Core.

Controlador de circuito del lado servidor para capturar usuarios para servicios personalizados

Use un CircuitHandler para capturar un usuario del AuthenticationStateProvider y establecer ese usuario en un servicio. Para más información y código de ejemplo, consulte ASP.NET Core Blazor en el lado del servidor: escenarios de seguridad adicionales.

Cierre de circuitos cuando no haya componentes del servidor interactivo restantes

Los componentes del Servidor interactivo controlan los eventos de la interfaz de usuario web mediante una conexión en tiempo real con el explorador denominado circuito. Un circuito y su estado asociado se crean cuando se representa un componente de Servidor interactivo raíz. El circuito se cierra cuando no quedan componentes de Servidor interactivo en la página, lo que libera los recursos del servidor.

IHttpContextAccessor/HttpContext en Razor componentes

IHttpContextAccessor debe evitarse con la representación interactiva porque no hay un HttpContext válido disponible.

IHttpContextAccessor se puede usar para los componentes que se representan estáticamente en el servidor. Sin embargo, se recomienda evitarlo si es posible.

HttpContextse puede usar como parámetro en cascada solo en componentes raíz representados estáticamente para tareas generales, como inspeccionar y modificar encabezados u otras propiedades del componente App (Components/App.razor). El valor siempre es null para la representación interactiva.

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

En escenarios en los que se requiere el HttpContext en componentes interactivos, se recomienda hacer fluir los datos a través del estado del componente persistente desde el servidor. Para más información, veaOtros escenarios de seguridad de Blazor en ASP.NET Core del lado servidor.

No use IHttpContextAccessor/HttpContext directa o indirectamente en los componentes Razor de las aplicaciones Blazor del lado servidor. Las aplicaciones de Blazor se ejecutan fuera del contexto de la canalización de ASP.NET Core. No se garantiza que HttpContext esté disponible en IHttpContextAccessor, ni tampoco que HttpContext contenga el contexto que ha iniciado la aplicación de Blazor.

El enfoque recomendado para pasar el estado de solicitud a la aplicación de Blazor es a través de parámetros de componente raíz durante la representación inicial de la aplicación. Como alternativa, la aplicación puede copiar los datos en un servicio con ámbito en el evento de ciclo de vida de inicialización del componente raíz para usarlos en toda la aplicación. Para más información, veaOtros escenarios de seguridad de Blazor en ASP.NET Core del lado servidor.

Un aspecto crítico de la seguridad de Blazor en el lado del servidor es que el usuario asociado a un circuito determinado se puede actualizar en algún momento después de que se establezca el circuito de Blazor, pero IHttpContextAccessor no se actualice. Para obtener más información sobre cómo abordar esta situación con servicios personalizados, consulte: ASP.NET Core Blazor en el lado del servidor: escenarios de seguridad adicionales.

Recursos adicionales del lado servidor