Cliente de JavaScript SignalR en ASP.NET Core

La biblioteca de cliente JavaScript de ASP.NET Core SignalR permite a los desarrolladores llamar al código del hub del servidor SignalR. En este artículo se describe cómo usar las API para conectarse a un SignalR hub y llamar a los métodos del hub y del cliente de JavaScript.

Vea o descargue el código de ejemplo (cómo descargarlo)

Instalación del paquete cliente SignalR

La biblioteca de cliente de JavaScript SignalR se entrega como un paquete npm. Hay varias maneras de instalar la biblioteca cliente:

  • Ejecute comandos npm en Visual Studio Administrador de paquetes Console.
  • Ejecute comandos npm en el terminal integrado de Visual Studio Code.
  • Haga referencia a una copia de la biblioteca cliente hospedada en una red de entrega de contenido (CDN).
  • Use LibMan e instale archivos de biblioteca cliente específicos desde una biblioteca cliente hospedada en CDN.

Instalación con npm

Ejecute los siguientes comandos npm en la ventana Administrador de paquetes Console:

npm init -y
npm install @microsoft/signalr

npm instala el contenido del paquete en la carpeta node_modules\@microsoft\signalr\dist\browser .

Completa la configuración:

  1. Cree la carpeta wwwroot/lib/signalr .

  2. Copie el archivo signalr.js en la carpeta wwwroot/lib/signalr .

Ahora puede hacer referencia al cliente de JavaScript instalado en el elemento SignalR. Por ejemplo:

<script src="~/lib/signalr/signalr.js"></script>

Uso de una red CDN

Para usar la biblioteca cliente sin el requisito previo de npm, haga referencia a una copia hospedada en CDN de la biblioteca cliente. Por ejemplo:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

El ejemplo de código especifica la versión 6.0.1. Para obtener la versión más reciente de la biblioteca cliente, elija una de las siguientes redes CDN:

Instalación con LibMan

Otro enfoque consiste en usar LibMan e instalar solo archivos de biblioteca cliente específicos desde la biblioteca cliente hospedada en CDN. Por ejemplo, solo puede agregar el archivo JavaScript mínimo al proyecto.

Para obtener más información sobre este enfoque, consulte Incorporación de la SignalR biblioteca cliente.

Conexión a un centro

El código siguiente crea e inicia una conexión. El nombre del centro no distingue mayúsculas de minúsculas:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Conexiones entre distintos orígenes (CORS)

Normalmente, los exploradores cargan conexiones desde el mismo dominio que la página solicitada. Sin embargo, hay ocasiones en las que se requiere una conexión a otro dominio.

Para las solicitudes entre dominios, el código de cliente debe usar una dirección URL absoluta en lugar de una dirección URL relativa. Cuando use solicitudes entre dominios, cambie .withUrl("/chathub") a .withUrl("https://{App domain name}/chathub").

Para evitar que un sitio malintencionado lea datos confidenciales de otro sitio, las conexiones entre orígenes están deshabilitadas de forma predeterminada. Para permitir una solicitud entre orígenes, habilite CORS:

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder.WithOrigins("https://example.com")
                .AllowAnyHeader()
                .WithMethods("GET", "POST")
                .AllowCredentials();
        });
});

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

// UseCors must be called before MapHub.
app.UseCors();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();

Se debe llamar al método UseCors antes de llamar al método MapHub.

Llamada a los métodos del centro desde el cliente

Los clientes de JavaScript llaman a métodos públicos en centros a través del método invoke de HubConnection. El método invoke acepta:

  • Nombre del método de concentrador.
  • Cualquiera de los argumentos definidos en el método del hub.

En el código resaltado siguiente, el nombre del método en el centro es SendMessage. El segundo y el tercer argumento que se pasan a invoke se corresponden con los parámetros user y message del método del hub:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

La llamada a métodos del hub desde un cliente solo se admite cuando se usa el servicio Azure SignalR en modo predeterminado.

El invoke método devuelve un objeto Promise de JavaScript. El objeto Promise se resuelve con el valor devuelto (si lo hay) cuando el método del servidor devuelve un resultado. Si el método del servidor produce un error, el Promise objeto se rechaza con el mensaje de error. Para gestionar estos casos, use async y await o los métodos Promise y then del objeto catch.

Los clientes de JavaScript también pueden llamar a métodos públicos de los hubs a través del método send del HubConnection. A diferencia del método invoke, el método send no espera una respuesta del servidor. El send método devuelve un objeto JavaScript Promise . El Promise objeto se resuelve cuando el mensaje se envía al servidor. Si se produce un error al enviar el mensaje, el Promise objeto se rechaza con el mensaje de error. Para gestionar estos casos, use async y await o los métodos Promise y then del objeto catch.

El uso de sendno espera a que el servidor reciba el mensaje, por lo que no es posible devolver datos ni errores desde el servidor.

Llamada a métodos de cliente desde el centro

Para recibir mensajes del centro, defina un método mediante el método on de HubConnection. El método on acepta:

  • Nombre del método cliente de JavaScript.
  • Argumentos que el centro pasa al método .

En el ejemplo siguiente, el nombre del método es ReceiveMessage. Los nombres de argumento son user y message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

El código anterior de connection.on se ejecuta cuando el código del lado servidor lo llama mediante el SendAsync método :

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs;

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

SignalR determina a qué método cliente se debe llamar haciendo coincidir el nombre del método y los argumentos definidos en SendAsync y connection.on.

Un procedimiento recomendado es llamar al método start en HubConnection después de on. Este enfoque garantiza que los controladores se registren antes de recibir los mensajes.

Registro y control de errores

Use console.error para mostrar errores en la consola del navegador cuando el cliente no pueda conectarse o enviar un mensaje:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Configure el seguimiento de registros del lado cliente pasando un registrador y un tipo de evento para registrar cuándo se realiza la conexión. Los mensajes se registran con el nivel de registro especificado y superiores. Los niveles de registro disponibles son los siguientes:

Nivel de registro Datos registrados Descripción
signalR.LogLevel.Error Mensajes de error Solo registra mensajes de Error.
signalR.LogLevel.Warning Mensajes de advertencia sobre posibles errores Registra mensajes Warning y Error.
signalR.LogLevel.Information Mensajes de estado sin errores Registra mensajes de Information, Warning y Error.
signalR.LogLevel.Trace Mensajes de seguimiento Registra todo, incluidos los datos transportados entre el centro y el cliente.

Use el método configureLogging en HubConnectionBuilder para configurar el nivel de registro. Los mensajes se registran en la consola del explorador:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Volver a conectar clientes

Para volver a conectarse con clientes, puede configurar la reconexión automática o configurar la conexión manualmente.

Volver a conectarse automáticamente

El cliente de JavaScript para SignalR se puede configurar para volver a conectarse automáticamente mediante el método WithAutomaticReconnect en HubConnectionBuilder. De forma predeterminada, no se vuelve a conectar automáticamente.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Sin ningún parámetro, WithAutomaticReconnect configura el cliente para que espere 0, 2, 10 y 30 segundos respectivamente antes de intentar cada intento de reconexión. Después de cuatro intentos fallidos, deja de intentar volver a conectarse.

Antes de iniciar cualquier reconexión, el HubConnection:

  • Pasa al estado HubConnectionState.Reconnecting y activa sus onreconnecting devoluciones de llamada.
  • No pasa al estado Disconnected ni activa sus onclose devoluciones de llamada como un HubConnection sin reconexión automática configurada.

El enfoque de reconexión proporciona una oportunidad para advertir a los usuarios de que la conexión se pierde y para deshabilitar los elementos de la interfaz de usuario.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Si el cliente se reconecta correctamente en sus cuatro primeros intentos, HubConnection vuelve al estado Connected y activa sus callbacks onreconnected. Este enfoque proporciona una oportunidad para informar a los usuarios de que la conexión se ha restablecido.

Dado que la conexión parece completamente nueva para el servidor, se proporciona un nuevo connectionId a la devolución de llamada onreconnected.

El parámetro connectionId de la devolución de llamada onreconnected es indefinido si el HubConnection está configurado para omitir la negociación.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect no configura el/la HubConnection para reintentar los fallos iniciales de arranque, por lo que los fallos de arranque deben gestionarse manualmente:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Si el cliente no se reconecta con éxito en sus cuatro primeros intentos, el HubConnection pasa al estado Disconnected y desencadena sus devoluciones de llamada onclose. Este enfoque proporciona una oportunidad para informar a los usuarios de que la conexión se pierde permanentemente y para intentar actualizar la página:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

Para configurar un número personalizado de intentos de reconexión antes de desconectar o cambiar el tiempo de reconexión, withAutomaticReconnect acepta una matriz de números que representa el retraso en milisegundos para esperar antes de iniciar cada intento de reconexión.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

El ejemplo anterior configura el HubConnection para que empiece a intentar reconectarse inmediatamente después de que se pierda la conexión. La configuración predeterminada también espera cero (0) segundos para intentar volver a conectarse.

  • Si se produce un error en el primer intento de reconexión, el segundo intento de reconexión también se inicia inmediatamente en lugar de esperar 2 segundos como se define en la configuración predeterminada.

  • Si se produce un error en el segundo intento de reconexión, el tercer intento de reconexión se inicia en 10 segundos, que es el mismo comportamiento definido en la configuración predeterminada.

  • El tiempo de reconexión configurado difiere del comportamiento predeterminado deteniendo después del tercer error de intento de reconexión. En la configuración predeterminada, se realiza un intento de reconexión más después de otros 30 segundos.

Para un mayor control sobre el tiempo y el número de intentos de reconexión automática, withAutomaticReconnect acepta un objeto que implemente la interfaz IRetryPolicy, que tiene un único método llamado nextRetryDelayInMilliseconds. nextRetryDelayInMilliseconds toma un único argumento con el tipo RetryContext. RetryContext Tiene tres propiedades: previousRetryCount (tipo number), elapsedMilliseconds (tipo number) y retryReason (tipo Error).

  • Antes del primer intento de reconexión, previousRetryCount y elapsedMilliseconds son cero (0) y retryReason es el error que provocó la conexión perdida.

  • Después de cada intento de reintento con error, previousRetryCount se incrementa en uno, elapsedMilliseconds se actualiza para reflejar la cantidad de tiempo invertido en volver a conectarse hasta ahora en milisegundos, y retryReason es el error que provocó el último intento de reconexión.

nextRetryDelayInMilliseconds debe devolver un número que represente el número de milisegundos que hay que esperar antes del siguiente intento de reconexión o null si HubConnection debe dejar de reconectarse.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Como alternativa, puede escribir código para volver a conectar el cliente manualmente, como se muestra en la sección siguiente.

Volver a conectarse manualmente

En el código siguiente se muestra un enfoque típico de reconexión manual:

  1. Para iniciar la conexión, cree una función. En este caso, la función start.

  2. Llame a la función start en el controlador de eventos onclose de la conexión.

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Las implementaciones de producción suelen usar un retroceso exponencial o reintentar un número especificado de veces.

Pestaña de suspensión del explorador

Algunos navegadores tienen una función de congelación de pestañas o suspensión de pestañas que reduce el uso de recursos del ordenador en las pestañas inactivas. Sin embargo, esta funcionalidad puede hacer SignalR que las conexiones se cierren y provoquen una experiencia de usuario no deseada.

Los exploradores implementan heurística para determinar si una pestaña se debe poner en suspensión, como:

  • Reproducción de audio
  • Mantener un bloqueo de la web
  • Mantener un bloqueo IndexedDB
  • Estar conectado a un dispositivo USB
  • Capturar vídeo o audio
  • Crear un reflejo
  • Capturar una ventana o pantalla

La heurística puede cambiar con el tiempo y también diferir entre exploradores. Compruebe la matriz de compatibilidad y descubra qué método funciona mejor para sus escenarios.

Para evitar poner una aplicación en suspensión, esta debe activar una de las heurísticas que utiliza el navegador.

En el ejemplo de código siguiente se muestra cómo usar un bloqueo web para mantener activa una pestaña y evitar un cierre de conexión inesperado:

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

En el código anterior:

  • Los bloqueos web son experimentales. La comprobación condicional confirma que el explorador admite bloqueos web.
  • El resolvedor de promesas de JavaScript (lockResolver) se almacena para poder liberar el bloqueo cuando sea aceptable que la pestaña se suspenda.
  • Cuando se cierra la conexión, el bloqueo se libera llamando a lockResolver(). Cuando se libera el bloqueo, la pestaña puede suspenderse.

Actualizar la autenticación sin volver a conectarse

En .NET 11 y versiones posteriores, un cliente puede reemplazar las credenciales de una conexión abierta en lugar de permitir que la conexión se cierre cuando expire su token de acceso. El servidor debe habilitar la actualización de autenticación para el centro. Llame withAuthenticationRefresh a para programar una actualización automática antes de la expiración que notifica el servidor:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub", { accessTokenFactory: () => getAccessToken() })
    .withAuthenticationRefresh()
    .build();

Cada actualización llama accessTokenFactory de nuevo para obtener un nuevo token. Para actualizar inmediatamente, por ejemplo, después de que la aplicación obtenga notificaciones actualizadas, llame a connection.refreshAuthentication().

Para las opciones de actualización, los controladores de éxito y error, y la configuración del servidor que habilita esta funcionalidad, consulte SignalR autenticación y autorización.

Por Rachel Appel

La biblioteca cliente de JavaScript de ASP.NET Core SignalR permite a los desarrolladores llamar al código del hub en el lado servidor.

Vea o descargue el código de ejemplo (cómo descargarlo)

Instalación del paquete cliente SignalR

La biblioteca de cliente de JavaScript SignalR se entrega como un paquete npm. En las secciones siguientes se describen diferentes formas de instalar la biblioteca cliente.

Instalación con npm

Para Visual Studio, ejecute los siguientes comandos desde la consola del administrador de paquetes mientras se encuentra en la carpeta raíz. Para Visual Studio Code, ejecute los siguientes comandos desde el Terminal integrado.

npm init -y
npm install @microsoft/signalr

npm instala el contenido del paquete en la carpeta node_modules\@microsoft\signalr\dist\browser . Cree una carpeta denominada signalr en la carpeta wwwroot\lib . Copie el signalr.js archivo en la carpeta wwwroot\lib\signalr .

Haga referencia al cliente de JavaScript SignalR en el elemento <script>. Por ejemplo:

<script src="~/lib/signalr/signalr.js"></script>

Uso de una red de entrega de contenido (CDN)

Para usar la biblioteca cliente sin el requisito previo de npm, haga referencia a una copia hospedada en CDN de la biblioteca cliente. Por ejemplo:

<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.7/signalr.js"></script>

La biblioteca cliente está disponible en las siguientes redes CDN:

Instalación con LibMan

LibMan se puede usar para instalar archivos de biblioteca cliente específicos desde la biblioteca cliente hospedada en CDN. Por ejemplo, agregue solo el archivo JavaScript minimizado al proyecto. Para más detalles sobre ese método, consulte Agregue la biblioteca cliente SignalR.

Conexión a un centro

El código siguiente crea e inicia una conexión. El nombre del centro no distingue mayúsculas de minúsculas:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Start the connection.
start();

Conexiones entre orígenes

Normalmente, los exploradores cargan conexiones desde el mismo dominio que la página solicitada. Sin embargo, hay ocasiones en las que se requiere una conexión a otro dominio.

Important

El código de cliente debe usar una dirección URL absoluta en lugar de una dirección URL relativa. Cambio de .withUrl("/chathub") a .withUrl("https://myappurl/chathub").

Para evitar que un sitio malintencionado lea datos confidenciales de otro sitio, las conexiones entre orígenes están deshabilitadas de forma predeterminada. Para permitir una solicitud de origen cruzado, habilítela en la clase Startup:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SignalRChat.Hubs;

namespace SignalRChat
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            services.AddSignalR();

            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("https://example.com")
                        .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseRouting();

            app.UseCors();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapHub<ChatHub>("/chathub");
            });
        }
    }
}

Llamada a los métodos del centro desde el cliente

Los clientes de JavaScript llaman a métodos públicos en centros a través del método invoke de HubConnection. El método invoke acepta:

  • Nombre del método de concentrador.
  • Cualquiera de los argumentos definidos en el método del hub.

En el siguiente ejemplo, el nombre del método en el centro es SendMessage. El segundo y el tercer argumento que se pasan a invoke se corresponden con los parámetros user y message del método del hub:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Note

La llamada a métodos del concentrador desde un cliente solo se admite al usar el servicio Azure SignalR en modo predeterminado.

El método invoke devuelve un objeto Promise de JavaScript. Promise se resuelve con el valor devuelto (si existe) cuando el método del servidor devuelve. Si el método del servidor produce un error, Promise se rechaza con el mensaje de error. Use async y await o los métodos Promise y then de catch para controlar estos casos.

Los clientes de JavaScript también pueden llamar a métodos públicos de los hubs a través del método send del HubConnection. A diferencia del método invoke, el método send no espera una respuesta del servidor. El método send devuelve un objeto Promise de JavaScript. El Promise se resuelve cuando el mensaje ha sido enviado al servidor. Si se produce un error al enviar el mensaje, Promise se rechaza con el mensaje de error. Use async y await o los métodos Promise y then de catch para controlar estos casos.

Note

El uso de send no espera a que el servidor haya recibido el mensaje. Por lo tanto, no es posible devolver datos o errores del servidor.

Llamada a métodos de cliente desde el centro

Para recibir mensajes desde el centro, defina un método mediante el método on del HubConnection.

  • Nombre del método cliente de JavaScript.
  • Argumentos que el centro pasa al método .

En el ejemplo siguiente, el nombre del método es ReceiveMessage. Los nombres de argumento son user y message:

connection.on("ReceiveMessage", (user, message) => {
    const li = document.createElement("li");
    li.textContent = `${user}: ${message}`;
    document.getElementById("messageList").appendChild(li);
});

El código anterior de connection.on se ejecuta cuando el código del lado servidor lo llama mediante el método SendAsync:

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user, message);
}

SignalR determina a qué método cliente se debe llamar haciendo coincidir el nombre del método y los argumentos definidos en SendAsync y connection.on.

Note

Un procedimiento recomendado es llamar al método start en HubConnection después de on. Al hacerlo, se asegura de que los controladores se registren antes de que se reciban mensajes.

Registro y control de errores

Utilice try y catch con async y await o el método Promise de catch para controlar los errores del lado del cliente. Use console.error para mostrar errores en la consola del navegador:

try {
    await connection.invoke("SendMessage", user, message);
} catch (err) {
    console.error(err);
}

Configure el seguimiento de registros del lado cliente pasando un registrador y un tipo de evento para registrar cuándo se realiza la conexión. Los mensajes se registran con el nivel de registro especificado y superiores. Los niveles de registro disponibles son los siguientes:

  • signalR.LogLevel.Error: mensajes de error. Solo registra mensajes de Error.
  • signalR.LogLevel.Warning: mensajes de advertencia sobre posibles errores. Registra mensajes de Warning y Error.
  • signalR.LogLevel.Information: mensajes de estado sin errores. Registra mensajes de Information, Warning y Error.
  • signalR.LogLevel.Trace: mensajes de seguimiento. Registra todo, incluidos los datos transportados entre el centro y el cliente.

Use el método configureLogging en HubConnectionBuilder para configurar el nivel de registro. Los mensajes se registran en la consola del explorador:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Volver a conectar clientes

Volver a conectarse automáticamente

El cliente de JavaScript para SignalR puede configurarse para reconectarse automáticamente usando el método withAutomaticReconnect en HubConnectionBuilder. No se volverá a conectar automáticamente de forma predeterminada.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect()
    .build();

Sin ningún parámetro, withAutomaticReconnect() configura el cliente para que espere 0, 2, 10 y 30 segundos respectivamente antes de intentar cada intento de reconexión, deteniéndose tras cuatro intentos fallidos.

Antes de iniciar cualquier intento de reconexión, el HubConnection pasará al estado HubConnectionState.Reconnecting y ejecutará sus callbacks de onreconnecting en lugar de pasar al estado Disconnected y ejecutar sus callbacks de onclose, como lo haría un HubConnection sin reconexión automática configurada. Esto proporciona una oportunidad para advertir a los usuarios de que se ha perdido la conexión y deshabilitar los elementos de la interfaz de usuario.

connection.onreconnecting(error => {
    console.assert(connection.state === signalR.HubConnectionState.Reconnecting);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection lost due to error "${error}". Reconnecting.`;
    document.getElementById("messageList").appendChild(li);
});

Si el cliente se reconecta con éxito en sus cuatro primeros intentos, el HubConnection vuelve al estado Connected y desencadena sus devoluciones de llamada de onreconnected. Esto proporciona la oportunidad de informar a los usuarios de la conexión que se ha restablecido.

Dado que la conexión parece totalmente nueva para el servidor, se proporciona un nuevo connectionId a la devolución de llamada de onreconnected.

Warning

El parámetro connectionId de la devolución de llamada onreconnected será indefinido si HubConnection se configuró para omitir la negociación.

connection.onreconnected(connectionId => {
    console.assert(connection.state === signalR.HubConnectionState.Connected);

    document.getElementById("messageInput").disabled = false;

    const li = document.createElement("li");
    li.textContent = `Connection reestablished. Connected with connectionId "${connectionId}".`;
    document.getElementById("messageList").appendChild(li);
});

withAutomaticReconnect() no configurará el HubConnection para reintentar los fallos de arranque iniciales, por lo que los fallos de arranque deben controlarse manualmente:

async function start() {
    try {
        await connection.start();
        console.assert(connection.state === signalR.HubConnectionState.Connected);
        console.log("SignalR Connected.");
    } catch (err) {
        console.assert(connection.state === signalR.HubConnectionState.Disconnected);
        console.log(err);
        setTimeout(() => start(), 5000);
    }
};

Si el cliente no se reconecta con éxito en sus cuatro primeros intentos, HubConnection pasa al estado Disconnected y desencadena sus devoluciones de llamada onclose. Esto proporciona la oportunidad de informar a los usuarios de que la conexión se ha perdido permanentemente y recomendar la actualización de la página:

connection.onclose(error => {
    console.assert(connection.state === signalR.HubConnectionState.Disconnected);

    document.getElementById("messageInput").disabled = true;

    const li = document.createElement("li");
    li.textContent = `Connection closed due to error "${error}". Try refreshing this page to restart the connection.`;
    document.getElementById("messageList").appendChild(li);
});

Para configurar un número personalizado de intentos de reconexión antes de desconectar o cambiar el tiempo de reconexión, withAutomaticReconnect acepta una matriz de números que representa el retraso en milisegundos para esperar antes de iniciar cada intento de reconexión.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect([0, 0, 10000])
    .build();

    // .withAutomaticReconnect([0, 2000, 10000, 30000]) yields the default behavior

El ejemplo anterior configura el HubConnection para que empiece a intentar reconectarse inmediatamente después de que se pierda la conexión. Esto también es cierto para la configuración predeterminada.

Si se produce un error en el primer intento de reconexión, el segundo intento de reconexión también se iniciará inmediatamente en lugar de esperar 2 segundos como lo haría en la configuración predeterminada.

Si se produce un error en el segundo intento de reconexión, el tercer intento de reconexión se iniciará en 10 segundos, como la configuración predeterminada.

Después, el comportamiento personalizado vuelve a desviarse del comportamiento predeterminado al detenerse tras el fracaso del tercer intento de reconexión en lugar de intentar una reconexión más en otros 30 segundos como haría en la configuración predeterminada.

Si quiere aún más control sobre el tiempo y el número de intentos de reconexión automática, withAutomaticReconnect acepta un objeto que implemente la interfaz IRetryPolicy, que tiene un único método llamado nextRetryDelayInMilliseconds.

nextRetryDelayInMilliseconds toma un único argumento con el tipo RetryContext. El RetryContext tiene tres propiedades: previousRetryCount, elapsedMilliseconds y retryReason que son number, number y Error respectivamente. Antes del primer intento de reconexión, tanto previousRetryCount como elapsedMilliseconds serán cero, y retryReason será el Error que provocó la pérdida de conexión. Después de cada intento de reconexión fallido, previousRetryCount se incrementará en uno, elapsedMilliseconds se actualizará para reflejar la cantidad de tiempo empleado en la reconexión hasta el momento en milisegundos, y retryReason será el Error que causó el último intento de reconexión fallido.

nextRetryDelayInMilliseconds debe devolver un número que represente el número de milisegundos que hay que esperar antes del siguiente intento de reconexión o null si HubConnection debe dejar de reconectarse.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chathub")
    .withAutomaticReconnect({
        nextRetryDelayInMilliseconds: retryContext => {
            if (retryContext.elapsedMilliseconds < 60000) {
                // If we've been reconnecting for less than 60 seconds so far,
                // wait between 0 and 10 seconds before the next reconnect attempt.
                return Math.random() * 10000;
            } else {
                // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
                return null;
            }
        }
    })
    .build();

Como alternativa, puede escribir código que vuelva a conectar el cliente manualmente, como se muestra en Reconexión manual.

Volver a conectarse manualmente

En el código siguiente se muestra un enfoque típico de reconexión manual:

  1. Se crea una función (en este caso, la función start) para iniciar la conexión.
  2. Llame a la función start en el controlador de eventos onclose de la conexión.
async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

Las implementaciones de producción suelen usar un retroceso exponencial o reintentar un número especificado de veces.

Pestaña de suspensión del explorador

Algunos exploradores tienen una característica de bloqueo de pestañas o de suspensión para reducir el uso de recursos del equipo para pestañas inactivas. Esto puede provocar el cierre de conexiones de SignalR y puede dar lugar a una experiencia de usuario no deseada. Los exploradores usan heurística para averiguar si se debe poner en suspensión una pestaña, como:

  • Reproducción de audio
  • Mantener un bloqueo de la web
  • Mantener un bloqueo IndexedDB
  • Estar conectado a un dispositivo USB
  • Capturar vídeo o audio
  • Crear un reflejo
  • Capturar una ventana o pantalla

Note

Esta heurística puede cambiar con el tiempo o diferir entre navegadores. Compruebe la matriz de compatibilidad y descubra qué método funciona mejor para sus escenarios.

Para evitar poner una aplicación en suspensión, esta debe activar una de las heurísticas que utiliza el navegador.

En el ejemplo de código siguiente se muestra cómo usar un bloqueo web para mantener activa una pestaña y evitar un cierre de conexión inesperado.

var lockResolver;
if (navigator && navigator.locks && navigator.locks.request) {
    const promise = new Promise((res) => {
        lockResolver = res;
    });

    navigator.locks.request('unique_lock_name', { mode: "shared" }, () => {
        return promise;
    });
}

Para el ejemplo de código anterior:

  • Los bloqueos web son experimentales. La comprobación condicional confirma que el explorador admite bloqueos web.
  • El solucionador de promesas (lockResolver) se almacena para que el bloqueo se pueda liberar cuando sea aceptable que la pestaña se suspenda.
  • Al cerrar la conexión, el bloqueo se libera mediante una llamada a lockResolver(). Cuando se libera el bloqueo, la pestaña puede suspenderse.

Recursos adicionales